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:
1 parent
9390f3c2a6
commit
485cec0372
3 files changed
+55
No files matched your search
@@ -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}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
Reference in new issue
Block a user