feat: orb_pmhigh_gate_v1 — premarket-high reclaim gate, forward OOS test

H3 (enter the pm-high break at 9:30 instead of the ORB) FAILED its
pre-registered bar on the year replay: paired delta -0.069R (n=689) —
entering earlier on the same names does not pay; the 5-minute wait stays.

The decomposition found the real signal: ORB fires where RTH price also
reclaimed the premarket high ran +0.316R (n=689) vs -0.298R (n=477) where
it never did — 100th percentile of a 2000-draw random-gate null at the
same keep-rate (null p99 +0.127), sign-consistent 14/14 months. First
lever of 15 to survive both checks in-sample.

Because the split was found in this dataset, it counts for nothing until
fresh fires confirm: new 'pmhigh_gate' intervention kind (fail-open on a
missing premarket_structure row, RTH-only reclaim check at/before the
entry bar) + registered spec with expR_delta >= 0.05 at n >= 200 forward
bar. Cohort self-registers on the next nightly experiments run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
momentum devandClaude Fable 5 committed 2026-07-31 22:20:25 -05:00
1 parent 3fe0cb0891
commit c7249b22e2
3 files changed
+96

No files matched your search

@@ -0,0 +1,56 @@
"""iv_pmhigh_gate: keep when RTH reclaimed the premarket high, zero otherwise."""
from datetime import date
import pandas as pd
from research.experiments.replay import Fire, iv_pmhigh_gate
class _Con:
def __init__(self, pm_high):
self._pm = pm_high
def execute(self, sql, params=None):
self._rows = [(self._pm,)] if self._pm is not None else []
return self
def fetchall(self):
return self._rows
def _bars(rows):
"""rows: (high, is_regular_hours)"""
return pd.DataFrame([{"timestamp": i, "high": h, "low": h - 0.2, "open": h - 0.1,
"close": h - 0.05, "is_regular_hours": reg}
for i, (h, reg) in enumerate(rows)])
def _fire():
return Fire(fire_id="o1", symbol="TST", session_date=date(2026, 7, 31), fill_ts=None,
entry_price=10.0, stop_price=9.0, exit_price=11.0, baseline_r=1.0,
trigger_price=10.0)
def test_kept_when_rth_broke_pm_high():
bars = _bars([(9.5, False), (10.6, True), (10.2, True)]) # RTH high 10.6 >= 10.5
v, d = iv_pmhigh_gate(_Con(10.5), _fire(), bars, 2, 1.0)
assert v == 1.0 and d["broke"] is True
def test_zeroed_when_pm_high_never_reclaimed():
# premarket touched 10.5 (definitionally its own high) but RTH never did
bars = _bars([(10.5, False), (10.1, True), (10.2, True)])
v, d = iv_pmhigh_gate(_Con(10.5), _fire(), bars, 2, 1.0)
assert v == 0.0 and d["broke"] is False
def test_fail_open_without_pm_row():
bars = _bars([(10.6, True)])
v, d = iv_pmhigh_gate(_Con(None), _fire(), bars, 0, 1.0)
assert v == 1.0 and d["broke"] is None and d["reason"] == "no_pm_row"
def test_bars_after_entry_do_not_count():
# pm high broken only AFTER the entry bar (index 1) -> gated at entry time
bars = _bars([(9.5, False), (10.1, True), (10.9, True)])
v, d = iv_pmhigh_gate(_Con(10.5), _fire(), bars, 1, 1.0)
assert v == 0.0 and d["broke"] is False