feat: wide_stop_veto_v1 — skip entries with stops >12% below entry (shadow)

Year-replay stop-width buckets: <8% +0.072R, 8-12% +0.120R, 12-16%
-0.054R, >16% -0.092R — the wide cohort holds every recent dollar
disaster (BIYA 16.7%, FCUV day-2 17.5%), where the exit ladder's blind
spot (BE/trail only arm on the way up) rides the full catastrophe
distance. New stopwidth_gate intervention (fail-open without levels) +
registered spec, same forward bar as the pm-high gate; the two cohorts
overlap so the adjudicator attributes which does the work. In-sample
counts for nothing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
momentum devandClaude Fable 5 committed 2026-08-03 21:06:37 -05:00
1 parent 9390f3c2a6
commit 485cec0372
3 files changed
+55

No files matched your search

+9
View File
@@ -0,0 +1,9 @@
{
"id": "wide_stop_veto_v1",
"hypothesis": "Skip ORB entries whose stop sits more than 12% below entry. Year-replay buckets (2026-08-03): <8% +0.072R, 8-12% +0.120R, 12-16% -0.054R, >16% -0.092R; both July dollar-disasters (BIYA 16.7%, FCUV day-2 17.5%) were wide-stop, where the exit ladder's blind spot (nothing arms until price rises) rides the full catastrophe distance. In-sample evidence counts for nothing toward the bar; overlaps orb_pmhigh_gate_v1's cohort — adjudicate both to attribute the work.",
"population": {"min_entry_gap_pct": 0.10, "min_day_volume": 300000, "price_min": 2.0, "price_max": 20.0},
"intervention": {"kind": "stopwidth_gate", "max_stop_pct": 0.12},
"bars": {"expR_delta_min": 0.05, "min_n": 200},
"futility": {"min_n": 400, "expR_delta_max": -0.02},
"fidelity": {"atr_frac": 0.25, "spread_mult": 3, "adverse_ticks": 1}
}
+26
View File
@@ -476,6 +476,26 @@ def iv_asym_exit(con, fire, bars, bars_after, params, fidelity):
# multi-fire coordination needed, so it plugs into the per-fire _compute_fire # multi-fire coordination needed, so it plugs into the per-fire _compute_fire
# path instead of a _run_rank-style batch). # path instead of a _run_rank-style batch).
# --------------------------------------------------------------------------- # # --------------------------------------------------------------------------- #
def iv_stopwidth_gate(fire: "Fire", baseline: float | None,
params: dict) -> tuple[float | None, dict]:
"""variant_r = 0.0 when the entry's stop distance exceeds ``max_stop_pct``
of the entry price, else keep baseline.
Year-replay buckets (2026-08-03): stop <8% +0.072R (n=647), 8-12% +0.120R
(n=340), 12-16% -0.054R (n=112), >16% -0.092R (n=67) — and every dollar
disaster of late July (BIYA 16.7%, FCUV day-2 17.5%) sat in the wide
cohort, where the exit ladder's blind spot (nothing arms until price
rises) rides the full catastrophe distance. Overlaps the pmhigh_gate
cohort (faded day-2 monsters have both traits); running both lets the
adjudicator attribute the work."""
if fire.entry_price in (None, 0) or fire.stop_price is None:
return baseline, {"kind": "stopwidth_gate", "stop_pct": None, "reason": "no_levels"}
stop_pct = (fire.entry_price - fire.stop_price) / fire.entry_price
wide = stop_pct > float(params.get("max_stop_pct", 0.12))
return (0.0 if wide else baseline), {"kind": "stopwidth_gate",
"stop_pct": round(stop_pct, 4), "gated": wide}
def iv_pmhigh_gate(con, fire: "Fire", bars, entry_idx: int, def iv_pmhigh_gate(con, fire: "Fire", bars, entry_idx: int,
baseline: float | None) -> tuple[float | None, dict]: baseline: float | None) -> tuple[float | None, dict]:
"""variant_r = baseline if REGULAR-HOURS price reclaimed the PREMARKET high """variant_r = baseline if REGULAR-HOURS price reclaimed the PREMARKET high
@@ -857,6 +877,12 @@ def _compute_fire(con, exp: dict, fire: Fire, favorable: dict | None = None,
if fire.sim_baseline: if fire.sim_baseline:
d["source"] = "setup_sim" d["source"] = "setup_sim"
return {"baseline_r": baseline, "variant_r": v, "detail": d} return {"baseline_r": baseline, "variant_r": v, "detail": d}
if kind == "stopwidth_gate":
baseline = _sim_baseline_r(fire, bars_after, fidelity) if fire.sim_baseline else fire.baseline_r
v, d = iv_stopwidth_gate(fire, baseline, params)
if fire.sim_baseline:
d["source"] = "setup_sim"
return {"baseline_r": baseline, "variant_r": v, "detail": d}
if kind == "regime_gate": if kind == "regime_gate":
# needs the baseline BEFORE computing variant (unlike the other kinds, # needs the baseline BEFORE computing variant (unlike the other kinds,
# which compute variant first and attach fire.sim_baseline's simulated # which compute variant first and attach fire.sim_baseline's simulated
@@ -54,3 +54,23 @@ def test_bars_after_entry_do_not_count():
bars = _bars([(9.5, False), (10.1, True), (10.9, True)]) bars = _bars([(9.5, False), (10.1, True), (10.9, True)])
v, d = iv_pmhigh_gate(_Con(10.5), _fire(), bars, 1, 1.0) v, d = iv_pmhigh_gate(_Con(10.5), _fire(), bars, 1, 1.0)
assert v == 0.0 and d["broke"] is False assert v == 0.0 and d["broke"] is False
def test_stopwidth_gate_zeroes_wide_and_keeps_tight():
from research.experiments.replay import iv_stopwidth_gate
P = {"max_stop_pct": 0.12}
wide = Fire(fire_id="o1", symbol="W", session_date=date(2026, 8, 3), fill_ts=None,
entry_price=17.60, stop_price=14.43, exit_price=None, baseline_r=-0.7,
trigger_price=17.35) # 18% stop -> gated
v, d = iv_stopwidth_gate(wide, -0.7, P)
assert v == 0.0 and d["gated"] is True
tight = Fire(fire_id="o2", symbol="T", session_date=date(2026, 8, 3), fill_ts=None,
entry_price=10.0, stop_price=9.2, exit_price=None, baseline_r=0.5,
trigger_price=10.0) # 8% stop -> kept
v, d = iv_stopwidth_gate(tight, 0.5, P)
assert v == 0.5 and d["gated"] is False
nolvl = Fire(fire_id="o3", symbol="N", session_date=date(2026, 8, 3), fill_ts=None,
entry_price=None, stop_price=None, exit_price=None, baseline_r=0.3,
trigger_price=None)
v, d = iv_stopwidth_gate(nolvl, 0.3, P)
assert v == 0.3 and d["reason"] == "no_levels" # fail-open