feat: grade gate-ablation variants under the hold-to-horizon exit too
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:
@@ -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],
|
||||
|
||||
Reference in New Issue
Block a user