You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
105 lines
4.5 KiB
Python
105 lines
4.5 KiB
Python
"""Unit tests for store_builder.py."""
|
|
import datetime as dt
|
|
|
|
import numpy as np
|
|
import pytest
|
|
|
|
from libs.backtest.scenarios.price_gen import PriceRegime
|
|
from libs.backtest.scenarios.scenarios import SCENARIO_REGISTRY, STEADY_BULL, NO_SIGNAL
|
|
from libs.backtest.scenarios.store_builder import _get_trading_dates, build_synthetic_store
|
|
from libs.backtest.snapshot_store import SnapshotStore
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestGetTradingDates:
|
|
def test_returns_correct_count(self):
|
|
dates = _get_trading_dates(50)
|
|
assert len(dates) == 50
|
|
|
|
def test_dates_are_weekdays(self):
|
|
dates = _get_trading_dates(30)
|
|
for d in dates:
|
|
assert d.weekday() < 5, f"Non-weekday in trading dates: {d}"
|
|
|
|
def test_dates_are_sorted_ascending(self):
|
|
dates = _get_trading_dates(30)
|
|
assert dates == sorted(dates)
|
|
|
|
def test_starts_on_or_after_start_date(self):
|
|
start = dt.date(2024, 1, 2)
|
|
dates = _get_trading_dates(20, start=start)
|
|
assert dates[0] >= start
|
|
|
|
|
|
@pytest.mark.unit
|
|
class TestBuildSyntheticStore:
|
|
def test_returns_snapshot_store(self):
|
|
store = build_synthetic_store(STEADY_BULL, rng=np.random.default_rng(42))
|
|
assert isinstance(store, SnapshotStore)
|
|
|
|
def test_store_has_candidates(self):
|
|
store = build_synthetic_store(STEADY_BULL, rng=np.random.default_rng(42))
|
|
exec_dates = store.all_execution_dates()
|
|
assert len(exec_dates) > 0, "No candidates generated"
|
|
|
|
def test_store_has_macro_data(self):
|
|
store = build_synthetic_store(STEADY_BULL, rng=np.random.default_rng(42))
|
|
trading_days = store.all_trading_days()
|
|
assert len(trading_days) > 0
|
|
|
|
def test_store_has_bar_data(self):
|
|
store = build_synthetic_store(STEADY_BULL, rng=np.random.default_rng(42))
|
|
trading_days = store.all_trading_days()
|
|
assert len(trading_days) > 0
|
|
|
|
def test_spy_and_qqq_in_bars(self):
|
|
store = build_synthetic_store(STEADY_BULL, rng=np.random.default_rng(42))
|
|
trading_days = store.all_trading_days()
|
|
some_day = trading_days[10]
|
|
assert store.get_bar("SPY", some_day) is not None
|
|
assert store.get_bar("QQQ", some_day) is not None
|
|
|
|
def test_macro_has_required_keys(self):
|
|
store = build_synthetic_store(STEADY_BULL, rng=np.random.default_rng(42))
|
|
trading_days = store.all_trading_days()
|
|
# Pick a date past rolling window warm-up
|
|
late_date = trading_days[60]
|
|
macro = store.get_macro_for_date(late_date)
|
|
for key in ("VIXCLS", "macro_vix", "macro_hy_spread", "spy_close", "qqq_close"):
|
|
assert key in macro, f"Missing macro key: {key}"
|
|
|
|
def test_seed_is_deterministic(self):
|
|
store_a = build_synthetic_store(STEADY_BULL, rng=np.random.default_rng(123))
|
|
store_b = build_synthetic_store(STEADY_BULL, rng=np.random.default_rng(123))
|
|
# Same seed → same exec dates
|
|
assert store_a.all_execution_dates() == store_b.all_execution_dates()
|
|
|
|
def test_no_signal_scenario_produces_store(self):
|
|
"""no_signal scenario should assemble without error."""
|
|
store = build_synthetic_store(NO_SIGNAL, rng=np.random.default_rng(1))
|
|
assert isinstance(store, SnapshotStore)
|
|
|
|
def test_scenario_seed_used_when_rng_none(self):
|
|
"""If rng=None, scenario.seed is used for determinism."""
|
|
from dataclasses import replace
|
|
seeded_scenario = replace(STEADY_BULL, seed=77)
|
|
store_a = build_synthetic_store(seeded_scenario, rng=None)
|
|
store_b = build_synthetic_store(seeded_scenario, rng=None)
|
|
assert store_a.all_execution_dates() == store_b.all_execution_dates()
|
|
|
|
def test_candidate_exec_dates_align_with_macro_dates(self):
|
|
"""All candidate exec_dates should exist in trading days (have macro)."""
|
|
store = build_synthetic_store(STEADY_BULL, rng=np.random.default_rng(42))
|
|
macro_trading_days = set(store.all_trading_days())
|
|
for exec_date in store.all_execution_dates():
|
|
assert exec_date in macro_trading_days, f"exec_date {exec_date} not in trading days"
|
|
|
|
def test_all_registered_scenarios_can_build(self):
|
|
"""Smoke-test: every scenario in SCENARIO_REGISTRY builds without error."""
|
|
for name, scenario in SCENARIO_REGISTRY.items():
|
|
try:
|
|
store = build_synthetic_store(scenario, rng=np.random.default_rng(0))
|
|
assert isinstance(store, SnapshotStore), f"Bad store for {name}"
|
|
except Exception as exc:
|
|
pytest.fail(f"build_synthetic_store failed for scenario '{name}': {exc}")
|