Prune Form4 experimental presets

main
I Luk Kim 4 months ago
parent 4b1d9afde7
commit f21caf23cb

@ -1226,11 +1226,18 @@ def list_snapshots() -> dict[str, Any]:
import pandas as pd # type: ignore import pandas as pd # type: ignore
def _read_snap(snap_dir: Path) -> dict[str, Any] | None: def _read_snap(snap_dir: Path) -> dict[str, Any] | None:
train = snap_dir / "train.parquet" all_dfs = []
if not train.exists(): for split in ("train", "valid", "test"):
p = snap_dir / f"{split}.parquet"
if p.exists():
try:
all_dfs.append(pd.read_parquet(p, columns=["event_date"]))
except Exception:
pass
if not all_dfs:
return None return None
try: try:
df = pd.read_parquet(train, columns=["event_date"]) df = pd.concat(all_dfs, ignore_index=True)
return { return {
"id": snap_dir.name, "id": snap_dir.name,
"start": str(df["event_date"].min()), "start": str(df["event_date"].min()),
@ -1258,8 +1265,8 @@ def list_snapshots() -> dict[str, Any]:
for snap_dir in sorted(parquet_dir.iterdir()): for snap_dir in sorted(parquet_dir.iterdir()):
if snap_dir.name in seen: if snap_dir.name in seen:
continue continue
# Only include OOT / historical snapshots (skip active live ones) # Only include OOT / historical / test / fix snapshots (skip active live ones)
if "oot" not in snap_dir.name and "2020" not in snap_dir.name and "2021" not in snap_dir.name: if "oot" not in snap_dir.name and "2020" not in snap_dir.name and "2021" not in snap_dir.name and "test" not in snap_dir.name and "fix" not in snap_dir.name:
continue continue
if not snap_dir.is_dir(): if not snap_dir.is_dir():
continue continue
@ -1329,8 +1336,17 @@ def get_form4_capture_presets() -> dict[str, Any]:
"reserve_form4_cluster_plus_fresh", "reserve_form4_cluster_plus_fresh",
"reserve_form4_cluster_plus_fresh_same_day", "reserve_form4_cluster_plus_fresh_same_day",
] ]
experimental_v3 = [
"reserve_form4_cluster_plus_fresh_same_day_v3_aggressive_plus_cooldown180_high",
"reserve_form4_cluster_plus_fresh_same_day_v3_aggressive_plus_cooldown180_ultra",
]
presets = [name for name in curated if name in FORM4_CAPTURE_SLEEVE_PRESETS] presets = [name for name in curated if name in FORM4_CAPTURE_SLEEVE_PRESETS]
groups = {"Residual Insider Sleeve": presets} v3_presets = [name for name in experimental_v3 if name in FORM4_CAPTURE_SLEEVE_PRESETS]
groups = {
"Residual Insider Sleeve": presets,
"Residual Insider Sleeve (V3 Experimental)": v3_presets,
}
presets = presets + v3_presets
return {"presets": presets, "groups": groups} return {"presets": presets, "groups": groups}

@ -1080,11 +1080,13 @@ IDLE_ALPHA_SLEEVE_PRESETS: dict[str, dict[str, Any]] = {
# --------------------------------------------------------------------------- # ---------------------------------------------------------------------------
DIVIDEND_CAPTURE_SLEEVE_PRESETS: dict[str, dict[str, Any]] = {} DIVIDEND_CAPTURE_SLEEVE_PRESETS: dict[str, dict[str, Any]] = {}
FORM4_PIT_EVENTS_V1_PATH = "data/reference/form4_daily_events_pit.parquet"
FORM4_PIT_EVENTS_V3_PATH = "data/reference/form4_daily_events_pit_v3.parquet"
FORM4_CAPTURE_SLEEVE_PRESETS: dict[str, dict[str, Any]] = { FORM4_CAPTURE_SLEEVE_PRESETS: dict[str, dict[str, Any]] = {
"reserve_form4_cluster": { "reserve_form4_cluster": {
"enabled": True, "enabled": True,
"pit_events_path": "data/reference/form4_daily_events_pit.parquet", "pit_events_path": FORM4_PIT_EVENTS_V1_PATH,
"reserve_pct": 0.04, "reserve_pct": 0.04,
"min_owner_count": 2, "min_owner_count": 2,
"min_total_value": 5_000_000.0, "min_total_value": 5_000_000.0,
@ -1096,7 +1098,7 @@ FORM4_CAPTURE_SLEEVE_PRESETS: dict[str, dict[str, Any]] = {
}, },
"reserve_form4_cluster_plus": { "reserve_form4_cluster_plus": {
"enabled": True, "enabled": True,
"pit_events_path": "data/reference/form4_daily_events_pit.parquet", "pit_events_path": FORM4_PIT_EVENTS_V1_PATH,
"reserve_pct": 0.06, "reserve_pct": 0.06,
"min_owner_count": 2, "min_owner_count": 2,
"min_total_value": 5_000_000.0, "min_total_value": 5_000_000.0,
@ -1108,7 +1110,7 @@ FORM4_CAPTURE_SLEEVE_PRESETS: dict[str, dict[str, Any]] = {
}, },
"reserve_form4_cluster_plus_fresh": { "reserve_form4_cluster_plus_fresh": {
"enabled": True, "enabled": True,
"pit_events_path": "data/reference/form4_daily_events_pit.parquet", "pit_events_path": FORM4_PIT_EVENTS_V1_PATH,
"reserve_pct": 0.06, "reserve_pct": 0.06,
"min_owner_count": 2, "min_owner_count": 2,
"min_total_value": 5_000_000.0, "min_total_value": 5_000_000.0,
@ -1122,7 +1124,7 @@ FORM4_CAPTURE_SLEEVE_PRESETS: dict[str, dict[str, Any]] = {
}, },
"reserve_form4_cluster_plus_fresh_same_day": { "reserve_form4_cluster_plus_fresh_same_day": {
"enabled": True, "enabled": True,
"pit_events_path": "data/reference/form4_daily_events_pit.parquet", "pit_events_path": FORM4_PIT_EVENTS_V1_PATH,
"reserve_pct": 0.06, "reserve_pct": 0.06,
"min_owner_count": 2, "min_owner_count": 2,
"min_total_value": 5_000_000.0, "min_total_value": 5_000_000.0,
@ -1135,6 +1137,39 @@ FORM4_CAPTURE_SLEEVE_PRESETS: dict[str, dict[str, Any]] = {
"max_positions": 6, "max_positions": 6,
"max_new_per_day": 2, "max_new_per_day": 2,
}, },
# Experimental lane. Keep v1 presets frozen so existing stacks remain reproducible.
"reserve_form4_cluster_plus_fresh_same_day_v3_aggressive_plus_cooldown180_high": {
"enabled": True,
"pit_events_path": FORM4_PIT_EVENTS_V3_PATH,
"reserve_pct": 0.46,
"min_owner_count": 2,
"min_total_value": 5_000_000.0,
"min_purchase_pct": 0.005,
"min_transaction_count": 2,
"max_lag_days": 2,
"max_min_lag_days": 1,
"max_transaction_span_days": 0,
"symbol_cooldown_days_after_loss": 180,
"hold_days": 20,
"max_positions": 6,
"max_new_per_day": 2,
},
"reserve_form4_cluster_plus_fresh_same_day_v3_aggressive_plus_cooldown180_ultra": {
"enabled": True,
"pit_events_path": FORM4_PIT_EVENTS_V3_PATH,
"reserve_pct": 0.50,
"min_owner_count": 2,
"min_total_value": 5_000_000.0,
"min_purchase_pct": 0.005,
"min_transaction_count": 2,
"max_lag_days": 2,
"max_min_lag_days": 1,
"max_transaction_span_days": 0,
"symbol_cooldown_days_after_loss": 180,
"hold_days": 20,
"max_positions": 6,
"max_new_per_day": 2,
},
} }
@ -1798,6 +1833,9 @@ class Form4CaptureConfig(BaseModel):
max_min_lag_days: int | None = None max_min_lag_days: int | None = None
max_transaction_span_days: int | None = None max_transaction_span_days: int | None = None
require_officer_or_director: bool = False require_officer_or_director: bool = False
symbol_cooldown_days_after_loss: int = 0
symbol_max_entries_in_lookback: int | None = None
symbol_entry_lookback_days: int = 365
disable_day1_early_failure: bool = False disable_day1_early_failure: bool = False
no_progress_days_override: int | None = None no_progress_days_override: int | None = None
no_progress_r_override: float | None = None no_progress_r_override: float | None = None

@ -15,6 +15,10 @@ from libs.backtest.domain import (
ExitReason, ExitReason,
ExecutionConfig, ExecutionConfig,
ExperimentManifest, ExperimentManifest,
FORM4_CAPTURE_SLEEVE_PRESETS,
FORM4_PIT_EVENTS_V1_PATH,
FORM4_PIT_EVENTS_V3_PATH,
Form4CaptureConfig,
FilledTrade, FilledTrade,
MetricsBundle, MetricsBundle,
OpenPosition, OpenPosition,
@ -244,6 +248,58 @@ class TestConfigModels:
assert m.experiment_name == "test_exp" assert m.experiment_name == "test_exp"
assert m.splits == [] assert m.splits == []
def test_form4_v1_presets_are_frozen_to_v1_cache(self):
assert FORM4_CAPTURE_SLEEVE_PRESETS["reserve_form4_cluster"]["pit_events_path"] == FORM4_PIT_EVENTS_V1_PATH
assert FORM4_CAPTURE_SLEEVE_PRESETS["reserve_form4_cluster_plus"]["pit_events_path"] == FORM4_PIT_EVENTS_V1_PATH
assert FORM4_CAPTURE_SLEEVE_PRESETS["reserve_form4_cluster_plus_fresh"]["pit_events_path"] == FORM4_PIT_EVENTS_V1_PATH
assert FORM4_CAPTURE_SLEEVE_PRESETS["reserve_form4_cluster_plus_fresh_same_day"]["pit_events_path"] == FORM4_PIT_EVENTS_V1_PATH
def test_form4_v3_presets_use_separate_cache(self):
assert (
FORM4_CAPTURE_SLEEVE_PRESETS[
"reserve_form4_cluster_plus_fresh_same_day_v3_aggressive_plus_cooldown180_high"
]["pit_events_path"]
== FORM4_PIT_EVENTS_V3_PATH
)
assert (
FORM4_CAPTURE_SLEEVE_PRESETS[
"reserve_form4_cluster_plus_fresh_same_day_v3_aggressive_plus_cooldown180_ultra"
]["pit_events_path"]
== FORM4_PIT_EVENTS_V3_PATH
)
def test_form4_v3_aggressive_plus_cooldown180_high_preset_shape(self):
preset = FORM4_CAPTURE_SLEEVE_PRESETS[
"reserve_form4_cluster_plus_fresh_same_day_v3_aggressive_plus_cooldown180_high"
]
assert preset["reserve_pct"] == 0.46
assert preset["min_purchase_pct"] == 0.005
assert preset["symbol_cooldown_days_after_loss"] == 180
assert preset["max_transaction_span_days"] == 0
def test_form4_v3_aggressive_plus_cooldown180_ultra_preset_shape(self):
preset = FORM4_CAPTURE_SLEEVE_PRESETS[
"reserve_form4_cluster_plus_fresh_same_day_v3_aggressive_plus_cooldown180_ultra"
]
assert preset["reserve_pct"] == 0.50
assert preset["min_purchase_pct"] == 0.005
assert preset["symbol_cooldown_days_after_loss"] == 180
assert preset["max_transaction_span_days"] == 0
def test_form4_capture_config_supports_symbol_cooldown_fields(self):
config = Form4CaptureConfig.model_validate(
{
"enabled": True,
"symbol_cooldown_days_after_loss": 45,
"symbol_max_entries_in_lookback": 3,
"symbol_entry_lookback_days": 540,
}
)
assert config.symbol_cooldown_days_after_loss == 45
assert config.symbol_max_entries_in_lookback == 3
assert config.symbol_entry_lookback_days == 540
class TestEventTypeProfile: class TestEventTypeProfile:
def test_defaults(self): def test_defaults(self):

Loading…
Cancel
Save