This project is structured for research accuracy first. Contributions should preserve the execution contract, keep module boundaries clear, and avoid hidden behavior changes.
Before changing code, read:
If dev_context/ exists locally, dev_context/CLEAN_CODE_MCP.md is a useful internal reference, but it is not required for external contributors.
git clone https://github.com/DanRedelien/futures-backtesting-engine.git
cd futures-backtesting-engine
pip install -r requirements.txt
pytest tests/Python 3.11+ is recommended.
If you need scenario queueing from the terminal UI, install Redis separately.
# single backtest
python run.py --backtest --strategy sma_pullback --symbol ES --tf 1h
# walk-forward optimization
python run.py --wfo --strategy ict_ob --symbol ES --tf 1h
# portfolio backtest
python run.py --portfolio-backtest
# lightweight batch backtests
python run.py batch --strategies sma_pullback ict_ob --symbol ES NQ --tf 1h 30m
# lightweight WFO batch sweep
python run.py wfo-batch --strategies sma_pullback ict_ob --symbol ES --tf 1h
# terminal UI
python run.py --dashboard- Make the smallest coherent change.
- Keep CLI modules thin; orchestration belongs in
src/backtest_engine/services/. - Keep analytics builders pure when possible.
- Add or update tests for behavior changes.
- Update documentation when module boundaries or usage change.
run.pyparses arguments and dispatches.cli/adapts CLI flags into service calls.src/backtest_engine/services/owns use-case orchestration.- engines execute backtests.
runtime/terminal_ui/serves analytics and operational endpoints.
There are two different engine entry points and they should not be conflated:
-
src/backtest_engine/single_asset/engine.pyBacktestEngine- Single-asset bar-by-bar event loop
- Owns one portfolio object, one strategy instance, and one instrument stream
- Used by single-run backtests and as the execution core under WFO
-
src/backtest_engine/portfolio_layer/engine/engine.pyPortfolioBacktestEngine- Multi-strategy, multi-symbol portfolio event loop
- Owns shared capital, target allocation, slot-level execution handlers, and unified timeline orchestration
- Used by portfolio backtests and scenario reruns
Use the single engine when the task is about one strategy on one symbol. Use the portfolio engine when the task depends on slot coordination, rebalancing, shared equity, or portfolio reporting.
The most important invariant in this repository:
- strategy sees
bar[t] - signal is produced from information available at
t - order fills at
open[t+1]
Do not add shifts or data access patterns that violate this.
- Add the implementation in
src/strategies/. - Inherit from
BaseStrategy. - Precompute indicators in
__init__. - Keep
on_bar()lightweight and O(1) per bar. - Expose
get_search_space()if the strategy supports optimization. - Register the strategy in
src/strategies/registry.py. - Add tests if the behavior is novel or fragile.
See src/strategies/README.md for the detailed contract.
- Pure metrics and transforms belong in
src/backtest_engine/analytics/. - UI payload builders belong in
src/backtest_engine/runtime/terminal_ui/. - Route handlers should stay thin and delegate to services/builders.
- If artifact structure changes, update loaders, tests, and docs together.
Batch-specific note:
batchandwfo-batchare intentionally lightweight paths.- They coordinate many independent scenarios and render Matplotlib summaries instead of writing the full dashboard artifact flow for every run.
pytest tests/
pytest tests/unit/
pytest tests/regression/
pytest tests/unit/test_engine_regressions.pyAt minimum, run the most relevant tests for the area you changed.
Update docs when you change:
- public CLI behavior
- module ownership or imports
- artifact paths or contracts
- strategy registration flow
- terminal UI routing or runtime composition
For open-source hygiene, prefer updating the closest README or doc instead of adding a new spec file unless the topic spans multiple packages.
Good PRs in this repository usually have:
- one focused behavioral goal
- matching tests or a clear reason tests were not added
- doc updates if public behavior changed
- explicit mention of any artifact-contract change
Avoid overengineering. Favor:
- thin adapters
- explicit data flow
- pure helpers over deep abstractions
- docs that explain the real workflow, not hypothetical future layers
If a new concept only matters in one module, document it close to that module instead of creating another top-level document.