feat: add selectable daily backtest cadence
This commit is contained in:
@@ -658,6 +658,36 @@ class TestSimulatePortfolio:
|
||||
for row in comparison_rows
|
||||
)
|
||||
|
||||
def test_production_cadence_comparison_names_exact_two_arms(self):
|
||||
monitor = {
|
||||
"runs": [
|
||||
{
|
||||
"comparison_arm": "live_no_lockdown",
|
||||
"lookback": "all",
|
||||
"reentry_lockdown_sessions": 0,
|
||||
"trades": 10,
|
||||
"equity_curve": [{"date": "2026-01-01", "value": 1.0}],
|
||||
},
|
||||
{
|
||||
"comparison_arm": "live_lockdown_5",
|
||||
"lookback": "all",
|
||||
"reentry_lockdown_sessions": 5,
|
||||
"trades": 8,
|
||||
"benchmark_curve": [{"date": "2026-01-01", "value": 1.0}],
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
comparison = bt._production_cadence_comparison(monitor, "daily")
|
||||
|
||||
assert comparison is not None
|
||||
assert [row["arm"] for row in comparison["arms"]] == [
|
||||
"prod_live_setup_daily",
|
||||
"cooldown_5_daily",
|
||||
]
|
||||
assert all("equity_curve" not in row for row in comparison["arms"])
|
||||
assert all("benchmark_curve" not in row for row in comparison["arms"])
|
||||
|
||||
def test_initial_stop_can_refresh_lower_and_survive_same_bar(self):
|
||||
closes = [100.0, 94.0, 96.0]
|
||||
prices = {"AAA": _sim_prices(self.ORD, closes)}
|
||||
@@ -929,6 +959,15 @@ def test_backtest_target_model_is_small_and_validated():
|
||||
bt.validate_backtest_target_model("legacy_range_grid_touch")
|
||||
|
||||
|
||||
def test_backtest_cadence_is_small_validated_and_session_based():
|
||||
assert bt.validate_backtest_cadence(" WEEKLY ") == "weekly"
|
||||
assert bt.validate_backtest_cadence("daily") == "daily"
|
||||
assert bt.backtest_step_sessions("weekly") == 5
|
||||
assert bt.backtest_step_sessions("daily") == 1
|
||||
with pytest.raises(ValueError, match="Unknown backtest cadence"):
|
||||
bt.validate_backtest_cadence("monthly")
|
||||
|
||||
|
||||
def _flat_window_records():
|
||||
return [
|
||||
SimpleNamespace(
|
||||
@@ -1022,6 +1061,46 @@ def test_replay_ticker_candidates_carry_gate_fields():
|
||||
assert c.get("action") is not None
|
||||
assert "risk_level" in c
|
||||
assert c["target_model"] == bt.PRODUCTION_GTL_TARGET_MODEL
|
||||
assert c["ranking_period"][0] == "week"
|
||||
|
||||
daily_cands = bt._replay_ticker(
|
||||
"OSC",
|
||||
bars,
|
||||
dict(DEFAULT_RECOMMENDATION_CONFIG),
|
||||
dict(ACTIVATION_DEFAULTS),
|
||||
cadence="daily",
|
||||
)
|
||||
assert len(daily_cands) > len(cands)
|
||||
assert all(c["ranking_period"][0] == "date" for c in daily_cands)
|
||||
|
||||
|
||||
def test_daily_replay_uses_exact_date_ranking_periods():
|
||||
candidates = [
|
||||
{
|
||||
"iso_week": (2026, 1),
|
||||
"ranking_period": ("date", date(2026, 1, 5).toordinal()),
|
||||
"momentum": 0.10,
|
||||
},
|
||||
{
|
||||
"iso_week": (2026, 1),
|
||||
"ranking_period": ("date", date(2026, 1, 5).toordinal()),
|
||||
"momentum": 0.20,
|
||||
},
|
||||
{
|
||||
"iso_week": (2026, 1),
|
||||
"ranking_period": ("date", date(2026, 1, 6).toordinal()),
|
||||
"momentum": 0.90,
|
||||
},
|
||||
{
|
||||
"iso_week": (2026, 1),
|
||||
"ranking_period": ("date", date(2026, 1, 6).toordinal()),
|
||||
"momentum": 0.30,
|
||||
},
|
||||
]
|
||||
|
||||
bt._assign_momentum_percentiles(candidates)
|
||||
|
||||
assert [row["momentum_percentile"] for row in candidates] == [0.0, 100.0, 100.0, 0.0]
|
||||
|
||||
|
||||
async def _seed_oscillating_ticker(session, symbol: str, n: int = 160) -> None:
|
||||
@@ -1063,6 +1142,8 @@ async def test_run_backtest_smoke(session):
|
||||
assert report["params"]["cost_per_side_pct"] == pytest.approx(bt.COST_PER_SIDE * 100)
|
||||
assert report["params"]["target_model"] == bt.PRODUCTION_GTL_TARGET_MODEL
|
||||
assert report["params"]["is_production_target_model"] is True
|
||||
assert report["params"]["entry_cadence"] == "weekly"
|
||||
assert report["params"]["step_sessions"] == 5
|
||||
assert (
|
||||
report["params"]["production_reentry_lockdown_sessions"]
|
||||
== bt.REENTRY_LOCKDOWN_SESSIONS
|
||||
@@ -1073,6 +1154,11 @@ async def test_run_backtest_smoke(session):
|
||||
# carries the hold-to-horizon grading alongside the target model
|
||||
ablation = {r["variant"]: r for r in report["gate_ablation"]}
|
||||
assert ablation["all_floors"]["total"] == report["overall_qualified"]["total"]
|
||||
|
||||
daily_report = await bt.run_backtest(session, cadence="daily")
|
||||
assert daily_report["params"]["entry_cadence"] == "daily"
|
||||
assert daily_report["params"]["step_sessions"] == 1
|
||||
assert daily_report["candidates"] > report["candidates"]
|
||||
for row in report["gate_ablation"]:
|
||||
assert "hold_net_avg_r" in row
|
||||
|
||||
|
||||
Reference in New Issue
Block a user