diff --git a/docs/research/fip-breadth-ic.md b/docs/research/fip-breadth-ic.md index d02e879..cef816d 100644 --- a/docs/research/fip-breadth-ic.md +++ b/docs/research/fip-breadth-ic.md @@ -1,6 +1,6 @@ # Broad-universe fip_id IC research (Phase B) -Generated: 2026-07-18T19:48:28.127710 +Generated: 2026-07-18T21:14:40.170961 ## Scope @@ -22,33 +22,25 @@ Generated: 2026-07-18T19:48:28.127710 ## Liquid-breadth signal_eval (fip_id) -**Not run yet in this environment** (no Alpaca credentials to build -`backtest_snapshots/research.sqlite`). Local machine with keys: - -```bash -python scripts/extend_snapshot_universe.py --force-copy -python scripts/run_fip_breadth_research.py --skip-fingerprint --allow-spawn --workers 6 -``` - -(`--skip-fingerprint` only after a green fingerprint on this machine.) - | metric | value | |---|---| -| mean_ic | _pending_ | -| ic_t_stat | _pending_ | -| weeks | _pending_ | -| avg_cross_section | _pending_ | -| reliable | _pending_ | +| mean_ic | 0.0575 | +| ic_t_stat | 5.12 | +| ic_positive_pct | 88.6 | +| weeks | 35 | +| avg_cross_section | 1471.2 | +| reliable | True | +| mean_quintile_spread | 0.0199 | ## Verdict (iron rule) -- **Fingerprint:** green (IC −0.045 / t −2.91 / reliable / 35 weeks). -- **Breadth iron rule:** **pending** until research.sqlite run completes. -- Green breadth would authorize a **follow-up proposal** only (two-tier universe / - gate revalidation) — **not** production wire-in. +- **Green: False** +- iron rule not met on liquid-breadth cross-section +- Checks: `{"mean_ic": 0.0575, "abs_mean_ic_ge_0_03": true, "sign_negative": false, "ic_t_stat": 5.12, "reliable": true, "weeks": 35, "avg_cross_section": 1471.2}` + +A green verdict authorizes a **follow-up proposal** only (two-tier universe / gate revalidation) — **not** production wire-in. ## Artifacts -- Fingerprint report: `reports/fip-breadth-20260718-194828-fingerprint.json` - (and summary `reports/fip-breadth-20260718-194828.json`) -- Breadth report: _pending_ +- Fingerprint report: `reports/fip-breadth-20260718-211440-fingerprint.json` +- Breadth report: `reports/fip-breadth-20260718-211440-breadth.json` diff --git a/reports/fip-breadth-20260718-211440-breadth.json b/reports/fip-breadth-20260718-211440-breadth.json new file mode 100644 index 0000000..c586f74 --- /dev/null +++ b/reports/fip-breadth-20260718-211440-breadth.json @@ -0,0 +1,596 @@ +{ + "generated_at": "2026-07-18T19:23:17.726528+00:00", + "tickers": 4650, + "rank_only_tickers": 4144, + "candidates": 202769, + "qualified": 1086, + "params": { + "step_days": 5, + "step_sessions": 5, + "entry_cadence": "weekly", + "signal_eval_cadence": "weekly", + "horizon_days": 30, + "min_lookback": 60, + "cost_per_side_pct": 0.1, + "target_model": "production_gtl", + "target_model_label": "Live GTL (production)", + "is_production_target_model": true, + "production_reentry_policy": "gate_reset", + "liquid_breadth_top_n": 1500, + "liquid_min_price": 5.0, + "signal_eval_only": true + }, + "activation": { + "min_momentum_percentile": 80.0, + "min_rr": 2.0, + "min_confidence": 0.0, + "require_high_conviction": false, + "exclude_conflicts": false, + "exclude_neutral": true + }, + "overall_qualified": { + "total": 1086, + "wins": 379, + "losses": 591, + "expired": 116, + "hit_rate": 39.1, + "avg_r": 0.255, + "total_r": 276.76, + "net_avg_r": 0.209, + "net_total_r": 226.56, + "best_r": 8.85, + "worst_r": -3.38, + "avg_hold_days": 12.0, + "net_r_per_day": 0.0174, + "median_net_r": -1.031, + "profit_factor": 1.34, + "net_avg_r_ex_top5": 0.049 + }, + "overall_all": { + "total": 202769, + "wins": 82221, + "losses": 113812, + "expired": 6736, + "hit_rate": 41.9, + "avg_r": -0.04, + "total_r": -8188.17, + "net_avg_r": -0.095, + "net_total_r": -19186.68, + "best_r": 9.24, + "worst_r": -16.42, + "avg_hold_days": 8.2, + "net_r_per_day": -0.0115, + "median_net_r": -1.035, + "profit_factor": 0.85, + "net_avg_r_ex_top5": -0.22 + }, + "by_direction": { + "long": { + "total": 1086, + "wins": 379, + "losses": 591, + "expired": 116, + "hit_rate": 39.1, + "avg_r": 0.255, + "total_r": 276.76, + "net_avg_r": 0.209, + "net_total_r": 226.56, + "best_r": 8.85, + "worst_r": -3.38, + "avg_hold_days": 12.0, + "net_r_per_day": 0.0174, + "median_net_r": -1.031, + "profit_factor": 1.34, + "net_avg_r_ex_top5": 0.049 + }, + "short": { + "total": 0, + "wins": 0, + "losses": 0, + "expired": 0, + "hit_rate": null, + "avg_r": null, + "total_r": null, + "net_avg_r": null, + "net_total_r": null, + "best_r": null, + "worst_r": null, + "avg_hold_days": null, + "net_r_per_day": null, + "median_net_r": null, + "profit_factor": null, + "net_avg_r_ex_top5": null + } + }, + "min_momentum_percentile": 80.0, + "sweep": [ + { + "min_momentum_percentile": 90.0, + "total": 497, + "wins": 177, + "losses": 269, + "expired": 51, + "hit_rate": 39.7, + "avg_r": 0.276, + "total_r": 137.05, + "net_avg_r": 0.235, + "net_total_r": 116.55, + "best_r": 8.85, + "worst_r": -3.38, + "avg_hold_days": 11.8, + "net_r_per_day": 0.0199, + "median_net_r": -1.026, + "profit_factor": 1.39, + "net_avg_r_ex_top5": 0.071 + }, + { + "min_momentum_percentile": 80.0, + "total": 1086, + "wins": 379, + "losses": 591, + "expired": 116, + "hit_rate": 39.1, + "avg_r": 0.255, + "total_r": 276.76, + "net_avg_r": 0.209, + "net_total_r": 226.56, + "best_r": 8.85, + "worst_r": -3.38, + "avg_hold_days": 12.0, + "net_r_per_day": 0.0174, + "median_net_r": -1.031, + "profit_factor": 1.34, + "net_avg_r_ex_top5": 0.049 + }, + { + "min_momentum_percentile": 70.0, + "total": 1841, + "wins": 597, + "losses": 1062, + "expired": 182, + "hit_rate": 36.0, + "avg_r": 0.152, + "total_r": 280.26, + "net_avg_r": 0.104, + "net_total_r": 190.75, + "best_r": 8.85, + "worst_r": -4.21, + "avg_hold_days": 11.8, + "net_r_per_day": 0.0088, + "median_net_r": -1.037, + "profit_factor": 1.16, + "net_avg_r_ex_top5": -0.055 + }, + { + "min_momentum_percentile": 60.0, + "total": 2772, + "wins": 873, + "losses": 1611, + "expired": 288, + "hit_rate": 35.1, + "avg_r": 0.126, + "total_r": 348.07, + "net_avg_r": 0.075, + "net_total_r": 209.25, + "best_r": 8.85, + "worst_r": -4.21, + "avg_hold_days": 12.0, + "net_r_per_day": 0.0063, + "median_net_r": -1.04, + "profit_factor": 1.12, + "net_avg_r_ex_top5": -0.077 + }, + { + "min_momentum_percentile": 50.0, + "total": 3901, + "wins": 1182, + "losses": 2295, + "expired": 424, + "hit_rate": 34.0, + "avg_r": 0.089, + "total_r": 345.37, + "net_avg_r": 0.038, + "net_total_r": 146.96, + "best_r": 8.85, + "worst_r": -4.86, + "avg_hold_days": 12.1, + "net_r_per_day": 0.0031, + "median_net_r": -1.042, + "profit_factor": 1.06, + "net_avg_r_ex_top5": -0.114 + }, + { + "min_momentum_percentile": 0.0, + "total": 14589, + "wins": 3719, + "losses": 9272, + "expired": 1598, + "hit_rate": 28.6, + "avg_r": -0.065, + "total_r": -953.08, + "net_avg_r": -0.115, + "net_total_r": -1677.9, + "best_r": 9.24, + "worst_r": -15.94, + "avg_hold_days": 12.1, + "net_r_per_day": -0.0095, + "median_net_r": -1.043, + "profit_factor": 0.84, + "net_avg_r_ex_top5": -0.275 + } + ], + "gate_ablation": [ + { + "variant": "all_floors", + "total": 1086, + "wins": 379, + "losses": 591, + "expired": 116, + "hit_rate": 39.1, + "avg_r": 0.255, + "total_r": 276.76, + "net_avg_r": 0.209, + "net_total_r": 226.56, + "best_r": 8.85, + "worst_r": -3.38, + "avg_hold_days": 12.0, + "net_r_per_day": 0.0174, + "median_net_r": -1.031, + "profit_factor": 1.34, + "net_avg_r_ex_top5": 0.049, + "hold_days": 30, + "hold_avg_r": 0.631, + "hold_net_avg_r": 0.585, + "hold_total_r": 684.97 + }, + { + "variant": "no_confidence_floor", + "total": 1093, + "wins": 380, + "losses": 596, + "expired": 117, + "hit_rate": 38.9, + "avg_r": 0.25, + "total_r": 273.55, + "net_avg_r": 0.204, + "net_total_r": 222.99, + "best_r": 8.85, + "worst_r": -3.38, + "avg_hold_days": 11.9, + "net_r_per_day": 0.0171, + "median_net_r": -1.031, + "profit_factor": 1.33, + "net_avg_r_ex_top5": 0.045, + "hold_days": 30, + "hold_avg_r": 0.626, + "hold_net_avg_r": 0.58, + "hold_total_r": 684.08 + }, + { + "variant": "no_rr_floor", + "total": 6849, + "wins": 3235, + "losses": 3409, + "expired": 205, + "hit_rate": 48.7, + "avg_r": 0.112, + "total_r": 770.17, + "net_avg_r": 0.061, + "net_total_r": 418.96, + "best_r": 8.85, + "worst_r": -5.16, + "avg_hold_days": 7.9, + "net_r_per_day": 0.0078, + "median_net_r": -0.061, + "profit_factor": 1.11, + "net_avg_r_ex_top5": -0.061, + "hold_days": 30, + "hold_avg_r": 0.354, + "hold_net_avg_r": 0.303, + "hold_total_r": 2425.86 + }, + { + "variant": "no_neutral_exclusion", + "total": 2313, + "wins": 770, + "losses": 1279, + "expired": 264, + "hit_rate": 37.6, + "avg_r": 0.2, + "total_r": 462.35, + "net_avg_r": 0.154, + "net_total_r": 355.89, + "best_r": 8.85, + "worst_r": -3.38, + "avg_hold_days": 12.6, + "net_r_per_day": 0.0122, + "median_net_r": -1.031, + "profit_factor": 1.25, + "net_avg_r_ex_top5": 0.004, + "hold_days": 30, + "hold_avg_r": 0.583, + "hold_net_avg_r": 0.537, + "hold_total_r": 1348.89 + }, + { + "variant": "momentum_only", + "total": 14696, + "wins": 6827, + "losses": 7359, + "expired": 510, + "hit_rate": 48.1, + "avg_r": 0.114, + "total_r": 1669.64, + "net_avg_r": 0.064, + "net_total_r": 936.93, + "best_r": 8.85, + "worst_r": -5.71, + "avg_hold_days": 8.4, + "net_r_per_day": 0.0076, + "median_net_r": -1.013, + "profit_factor": 1.11, + "net_avg_r_ex_top5": -0.055, + "hold_days": 30, + "hold_avg_r": 0.395, + "hold_net_avg_r": 0.345, + "hold_total_r": 5798.82 + } + ], + "gate_ablation_note": "Each row re-qualifies the same candidates at the current momentum cutoff (80) with one floor removed (long-only while the momentum gate is active). If dropping a floor doesn't hurt net expectancy, that floor isn't pulling its weight. The Hold columns grade the same variants under the hold-to-horizon time exit instead of the S/R target \u2014 the view that matters if the exit policy moves to a fixed hold.", + "time_exit_sweep": [ + { + "hold_days": 5, + "total": 1086, + "wins": 603, + "win_rate": 55.5, + "avg_r": 0.175, + "total_r": 190.16, + "net_avg_r": 0.129, + "net_total_r": 139.97, + "best_r": 5.09, + "worst_r": -2.51, + "avg_hold_days": 4.5, + "net_r_per_day": 0.0285, + "median_net_r": 0.115, + "profit_factor": 1.36, + "net_avg_r_ex_top5": -0.002 + }, + { + "hold_days": 10, + "total": 1086, + "wins": 559, + "win_rate": 51.5, + "avg_r": 0.357, + "total_r": 387.9, + "net_avg_r": 0.311, + "net_total_r": 337.7, + "best_r": 6.73, + "worst_r": -2.51, + "avg_hold_days": 7.9, + "net_r_per_day": 0.0395, + "median_net_r": 0.031, + "profit_factor": 1.67, + "net_avg_r_ex_top5": 0.112 + }, + { + "hold_days": 21, + "total": 1086, + "wins": 487, + "win_rate": 44.8, + "avg_r": 0.525, + "total_r": 570.33, + "net_avg_r": 0.479, + "net_total_r": 520.14, + "best_r": 9.86, + "worst_r": -3.38, + "avg_hold_days": 13.7, + "net_r_per_day": 0.0349, + "median_net_r": -1.027, + "profit_factor": 1.81, + "net_avg_r_ex_top5": 0.191 + }, + { + "hold_days": 30, + "total": 1086, + "wins": 434, + "win_rate": 40.0, + "avg_r": 0.631, + "total_r": 684.97, + "net_avg_r": 0.585, + "net_total_r": 634.78, + "best_r": 12.87, + "worst_r": -3.38, + "avg_hold_days": 17.8, + "net_r_per_day": 0.0329, + "median_net_r": -1.033, + "profit_factor": 1.9, + "net_avg_r_ex_top5": 0.212 + } + ], + "portfolio_sim": { + "params": { + "starting_capital": 10000.0, + "max_positions": 10, + "risk_per_trade_pct": 1.0, + "notional_cap_pct": 20.0, + "cost_per_side_pct": 0.1, + "hold_days": 30 + }, + "policies": [], + "note": "One capital-constrained book over the same qualified setups the tables above grade per-setup: at most 10 concurrent positions (one per ticker), best momentum first, fixed-fractional risk sizing with a no-leverage cap, entries at the detection close, stops filled at the worse of stop or open. 'target' races the S/R target against the stop (timeout at the horizon); 'hold' keeps the initial stop and exits at the horizon close. SPY return is price-only over the same window. In-sample; no dividends." + }, + "strategy_variants": { + "variants": [], + "note": "Research-only hold-to-horizon portfolio variants. Production now uses residual 12-1 momentum at cutoff 80; the remaining rows compare the legacy raw rank, raw cutoff 90, one max-15 capacity check, and volatility overlays." + }, + "exit_policy_variants": { + "variants": [], + "note": "Research-only exit policies over the residual/high-vol 80/20 entry candidate. Every row uses the same entry qualification/ranking and changes only the exit discipline." + }, + "portfolio_monitor": null, + "production_cadence_comparison": null, + "holdout": null, + "min_rr_sweep": null, + "target_model_diagnostics": { + "target_model": "production_gtl", + "target_model_label": "Live GTL (production)", + "candidate_count": 202769, + "primary_source_counts": { + "pivot_point": 196294, + "range_grid": 180039 + }, + "primary_round_only": 0, + "primary_strength_100": 138599, + "avg_primary_strength": 80.109, + "avg_primary_distance_atr": 2.293, + "avg_primary_rejection_count": 41.907, + "avg_raw_level_count": 53.204, + "avg_gate_level_count": 53.204 + }, + "signal_eval": [ + { + "signal": "high_52w", + "weeks": 35, + "avg_cross_section": 1471.2, + "mean_ic": 0.1283, + "ic_t_stat": 4.28, + "ic_positive_pct": 85.7, + "mean_quintile_spread": -0.1009, + "reliable": true, + "liquid_breadth_top_n": 1500, + "liquid_min_price": 5.0 + }, + { + "signal": "mom_12_1", + "weeks": 35, + "avg_cross_section": 1471.2, + "mean_ic": 0.0997, + "ic_t_stat": 4.56, + "ic_positive_pct": 88.6, + "mean_quintile_spread": -0.1001, + "reliable": true, + "liquid_breadth_top_n": 1500, + "liquid_min_price": 5.0 + }, + { + "signal": "mom_6_1", + "weeks": 40, + "avg_cross_section": 1474.8, + "mean_ic": 0.0681, + "ic_t_stat": 3.45, + "ic_positive_pct": 77.5, + "mean_quintile_spread": -0.0322, + "reliable": true, + "liquid_breadth_top_n": 1500, + "liquid_min_price": 5.0 + }, + { + "signal": "fip_id", + "weeks": 35, + "avg_cross_section": 1471.2, + "mean_ic": 0.0575, + "ic_t_stat": 5.12, + "ic_positive_pct": 88.6, + "mean_quintile_spread": 0.0199, + "reliable": true, + "liquid_breadth_top_n": 1500, + "liquid_min_price": 5.0 + }, + { + "signal": "trend_200", + "weeks": 37, + "avg_cross_section": 1472.8, + "mean_ic": 0.0538, + "ic_t_stat": 2.33, + "ic_positive_pct": 75.7, + "mean_quintile_spread": -0.0675, + "reliable": true, + "liquid_breadth_top_n": 1500, + "liquid_min_price": 5.0 + }, + { + "signal": "mom_3_1", + "weeks": 42, + "avg_cross_section": 1476.0, + "mean_ic": 0.0523, + "ic_t_stat": 3.27, + "ic_positive_pct": 73.8, + "mean_quintile_spread": -0.0194, + "reliable": true, + "liquid_breadth_top_n": 1500, + "liquid_min_price": 5.0 + }, + { + "signal": "mom_12_1_resid", + "weeks": 35, + "avg_cross_section": 1471.2, + "mean_ic": 0.0388, + "ic_t_stat": 2.28, + "ic_positive_pct": 74.3, + "mean_quintile_spread": -0.0542, + "reliable": true, + "liquid_breadth_top_n": 1500, + "liquid_min_price": 5.0 + }, + { + "signal": "reversal_1m", + "weeks": 43, + "avg_cross_section": 1476.6, + "mean_ic": 0.0155, + "ic_t_stat": 0.85, + "ic_positive_pct": 48.8, + "mean_quintile_spread": -0.0862, + "reliable": true, + "liquid_breadth_top_n": 1500, + "liquid_min_price": 5.0 + }, + { + "signal": "vol_6m", + "weeks": 40, + "avg_cross_section": 1474.8, + "mean_ic": -0.1584, + "ic_t_stat": -6.05, + "ic_positive_pct": 12.5, + "mean_quintile_spread": 0.0164, + "reliable": true, + "liquid_breadth_top_n": 1500, + "liquid_min_price": 5.0 + } + ], + "signal_eval_note": "Cross-sectional rank-IC of price-only signals vs the forward 30-day return (min 20 names/window). |IC| \u2273 0.03 with a consistent sign is a real (if small) edge; near 0 means ranking on it sorts nothing. Momentum factors and high_52w are expected positive; reversal_1m and vol_6m expected negative (mean-reversion / low-vol anomaly). IC is measured on non-overlapping windows; signals with fewer than 12 independent windows are flagged unreliable (too few regimes \u2014 deepen history with the Data Backfill job).", + "note": "Sentiment & fundamentals held neutral (no point-in-time history). Stops fill at the worse of the stop or the bar's open (gaps through the stop are modeled, so a loss can exceed \u22121R); targets never fill better than their level. ~6 months \u2248 one market regime \u2014 treat as directional, not gospel.", + "recommendation": { + "headline": "Trade the qualified list long-only; hold 30 trading days with the initial ATR stop.", + "items": [ + { + "topic": "exit", + "text": "Legacy exit diagnostic: hold 30 trading days with the initial stop (+0.58R net/trade vs +0.21R for the S/R target exit)." + }, + { + "topic": "gate", + "text": "Gate: the confidence floor adds nothing \u2014 dropping it costs +0.01R/trade and adds 7 trades." + }, + { + "topic": "gate", + "text": "Gate: keep the R:R floor (worth +0.28R/trade under the hold exit)." + }, + { + "topic": "gate", + "text": "Gate: keep the NEUTRAL exclusion (worth +0.05R/trade under the hold exit)." + }, + { + "topic": "cutoff", + "text": "Residual-momentum cutoff: 90 has the best per-trade net (+0.23R over 497 setups)." + }, + { + "topic": "robustness", + "text": "Robustness: expectancy survives removing the top 5% of winners (+0.21R net/trade under the recommended 30d hold) \u2014 the edge is not a handful of outliers." + } + ], + "note": "Derived from this report's numbers on every run \u2014 the advice flips if the data does." + }, + "research_recommendation": { + "items": [], + "note": "Strategy variants unavailable; re-run the backtest after benchmark data is present." + } +} \ No newline at end of file diff --git a/reports/fip-breadth-20260718-211440-fingerprint.json b/reports/fip-breadth-20260718-211440-fingerprint.json new file mode 100644 index 0000000..0fd69cf --- /dev/null +++ b/reports/fip-breadth-20260718-211440-fingerprint.json @@ -0,0 +1,578 @@ +{ + "generated_at": "2026-07-18T19:16:37.954856+00:00", + "tickers": 506, + "rank_only_tickers": 0, + "candidates": 202765, + "qualified": 1086, + "params": { + "step_days": 5, + "step_sessions": 5, + "entry_cadence": "weekly", + "signal_eval_cadence": "weekly", + "horizon_days": 30, + "min_lookback": 60, + "cost_per_side_pct": 0.1, + "target_model": "production_gtl", + "target_model_label": "Live GTL (production)", + "is_production_target_model": true, + "production_reentry_policy": "gate_reset", + "liquid_breadth_top_n": null, + "liquid_min_price": null, + "signal_eval_only": true + }, + "activation": { + "min_momentum_percentile": 80.0, + "min_rr": 2.0, + "min_confidence": 0.0, + "require_high_conviction": false, + "exclude_conflicts": false, + "exclude_neutral": true + }, + "overall_qualified": { + "total": 1086, + "wins": 379, + "losses": 591, + "expired": 116, + "hit_rate": 39.1, + "avg_r": 0.255, + "total_r": 276.76, + "net_avg_r": 0.209, + "net_total_r": 226.56, + "best_r": 8.85, + "worst_r": -3.38, + "avg_hold_days": 12.0, + "net_r_per_day": 0.0174, + "median_net_r": -1.031, + "profit_factor": 1.34, + "net_avg_r_ex_top5": 0.049 + }, + "overall_all": { + "total": 202765, + "wins": 82220, + "losses": 113809, + "expired": 6736, + "hit_rate": 41.9, + "avg_r": -0.04, + "total_r": -8186.18, + "net_avg_r": -0.095, + "net_total_r": -19184.55, + "best_r": 9.24, + "worst_r": -16.42, + "avg_hold_days": 8.2, + "net_r_per_day": -0.0115, + "median_net_r": -1.035, + "profit_factor": 0.85, + "net_avg_r_ex_top5": -0.22 + }, + "by_direction": { + "long": { + "total": 1086, + "wins": 379, + "losses": 591, + "expired": 116, + "hit_rate": 39.1, + "avg_r": 0.255, + "total_r": 276.76, + "net_avg_r": 0.209, + "net_total_r": 226.56, + "best_r": 8.85, + "worst_r": -3.38, + "avg_hold_days": 12.0, + "net_r_per_day": 0.0174, + "median_net_r": -1.031, + "profit_factor": 1.34, + "net_avg_r_ex_top5": 0.049 + }, + "short": { + "total": 0, + "wins": 0, + "losses": 0, + "expired": 0, + "hit_rate": null, + "avg_r": null, + "total_r": null, + "net_avg_r": null, + "net_total_r": null, + "best_r": null, + "worst_r": null, + "avg_hold_days": null, + "net_r_per_day": null, + "median_net_r": null, + "profit_factor": null, + "net_avg_r_ex_top5": null + } + }, + "min_momentum_percentile": 80.0, + "sweep": [ + { + "min_momentum_percentile": 90.0, + "total": 497, + "wins": 177, + "losses": 269, + "expired": 51, + "hit_rate": 39.7, + "avg_r": 0.276, + "total_r": 137.05, + "net_avg_r": 0.235, + "net_total_r": 116.55, + "best_r": 8.85, + "worst_r": -3.38, + "avg_hold_days": 11.8, + "net_r_per_day": 0.0199, + "median_net_r": -1.026, + "profit_factor": 1.39, + "net_avg_r_ex_top5": 0.071 + }, + { + "min_momentum_percentile": 80.0, + "total": 1086, + "wins": 379, + "losses": 591, + "expired": 116, + "hit_rate": 39.1, + "avg_r": 0.255, + "total_r": 276.76, + "net_avg_r": 0.209, + "net_total_r": 226.56, + "best_r": 8.85, + "worst_r": -3.38, + "avg_hold_days": 12.0, + "net_r_per_day": 0.0174, + "median_net_r": -1.031, + "profit_factor": 1.34, + "net_avg_r_ex_top5": 0.049 + }, + { + "min_momentum_percentile": 70.0, + "total": 1841, + "wins": 597, + "losses": 1062, + "expired": 182, + "hit_rate": 36.0, + "avg_r": 0.152, + "total_r": 280.26, + "net_avg_r": 0.104, + "net_total_r": 190.75, + "best_r": 8.85, + "worst_r": -4.21, + "avg_hold_days": 11.8, + "net_r_per_day": 0.0088, + "median_net_r": -1.037, + "profit_factor": 1.16, + "net_avg_r_ex_top5": -0.055 + }, + { + "min_momentum_percentile": 60.0, + "total": 2772, + "wins": 873, + "losses": 1611, + "expired": 288, + "hit_rate": 35.1, + "avg_r": 0.126, + "total_r": 348.07, + "net_avg_r": 0.075, + "net_total_r": 209.25, + "best_r": 8.85, + "worst_r": -4.21, + "avg_hold_days": 12.0, + "net_r_per_day": 0.0063, + "median_net_r": -1.04, + "profit_factor": 1.12, + "net_avg_r_ex_top5": -0.077 + }, + { + "min_momentum_percentile": 50.0, + "total": 3901, + "wins": 1182, + "losses": 2295, + "expired": 424, + "hit_rate": 34.0, + "avg_r": 0.089, + "total_r": 345.37, + "net_avg_r": 0.038, + "net_total_r": 146.96, + "best_r": 8.85, + "worst_r": -4.86, + "avg_hold_days": 12.1, + "net_r_per_day": 0.0031, + "median_net_r": -1.042, + "profit_factor": 1.06, + "net_avg_r_ex_top5": -0.114 + }, + { + "min_momentum_percentile": 0.0, + "total": 14588, + "wins": 3719, + "losses": 9271, + "expired": 1598, + "hit_rate": 28.6, + "avg_r": -0.065, + "total_r": -952.08, + "net_avg_r": -0.115, + "net_total_r": -1676.86, + "best_r": 9.24, + "worst_r": -15.94, + "avg_hold_days": 12.1, + "net_r_per_day": -0.0095, + "median_net_r": -1.043, + "profit_factor": 0.84, + "net_avg_r_ex_top5": -0.275 + } + ], + "gate_ablation": [ + { + "variant": "all_floors", + "total": 1086, + "wins": 379, + "losses": 591, + "expired": 116, + "hit_rate": 39.1, + "avg_r": 0.255, + "total_r": 276.76, + "net_avg_r": 0.209, + "net_total_r": 226.56, + "best_r": 8.85, + "worst_r": -3.38, + "avg_hold_days": 12.0, + "net_r_per_day": 0.0174, + "median_net_r": -1.031, + "profit_factor": 1.34, + "net_avg_r_ex_top5": 0.049, + "hold_days": 30, + "hold_avg_r": 0.631, + "hold_net_avg_r": 0.585, + "hold_total_r": 684.97 + }, + { + "variant": "no_confidence_floor", + "total": 1093, + "wins": 380, + "losses": 596, + "expired": 117, + "hit_rate": 38.9, + "avg_r": 0.25, + "total_r": 273.55, + "net_avg_r": 0.204, + "net_total_r": 222.99, + "best_r": 8.85, + "worst_r": -3.38, + "avg_hold_days": 11.9, + "net_r_per_day": 0.0171, + "median_net_r": -1.031, + "profit_factor": 1.33, + "net_avg_r_ex_top5": 0.045, + "hold_days": 30, + "hold_avg_r": 0.626, + "hold_net_avg_r": 0.58, + "hold_total_r": 684.08 + }, + { + "variant": "no_rr_floor", + "total": 6849, + "wins": 3235, + "losses": 3409, + "expired": 205, + "hit_rate": 48.7, + "avg_r": 0.112, + "total_r": 770.17, + "net_avg_r": 0.061, + "net_total_r": 418.96, + "best_r": 8.85, + "worst_r": -5.16, + "avg_hold_days": 7.9, + "net_r_per_day": 0.0078, + "median_net_r": -0.061, + "profit_factor": 1.11, + "net_avg_r_ex_top5": -0.061, + "hold_days": 30, + "hold_avg_r": 0.354, + "hold_net_avg_r": 0.303, + "hold_total_r": 2425.86 + }, + { + "variant": "no_neutral_exclusion", + "total": 2313, + "wins": 770, + "losses": 1279, + "expired": 264, + "hit_rate": 37.6, + "avg_r": 0.2, + "total_r": 462.35, + "net_avg_r": 0.154, + "net_total_r": 355.89, + "best_r": 8.85, + "worst_r": -3.38, + "avg_hold_days": 12.6, + "net_r_per_day": 0.0122, + "median_net_r": -1.031, + "profit_factor": 1.25, + "net_avg_r_ex_top5": 0.004, + "hold_days": 30, + "hold_avg_r": 0.583, + "hold_net_avg_r": 0.537, + "hold_total_r": 1348.89 + }, + { + "variant": "momentum_only", + "total": 14696, + "wins": 6827, + "losses": 7359, + "expired": 510, + "hit_rate": 48.1, + "avg_r": 0.114, + "total_r": 1669.64, + "net_avg_r": 0.064, + "net_total_r": 936.93, + "best_r": 8.85, + "worst_r": -5.71, + "avg_hold_days": 8.4, + "net_r_per_day": 0.0076, + "median_net_r": -1.013, + "profit_factor": 1.11, + "net_avg_r_ex_top5": -0.055, + "hold_days": 30, + "hold_avg_r": 0.395, + "hold_net_avg_r": 0.345, + "hold_total_r": 5798.82 + } + ], + "gate_ablation_note": "Each row re-qualifies the same candidates at the current momentum cutoff (80) with one floor removed (long-only while the momentum gate is active). If dropping a floor doesn't hurt net expectancy, that floor isn't pulling its weight. The Hold columns grade the same variants under the hold-to-horizon time exit instead of the S/R target \u2014 the view that matters if the exit policy moves to a fixed hold.", + "time_exit_sweep": [ + { + "hold_days": 5, + "total": 1086, + "wins": 603, + "win_rate": 55.5, + "avg_r": 0.175, + "total_r": 190.16, + "net_avg_r": 0.129, + "net_total_r": 139.97, + "best_r": 5.09, + "worst_r": -2.51, + "avg_hold_days": 4.5, + "net_r_per_day": 0.0285, + "median_net_r": 0.115, + "profit_factor": 1.36, + "net_avg_r_ex_top5": -0.002 + }, + { + "hold_days": 10, + "total": 1086, + "wins": 559, + "win_rate": 51.5, + "avg_r": 0.357, + "total_r": 387.9, + "net_avg_r": 0.311, + "net_total_r": 337.7, + "best_r": 6.73, + "worst_r": -2.51, + "avg_hold_days": 7.9, + "net_r_per_day": 0.0395, + "median_net_r": 0.031, + "profit_factor": 1.67, + "net_avg_r_ex_top5": 0.112 + }, + { + "hold_days": 21, + "total": 1086, + "wins": 487, + "win_rate": 44.8, + "avg_r": 0.525, + "total_r": 570.33, + "net_avg_r": 0.479, + "net_total_r": 520.14, + "best_r": 9.86, + "worst_r": -3.38, + "avg_hold_days": 13.7, + "net_r_per_day": 0.0349, + "median_net_r": -1.027, + "profit_factor": 1.81, + "net_avg_r_ex_top5": 0.191 + }, + { + "hold_days": 30, + "total": 1086, + "wins": 434, + "win_rate": 40.0, + "avg_r": 0.631, + "total_r": 684.97, + "net_avg_r": 0.585, + "net_total_r": 634.78, + "best_r": 12.87, + "worst_r": -3.38, + "avg_hold_days": 17.8, + "net_r_per_day": 0.0329, + "median_net_r": -1.033, + "profit_factor": 1.9, + "net_avg_r_ex_top5": 0.212 + } + ], + "portfolio_sim": { + "params": { + "starting_capital": 10000.0, + "max_positions": 10, + "risk_per_trade_pct": 1.0, + "notional_cap_pct": 20.0, + "cost_per_side_pct": 0.1, + "hold_days": 30 + }, + "policies": [], + "note": "One capital-constrained book over the same qualified setups the tables above grade per-setup: at most 10 concurrent positions (one per ticker), best momentum first, fixed-fractional risk sizing with a no-leverage cap, entries at the detection close, stops filled at the worse of stop or open. 'target' races the S/R target against the stop (timeout at the horizon); 'hold' keeps the initial stop and exits at the horizon close. SPY return is price-only over the same window. In-sample; no dividends." + }, + "strategy_variants": { + "variants": [], + "note": "Research-only hold-to-horizon portfolio variants. Production now uses residual 12-1 momentum at cutoff 80; the remaining rows compare the legacy raw rank, raw cutoff 90, one max-15 capacity check, and volatility overlays." + }, + "exit_policy_variants": { + "variants": [], + "note": "Research-only exit policies over the residual/high-vol 80/20 entry candidate. Every row uses the same entry qualification/ranking and changes only the exit discipline." + }, + "portfolio_monitor": null, + "production_cadence_comparison": null, + "holdout": null, + "min_rr_sweep": null, + "target_model_diagnostics": { + "target_model": "production_gtl", + "target_model_label": "Live GTL (production)", + "candidate_count": 202765, + "primary_source_counts": { + "pivot_point": 196290, + "range_grid": 180036 + }, + "primary_round_only": 0, + "primary_strength_100": 138596, + "avg_primary_strength": 80.109, + "avg_primary_distance_atr": 2.293, + "avg_primary_rejection_count": 41.908, + "avg_raw_level_count": 53.204, + "avg_gate_level_count": 53.204 + }, + "signal_eval": [ + { + "signal": "vol_6m", + "weeks": 39, + "avg_cross_section": 498.2, + "mean_ic": 0.0609, + "ic_t_stat": 1.48, + "ic_positive_pct": 64.1, + "mean_quintile_spread": 0.0337, + "reliable": true + }, + { + "signal": "mom_12_1_resid", + "weeks": 35, + "avg_cross_section": 497.7, + "mean_ic": 0.0552, + "ic_t_stat": 1.98, + "ic_positive_pct": 60.0, + "mean_quintile_spread": 0.0207, + "reliable": true + }, + { + "signal": "mom_12_1", + "weeks": 35, + "avg_cross_section": 497.7, + "mean_ic": 0.0531, + "ic_t_stat": 1.61, + "ic_positive_pct": 65.7, + "mean_quintile_spread": 0.0206, + "reliable": true + }, + { + "signal": "trend_200", + "weeks": 37, + "avg_cross_section": 497.9, + "mean_ic": 0.0161, + "ic_t_stat": 0.44, + "ic_positive_pct": 59.5, + "mean_quintile_spread": 0.006, + "reliable": true + }, + { + "signal": "reversal_1m", + "weeks": 43, + "avg_cross_section": 498.7, + "mean_ic": 0.0059, + "ic_t_stat": 0.22, + "ic_positive_pct": 53.5, + "mean_quintile_spread": 0.0053, + "reliable": true + }, + { + "signal": "mom_6_1", + "weeks": 39, + "avg_cross_section": 498.2, + "mean_ic": 0.0051, + "ic_t_stat": 0.21, + "ic_positive_pct": 56.4, + "mean_quintile_spread": 0.0087, + "reliable": true + }, + { + "signal": "mom_3_1", + "weeks": 42, + "avg_cross_section": 498.5, + "mean_ic": -0.0064, + "ic_t_stat": -0.25, + "ic_positive_pct": 50.0, + "mean_quintile_spread": 0.0046, + "reliable": true + }, + { + "signal": "high_52w", + "weeks": 35, + "avg_cross_section": 497.7, + "mean_ic": -0.0086, + "ic_t_stat": -0.26, + "ic_positive_pct": 54.3, + "mean_quintile_spread": -0.0088, + "reliable": true + }, + { + "signal": "fip_id", + "weeks": 35, + "avg_cross_section": 497.7, + "mean_ic": -0.045, + "ic_t_stat": -2.91, + "ic_positive_pct": 25.7, + "mean_quintile_spread": -0.0168, + "reliable": true + } + ], + "signal_eval_note": "Cross-sectional rank-IC of price-only signals vs the forward 30-day return (min 20 names/window). |IC| \u2273 0.03 with a consistent sign is a real (if small) edge; near 0 means ranking on it sorts nothing. Momentum factors and high_52w are expected positive; reversal_1m and vol_6m expected negative (mean-reversion / low-vol anomaly). IC is measured on non-overlapping windows; signals with fewer than 12 independent windows are flagged unreliable (too few regimes \u2014 deepen history with the Data Backfill job).", + "note": "Sentiment & fundamentals held neutral (no point-in-time history). Stops fill at the worse of the stop or the bar's open (gaps through the stop are modeled, so a loss can exceed \u22121R); targets never fill better than their level. ~6 months \u2248 one market regime \u2014 treat as directional, not gospel.", + "recommendation": { + "headline": "Trade the qualified list long-only; hold 30 trading days with the initial ATR stop.", + "items": [ + { + "topic": "exit", + "text": "Legacy exit diagnostic: hold 30 trading days with the initial stop (+0.58R net/trade vs +0.21R for the S/R target exit)." + }, + { + "topic": "gate", + "text": "Gate: the confidence floor adds nothing \u2014 dropping it costs +0.01R/trade and adds 7 trades." + }, + { + "topic": "gate", + "text": "Gate: keep the R:R floor (worth +0.28R/trade under the hold exit)." + }, + { + "topic": "gate", + "text": "Gate: keep the NEUTRAL exclusion (worth +0.05R/trade under the hold exit)." + }, + { + "topic": "cutoff", + "text": "Residual-momentum cutoff: 90 has the best per-trade net (+0.23R over 497 setups)." + }, + { + "topic": "robustness", + "text": "Robustness: expectancy survives removing the top 5% of winners (+0.21R net/trade under the recommended 30d hold) \u2014 the edge is not a handful of outliers." + } + ], + "note": "Derived from this report's numbers on every run \u2014 the advice flips if the data does." + }, + "research_recommendation": { + "items": [], + "note": "Strategy variants unavailable; re-run the backtest after benchmark data is present." + } +} \ No newline at end of file diff --git a/reports/fip-breadth-20260718-211440.json b/reports/fip-breadth-20260718-211440.json new file mode 100644 index 0000000..5211c38 --- /dev/null +++ b/reports/fip-breadth-20260718-211440.json @@ -0,0 +1,59 @@ +{ + "generated_at": "2026-07-18T21:14:40.170961", + "liquid_breadth_top_n": 1500, + "liquid_min_price": 5.0, + "fingerprint": { + "signal": "fip_id", + "weeks": 35, + "avg_cross_section": 497.7, + "mean_ic": -0.045, + "ic_t_stat": -2.91, + "ic_positive_pct": 25.7, + "mean_quintile_spread": -0.0168, + "reliable": true, + "pass": true, + "expected_ic": -0.045, + "expected_t": -2.9 + }, + "breadth": { + "signal": "fip_id", + "weeks": 35, + "avg_cross_section": 1471.2, + "mean_ic": 0.0575, + "ic_t_stat": 5.12, + "ic_positive_pct": 88.6, + "mean_quintile_spread": 0.0199, + "reliable": true, + "liquid_breadth_top_n": 1500, + "liquid_min_price": 5.0 + }, + "verdict": { + "green": false, + "reason": "iron rule not met on liquid-breadth cross-section", + "checks": { + "mean_ic": 0.0575, + "abs_mean_ic_ge_0_03": true, + "sign_negative": false, + "ic_t_stat": 5.12, + "reliable": true, + "weeks": 35, + "avg_cross_section": 1471.2 + }, + "row": { + "signal": "fip_id", + "weeks": 35, + "avg_cross_section": 1471.2, + "mean_ic": 0.0575, + "ic_t_stat": 5.12, + "ic_positive_pct": 88.6, + "mean_quintile_spread": 0.0199, + "reliable": true, + "liquid_breadth_top_n": 1500, + "liquid_min_price": 5.0 + } + }, + "fingerprint_report_path": "reports/fip-breadth-20260718-211440-fingerprint.json", + "breadth_report_path": "reports/fip-breadth-20260718-211440-breadth.json", + "breadth_tickers": 4650, + "breadth_rank_only_tickers": 4144 +} \ No newline at end of file