Trigger book: mega-gapper squeeze exception (the LHSW/NXTS fix)

LHSW gapped +278% on 100M shares, was rank 2 in the trigger book at
4:49am, ran +346%, and was the source trader's entire day — the bot's
TRIGGER_GAP_MAX=35 capped it out, same as NXTS two weeks ago, while the
bot spent its entries on the shadow round-trips (SEER/ELAB/PLUG/YRD).

Year-scale replay (n=2,879 ORB entries, 14 months, reverse-split
artifacts excluded by requiring a real volume surge): the 35-150% band
is dead (+0.02R / +0.00R — the cap earns its keep there), but REAL
>150% squeezes (n=82) ran +0.63R with 8/14 months >= 0, 4x the core
band's edge. New gap_squeeze_min/gap_squeeze_rvol re-admit gaps >=150%
only when rvol >= 5 (split repricings show rvol ~0.01 and stay out).
Live: TRIGGER_GAP_SQUEEZE_MIN=150, TRIGGER_GAP_SQUEEZE_RVOL=5.0.
Existing guards (trigger/day extension, halt, BE@+5% ratchet, sizing)
still apply to these names.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
momentum devandClaude Opus 4.8 committed 2026-07-06 16:13:04 -04:00
1 parent 842b54c802
commit 2e5346e035
3 files changed
+40 -1

No files matched your search

+2
View File
@@ -357,6 +357,8 @@ def build_runtime(args: argparse.Namespace) -> dict:
max_armed=int(os.environ.get("TRIGGER_MAX_ARMED", "6")),
gap_min=float(os.environ.get("TRIGGER_GAP_MIN", "3.0")),
gap_max=float(os.environ.get("TRIGGER_GAP_MAX", "1e9")),
gap_squeeze_min=float(os.environ.get("TRIGGER_GAP_SQUEEZE_MIN", "0")),
gap_squeeze_rvol=float(os.environ.get("TRIGGER_GAP_SQUEEZE_RVOL", "5.0")),
rvol_min=float(os.environ.get("TRIGGER_RVOL_MIN", "2.0")),
min_range_pct=float(os.environ.get("TRIGGER_MIN_RANGE_PCT", "0.004")),
min_dollar_vol=float(os.environ.get("TRIGGER_MIN_DOLLAR_VOL", "0")),
+15 -1
View File
@@ -101,6 +101,13 @@ class ArmedTriggerBook:
max_armed: int = 6
gap_min: float = 3.0 # min overnight gap % to be eligible to fire
gap_max: float = 1e9 # max gap % — skip blow-off gappers (they fade)
# mega-gapper exception: re-admit gaps >= this % DESPITE gap_max, but only on a
# real volume surge (rvol floor below) so reverse-split repricings stay out.
# Year-scale replay 2026-07-06 (n=2,879, split artifacts removed): the 35-150%
# band is dead (+0.02/+0.00R — gap_max earns its keep there), but REAL >150%
# squeezes (n=82, the LHSW/NXTS class) ran +0.63R with 8/14 months >= 0. 0 = off.
gap_squeeze_min: float = 0.0
gap_squeeze_rvol: float = 5.0 # rvol floor for the exception (split fakes ~0.01)
rvol_min: float = 2.0 # min relative volume to be eligible to fire
min_range_pct: float = 0.004 # opening range must be at least this wide
# min recent $-volume to fire. rvol is RELATIVE, so 2x of near-nothing still
@@ -114,12 +121,19 @@ class ArmedTriggerBook:
triggers: dict[str, ArmedTrigger] = field(default_factory=dict)
_lock: object = field(default_factory=threading.RLock, repr=False, compare=False)
def _gap_ok(self, t: ArmedTrigger) -> bool:
if self.gap_min <= t.gap_pct <= self.gap_max:
return True
return (self.gap_squeeze_min > 0
and t.gap_pct >= self.gap_squeeze_min
and t.rvol >= self.gap_squeeze_rvol)
def _eligible(self, t: ArmedTrigger) -> bool:
return (
t.trigger is not None
and t.stop is not None
and t.stop < t.trigger
and self.gap_min <= t.gap_pct <= self.gap_max
and self._gap_ok(t)
and t.rvol >= self.rvol_min
and t.range_pct >= self.min_range_pct
and t.dollar_vol >= self.min_dollar_vol
+23
View File
@@ -363,3 +363,26 @@ def test_guard_grace_window_protects_just_armed_entry():
"armed_at": datetime.now(), "checks": 0} # armed just now
assert svc.expire_stale_entries() == [] # grace -> not cancelled
assert "o1" in svc._armed and client.canceled == []
def test_gap_cap_blocks_mid_band_but_squeeze_exception_readmits_real_monsters():
"""2026-07-06 (LHSW +278%, the trader's only real profit, capped out at 4:49am):
gap_max still blocks the dead 35-150% band, but a REAL mega-gapper — huge gap
AND a genuine volume surge — re-arms via the squeeze exception. A reverse-split
repricing (huge "gap", no volume) must stay excluded."""
b = ArmedTriggerBook(gap_min=10, gap_max=35, gap_squeeze_min=150,
gap_squeeze_rvol=5.0, rvol_min=2)
b.arm([
_cand("MID", gap=80.0, rvol=20.0), # dead band: capped
_cand("LHSW", gap=278.0, rvol=40.0), # real monster: admitted
_cand("SPLIT", gap=278.0, rvol=0.4), # reverse-split fake: excluded
])
assert b.triggers["MID"].state == WEAK
assert b.triggers["LHSW"].state == ARMED
assert b.triggers["SPLIT"].state == WEAK
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