This file guides Codex (Codex.ai/code) when working in this repository. It is kept deliberately factual — every claim below was verified against the source tree. When you change something structural, update this file.
Backtrader is a Python algorithmic-trading backtesting framework supporting low-, mid-, and high-frequency strategy development, backtesting, and live trading. This repo is a performance-oriented fork of the original backtrader that removes metaclass-based metaprogramming in favor of explicit mixin + factory initialization while keeping the public API compatible.
- Version:
1.3.0(seebacktrader/version.py) - License: GPLv3
- Python: 3.8–3.13 (classifiers in
setup.py; 3.11 recommended) - Not on PyPI — install from source only.
This repository uses a three-branch model (authoritative source:
docs/source/developer-guide/branch-governance.md):
dev— daily development entry. Routine features, ordinary bug fixes, docs, tests, refactors, and community contributions land here first.development— improved & optimized version. Optimization capabilities, architecture improvements, and optimization-only regression fixes.master— original Backtrader baseline. Only bug/compatibility/security fixes that reproduce on the original baseline, viahotfix/master-*PRs. Used as the correctness baseline (regression tests bake master's metrics as expected values).
Other branches (crypto, ctp, dev_cython, etc.) are feature/experiment
branches; do not target them unless asked.
Do not push directly to
masterordevelopment. Push todev.git pushis configured to push to both GitHub (cloudQuant/backtrader) and Gitee (yunjinqi/backtrader) remotes.
These correct common stale assumptions — verify before relying on docs:
- Pure Python today. Although
cython>=0.29.0is a declared dependency and older docs referencecompile_cython_numba_files.py, there are currently no tracked.pyxfiles and noext_modulesinsetup.py.pip installbuilds a pure-Python package. The only native-ish acceleration in the tree isnumbaused insidebacktrader/utils/dateintern.py. Do not assume a Cython build step is required or present. - Metaclasses are gone. Object construction goes through
metabase.ObjectFactory/BaseMixin.donewandParamsMixin.__init_subclass__(apatched_initwrapper), not a metaclass__call__. - File sizes below are real line counts, not the inflated numbers in earlier revisions of this doc.
pip install -r requirements.txt # core + dev deps
pip install -U . # build & install
pip install -e . # editable/dev installNo separate Cython compile step is needed for a normal install.
The strategy regression suite is large (~10 min full). Tests are split into tiers by measured per-file duration, applied dynamically at collection time (no test files are edited):
make test-fast # ~3.5 min: all non-strategy tests + fastest ~35% of
# strategy tests. Daily "did I break anything" loop.
# == pytest tests -m "not slow" -n 8 -q
make test-slow # the slowest ~65% strategy tests test-fast skips
make test-strategies # all 1,271 strategy regression tests (~9 min)
make test-all # entire suite in parallel (~10 min)
make test-coverage # coverage report
# Single test, verbose:
pytest tests/path/to/test_file.py::test_name -v --tb=shortHow the split works:
conftest.py::pytest_collection_modifyitemsreadstests/functional/strategies/.test_durations.json(committed), computes theBT_SLOW_PERCENTILEth percentile (default 35) of recorded durations, and tags any strategy file at/above it with the existingslowmarker.- Unknown/new files default to the FAST tier, so newly added or regenerated
tests always run on
test-fast— exactly what you want for catching new bugs. - Tune coverage vs speed:
BT_SLOW_PERCENTILE=25 make test-fast(faster) …=50(broader). - Refresh timings after adding/removing strategy tests:
python scripts/refresh_strategy_durations.py.
Running pytest from the repo root resolves import backtrader to the local
repo copy by default. To test the installed site-packages copy instead:
BACKTRADER_USE_INSTALLED=1 pytest ... # env var
pytest ... --use-installed-backtrader # CLI flagThe active backtrader.__file__ is printed in the pytest session header. The
switch works under pytest-xdist parallel mode. Logic lives in conftest.py.
make format # black, line-length 100
make format-check
make lint # ruff
make type-check # mypy
make security # bandit
make quality-check # all of the above (no tests)
bash scripts/optimize_code.sh # pyupgrade + isort + black + ruff + testsmake docs / docs-en / docs-zh # Sphinx docs (English + Chinese)
make help # list all make targets
make clean # clean build artifactsObject creation flows through backtrader/metabase.py:
ObjectFactory.create(cls, *args, **kwargs)runs the lifecycle hooks:doprenew → donew → dopreinit → doinit → dopostinit.BaseMixinprovides defaultdonew/dopreinit/doinit/dopostinit.ParamsMixin.__init_subclass__installs apatched_initwrapper on each subclass's__init__that wires upself.p/self.params, setsdata0/data1aliases, and runs the lifecycle. Most indicators are constructed through thispatched_initpath, notObjectFactory.createdirectly.- Owner discovery uses
metabase.OwnerContext(a context stack) andmetabase.findowner()— the legacy stack-frame inspection is gone.
Strategy has a separate explicit path: Strategy.__new__() creates self.p
and broker/analyzer state, and Strategy.__init__() creates datas/data aliases
and _clock before calling the direct subclass's user __init__(). Therefore a
class that directly subclasses bt.Strategy does not need to call
super().__init__(); doing so currently re-enters Strategy.__init__().
Cooperative custom Strategy parents/mixins must still call super() when their
own parent initialization is required. Indicators and other ParamsMixin
objects follow their own patched-init lifecycle and must not be validated using
the direct-Strategy exception. Never reintroduce a metaclass — use mixins +
donew().
LineRoot → LineBuffer → LineSeries → LineIterator
lineroot.py— base interfaces, period management, stage1/stage2.linebuffer.py(~2,800 lines) — circular-buffer line storage; also definesLineActions/LinesOperation(the objects produced by expressions like(data.high + data.low) / 2.0).lineseries.py(~2,450 lines) —Lines/LineSeries,LineSeriesStub,LineSeriesMaker.lineiterator.py(~2,920 lines) —LineIterator,IndicatorBase,DataAccessor; iteration phases and the_clockresolution helpers (_line_like_source_clock,_resolve_authoritative_buflen,_ensure_lineactions_inputs_computed).
Access patterns: data.close[0] (current bar), data.close[-1] (previous).
indicator.py(Indicator,_ltype=IndType=0) +indicators/(50 files).observer.py+observers/— chart observers; notablyobservers/trade_logger.py(TradeLogger) for JSON order/trade/signal/ position logs (used by the branch-compare tooling).analyzer.py+analyzers/(17 files) — Sharpe, drawdown, returns, SQN, …sizer.py+sizers/,signal.py+signals/,comminfo.py+commissions/.
feed.py+feeds/(17 files) — CSV, pandas, IB, CCXT, etc.;resamplerfilter.pyfor resample/replay.broker.py+brokers/— order matching and portfolio state.cerebro.py(~2,440 lines) — orchestrator.run()→runstrategies()→_runonce()(vectorized) or_runnext()(event-driven). Tick-level mode is also supported.
- An indicator registers with its owner via
LineIterator.addindicator()(lineiterator.py:1584), appending toowner._lineiterators[ind._ltype]. If an indicator isn't registered it won't update during the run. - Multi-timeframe gotcha: an indicator built on a secondary feed — e.g.
SMA((h1.high + h1.low)/2.0)orEMA(EMA(h4.close))inside an M15 strategy — must advance on the secondary feed's clock, not the strategy's primary feed. In runonce mode this is handled inStrategy._periodset(), which resolves each indicator's data dependency to its concrete feed and pinsindicator._resolved_secondary_clock; the post-phase advance loop in_oncepost()andIndicator.advance()honor that clock. Seedocs/DEV_REGRESSION_FAILURES.mdfor the full diagnosis of the bug class this fixes. When touching clock/minperiod logic, runmake test-strategies— these multi-data cases are exactly what regress.
prenext (before minperiod) → nextstart (minperiod first met) → next
(normal). Vectorized mode uses once() (preonce/oncestart/once) to fill
whole line arrays in batch, then replays per bar.
Data Feed(s) → Cerebro → Strategy → Indicators / Observers / Analyzers
↓
Broker ← Orders
- TS (time series) and CS (cross-section) modes for multi-asset
portfolio backtests (
utils/helpers; some docs reference dedicated value calculators — confirm presence before relying on them). - Multiple plotting backends: Plotly (
plot/), Bokeh (bokeh/), Matplotlib. - Report generation:
reports/(reporter.py,performance.py,charts.py).
backtrader/ core library
cerebro.py strategy.py indicator.py analyzer.py observer.py broker.py feed.py
metabase.py parameters.py
lineroot.py linebuffer.py lineseries.py lineiterator.py dataseries.py
indicators/ analyzers/ observers/ feeds/ brokers/ filters/ sizers/ signals/
commissions/ stores/ channels/ mixins/ plot/ bokeh/ reports/ configs/ utils/
AI strategy products are maintained outside this repository:
cloudQuant/backtrader-skills standalone author/review/test skills product
cloudQuant/backtrader-mcp standalone local-stdio MCP product
cloudQuant/backtrader-agent standalone stateful agent product
tests/ unit/ functional/ integration/ performance/ original_tests/
add_tests/ strategies/ bench/ datas/ fixtures/ factories/ test_utils/
functional/strategies/ 1,271 inlined regression tests in ~30 categories
docs/ Sphinx docs (EN + ZH) + design/bug notes
scripts/ optimize_code.sh, refresh_strategy_durations.py,
run_strategy_branch_compare.py, …
studies/ research/diagnostic scripts (e.g. branch_compare/)
Makefile pyproject.toml setup.py pytest.ini requirements.txt conftest.py
The three AI products are not vendored and are not Git submodules. Make product changes, packaging releases, and product-specific acceptance changes in their respective repositories; this repository only links to them from its README.
tests/functional/strategies/holds 1,271 inlined regression tests across ~30 categories (trend_following, mean_reversion, asset_allocation, machine_learning, options, pairs_trading, …). Each is self-contained: inline strategy + data loader +cerebro.run()+ assertions against master-baselined metrics.tests/unit/,tests/integration/,tests/performance/,tests/original_tests/,tests/add_tests/cover the framework itself.- Config:
pytest.ini(markers incl.slow, warning filters),conftest.py(temp cleanup, installed-vs-local switch, slow auto-marking). tests/datas/holds fixtures; MT5 daily CSVs intests/datas/mt5_1d_data/.- New regression tests should pass on both
devandmaster(bake master's output as the expected values). Sometests/unit/brokers/*_performancetests are flaky under heavy-n 8parallelism (timing-sensitive; pass in isolation).
- New file in
backtrader/indicators/; subclassbt.Indicator. lines = ('out',),params = (('period', 30),).- Build the calculation in
__init__(assignself.lines.out = ...) and/or implementnext()/once(start, end)for explicit modes. - Register in
indicators/__init__.py. - If it consumes a secondary feed or a
LinesOperation, test runonce vs runnext parity (multi-data clock alignment).
- Subclass
bt.Strategy; declareparams. - Build indicators in
__init__; trading logic innext(). - Use
self.buy()/sell()/close().
len(obj),obj._minperiod,obj._owner,obj._ltype == 0(IndType).- Confirm
obj in owner._lineiterators[0]. - For multi-data drift, inspect
obj._clockandobj._resolved_secondary_clockand compare runonce vs runnext output (the branch-compare harness instudies/branch_compare/+scripts/run_strategy_branch_compare.pywithTradeLoggeris the established way to localize divergences).
- Line length 100 (black); ruff/isort at 121. Type hints encouraged.
- Bilingual (EN/ZH) comments are normal in this codebase.
- Never introduce new metaclasses — use mixins with the
donew()pattern. - Preserve public API compatibility.
- Minimize
isinstance()/hasattr()/len()in hot paths. - Performance work already done: metaclass removal, broker
__getattribute__/param-cache optimization, indicatoronce()tuning.
pyproject.toml— black, ruff, isort, mypy, bandit, coverage.pytest.ini— discovery, markers, warning filters. No Kiro steering files are tracked; use thisAGENTS.md,README.md, and the project configuration files as the current build/test/structure guidance.
CRITICAL: 当你遇到文件引用时(例如 @rules/general.md),使用你的读取工具按需加载。它们与当前具体任务相关。
说明:
- 不要预先加载所有引用 - 基于实际需求使用懒加载
- 加载后,将内容视为强制性指令,覆盖默认设置
- 在需要时递归地遵循引用
当前项目下存在前端和后端项目,开发前请阅读并遵守以下开发规范
后端开发规范:@.joyincode/rules/backend.md 前端开发规范:@.joyincode/rules/frontend.md