feat: grade gate-ablation variants under the hold-to-horizon exit too
Deploy / lint (push) Successful in 6s
Deploy / test (push) Successful in 55s
Deploy / deploy (push) Successful in 33s

The ablation judged floors under the target/stop model, but the exit
sweeps point at replacing that exit with a fixed hold — under which the
R:R floor's rationale (bigger payoff at the target) may not apply. Each
ablation row now also carries hold_avg_r / hold_net_avg_r / hold_total_r
(30d hold, initial stop only), so the Phase 3 gate decision can be read
under the exit policy that would actually be used.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 11:34:41 +02:00
parent 8750aac6d9
commit 942a22ce65
4 changed files with 47 additions and 11 deletions
+24 -10
View File
@@ -911,16 +911,27 @@ def _gate_ablation(candidates: list[dict], activation: dict, threshold: float) -
("no_neutral_exclusion", [rr_ok, conf_ok, tighteners_ok]),
("momentum_only", []),
]
return [
{
# Grade each variant under BOTH exit models: the target/stop outcome
# (_bucket_stats) and the hold-to-horizon time exit. A floor that pays under
# the target model may be meaningless once the exit is a fixed hold — the
# hold_* columns are what a time-exit gate decision should read.
hold_days = max(TIME_EXIT_DAYS)
rows: list[dict] = []
for name, checks in variants:
matching = [
c for c in candidates
if momentum_ok(c) and all(check(c) for check in checks)
]
hold = _time_exit_bucket(matching, hold_days)
rows.append({
"variant": name,
**_bucket_stats([
c for c in candidates
if momentum_ok(c) and all(check(c) for check in checks)
]),
}
for name, checks in variants
]
**_bucket_stats(matching),
"hold_days": hold_days,
"hold_avg_r": hold["avg_r"],
"hold_net_avg_r": hold["net_avg_r"],
"hold_total_r": hold["total_r"],
})
return rows
async def run_backtest(
@@ -1051,7 +1062,10 @@ async def run_backtest(
"Each row re-qualifies the same candidates at the current momentum "
f"cutoff ({current_min_pct:.0f}) 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."
"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 — the view that matters if the exit "
"policy moves to a fixed hold."
),
"take_profit_sweep": [_take_profit_bucket(qualified, tp) for tp in TP_LEVELS],
"trailing_sweep": [_trailing_bucket(qualified, round(f * 100)) for f in TRAIL_LEVELS],