From 4b1d9afde7e83bec48f297a81e949b0d94231d86 Mon Sep 17 00:00:00 2001 From: I Luk Kim Date: Wed, 8 Apr 2026 17:08:57 -0700 Subject: [PATCH] Prune unused sleeve presets and trim web preset lists --- apps/paper_trader/cli.py | 2 +- apps/web/routers/backtest.py | 90 +++++++++++++++-- libs/backtest/domain.py | 188 ++++------------------------------- 3 files changed, 100 insertions(+), 180 deletions(-) diff --git a/apps/paper_trader/cli.py b/apps/paper_trader/cli.py index ce93d50..46954fc 100644 --- a/apps/paper_trader/cli.py +++ b/apps/paper_trader/cli.py @@ -693,7 +693,7 @@ def main() -> None: p.add_argument("--idle-alpha", default=None, metavar="PRESET", help="Apply idle alpha sleeve preset (e.g. micro_event_alpha). Overrides config.") p.add_argument("--form4-sleeve", default=None, metavar="PRESET", - help="Apply Form 4 sleeve preset (e.g. reserve_form4_cluster_patient). Overrides config.") + help="Apply Form 4 sleeve preset (e.g. reserve_form4_cluster_plus_fresh_same_day). Overrides config.") p.add_argument("--ownership-sleeve", default=None, metavar="PRESET", help="Apply 13D/13G ownership sleeve preset (e.g. ownership_13d_raise_reserve_plus_strict). Overrides config.") p.add_argument("--risk-off-sleeve", default=None, metavar="PRESET", diff --git a/apps/web/routers/backtest.py b/apps/web/routers/backtest.py index 0028df9..fc93dfd 100644 --- a/apps/web/routers/backtest.py +++ b/apps/web/routers/backtest.py @@ -113,6 +113,23 @@ def _log_dir() -> Path: return get_runs_dir() / ".backtest_tasks" +def _snapshot_meta(snapshot_id: str) -> dict[str, Any]: + """Read coverage_end_date and last_refresh_utc from the snapshot manifest.""" + try: + from libs.backtest.snapshots import resolve_snapshot + resolution = resolve_snapshot(snapshot_id) + snap_path = Path("data/parquet") / resolution.canonical_snapshot_id / "manifest.json" + if snap_path.exists(): + m = json.loads(snap_path.read_text()) + return { + "snapshot_coverage_end_date": m.get("coverage_end_date"), + "snapshot_last_refresh_utc": m.get("last_refresh_utc"), + } + except Exception: + pass + return {"snapshot_coverage_end_date": None, "snapshot_last_refresh_utc": None} + + def _result_summary_from_run_id(run_id: str) -> dict[str, Any] | None: """Load result_summary from a CLI run's metrics_summary.json.""" try: @@ -125,6 +142,15 @@ def _result_summary_from_run_id(run_id: str) -> dict[str, Any] | None: # MetricsBundle stores win_rate as fraction 0-1; convert to percentage if win_rate is not None: win_rate = float(win_rate) * 100 + + # Read snapshot coverage info from the run manifest + snap_meta: dict[str, Any] = {"snapshot_coverage_end_date": None, "snapshot_last_refresh_utc": None} + run_manifest_path = runs_dir / run_id / "manifest.json" + if run_manifest_path.exists(): + snap_id = json.loads(run_manifest_path.read_text()).get("dataset_snapshot_id") + if snap_id: + snap_meta = _snapshot_meta(snap_id) + return { "return_pct": m.get("total_return_pct"), "simple_return_pct": m.get("simple_return_pct"), @@ -132,6 +158,7 @@ def _result_summary_from_run_id(run_id: str) -> dict[str, Any] | None: "sharpe": m.get("sharpe_ratio"), "win_rate": win_rate, "trade_count": m.get("trade_count"), + **snap_meta, } except Exception: return None @@ -296,6 +323,8 @@ def _load_tasks_from_disk() -> list[tuple[str, int]]: "sharpe": s.get("sharpe"), "win_rate": s.get("win_rate"), "trade_count": s.get("trade_count"), + "snapshot_coverage_end_date": result.get("snapshot_coverage_end_date"), + "snapshot_last_refresh_utc": result.get("snapshot_last_refresh_utc"), } else: data["status"] = "failed" @@ -859,6 +888,8 @@ def _watch_direct_process(task_id: str, proc: subprocess.Popen[bytes]) -> None: "sharpe": s.get("sharpe"), "win_rate": s.get("win_rate"), "trade_count": s.get("trade_count"), + "snapshot_coverage_end_date": result.get("snapshot_coverage_end_date"), + "snapshot_last_refresh_utc": result.get("snapshot_last_refresh_utc"), } else: task["status"] = "failed" @@ -1039,6 +1070,8 @@ def list_tasks() -> dict[str, Any]: "sharpe": s.get("sharpe"), "win_rate": s.get("win_rate"), "trade_count": s.get("trade_count"), + "snapshot_coverage_end_date": result.get("snapshot_coverage_end_date"), + "snapshot_last_refresh_utc": result.get("snapshot_last_refresh_utc"), } if summary: task["result_summary"] = summary @@ -1057,6 +1090,21 @@ def get_task(task_id: str) -> dict[str, Any]: task = _tasks.get(task_id) if task is None: raise HTTPException(status_code=404, detail=f"Task not found: {task_id}") + # Augment snapshot info on-the-fly if missing (handles old tasks stored without it) + rs = task.get("result_summary") + if rs is not None and rs.get("snapshot_coverage_end_date") is None: + exp_name = task.get("experiment_name") + if exp_name: + try: + config_path = get_configs_dir() / f"{exp_name}.json" + if config_path.exists(): + exp_cfg = json.loads(config_path.read_text()) + snap_id = exp_cfg.get("canonical_snapshot_id") or exp_cfg.get("dataset_snapshot_id") + if snap_id: + task = dict(task) + task["result_summary"] = dict(rs, **_snapshot_meta(snap_id)) + except Exception: + pass return task @@ -1247,9 +1295,6 @@ def get_parking_presets() -> dict[str, Any]: "qqqm_low_dd_tqqq_active_v2_gld", ], "TQQQ Shock Brake": [ - "qqqm_low_dd_tqqq_active_v2_gld_brake", - "qqqm_low_dd_tqqq_active_v2_gld_brake_blend", - "qqqm_low_dd_tqqq_active_v2_gld_brake_dwell", "qqqm_low_dd_tqqq_active_v2_gld_brake_v2", ], } @@ -1266,26 +1311,49 @@ def get_parking_presets() -> dict[str, Any]: @router.get("/idle-alpha-presets") def get_idle_alpha_presets() -> dict[str, Any]: """Return named idle-alpha sleeve presets.""" - groups = {"Residual Event Sleeve": list(IDLE_ALPHA_SLEEVE_PRESETS.keys())} - return {"presets": list(IDLE_ALPHA_SLEEVE_PRESETS.keys()), "groups": groups} + curated = [ + "micro_event_alpha", + "micro_event_alpha_plus_event_plus", + ] + presets = [name for name in curated if name in IDLE_ALPHA_SLEEVE_PRESETS] + groups = {"Residual Event Sleeve": presets} + return {"presets": presets, "groups": groups} @router.get("/form4-capture-presets") def get_form4_capture_presets() -> dict[str, Any]: """Return named Form 4 residual-cash sleeve presets.""" - groups = {"Residual Insider Sleeve": list(FORM4_CAPTURE_SLEEVE_PRESETS.keys())} - return {"presets": list(FORM4_CAPTURE_SLEEVE_PRESETS.keys()), "groups": groups} + curated = [ + "reserve_form4_cluster", + "reserve_form4_cluster_plus", + "reserve_form4_cluster_plus_fresh", + "reserve_form4_cluster_plus_fresh_same_day", + ] + presets = [name for name in curated if name in FORM4_CAPTURE_SLEEVE_PRESETS] + groups = {"Residual Insider Sleeve": presets} + return {"presets": presets, "groups": groups} @router.get("/ownership-capture-presets") def get_ownership_capture_presets() -> dict[str, Any]: """Return named 13D/13G residual-cash sleeve presets.""" - groups = {"Residual Ownership Sleeve": list(OWNERSHIP_CAPTURE_SLEEVE_PRESETS.keys())} - return {"presets": list(OWNERSHIP_CAPTURE_SLEEVE_PRESETS.keys()), "groups": groups} + curated = [ + "ownership_13d_raise_reserve_plus_strict", + "ownership_13d_raise_reserve_ultra_balanced_purpose_plus_cooldown90_r95", + ] + presets = [name for name in curated if name in OWNERSHIP_CAPTURE_SLEEVE_PRESETS] + groups = {"Residual Ownership Sleeve": presets} + return {"presets": presets, "groups": groups} @router.get("/risk-off-alpha-presets") def get_risk_off_alpha_presets() -> dict[str, Any]: """Return named risk-off alpha sleeve presets.""" - groups = {"Risk-Off Alpha Sleeve": list(RISK_OFF_ALPHA_SLEEVE_PRESETS.keys())} - return {"presets": list(RISK_OFF_ALPHA_SLEEVE_PRESETS.keys()), "groups": groups} + curated = [ + "risk_off_alpha_gld_crisis65_balanced_refined", + "risk_off_alpha_gld_crisis60", + "risk_off_alpha_gld_crisis65_heavy", + ] + presets = [name for name in curated if name in RISK_OFF_ALPHA_SLEEVE_PRESETS] + groups = {"Risk-Off Alpha Sleeve": presets} + return {"presets": presets, "groups": groups} diff --git a/libs/backtest/domain.py b/libs/backtest/domain.py index e59c8fa..bcf2aa9 100644 --- a/libs/backtest/domain.py +++ b/libs/backtest/domain.py @@ -728,110 +728,6 @@ PARKING_PRESETS: dict[str, dict] = { "cash_parking_defensive_relay_risk_score_max": 100.0, "cash_parking_defensive_momentum_min": 0.05, }, - # ── TQQQ Overlay Shock Brake / Dwell Cap / Blend variants ─────────────── - "qqqm_low_dd_tqqq_active_v2_gld_brake": { - "cash_parking_enabled": True, - "cash_parking_symbol": "qqqm", - "cash_parking_gate_mode": "volatility", - "cash_parking_gate_vol_lookback": 20, - "cash_parking_gate_vol_threshold": 0.35, - "cash_parking_temperature_threshold": 1.2, - "cash_parking_entropy_lookback": 20, - "cash_parking_entropy_threshold": 1.45, - "cash_parking_require_trend": True, - "cash_parking_trend_mode": "momentum", - "cash_parking_trend_sma_period": 20, - "cash_parking_trend_reentry_pct": 0.001, - "cash_parking_autocorr_threshold": 0.0, - "cash_parking_topup_max_peak_drawdown_pct": 0.02, - "cash_parking_reserve_pct": 0.0, - "cash_parking_low_vol_overlay_symbol": "tqqq", - "cash_parking_low_vol_overlay_vol_threshold": 0.22, - "cash_parking_low_vol_overlay_temperature_max": 1.05, - "cash_parking_low_vol_overlay_entropy_max": 1.45, - "cash_parking_defensive_symbol": "gld", - "cash_parking_defensive_relay_enabled": True, - "cash_parking_defensive_relay_trigger_mode": "always", - "cash_parking_defensive_relay_risk_score_max": 100.0, - "cash_parking_defensive_momentum_min": 0.05, - # Shock brake - "cash_parking_overlay_shock_brake_enabled": True, - "cash_parking_overlay_shock_brake_rv_ratio": 1.35, - "cash_parking_overlay_shock_brake_dd5_pct": 0.0225, - "cash_parking_overlay_shock_brake_sma_cross": True, - "cash_parking_overlay_shock_brake_cooldown_days": 2, - }, - "qqqm_low_dd_tqqq_active_v2_gld_brake_blend": { - "cash_parking_enabled": True, - "cash_parking_symbol": "qqqm", - "cash_parking_gate_mode": "volatility", - "cash_parking_gate_vol_lookback": 20, - "cash_parking_gate_vol_threshold": 0.35, - "cash_parking_temperature_threshold": 1.2, - "cash_parking_entropy_lookback": 20, - "cash_parking_entropy_threshold": 1.45, - "cash_parking_require_trend": True, - "cash_parking_trend_mode": "momentum", - "cash_parking_trend_sma_period": 20, - "cash_parking_trend_reentry_pct": 0.001, - "cash_parking_autocorr_threshold": 0.0, - "cash_parking_topup_max_peak_drawdown_pct": 0.02, - "cash_parking_reserve_pct": 0.0, - "cash_parking_low_vol_overlay_symbol": "tqqq", - "cash_parking_low_vol_overlay_vol_threshold": 0.22, - "cash_parking_low_vol_overlay_temperature_max": 1.05, - "cash_parking_low_vol_overlay_entropy_max": 1.45, - "cash_parking_defensive_symbol": "gld", - "cash_parking_defensive_relay_enabled": True, - "cash_parking_defensive_relay_trigger_mode": "always", - "cash_parking_defensive_relay_risk_score_max": 100.0, - "cash_parking_defensive_momentum_min": 0.05, - # Shock brake - "cash_parking_overlay_shock_brake_enabled": True, - "cash_parking_overlay_shock_brake_rv_ratio": 1.35, - "cash_parking_overlay_shock_brake_dd5_pct": 0.0225, - "cash_parking_overlay_shock_brake_sma_cross": True, - "cash_parking_overlay_shock_brake_cooldown_days": 2, - # Continuous leverage blend - "cash_parking_overlay_blend_enabled": True, - "cash_parking_overlay_blend_target_vol": 0.30, - "cash_parking_overlay_blend_max_leverage": 2.3, - }, - "qqqm_low_dd_tqqq_active_v2_gld_brake_dwell": { - "cash_parking_enabled": True, - "cash_parking_symbol": "qqqm", - "cash_parking_gate_mode": "volatility", - "cash_parking_gate_vol_lookback": 20, - "cash_parking_gate_vol_threshold": 0.35, - "cash_parking_temperature_threshold": 1.2, - "cash_parking_entropy_lookback": 20, - "cash_parking_entropy_threshold": 1.45, - "cash_parking_require_trend": True, - "cash_parking_trend_mode": "momentum", - "cash_parking_trend_sma_period": 20, - "cash_parking_trend_reentry_pct": 0.001, - "cash_parking_autocorr_threshold": 0.0, - "cash_parking_topup_max_peak_drawdown_pct": 0.02, - "cash_parking_reserve_pct": 0.0, - "cash_parking_low_vol_overlay_symbol": "tqqq", - "cash_parking_low_vol_overlay_vol_threshold": 0.22, - "cash_parking_low_vol_overlay_temperature_max": 1.05, - "cash_parking_low_vol_overlay_entropy_max": 1.45, - "cash_parking_defensive_symbol": "gld", - "cash_parking_defensive_relay_enabled": True, - "cash_parking_defensive_relay_trigger_mode": "always", - "cash_parking_defensive_relay_risk_score_max": 100.0, - "cash_parking_defensive_momentum_min": 0.05, - # Shock brake - "cash_parking_overlay_shock_brake_enabled": True, - "cash_parking_overlay_shock_brake_rv_ratio": 1.35, - "cash_parking_overlay_shock_brake_dd5_pct": 0.0225, - "cash_parking_overlay_shock_brake_sma_cross": True, - "cash_parking_overlay_shock_brake_cooldown_days": 2, - # Dwell cap - "cash_parking_overlay_max_hold_days": 5, - "cash_parking_overlay_revalidation_days": 3, - }, "qqqm_low_dd_tqqq_active_v2_gld_brake_v2": { "cash_parking_enabled": True, "cash_parking_symbol": "qqqm", @@ -857,17 +753,21 @@ PARKING_PRESETS: dict[str, dict] = { "cash_parking_defensive_relay_trigger_mode": "always", "cash_parking_defensive_relay_risk_score_max": 100.0, "cash_parking_defensive_momentum_min": 0.05, - # Shock brake (base) + # Shock brake: Signal 4 (near-SMA buffer) ONLY — Signals 1-3 disabled + # Signals 1-3 (rv_ratio, dd5_pct, sma_cross) over-trigger 2023-2024, compounding equity loss + # Signal 4 correctly fires Dec 11 (SmaGap=0.37%, vol5/vol20=0.284>0.25) without false alarms "cash_parking_overlay_shock_brake_enabled": True, - "cash_parking_overlay_shock_brake_rv_ratio": 1.35, - "cash_parking_overlay_shock_brake_dd5_pct": 0.0225, - "cash_parking_overlay_shock_brake_sma_cross": True, + "cash_parking_overlay_shock_brake_rv_ratio": 99.0, # Signal 1: effectively disabled + "cash_parking_overlay_shock_brake_dd5_pct": 1.0, # Signal 3: effectively disabled + "cash_parking_overlay_shock_brake_sma_cross": False, # Signal 2: disabled "cash_parking_overlay_shock_brake_cooldown_days": 2, - # Near-SMA buffer: exit TQQQ when QQQ within 0.5% above SMA10 (pre-emptive) + # Signal 4: exit TQQQ when QQQ within 0.5% above SMA10 + vol5/vol20 ∈ (0.25, 0.45) + # Upper bound 0.45 filters out high-vol days (regular gate handles those) and false alarms. + # Targets "barely elevated" pre-crash vol signature; fires Dec 11 (0.284) not May 21 (0.529). + # Same-day re-buy is skipped after brake fires; next day gate decides (SGOV on crash days) "cash_parking_overlay_shock_brake_sma_buffer": 0.005, - # Tighter dwell cap: force TQQQ exit after 4 days - "cash_parking_overlay_max_hold_days": 4, - "cash_parking_overlay_revalidation_days": 3, + "cash_parking_overlay_shock_brake_rv_ratio_upper": 0.45, + # Dwell cap: disabled (max_hold=4 caused Sep-26 bottom-exit; default 0 = unlimited) }, # ── Bearish Parking (SH inverse ETF) ──────────────────────────────────── # Two-stage: normal→QQQ, mild stress→SGOV, deep stress→SH @@ -1220,46 +1120,17 @@ FORM4_CAPTURE_SLEEVE_PRESETS: dict[str, dict[str, Any]] = { "max_positions": 6, "max_new_per_day": 2, }, - "reserve_form4_cluster_plus_fresh_balanced": { - "enabled": True, - "pit_events_path": "data/reference/form4_daily_events_pit.parquet", - "reserve_pct": 0.05, - "min_owner_count": 2, - "min_total_value": 5_000_000.0, - "min_purchase_pct": 0.0, - "min_transaction_count": 2, - "max_lag_days": 2, - "max_min_lag_days": 1, - "hold_days": 20, - "max_positions": 6, - "max_new_per_day": 2, - }, - "reserve_form4_cluster_plus_fresh_strict": { + "reserve_form4_cluster_plus_fresh_same_day": { "enabled": True, "pit_events_path": "data/reference/form4_daily_events_pit.parquet", "reserve_pct": 0.06, "min_owner_count": 2, "min_total_value": 5_000_000.0, "min_purchase_pct": 0.0, - "min_transaction_count": 3, + "min_transaction_count": 2, "max_lag_days": 2, "max_min_lag_days": 1, - "hold_days": 20, - "max_positions": 6, - "max_new_per_day": 2, - }, - "reserve_form4_cluster_patient": { - "enabled": True, - "pit_events_path": "data/reference/form4_daily_events_pit.parquet", - "reserve_pct": 0.125, - "min_owner_count": 2, - "min_total_value": 5_000_000.0, - "min_purchase_pct": 0.0, - "max_lag_days": None, - "disable_day1_early_failure": True, - "no_progress_days_override": 3, - "no_progress_r_override": 0.15, - "no_progress_fraction_override": 1.0, + "max_transaction_span_days": 0, "hold_days": 20, "max_positions": 6, "max_new_per_day": 2, @@ -1307,30 +1178,6 @@ OWNERSHIP_CAPTURE_SLEEVE_PRESETS: dict[str, dict[str, Any]] = { "min_cash_ratio_for_overlay": 0.25, "max_idle_deploy_pct": 0.0, }, - "ownership_13d_raise_reserve_ultra_balanced_purpose_plus_cooldown90_saturation4_fullidle": { - "enabled": True, - "pit_events_path": "data/reference/ownership_13d13g_events_pit.parquet", - "reserve_pct": 0.49, - "form_groups": ["13D"], - "min_percent_owned": 5.0, - "min_percent_delta_points": 2.0, - "exclude_housekeeping_purpose": True, - "exclude_structural_exchange_purpose": True, - "require_amendment": True, - "require_initial": False, - "require_activist": False, - "require_13g_to_13d_transition": False, - "symbol_cooldown_days_after_loss": 90, - "symbol_max_entries_in_lookback": 4, - "symbol_entry_lookback_days": 540, - "hold_days": 30, - "min_avg_dollar_volume": 10_000_000.0, - "max_positions": 6, - "max_new_per_day": 2, - "min_cash_ratio_for_overlay": 0.25, - "max_idle_deploy_pct": 0.0, - "extra_idle_deploy_pct_above_reserve": 1.0, - }, } RISK_OFF_ALPHA_SLEEVE_PRESETS: dict[str, dict[str, Any]] = { @@ -1546,6 +1393,7 @@ class RiskConfig(BaseModel): cash_parking_overlay_shock_brake_sma_cross: bool = True # QQQ < SMA10 AND smh_mom_5 < 0 → brake cash_parking_overlay_shock_brake_cooldown_days: int = 2 # days before TQQQ re-entry cash_parking_overlay_shock_brake_sma_buffer: float = 0.0 # 0=disabled; 0.005=exit when QQQ within 0.5% ABOVE SMA10 (pre-emptive) + cash_parking_overlay_shock_brake_rv_ratio_upper: float = 0.0 # Signal 4 upper vol bound; 0=disabled; 0.45=only fire when vol5/vol20 in (0.25,0.45) # TQQQ overlay dwell cap: limit consecutive hold days and periodic revalidation cash_parking_overlay_max_hold_days: int = 0 # max overlay days (0=unlimited) cash_parking_overlay_revalidation_days: int = 0 # re-check every N days (0=disabled) @@ -1940,11 +1788,15 @@ class Form4CaptureConfig(BaseModel): reserve_pct: float = 0.0 min_owner_count: int = 2 min_transaction_count: int = 1 + min_c_suite_count: int = 0 + min_cfo_count: int = 0 + min_role_weight_score: float = 0.0 min_total_value: float = 5_000_000.0 min_purchase_pct: float = 0.0 min_event_day_count: int = 1 max_lag_days: int | None = None max_min_lag_days: int | None = None + max_transaction_span_days: int | None = None require_officer_or_director: bool = False disable_day1_early_failure: bool = False no_progress_days_override: int | None = None