Align backtest production sim with live runtime config
The portfolio monitor's Production row now replays the live qualification flag and the Admin exit policy (mode/ATR multiplier/hold days) instead of a frozen research-variant gate, so Admin tuning is reflected in the next run. Single-source the 80/20 strategy_rank weights in momentum_service and pin every dual-defined constant with a parity test. Behavior-preserving today: the production sim reproduces the README baseline exactly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
"""Parity guards: the backtest's production strategy must equal the live setup.
|
||||
|
||||
The portfolio monitor's production row replays the live qualification flag and
|
||||
the runtime Admin exit policy, but several constants are still defined on both
|
||||
sides (defaults, trail width, ordering weights). These tests fail if the two
|
||||
sides drift, so a change to the live strategy forces the backtest — and vice
|
||||
versa — to move with it.
|
||||
"""
|
||||
|
||||
import pytest
|
||||
|
||||
from app.services import paper_trade_service
|
||||
from app.services.admin_service import ACTIVATION_DEFAULTS
|
||||
from app.services.backtest_service import (
|
||||
ATR_TRAIL_MULTIPLIER,
|
||||
LIVE_EXIT_MODE_TO_SIM,
|
||||
PORTFOLIO_MONITOR_STRATEGIES,
|
||||
PRODUCTION_PERCENTILE_KEY,
|
||||
RESIDUAL_HIGH_VOL_BLEND_80_20_KEY,
|
||||
TIME_EXIT_DAYS,
|
||||
_entry_variant_config,
|
||||
_momentum_qualifies,
|
||||
_qualifies_strategy_variant,
|
||||
)
|
||||
from app.services.momentum_service import (
|
||||
STRATEGY_RANK_MOMENTUM_WEIGHT,
|
||||
STRATEGY_RANK_VOL_WEIGHT,
|
||||
)
|
||||
|
||||
|
||||
def _production_monitor_row() -> dict:
|
||||
return next(s for s in PORTFOLIO_MONITOR_STRATEGIES if s.get("is_production"))
|
||||
|
||||
|
||||
def test_exit_defaults_match_the_simulated_exit() -> None:
|
||||
assert paper_trade_service.DEFAULT_EXIT_MODE == "atr_trailing"
|
||||
assert LIVE_EXIT_MODE_TO_SIM[paper_trade_service.DEFAULT_EXIT_MODE] == "atr_trail3"
|
||||
assert paper_trade_service.DEFAULT_ATR_MULTIPLIER == ATR_TRAIL_MULTIPLIER
|
||||
assert paper_trade_service.DEFAULT_HOLD_DAYS == max(TIME_EXIT_DAYS)
|
||||
|
||||
|
||||
def test_every_live_exit_mode_has_a_sim_mapping() -> None:
|
||||
assert set(paper_trade_service._VALID_EXIT_MODES) == set(LIVE_EXIT_MODE_TO_SIM)
|
||||
|
||||
|
||||
def test_gate_default_matches_the_promoted_cutoff() -> None:
|
||||
prod = _production_monitor_row()
|
||||
entry_cfg = _entry_variant_config(str(prod["entry_variant"]))
|
||||
assert entry_cfg is not None
|
||||
assert float(entry_cfg["cutoff"]) == float(ACTIVATION_DEFAULTS["min_momentum_percentile"])
|
||||
|
||||
|
||||
def test_production_ordering_weights_are_single_sourced() -> None:
|
||||
# The promoted ordering is 80/20 momentum/vol; the backtest imports the
|
||||
# weight, so equality here pins the *value* the promotion was validated at.
|
||||
assert STRATEGY_RANK_MOMENTUM_WEIGHT == 0.8
|
||||
assert STRATEGY_RANK_VOL_WEIGHT == pytest.approx(0.2)
|
||||
prod = _production_monitor_row()
|
||||
entry_cfg = _entry_variant_config(str(prod["entry_variant"]))
|
||||
assert entry_cfg is not None
|
||||
assert entry_cfg["ranking_key"] == RESIDUAL_HIGH_VOL_BLEND_80_20_KEY
|
||||
|
||||
|
||||
def test_production_monitor_row_replays_the_live_config() -> None:
|
||||
prod = _production_monitor_row()
|
||||
assert prod.get("use_live_config") is True
|
||||
assert prod["exit_policy"] == "atr_trail3"
|
||||
|
||||
|
||||
def test_live_gate_equals_the_production_variant_gate() -> None:
|
||||
"""The monitor's live-gate switch relies on the runtime `qualified` flag
|
||||
(_momentum_qualifies) selecting exactly what the frozen production variant
|
||||
gate selects at the default cutoff."""
|
||||
prod = _production_monitor_row()
|
||||
entry_cfg = _entry_variant_config(str(prod["entry_variant"]))
|
||||
assert entry_cfg is not None
|
||||
cutoff = float(ACTIVATION_DEFAULTS["min_momentum_percentile"])
|
||||
for cand in (
|
||||
{"meets_core": True, "direction": "long", PRODUCTION_PERCENTILE_KEY: 92.0},
|
||||
{"meets_core": True, "direction": "long", PRODUCTION_PERCENTILE_KEY: 80.0},
|
||||
{"meets_core": True, "direction": "long", PRODUCTION_PERCENTILE_KEY: 79.9},
|
||||
{"meets_core": True, "direction": "long", PRODUCTION_PERCENTILE_KEY: None},
|
||||
{"meets_core": True, "direction": "short", PRODUCTION_PERCENTILE_KEY: 95.0},
|
||||
{"meets_core": False, "direction": "long", PRODUCTION_PERCENTILE_KEY: 95.0},
|
||||
):
|
||||
assert _momentum_qualifies(cand, cutoff) == _qualifies_strategy_variant(
|
||||
cand, entry_cfg
|
||||
), cand
|
||||
Reference in New Issue
Block a user