from_env merges os.environ, and importing the .env-loading consumers populates TRADING_MIN_ENTRY_GAP_PCT — so the "bare shell disables the gap gate" assertion must delenv the key first. Test-only fix; the module behaviour is unchanged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
65 lines
2.8 KiB
Python
65 lines
2.8 KiB
Python
"""Module-level tests for strategy/population.py — the shared population gate.
|
|
|
|
Complements tests/unit/test_population_gate.py (which pins the LIVE seam through
|
|
the service). Here we test the module directly: from_env key parity with
|
|
production, the as_of contract docstring, and fail-open semantics.
|
|
"""
|
|
|
|
import inspect
|
|
|
|
from strategy.population import PopulationConfig, population_reject
|
|
|
|
|
|
def test_from_env_defaults_match_production_keys():
|
|
cfg = PopulationConfig.from_env(env={
|
|
"TRADING_PRICE_MIN": "2.0",
|
|
"TRADING_PRICE_MAX": "20.0",
|
|
"TRADING_MIN_ENTRY_GAP_PCT": "0.10",
|
|
"TRADING_MIN_DAY_VOLUME": "300000",
|
|
})
|
|
assert cfg.price_min == 2.0
|
|
assert cfg.price_max == 20.0
|
|
assert cfg.min_entry_gap_pct == 0.10
|
|
assert cfg.min_day_volume == 300_000
|
|
# constants are NOT env-driven
|
|
assert cfg.gap_dead_band_lo == 0.35
|
|
assert cfg.gap_dead_band_hi == 1.50
|
|
assert cfg.squeeze_rvol_min == 5.0
|
|
|
|
|
|
def test_from_env_bare_shell_disables_gap_gate(monkeypatch):
|
|
# No production env => min_entry_gap_pct defaults to 0.0 (gap gate off), the
|
|
# exact hazard the plan warns offline consumers about. (from_env merges
|
|
# os.environ, which .env-loading imports populate — so we clear the key.)
|
|
monkeypatch.delenv("TRADING_MIN_ENTRY_GAP_PCT", raising=False)
|
|
cfg = PopulationConfig.from_env(env={})
|
|
assert cfg.min_entry_gap_pct == 0.0
|
|
assert population_reject(cfg, gap_pct=0.05, price=10.0, volume=None, rvol=None) is None
|
|
|
|
|
|
def test_from_env_clamps_gap_floor_to_unit_interval():
|
|
# mirrors trading_execution._qfloat: percent-typed 40 must NOT survive as 40.
|
|
assert PopulationConfig.from_env(env={"TRADING_MIN_ENTRY_GAP_PCT": "40"}).min_entry_gap_pct == 1.0
|
|
assert PopulationConfig.from_env(env={"TRADING_MIN_ENTRY_GAP_PCT": "junk"}).min_entry_gap_pct == 0.0
|
|
|
|
|
|
def test_as_of_contract_documented():
|
|
# day-end rejection is by-convention (review-enforced), so the contract MUST
|
|
# be spelled out in the signature + docstring.
|
|
assert "as_of" in inspect.signature(population_reject).parameters
|
|
doc = population_reject.__doc__ or ""
|
|
assert "FRACTION" in doc and "forbidden" in doc.lower()
|
|
|
|
|
|
def test_fail_open_on_all_missing():
|
|
cfg = PopulationConfig(min_entry_gap_pct=0.10, min_day_volume=300_000)
|
|
assert population_reject(cfg, gap_pct=None, price=None, volume=None, rvol=None) is None
|
|
|
|
|
|
def test_offline_consumer_must_pass_prod_thresholds():
|
|
# bare config admits everything; explicit prod thresholds gate correctly.
|
|
bare = PopulationConfig()
|
|
assert population_reject(bare, gap_pct=0.02, price=3.0, volume=1, rvol=None) is None
|
|
prod = PopulationConfig(min_entry_gap_pct=0.10, min_day_volume=300_000)
|
|
assert population_reject(prod, gap_pct=0.02, price=3.0, volume=1, rvol=None) is not None
|