TRADING_GAP_SQUEEZE_ENABLED=0 rejects >=150% gappers in the population gate (watcher pre-filter and entry gate) and zeroes the trigger book's squeeze arm. One switch covers all three paths. Why: while testing the trader's "trade the leading gainers" idea on 1,093 live-band v1 ORB breaks (Jun-Sep 2026, current exits), top-3 gainers at the break did WORSE (diff -0.48R Jun-Jul, -0.40R Aug-Sep, 90% CIs excluding 0). That turned out to be almost entirely the >=150% squeeze-exception names: gap 10-35% Jun-Jul +0.27R (n=342) Aug-Sep +0.13R (n=327) gap >=150% Jun-Jul -0.21R (n=65) Aug-Sep -0.30R (n=53) Our own squeeze fills: -0.31R / -0.33R realized (n=16). By the time a squeeze name breaks its opening range the move has happened; the trader catches these names premarket, which the bot does not. This finding was post-hoc (not the pre-registered direction), but it held in both halves and in the real fills. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E3S5EFKThFVrjmN4HHsHMN
91 lines
2.9 KiB
Python
91 lines
2.9 KiB
Python
"""Characterization tests pinning the CURRENT behaviour of the live population
|
|
gate (``TradingExecutionService._population_reject``) BEFORE it is extracted to
|
|
``strategy/population.py`` (plan 004).
|
|
|
|
CONTRACT: ``gap_pct`` is a FRACTION (0.12 == 12%). These tests must stay green
|
|
byte-for-byte across the extraction — a change in any outcome means the move
|
|
altered live behaviour, which the plan forbids (it MOVES code, it does not
|
|
retune).
|
|
"""
|
|
|
|
from trading_execution import ExecutionSettings, TradingExecutionService
|
|
|
|
# production-shaped settings (deploy/docker/truenas-compose.yml env block)
|
|
PROD = dict(
|
|
min_entry_gap_pct=0.10,
|
|
min_day_volume=300_000,
|
|
price_band_min=2.0,
|
|
price_band_max=20.0,
|
|
)
|
|
|
|
|
|
def _gate(**overrides):
|
|
"""Return ``_population_reject`` bound to production-shaped settings.
|
|
|
|
Built the way the existing execution tests do it (``__new__`` + set
|
|
``.settings``) so nothing else in the service needs constructing.
|
|
"""
|
|
svc = TradingExecutionService.__new__(TradingExecutionService)
|
|
svc.settings = ExecutionSettings(**{**PROD, **overrides})
|
|
|
|
def call(gap_pct=0.12, cum_volume=1_000_000, price=10.0, rvol=None):
|
|
return svc._population_reject(gap_pct, cum_volume, price=price, rvol=rvol)
|
|
|
|
return call
|
|
|
|
|
|
def test_in_band_passes():
|
|
# $2-20, 12% open gap, healthy volume => tradeable
|
|
assert _gate()(gap_pct=0.12, price=10.0, cum_volume=1_000_000) is None
|
|
|
|
|
|
def test_below_gap_rejects():
|
|
assert _gate()(gap_pct=0.05) is not None
|
|
|
|
|
|
def test_dead_band_rejects():
|
|
assert _gate()(gap_pct=0.40) is not None
|
|
|
|
|
|
def test_squeeze_exception_passes():
|
|
# >=150% gap re-admitted ONLY with a verified squeeze (rvol >= 5)
|
|
assert _gate()(gap_pct=1.60, rvol=6.0) is None
|
|
|
|
|
|
def test_squeeze_rvol_fail_rejects():
|
|
assert _gate()(gap_pct=1.60, rvol=3.0) is not None
|
|
|
|
|
|
def test_price_band_low_rejects():
|
|
assert _gate()(price=1.50) is not None
|
|
|
|
|
|
def test_price_band_high_rejects():
|
|
assert _gate()(price=21.5) is not None
|
|
|
|
|
|
def test_volume_floor_rejects():
|
|
assert _gate()(cum_volume=100_000) is not None
|
|
|
|
|
|
def test_missing_data_fail_open():
|
|
# None gap / None volume / None price => never blocks
|
|
assert _gate()(gap_pct=None, cum_volume=None, price=None) is None
|
|
|
|
|
|
def test_gap_is_a_fraction_not_percent():
|
|
# 0.40 (== 40%) lands in the 35-150% dead band. A caller that wrongly passed
|
|
# 40.0 (percent) would trip the >=150% squeeze branch instead — a DIFFERENT
|
|
# reason. The distinct reason strings pin the fraction contract.
|
|
assert "dead band" in _gate()(gap_pct=0.40)
|
|
assert "squeeze" in _gate()(gap_pct=40.0)
|
|
|
|
|
|
def test_gap_gate_disabled_when_min_zero():
|
|
# The whole gap logic is nested under ``min_entry_gap_pct > 0``.
|
|
g = _gate(min_entry_gap_pct=0.0)
|
|
assert g(gap_pct=0.05) is None # below-floor not checked
|
|
assert g(gap_pct=0.40) is None # dead band not checked
|
|
assert g(gap_pct=1.60, rvol=1.0) is None # squeeze not checked
|
|
|