Goal
Stand up a minimal CLI script that lets a user run the HMM + StrategyOrchestrator stack on historic OHLCV data and see how it would have performed — without wiring the full backtesting.py adapter.
Why
shared/core/hmm/ is fully decoupled from backtest/. The only way to see the HMM interact with real price series today is to write bespoke glue. A reusable walk-forward script closes that gap and serves as a smoke test for the whole HMM pipeline.
Scope
scripts/hmm_backtest.py — CLI with:
--ticker (default SPY), --start, --end, --interval (default 1d), --warmup-pct (default 0.5)
- Fetches OHLCV via
YFinanceFetcher
- Runs
prepare_features_for_hmm on the full series, splits into train / out-of-sample at the warmup boundary
- Trains
HMMEngine on the warmup features
- Walks forward through OOS bars calling
predict_filtered(feature_vector, timestamp=bar_time) — never model.predict() (no look-ahead)
- For each OOS bar, asks
StrategyOrchestrator.should_enter_position for target position_size + leverage
- Computes bar-to-bar equity:
equity *= 1 + exposure * bar_return
- Prints: Final equity, Total return %, Annualised Sharpe, Max drawdown, Bar count, Regime distribution
tests/hmm/test_backtest_runner.py — pytest using the synthetic sample_ohlcv_data fixture. Validates:
- Equity curve is finite and positive
- No look-ahead: runner never calls
model.predict
- Output summary contains all required fields
Live smoke run — uv run python scripts/hmm_backtest.py --ticker SPY --start 2020-01-01 --end 2024-12-31 exits 0 and prints the summary block.
Non-goals
- Slippage / transaction cost modelling
- Full
HMMBacktestStrategy adapter on top of backtesting.py
- Stop-loss / take-profit execution simulation
- Multiple tickers / portfolio construction
Acceptance criteria
Goal
Stand up a minimal CLI script that lets a user run the HMM + StrategyOrchestrator stack on historic OHLCV data and see how it would have performed — without wiring the full
backtesting.pyadapter.Why
shared/core/hmm/is fully decoupled frombacktest/. The only way to see the HMM interact with real price series today is to write bespoke glue. A reusable walk-forward script closes that gap and serves as a smoke test for the whole HMM pipeline.Scope
scripts/hmm_backtest.py— CLI with:--ticker(defaultSPY),--start,--end,--interval(default1d),--warmup-pct(default0.5)YFinanceFetcherprepare_features_for_hmmon the full series, splits into train / out-of-sample at the warmup boundaryHMMEngineon the warmup featurespredict_filtered(feature_vector, timestamp=bar_time)— nevermodel.predict()(no look-ahead)StrategyOrchestrator.should_enter_positionfor target position_size + leverageequity *= 1 + exposure * bar_returntests/hmm/test_backtest_runner.py— pytest using the syntheticsample_ohlcv_datafixture. Validates:model.predictLive smoke run —
uv run python scripts/hmm_backtest.py --ticker SPY --start 2020-01-01 --end 2024-12-31exits 0 and prints the summary block.Non-goals
HMMBacktestStrategyadapter on top ofbacktesting.pyAcceptance criteria
scripts/hmm_backtest.pyexists with argparse CLItests/hmm/test_backtest_runner.pypasses with the synthetic fixturepredict_filtered, notmodel.predict— look-ahead-bias-freeuv run ruff check scripts tests/hmmexits 0uv run ruff format --check scripts tests/hmmexits 0uv run ty check scripts tests/hmmreports no in-scope errorsuv run python -m pytest tests/hmm -qshows all tests passing