population: characterization tests pinning live _population_reject (pre-extraction)

11 table-driven cases pin the CURRENT behaviour of the live population gate
before plan 004 extracts it to strategy/population.py: in-band pass, below-gap,
dead band, squeeze pass/fail, both price-band sides, volume floor, missing-data
fail-open, the gap-is-a-FRACTION unit pin, and the gap-gate-disabled nesting.
These must stay green byte-for-byte across the extraction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
momentum devandClaude Fable 5 committed 2026-07-13 22:32:03 -04:00
1 parent 23b3d01a00
commit 256481910f
1 file changed
+89
+89
View File
@@ -0,0 +1,89 @@
"""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