Skip to content

Commit 02d8777

Browse files
committed
Merge dev into development
2 parents c874506 + e73b42b commit 02d8777

396 files changed

Lines changed: 28650 additions & 21563 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/test.yml

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ jobs:
5151
- name: Install linting tools
5252
run: |
5353
python -m pip install --upgrade pip
54-
pip install ruff 'black==26.1.0' isort mypy bandit pip-audit
54+
pip install ruff 'black==26.1.0' isort 'mypy==1.16.1' bandit pip-audit
5555
5656
- name: Run ruff
5757
run: ruff check backtrader/
@@ -72,16 +72,15 @@ jobs:
7272
exit 1
7373
fi
7474
75-
- name: Type check with mypy (threshold gate)
75+
- name: Type check with mypy
7676
shell: bash
7777
run: |
78-
# S4 收敛后从「非阻塞趋势」升级为「阈值阻塞」:超过基线即失败,防止回流。
79-
# 当前实测 139(S4 基线 543 → 目标 <150)。留少量余量到 160 以吸收
80-
# 第三方 stub 版本漂移带来的抖动;低于阈值即通过。
81-
MYPY_THRESHOLD=160
78+
# mypy==1.16.1 keeps the Python 3.8 target available; the current
79+
# gate is fully clean and should fail on any new type error.
80+
MYPY_THRESHOLD=0
8281
mypy backtrader --config-file=pyproject.toml | tee mypy-report.txt || true
8382
count="$(grep -cE 'error:' mypy-report.txt || true)"
84-
echo "mypy error count: ${count} (S4 baseline 543, gate threshold ${MYPY_THRESHOLD})"
83+
echo "mypy error count: ${count} (gate threshold ${MYPY_THRESHOLD})"
8584
if [ "${count}" -gt "${MYPY_THRESHOLD}" ]; then
8685
echo "::error::mypy errors (${count}) exceed threshold (${MYPY_THRESHOLD}). New type errors were introduced."
8786
exit 1

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,9 +168,16 @@ examples/live_certification/hongyuan_penetration/*.docx
168168

169169
# Local workspace / editor artifacts
170170
.benchmarks/
171+
.agent/
172+
.agents/
171173
.claude/
174+
.codebuddy/
172175
.cursor/
173176
.idea/
177+
.kiro/skills/
178+
.opencode/
179+
.qoder/
180+
.qwen/
174181
.ruff_cache/
175182
.vscode/
176183
_bmad/

.windsurf/workflows/bmad-help.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ description: 'Get unstuck by showing what workflow steps come next or answering
77

88
# help
99

10-
Read the entire task file at: {project-root}/_bmad/core/tasks/help.md
10+
Use the installed BMad Help catalog and project artifacts:
1111

12-
Follow all instructions in the task file exactly as written.
12+
1. Read `{project-root}/_bmad/_config/bmad-help.csv`.
13+
2. Read relevant module config from `{project-root}/_bmad/**/config.yaml`.
14+
3. Inspect `{project-root}/_bmad-output/**` for existing planning, implementation, and test artifacts.
15+
4. Recommend the next BMad skill or workflow from the catalog, using the user's current request and existing artifacts as context.
16+
17+
Do not read `{project-root}/_bmad/core/tasks/help.md`; this project uses the installed BMad catalog-based help layout.

AGENTS.md

Lines changed: 296 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,296 @@
1+
# AGENTS.md
2+
3+
This file guides Codex (Codex.ai/code) when working in this repository.
4+
It is kept deliberately factual — every claim below was verified against the
5+
source tree. When you change something structural, update this file.
6+
7+
## Project Overview
8+
9+
Backtrader is a Python algorithmic-trading backtesting framework supporting
10+
low-, mid-, and high-frequency strategy development, backtesting, and live
11+
trading. This repo is a performance-oriented fork of the original
12+
[backtrader](https://www.backtrader.com/) that **removes metaclass-based
13+
metaprogramming** in favor of explicit mixin + factory initialization while
14+
keeping the public API compatible.
15+
16+
- **Version**: `1.1.0` (see `backtrader/version.py`)
17+
- **License**: GPLv3
18+
- **Python**: 3.8–3.13 (classifiers in `setup.py`; 3.11 recommended)
19+
- **Not on PyPI** — install from source only.
20+
21+
### Branch context
22+
23+
- `dev` — active development; the canonical branch. All work lands here first.
24+
- `master` — stable, aligned with upstream behavior. Used as the correctness
25+
baseline (regression tests bake master's metrics as expected values).
26+
- Other branches (`crypto`, `ctp`, `dev_cython`, `development`, etc.) are
27+
feature/experiment branches; do not target them unless asked.
28+
29+
> **Do not push directly to `master`.** Push to `dev`. `git push` is configured
30+
> to push to both GitHub (`cloudQuant/backtrader`) and Gitee
31+
> (`yunjinqi/backtrader`) remotes.
32+
33+
## Implementation status / reality checks
34+
35+
These correct common stale assumptions — verify before relying on docs:
36+
37+
- **Pure Python today.** Although `cython>=0.29.0` is a declared dependency and
38+
older docs reference `compile_cython_numba_files.py`, there are currently
39+
**no tracked `.pyx` files and no `ext_modules` in `setup.py`**. `pip install`
40+
builds a pure-Python package. The only native-ish acceleration in the tree is
41+
`numba` used inside `backtrader/utils/dateintern.py`. Do not assume a Cython
42+
build step is required or present.
43+
- **Metaclasses are gone.** Object construction goes through
44+
`metabase.ObjectFactory` / `BaseMixin.donew` and `ParamsMixin.__init_subclass__`
45+
(a `patched_init` wrapper), not a metaclass `__call__`.
46+
- File sizes below are real line counts, not the inflated numbers in earlier
47+
revisions of this doc.
48+
49+
## Development commands
50+
51+
### Install
52+
53+
```bash
54+
pip install -r requirements.txt # core + dev deps
55+
pip install -U . # build & install
56+
pip install -e . # editable/dev install
57+
```
58+
59+
No separate Cython compile step is needed for a normal install.
60+
61+
### Testing (tiered — see `Makefile` and `conftest.py`)
62+
63+
The strategy regression suite is large (~10 min full). Tests are split into
64+
tiers by **measured per-file duration**, applied dynamically at collection time
65+
(no test files are edited):
66+
67+
```bash
68+
make test-fast # ~3.5 min: all non-strategy tests + fastest ~35% of
69+
# strategy tests. Daily "did I break anything" loop.
70+
# == pytest tests -m "not slow" -n 8 -q
71+
make test-slow # the slowest ~65% strategy tests test-fast skips
72+
make test-strategies # all 1,271 strategy regression tests (~9 min)
73+
make test-all # entire suite in parallel (~10 min)
74+
make test-coverage # coverage report
75+
76+
# Single test, verbose:
77+
pytest tests/path/to/test_file.py::test_name -v --tb=short
78+
```
79+
80+
How the split works:
81+
82+
- `conftest.py::pytest_collection_modifyitems` reads
83+
`tests/functional/strategies/.test_durations.json` (committed), computes the
84+
`BT_SLOW_PERCENTILE`th percentile (default **35**) of recorded durations, and
85+
tags any strategy file at/above it with the existing `slow` marker.
86+
- **Unknown/new files default to the FAST tier**, so newly added or regenerated
87+
tests always run on `test-fast` — exactly what you want for catching new bugs.
88+
- Tune coverage vs speed: `BT_SLOW_PERCENTILE=25 make test-fast` (faster) …
89+
`=50` (broader).
90+
- Refresh timings after adding/removing strategy tests:
91+
`python scripts/refresh_strategy_durations.py`.
92+
93+
### Choosing which `backtrader` to test against
94+
95+
Running pytest from the repo root resolves `import backtrader` to the **local
96+
repo copy** by default. To test the installed site-packages copy instead:
97+
98+
```bash
99+
BACKTRADER_USE_INSTALLED=1 pytest ... # env var
100+
pytest ... --use-installed-backtrader # CLI flag
101+
```
102+
103+
The active `backtrader.__file__` is printed in the pytest session header. The
104+
switch works under `pytest-xdist` parallel mode. Logic lives in `conftest.py`.
105+
106+
### Code quality
107+
108+
```bash
109+
make format # black, line-length 100
110+
make format-check
111+
make lint # ruff
112+
make type-check # mypy
113+
make security # bandit
114+
make quality-check # all of the above (no tests)
115+
bash scripts/optimize_code.sh # pyupgrade + isort + black + ruff + tests
116+
```
117+
118+
### Docs & utilities
119+
120+
```bash
121+
make docs / docs-en / docs-zh # Sphinx docs (English + Chinese)
122+
make help # list all make targets
123+
make clean # clean build artifacts
124+
```
125+
126+
## Architecture
127+
128+
### Construction pipeline (replaces the old metaclass)
129+
130+
Object creation flows through `backtrader/metabase.py`:
131+
132+
- `ObjectFactory.create(cls, *args, **kwargs)` runs the lifecycle hooks:
133+
`doprenew → donew → dopreinit → doinit → dopostinit`.
134+
- `BaseMixin` provides default `donew/dopreinit/doinit/dopostinit`.
135+
- `ParamsMixin.__init_subclass__` installs a `patched_init` wrapper on each
136+
subclass's `__init__` that wires up `self.p`/`self.params`, sets `data0/data1`
137+
aliases, and runs the lifecycle. **Most indicators are constructed through
138+
this `patched_init` path, not `ObjectFactory.create` directly.**
139+
- Owner discovery uses `metabase.OwnerContext` (a context stack) and
140+
`metabase.findowner()` — the legacy stack-frame inspection is gone.
141+
142+
> Key rule: call `super().__init__()` **before** accessing `self.p`/`self.params`
143+
> or lines. Never reintroduce a metaclass — use mixins + `donew()`.
144+
145+
### Line system (bottom-up)
146+
147+
`LineRoot → LineBuffer → LineSeries → LineIterator`
148+
149+
- `lineroot.py` — base interfaces, period management, stage1/stage2.
150+
- `linebuffer.py` (~2,800 lines) — circular-buffer line storage; also defines
151+
`LineActions` / `LinesOperation` (the objects produced by expressions like
152+
`(data.high + data.low) / 2.0`).
153+
- `lineseries.py` (~2,450 lines) — `Lines`/`LineSeries`, `LineSeriesStub`,
154+
`LineSeriesMaker`.
155+
- `lineiterator.py` (~2,920 lines) — `LineIterator`, `IndicatorBase`,
156+
`DataAccessor`; iteration phases and the `_clock` resolution helpers
157+
(`_line_like_source_clock`, `_resolve_authoritative_buflen`,
158+
`_ensure_lineactions_inputs_computed`).
159+
160+
Access patterns: `data.close[0]` (current bar), `data.close[-1]` (previous).
161+
162+
### Components (all extend LineIterator)
163+
164+
- `indicator.py` (`Indicator`, `_ltype=IndType=0`) + `indicators/` (50 files).
165+
- `observer.py` + `observers/` — chart observers; notably
166+
`observers/trade_logger.py` (`TradeLogger`) for JSON order/trade/signal/
167+
position logs (used by the branch-compare tooling).
168+
- `analyzer.py` + `analyzers/` (17 files) — Sharpe, drawdown, returns, SQN, …
169+
- `sizer.py` + `sizers/`, `signal.py` + `signals/`, `comminfo.py` +
170+
`commissions/`.
171+
172+
### Data, broker, engine
173+
174+
- `feed.py` + `feeds/` (17 files) — CSV, pandas, IB, CCXT, etc.;
175+
`resamplerfilter.py` for resample/replay.
176+
- `broker.py` + `brokers/` — order matching and portfolio state.
177+
- `cerebro.py` (~2,440 lines) — orchestrator. `run()``runstrategies()`
178+
`_runonce()` (vectorized) or `_runnext()` (event-driven). Tick-level mode is
179+
also supported.
180+
181+
### Indicator registration & multi-data clocks (high-bug-risk area)
182+
183+
- An indicator registers with its owner via `LineIterator.addindicator()`
184+
(`lineiterator.py:1584`), appending to `owner._lineiterators[ind._ltype]`.
185+
If an indicator isn't registered it won't update during the run.
186+
- **Multi-timeframe gotcha:** an indicator built on a secondary feed — e.g.
187+
`SMA((h1.high + h1.low)/2.0)` or `EMA(EMA(h4.close))` inside an M15 strategy —
188+
must advance on the *secondary* feed's clock, not the strategy's primary feed.
189+
In runonce mode this is handled in `Strategy._periodset()`, which resolves each
190+
indicator's data dependency to its concrete feed and pins
191+
`indicator._resolved_secondary_clock`; the post-phase advance loop in
192+
`_oncepost()` and `Indicator.advance()` honor that clock. See
193+
`docs/DEV_REGRESSION_FAILURES.md` for the full diagnosis of the bug class this
194+
fixes. When touching clock/minperiod logic, run `make test-strategies` — these
195+
multi-data cases are exactly what regress.
196+
197+
### Execution phases
198+
199+
`prenext` (before minperiod) → `nextstart` (minperiod first met) → `next`
200+
(normal). Vectorized mode uses `once()` (`preonce`/`oncestart`/`once`) to fill
201+
whole line arrays in batch, then replays per bar.
202+
203+
### Data flow
204+
205+
```text
206+
Data Feed(s) → Cerebro → Strategy → Indicators / Observers / Analyzers
207+
208+
Broker ← Orders
209+
```
210+
211+
## Special modes
212+
213+
- **TS (time series)** and **CS (cross-section)** modes for multi-asset
214+
portfolio backtests (`utils/` helpers; some docs reference dedicated value
215+
calculators — confirm presence before relying on them).
216+
- Multiple plotting backends: Plotly (`plot/`), Bokeh (`bokeh/`), Matplotlib.
217+
- Report generation: `reports/` (`reporter.py`, `performance.py`, `charts.py`).
218+
219+
## Repository layout
220+
221+
```
222+
backtrader/ core library
223+
cerebro.py strategy.py indicator.py analyzer.py observer.py broker.py feed.py
224+
metabase.py parameters.py
225+
lineroot.py linebuffer.py lineseries.py lineiterator.py dataseries.py
226+
indicators/ analyzers/ observers/ feeds/ brokers/ filters/ sizers/ signals/
227+
commissions/ stores/ channels/ mixins/ plot/ bokeh/ reports/ configs/ utils/
228+
tests/ unit/ functional/ integration/ performance/ original_tests/
229+
add_tests/ strategies/ bench/ datas/ fixtures/ factories/ test_utils/
230+
functional/strategies/ 1,271 inlined regression tests in ~30 categories
231+
docs/ Sphinx docs (EN + ZH) + design/bug notes
232+
scripts/ optimize_code.sh, refresh_strategy_durations.py,
233+
run_strategy_branch_compare.py, …
234+
studies/ research/diagnostic scripts (e.g. branch_compare/)
235+
Makefile pyproject.toml setup.py pytest.ini requirements.txt conftest.py
236+
```
237+
238+
## Tests
239+
240+
- `tests/functional/strategies/` holds 1,271 inlined regression tests across ~30
241+
categories (trend_following, mean_reversion, asset_allocation,
242+
machine_learning, options, pairs_trading, …). Each is self-contained: inline
243+
strategy + data loader + `cerebro.run()` + assertions against master-baselined
244+
metrics.
245+
- `tests/unit/`, `tests/integration/`, `tests/performance/`,
246+
`tests/original_tests/`, `tests/add_tests/` cover the framework itself.
247+
- Config: `pytest.ini` (markers incl. `slow`, warning filters), `conftest.py`
248+
(temp cleanup, installed-vs-local switch, slow auto-marking).
249+
- `tests/datas/` holds fixtures; MT5 daily CSVs in `tests/datas/mt5_1d_data/`.
250+
- New regression tests should pass on **both** `dev` and `master` (bake master's
251+
output as the expected values). Some `tests/unit/brokers/*_performance` tests
252+
are flaky under heavy `-n 8` parallelism (timing-sensitive; pass in isolation).
253+
254+
## Common tasks
255+
256+
### Add an indicator
257+
258+
1. New file in `backtrader/indicators/`; subclass `bt.Indicator`.
259+
2. `lines = ('out',)`, `params = (('period', 30),)`.
260+
3. Build the calculation in `__init__` (assign `self.lines.out = ...`) and/or
261+
implement `next()` / `once(start, end)` for explicit modes.
262+
4. Register in `indicators/__init__.py`.
263+
5. If it consumes a secondary feed or a `LinesOperation`, test runonce vs
264+
runnext parity (multi-data clock alignment).
265+
266+
### Add a strategy
267+
268+
1. Subclass `bt.Strategy`; declare `params`.
269+
2. Build indicators in `__init__`; trading logic in `next()`.
270+
3. Use `self.buy()/sell()/close()`.
271+
272+
### Debug line/indicator issues
273+
274+
- `len(obj)`, `obj._minperiod`, `obj._owner`, `obj._ltype == 0` (IndType).
275+
- Confirm `obj in owner._lineiterators[0]`.
276+
- For multi-data drift, inspect `obj._clock` and `obj._resolved_secondary_clock`
277+
and compare runonce vs runnext output (the branch-compare harness in
278+
`studies/branch_compare/` + `scripts/run_strategy_branch_compare.py` with
279+
`TradeLogger` is the established way to localize divergences).
280+
281+
## Code style & constraints
282+
283+
- Line length 100 (black); ruff/isort at 121. Type hints encouraged.
284+
- Bilingual (EN/ZH) comments are normal in this codebase.
285+
- **Never introduce new metaclasses** — use mixins with the `donew()` pattern.
286+
- Preserve public API compatibility.
287+
- Minimize `isinstance()`/`hasattr()`/`len()` in hot paths.
288+
- Performance work already done: metaclass removal, broker
289+
`__getattribute__`/param-cache optimization, indicator `once()` tuning.
290+
291+
### Config files
292+
293+
- `pyproject.toml` — black, ruff, isort, mypy, bandit, coverage.
294+
- `pytest.ini` — discovery, markers, warning filters.
295+
- `.kiro/steering/{product,tech,structure}.md` — project conventions
296+
(authoritative for build/test/structure norms).

0 commit comments

Comments
 (0)