c15b51439eef8b96acc4b72c06c1d097eb1ca560
2
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
ea11efe3d1 |
Sweep the R:R floor; fix a holdout metric artifact
min_rr = 2.0 was hand-set in Admin (2026-06-24) and never swept — the gate ablation only tested the floor on-vs-off, never its level. It was the last un-swept knob in the live gate. Swept against portfolio Sharpe under the real exit, with a parity self-check (reproduces_production_gate: the row at the live floor must rebuild production's exact 1,089-setup qualified set — it does). min_rr qualified in-sample Sh/CAGR OOS Sh/CAGR (entries >= 2024-07) 0.0 6636 1.98 / 58.5% 2.02 / 66.2% 1.2 3897 1.34 / 33.9% 1.12 / 28.8% 1.5 3127 1.20 / 29.6% 1.12 / 28.8% 1.75 1974 1.64 / 44.5% 1.15 / 27.4% 2.0 (live) 1089 2.04 / 50.4% 2.78 / 73.3% 2.25 577 1.64 / 31.8% 1.71 / 31.9% 2.5 286 1.67 / 29.0% 0.68 / 8.7% KEEP 2.0. It is the optimum in both windows, and a peak that reproduces in data it was never fitted to is real evidence. But treat it as fragile: unlike the ATR trail (a plateau), this is a spike with a trough beside it — +/-0.25 costs ~0.4 Sharpe in-sample and ~1.6 out-of-sample — and the curve is bimodal (floor-off is good, 1.2-1.75 is bad, 2.0 is good). The hand-set value landed on the peak by luck, not by tuning. Do not nudge it. Worth knowing: turning the floor OFF entirely is the second-best row in both windows, with substantially higher CAGR (58.5% / 66.2%) and more trades. If CAGR ever outranks Sharpe here, "no R:R floor" is a live option — and it would sever the gate's last dependency on the weak S/R detector. Also fixes a metric artifact in the holdout harness. The train book's equity curve ran to the end of the data while its entries stopped at the split, so it sat in flat cash for two years and deflated its own CAGR/Sharpe (reported 0.95 / 14.6%; actually 1.31 / 29.6%). _simulate_portfolio now truncates the calendar to hold_days after the last entry when end_date is set — it only triggers on the holdout train window, so no other number moves. The clear-air OOS verdict is unaffected: it rests on the test row, whose entries and curve both start at the split and were always clean. Both holdout reports regenerated. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
85b3ef618f |
Research: S/R levels, the target exit, and the entry gate
Investigated whether our support/resistance detection follows best practice and whether we actually use it that way. Three findings, all backed by runs against the prod snapshot and written up in docs/research/sr-levels-and-exits.md: - The S/R target must NOT become an exit. Honoring it as a take-profit on top of the 3x ATR trail drops Sharpe 2.04 -> 1.47 and halves CAGR. Win rate rises (37.5% -> 40.0%), which is the tell: it truncates the right tail where momentum's edge lives. - The clear-air fallback (synthesize a 3xATR target where no resistance exists, so 52-week-high breakouts stop being vetoed) looked strictly better in-sample (Sharpe 2.04 -> 2.07, CAGR 50.4% -> 62.3%, DD 21.4% -> 20.1%) but FAILED a real out-of-sample holdout: on entries after 2024-07-01 it is worse on Sharpe (2.78 -> 2.45) and Calmar, better only on raw CAGR. Not shipped. - The detector itself is weak vs best practice (POC/VAH/VAL computed then discarded, HVN = any above-mean bin, 1.48x volume double-counting, "touch" counts pass-throughs, no round numbers), but its only causal path to P&L is the entry gate. Fix it for the displayed levels, not for returns. Method note: nested lookback windows are NOT out-of-sample. The in-sample result was clean, large, and consistent across five windows, and still did not survive a proper entry-date split. All research paths are off by default and the default report is unchanged: BACKTEST_RESEARCH_EXITS=1 take-profit exit rows BACKTEST_ATR_TARGET_FALLBACK=k synthetic k*ATR target when S/R offers none BACKTEST_FALLBACK_CLEAR_AIR_ONLY=1 restrict that to genuinely clear air BACKTEST_HOLDOUT_SPLIT=YYYY-MM-DD train/test split by entry date Also fixes two reproducibility holes found while reconciling our local baseline against the live report: - create_backtest_snapshot.py now copies paper_% settings. The production monitor row replays the runtime exit policy via get_exit_policy(); without those keys a snapshot silently falls back to code defaults, so a live-tuned exit would never be reflected. - Migration 020 drops activation_min_expected_value and activation_min_target_probability. Both are orphans of the June EV-gate redesign, read by no code path, but prod carries min_target_probability = 50.0 which implies a probability floor that is not enforced (the real floor is the 20% constant in qualification.py). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |