Population gate audit: static overnight gap everywhere + full band
The 2026-07-08 audit (owner: "figure out if there is some kind of logic or audit issue") found BOTH entry paths feeding the population gate the WRONG gap. The fast path used the trigger book's dynamic gap (latest price vs prev close), so intraday rallies masqueraded as gappers: TVRD (6.0% true gap), SKIN (2.2%, filled at $0.98), JLHL (4.3%) all traded outside the validated population. The slow path used max(gap, intraday) with "open" taken from the first lookback-window bar, and had no upper band at all — dead-band names SEER (38.6%) and IOTR (40.7%) entered. Fixes: candidates carry gap_open_pct (session open vs prev close); trigger eligibility + submit_breakout_now use it exclusively once the session is open; _population_reject now implements the full validated band (10-35% OR volume-verified >=150% squeeze, $1-20 price band) for both paths; evaluator gap uses the session open; signals carry relative_volume for squeeze verification. Plus momentum-testpg.service so the test-isolation Postgres survives reboots. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
1 parent
2e5346e035
commit
44ff36a669
8 files changed
+103
-13
No files matched your search
@@ -834,3 +834,24 @@ def test_breakout_now_respects_dilution_veto(store):
|
||||
service = _service_with_catalyst(store, advisory, veto_enabled=True)
|
||||
res = service.submit_breakout_now("DILUT", trigger=14.0, stop=13.45, last_price=14.0)
|
||||
assert res["ok"] is False and res["skipped"] == "dilution_veto"
|
||||
|
||||
|
||||
def test_population_gate_full_band_semantics():
|
||||
"""2026-07-08 audit: the gate must implement the VALIDATED population — static
|
||||
overnight gap in [10%, 35%] OR volume-verified squeeze >=150%; $1-20 price band.
|
||||
TVRD (6%), SKIN (2.2%, $0.98), JLHL (4.3%) traded outside it; SEER (38.6%) and
|
||||
IOTR (40.7%) came from the dead band."""
|
||||
from trading_execution import ExecutionSettings, TradingExecutionService
|
||||
svc = TradingExecutionService.__new__(TradingExecutionService)
|
||||
svc.settings = ExecutionSettings(min_entry_gap_pct=0.10, min_day_volume=300_000)
|
||||
ok = lambda **kw: svc._population_reject(**{"gap_pct": None, "cum_volume": 500_000, **kw})
|
||||
assert ok(gap_pct=0.06) is not None # TVRD: below floor
|
||||
assert ok(gap_pct=0.20) is None # core band trades
|
||||
assert ok(gap_pct=0.40) is not None # SEER/IOTR: dead band
|
||||
assert ok(gap_pct=2.78, rvol=40.0) is None # LHSW: verified squeeze
|
||||
assert ok(gap_pct=2.78, rvol=0.4) is not None # split fake: rejected
|
||||
assert ok(gap_pct=2.78) is not None # unverified squeeze: rejected
|
||||
assert ok(gap_pct=0.20, price=0.98) is not None # SKIN: below $1
|
||||
assert ok(gap_pct=0.20, price=21.5) is not None # above $20
|
||||
assert ok(gap_pct=0.20, price=5.0) is None
|
||||
assert ok(gap_pct=None) is None # fail-open on missing gap
|
||||
@@ -386,3 +386,18 @@ def test_squeeze_exception_off_by_default():
|
||||
b = ArmedTriggerBook(gap_min=10, gap_max=35, rvol_min=2)
|
||||
b.arm([_cand("LHSW", gap=278.0, rvol=40.0)])
|
||||
assert b.triggers["LHSW"].state == WEAK
|
||||
|
||||
|
||||
def test_trigger_eligibility_uses_static_open_gap_not_dynamic():
|
||||
"""2026-07-08 audit: TVRD's true overnight gap was 6% but its dynamic gap crossed
|
||||
10% on an intraday rally and it traded as a 'gapper'. Once the session open is
|
||||
known, eligibility must use the STATIC gap."""
|
||||
b = ArmedTriggerBook(gap_min=10, gap_max=35, rvol_min=2)
|
||||
c = _cand("TVRD", gap=13.2, rvol=6.0) # dynamic says 13.2%...
|
||||
c["gap_open_pct"] = 6.0 # ...but the true overnight gap is 6%
|
||||
b.arm([c])
|
||||
assert b.triggers["TVRD"].state == WEAK
|
||||
c2 = _cand("BABU", gap=25.0, rvol=6.0)
|
||||
c2["gap_open_pct"] = 19.0 # true gapper stays armed
|
||||
b.arm([c2])
|
||||
assert b.triggers["BABU"].state == ARMED
|
||||
Reference in new issue
Block a user