binance-quant-engine 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. binance_quant_engine-0.1.1/.env.example +5 -0
  2. binance_quant_engine-0.1.1/.github/workflows/ci.yml +49 -0
  3. binance_quant_engine-0.1.1/.github/workflows/commit-identity.yml +103 -0
  4. binance_quant_engine-0.1.1/.github/workflows/publish.yml +33 -0
  5. binance_quant_engine-0.1.1/.gitignore +9 -0
  6. binance_quant_engine-0.1.1/LICENSE +21 -0
  7. binance_quant_engine-0.1.1/PKG-INFO +159 -0
  8. binance_quant_engine-0.1.1/README.md +135 -0
  9. binance_quant_engine-0.1.1/docs/ARCHITECTURE.md +92 -0
  10. binance_quant_engine-0.1.1/docs/USAGE.md +102 -0
  11. binance_quant_engine-0.1.1/docs/images/demo-backtest.png +0 -0
  12. binance_quant_engine-0.1.1/pyproject.toml +45 -0
  13. binance_quant_engine-0.1.1/src/binance_quant_engine/__init__.py +3 -0
  14. binance_quant_engine-0.1.1/src/binance_quant_engine/backtest/__init__.py +1 -0
  15. binance_quant_engine-0.1.1/src/binance_quant_engine/backtest/vectorized.py +177 -0
  16. binance_quant_engine-0.1.1/src/binance_quant_engine/data/__init__.py +1 -0
  17. binance_quant_engine-0.1.1/src/binance_quant_engine/data/cache.py +540 -0
  18. binance_quant_engine-0.1.1/src/binance_quant_engine/data/klines.py +43 -0
  19. binance_quant_engine-0.1.1/src/binance_quant_engine/execution/__init__.py +1 -0
  20. binance_quant_engine-0.1.1/src/binance_quant_engine/execution/algo_api.py +84 -0
  21. binance_quant_engine-0.1.1/src/binance_quant_engine/execution/brackets.py +973 -0
  22. binance_quant_engine-0.1.1/src/binance_quant_engine/execution/host.py +65 -0
  23. binance_quant_engine-0.1.1/src/binance_quant_engine/execution/utils.py +46 -0
  24. binance_quant_engine-0.1.1/src/binance_quant_engine/mcp/__init__.py +1 -0
  25. binance_quant_engine-0.1.1/src/binance_quant_engine/mcp/server.py +115 -0
  26. binance_quant_engine-0.1.1/src/binance_quant_engine/strategy/__init__.py +1 -0
  27. binance_quant_engine-0.1.1/src/binance_quant_engine/strategy/demo_squeeze.py +183 -0
  28. binance_quant_engine-0.1.1/src/binance_quant_engine/strategy/protocol.py +95 -0
  29. binance_quant_engine-0.1.1/tests/test_backtest.py +71 -0
  30. binance_quant_engine-0.1.1/tests/test_demo_squeeze.py +51 -0
  31. binance_quant_engine-0.1.1/tests/test_execution_utils.py +33 -0
  32. binance_quant_engine-0.1.1/tests/test_mcp_server.py +37 -0
@@ -0,0 +1,5 @@
1
+ # Needed only for live execution (binance_quant_engine.execution) — not for backtests.
2
+ # Never commit real values. (.env is gitignored.)
3
+ BINANCE_API_KEY="your-api-key-here"
4
+ BINANCE_API_SECRET="your-api-secret-here"
5
+ BINANCE_TESTNET="true"
@@ -0,0 +1,49 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ lint:
11
+ runs-on: ubuntu-latest
12
+ steps:
13
+ - uses: actions/checkout@v7
14
+
15
+ - name: Set up Python
16
+ uses: actions/setup-python@v7
17
+ with:
18
+ python-version: "3.12"
19
+
20
+ - name: Install dependencies
21
+ run: pip install -e ".[dev,mcp]"
22
+
23
+ - name: Ruff
24
+ run: ruff check src tests
25
+
26
+ - name: Ruff format check
27
+ run: ruff format --check src tests
28
+ continue-on-error: true
29
+
30
+ test:
31
+ runs-on: ubuntu-latest
32
+ strategy:
33
+ fail-fast: false
34
+ matrix:
35
+ python-version: ["3.10", "3.11", "3.12", "3.13"]
36
+
37
+ steps:
38
+ - uses: actions/checkout@v7
39
+
40
+ - name: Set up Python ${{ matrix.python-version }}
41
+ uses: actions/setup-python@v7
42
+ with:
43
+ python-version: ${{ matrix.python-version }}
44
+
45
+ - name: Install dependencies
46
+ run: pip install -e ".[dev,mcp]"
47
+
48
+ - name: Run tests
49
+ run: pytest tests/ -v
@@ -0,0 +1,103 @@
1
+ name: Commit Identity Guard
2
+
3
+ # 커밋 작성자/커미터 이메일이 허용 목록에 없으면 실패한다.
4
+ #
5
+ # 2026-08-29 사고: Claude Code 세션들이 세션 컨텍스트에 주어진 사용자 이메일
6
+ # (회사 주소)을 git 커밋 신원으로 오용해, `git -c user.email=<회사주소> commit`
7
+ # 형태로 커밋했다. 레포 로컬·전역 설정은 개인 이메일로 올바랐지만 명령줄이 그걸
8
+ # 덮었다. 그 결과 6개 레포에 회사 이메일 커밋이 쌓였고 3개는 PUBLIC 상태였다.
9
+ #
10
+ # 설정으로는 막을 수 없다(`-c`가 항상 이긴다). 그래서 CI에서 막는다.
11
+ #
12
+ # 허용 목록을 바꾸려면 ALLOWED_EMAILS를 수정하라.
13
+ # dependabot[bot] 은 봇 신원이다 — 회사 주소가 아니라서 허용한다.
14
+
15
+ on:
16
+ push:
17
+ pull_request:
18
+
19
+ permissions:
20
+ contents: read
21
+
22
+ jobs:
23
+ check-identity:
24
+ # 브랜치·태그 삭제 푸시엔 검사할 커밋도, checkout 할 ref 도 없다.
25
+ if: ${{ !github.event.deleted }}
26
+ runs-on: ubuntu-latest
27
+ steps:
28
+ - uses: actions/checkout@v7
29
+ with:
30
+ fetch-depth: 0
31
+
32
+ - name: 커밋 신원 검사
33
+ env:
34
+ # 개인 이메일 + GitHub noreply만 허용.
35
+ # noreply@github.com 은 GitHub 머지 버튼이 만드는 커밋의 신원이다
36
+ # (Merge pull request #N ...). 회사 주소가 아니라 GitHub 자신이다.
37
+ # 에이전트 신원(noreply@anthropic.com 등)은 **허용하지 않는다** — 커밋은
38
+ # 사람 이름으로 남아야 한다. 에이전트 기여는 author/committer 가 아니라
39
+ # Co-Authored-By 트레일러로 적는다(그건 이 검사가 보지 않는다).
40
+ ALLOWED_EMAILS: |
41
+ chyohw97@gmail.com
42
+ 48156556+younghwan91@users.noreply.github.com
43
+ noreply@github.com
44
+ 49699333+dependabot[bot]@users.noreply.github.com
45
+ run: |
46
+ set -euo pipefail
47
+
48
+ # PR이면 base..head, push면 이번에 밀린 커밋만 본다.
49
+ # 히스토리 전체를 검사하면 과거 사고 때문에 영원히 실패한다.
50
+ if [ "${{ github.event_name }}" = "pull_request" ]; then
51
+ RANGE="${{ github.event.pull_request.base.sha }}..${{ github.event.pull_request.head.sha }}"
52
+ elif [ "${{ github.ref_type }}" = "tag" ]; then
53
+ # 태그 푸시는 새 커밋을 만들지 않는다. before 가 0 이라 아래 폴백을 타면
54
+ # 히스토리 50개를 다시 훑고, 과거 머지 커밋 때문에 영원히 실패한다.
55
+ # 기본 브랜치에 아직 없는 커밋만 본다 (보통 0개).
56
+ BASE="origin/${{ github.event.repository.default_branch }}"
57
+ if git rev-parse --verify -q "$BASE" >/dev/null; then
58
+ RANGE="$BASE..${{ github.sha }}"
59
+ else
60
+ RANGE="${{ github.sha }}~1..${{ github.sha }}"
61
+ fi
62
+ elif [ "${{ github.event.before }}" != "0000000000000000000000000000000000000000" ] \
63
+ && git cat-file -e "${{ github.event.before }}" 2>/dev/null; then
64
+ RANGE="${{ github.event.before }}..${{ github.sha }}"
65
+ else
66
+ # 새 브랜치/강제 푸시 등 before를 못 쓰는 경우: 최근 50개만
67
+ RANGE="$(git rev-list -n 50 ${{ github.sha }} | tail -1)^..${{ github.sha }}" || RANGE="${{ github.sha }}"
68
+ fi
69
+
70
+ echo "검사 범위: $RANGE"
71
+ echo
72
+
73
+ # 허용 목록을 정규화 (빈 줄·공백 제거)
74
+ ALLOWED=$(echo "$ALLOWED_EMAILS" | tr -d ' ' | grep -v '^$' | sort -u)
75
+ echo "허용된 이메일:"
76
+ echo "$ALLOWED" | sed 's/^/ /'
77
+ echo
78
+
79
+ BAD=0
80
+ while IFS='|' read -r sha author committer subject; do
81
+ [ -z "$sha" ] && continue
82
+ for email in "$author" "$committer"; do
83
+ if ! echo "$ALLOWED" | grep -Fxq "$email"; then
84
+ echo "::error::${sha:0:8} 허용되지 않은 이메일: $email ($subject)"
85
+ BAD=1
86
+ fi
87
+ done
88
+ done < <(git log --format='%H|%ae|%ce|%s' "$RANGE" 2>/dev/null || true)
89
+
90
+ if [ "$BAD" -eq 1 ]; then
91
+ echo
92
+ echo "커밋 신원이 허용 목록에 없다. 회사/조직 이메일이 섞였을 가능성이 높다."
93
+ echo
94
+ echo "고치는 법:"
95
+ echo " git config user.email chyohw97@gmail.com"
96
+ echo " git rebase -i --exec 'git commit --amend --reset-author --no-edit' <base>"
97
+ echo " git push --force-with-lease"
98
+ echo
99
+ echo "주의: 'git -c user.email=...' 로 신원을 덮어쓰지 말 것."
100
+ exit 1
101
+ fi
102
+
103
+ echo "✅ 커밋 신원 이상 없음"
@@ -0,0 +1,33 @@
1
+ name: Publish to PyPI
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+
8
+ jobs:
9
+ publish:
10
+ runs-on: ubuntu-latest
11
+ environment: release
12
+ permissions:
13
+ # id-token 만 적으면 나머지 스코프가 전부 none 이 된다.
14
+ # 그러면 GITHUB_TOKEN 이 레포를 못 읽어 checkout 이
15
+ # "remote: Repository not found" 로 죽는다 (레포가 비공개일 때).
16
+ contents: read
17
+ id-token: write
18
+
19
+ steps:
20
+ - uses: actions/checkout@v7
21
+
22
+ - uses: actions/setup-python@v7
23
+ with:
24
+ python-version: "3.12"
25
+
26
+ - name: Install build
27
+ run: pip install build
28
+
29
+ - name: Build
30
+ run: python -m build
31
+
32
+ - name: Publish to PyPI
33
+ uses: pypa/gh-action-pypi-publish@release/v1
@@ -0,0 +1,9 @@
1
+ __pycache__/
2
+ *.pyc
3
+ .venv/
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ *.egg-info/
7
+ dist/
8
+ build/
9
+ .mypy_cache/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Younghwan Chae
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,159 @@
1
+ Metadata-Version: 2.5
2
+ Name: binance-quant-engine
3
+ Version: 0.1.1
4
+ Summary: Strategy-agnostic Binance USDT-M futures backtest & execution engine — no-look-ahead, backtest/live parity, optional MCP server
5
+ Project-URL: Homepage, https://pypi.org/project/binance-quant-engine/
6
+ Project-URL: Repository, https://github.com/younghwan91/binance-quant-engine
7
+ Author-email: Younghwan Chae <chyohw97@gmail.com>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: algo-trading,backtest,binance,execution,futures,mcp,quant,trading
11
+ Requires-Python: >=3.10
12
+ Requires-Dist: numpy>=1.26
13
+ Requires-Dist: pandas>=2.0
14
+ Provides-Extra: dev
15
+ Requires-Dist: mcp>=1.0; extra == 'dev'
16
+ Requires-Dist: pytest>=8.0; extra == 'dev'
17
+ Requires-Dist: python-binance>=1.0.29; extra == 'dev'
18
+ Requires-Dist: ruff>=0.6; extra == 'dev'
19
+ Provides-Extra: live
20
+ Requires-Dist: python-binance>=1.0.29; extra == 'live'
21
+ Provides-Extra: mcp
22
+ Requires-Dist: mcp>=1.0; extra == 'mcp'
23
+ Description-Content-Type: text/markdown
24
+
25
+ # Binance Quant Engine ⚙️
26
+
27
+ [![CI](https://github.com/younghwan91/binance-quant-engine/actions/workflows/ci.yml/badge.svg)](https://github.com/younghwan91/binance-quant-engine/actions/workflows/ci.yml)
28
+ [![PyPI](https://img.shields.io/pypi/v/binance-quant-engine.svg)](https://pypi.org/project/binance-quant-engine/)
29
+ [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
30
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
31
+ [![LinkedIn](https://img.shields.io/badge/LinkedIn-younghwan--chae-0A66C2?logo=linkedin&logoColor=white)](https://www.linkedin.com/in/younghwan-chae/)
32
+
33
+ **A strategy-agnostic Binance USDT-M futures backtest & execution engine.** Backtest and live trading run through the *same* strategy code, so a look-ahead bug can't exist in one and not the other — and an [MCP server](#mcp-server-let-an-agent-run-your-backtests) lets any MCP-compatible agent run backtests without writing a line of Python.
34
+
35
+ Extracted from the infra layer of a real, currently-running Binance USDT-M futures bot — the alpha (strategy, parameters, live P&L) stays private; what's public is the part every retail algo trader rebuilds badly at least once: a backtester that can't cheat, and an execution layer that survives Binance's actual API quirks. The bundled demo strategy is textbook logic (Bollinger squeeze); it exists to exercise the engine end to end, not to make you money.
36
+
37
+ > This repository supersedes `quantbox-engine`, which is no longer maintained. If you have that repo cloned, switch to this one.
38
+
39
+ ## Why not just write your own backtest loop?
40
+
41
+ Because the failure modes here are the ones that quietly wreck a real account, not the ones a unit test catches:
42
+
43
+ | Problem | What usually happens | What this engine does |
44
+ |---|---|---|
45
+ | Look-ahead bias | A signal computed over a full array accidentally sees future bars | The backtester physically hands the strategy only `close[: t + 1]` each step — there is no future in the array to peek at ([enforced by test](tests/test_backtest.py)) |
46
+ | Backtest/live divergence | Backtest logic gets "ported" to the live bot and drifts | One `TradingStrategy` object, same method calls, same order, in both paths |
47
+ | Binance tick-size rejection | A price a float-division away from the tick grid gets silently rejected or mispriced | [`round_to_tick`](src/binance_quant_engine/execution/utils.py) snaps every stop/limit/activation price to the symbol's grid before it leaves the process |
48
+ | Dead-man's switch | Bot crashes → open position has no stop-loss | SL/TP/trailing stops are placed **on Binance's Algo Order API**, so the exchange — not your process — enforces the exit |
49
+
50
+ ## Quick start
51
+
52
+ ```bash
53
+ pip install binance-quant-engine
54
+ # or: uv add binance-quant-engine
55
+
56
+ bqe-backtest --demo # backtest the bundled demo strategy on synthetic data
57
+ ```
58
+
59
+ ![Demo backtest run](docs/images/demo-backtest.png)
60
+
61
+ > ⚠️ The numbers above are from a **demo strategy on synthetic data**. They prove the engine runs; they say nothing about profitability.
62
+
63
+ ## Backtest your own data
64
+
65
+ ```python
66
+ from binance_quant_engine.backtest.vectorized import run_backtest
67
+ from binance_quant_engine.data.klines import load_csv
68
+ from binance_quant_engine.strategy.demo_squeeze import SqueezeStrategy
69
+
70
+ high, low, close = load_csv("BTCUSDT_1h.csv") # columns: high,low,close
71
+ result = run_backtest(SqueezeStrategy(), high, low, close, fee=0.0004, slippage=0.0002)
72
+ print(result.summary())
73
+ # {'n_trades': ..., 'total_return': ..., 'win_rate': ..., 'profit_factor': ..., 'max_drawdown': ...}
74
+ ```
75
+
76
+ Bring your own strategy by implementing [`TradingStrategy`](src/binance_quant_engine/strategy/protocol.py) — a `typing.Protocol`, no base class required. Wiring it into live execution: [docs/USAGE.md](docs/USAGE.md).
77
+
78
+ ## MCP server — let an agent run your backtests
79
+
80
+ ```bash
81
+ pip install "binance-quant-engine[mcp]"
82
+ binance-quant-engine-mcp # stdio MCP server
83
+ ```
84
+
85
+ Exposes `run_demo_backtest`, `backtest_csv`, and `describe_strategy_protocol` to any MCP client (Claude Code, Claude Desktop, etc.) — point an agent at a CSV of OHLC data and it can backtest a strategy idea in the same turn, no local Python environment required on the agent's side.
86
+
87
+ **This is deliberately backtest-only.** Nothing under `execution/` (order placement, cancellation, bracket management) is reachable through the MCP server — there is no tool call that can touch a live Binance order. If you want an agent that also *trades*, that's a decision you wire yourself, explicitly, outside this server.
88
+
89
+ ## Design
90
+
91
+ | Design choice | How |
92
+ |---|---|
93
+ | **No-look-ahead by construction** | The backtester hands the strategy `close[:t+1]` (bars completed as of now) every step — there's no future in the array to see. |
94
+ | **Backtest = live, same code** | The same `TradingStrategy` object is driven by the backtester and the live bot, in the same call order. No reimplementation gap. |
95
+ | **Pluggable strategy** | The engine only ever talks to a strategy through [`TradingStrategy`](src/binance_quant_engine/strategy/protocol.py) (PEP 544). No inheritance required. |
96
+ | **Costs are net, not gross** | Taker fee + slippage charged on both entry and exit legs. |
97
+ | **Server-side exits** | SL/TP/trailing stops live on Binance's Algo Order API — the exchange enforces them even if your process dies. ([brackets.py](src/binance_quant_engine/execution/brackets.py)) |
98
+ | **Tick-safe prices** | Every price sent to the exchange is snapped to the symbol's tick grid first ([utils.py](src/binance_quant_engine/execution/utils.py)) — a category of Binance rejection this engine doesn't have. |
99
+
100
+ ## Structure
101
+
102
+ **Decide** (strategy) / **measure** (backtest) / **execute** (live) are kept apart and meet only at `TradingStrategy`.
103
+
104
+ ```mermaid
105
+ flowchart LR
106
+ subgraph Data["binance_quant_engine/data/"]
107
+ CSV[("OHLCV CSV\nload_csv()")]
108
+ SYN[["synth_ohlcv()\nsynthetic data"]]
109
+ CACHE["cache.py\nmemory + gzip cache"]
110
+ end
111
+
112
+ subgraph Decide["binance_quant_engine/strategy/ — decide"]
113
+ PROTO{{"TradingStrategy\n(PEP 544 protocol)"}}
114
+ SQZ["demo_squeeze.py\nSqueezeStrategy"]
115
+ PROTO -.implements.-> SQZ
116
+ end
117
+
118
+ CSV --> BT
119
+ SYN --> BT
120
+ CACHE -.caches.-> CSV
121
+
122
+ subgraph Measure["binance_quant_engine/backtest/ — measure"]
123
+ BT["vectorized.run_backtest()\nhands close[:t+1] only\n(no look-ahead)"]
124
+ RES["BacktestResult\ntrades · equity curve · MDD"]
125
+ BT --> RES
126
+ end
127
+
128
+ BT <-->|"update_market_data / on_bar\nopen·update·close_position"| PROTO
129
+
130
+ subgraph Execute["binance_quant_engine/execution/ — execute (.[live])"]
131
+ HOST["host.py\nScalperProtocol (live bot host)"]
132
+ BRACKET["brackets.py\nBracketMixin — SL/TP/trailing"]
133
+ UTIL["utils.py\nround_to_tick · dead-status set"]
134
+ ALGO["algo_api.py\nAlgoApiClient"]
135
+ HOST --> BRACKET --> ALGO
136
+ BRACKET -.-> UTIL
137
+ end
138
+
139
+ subgraph MCP["binance_quant_engine/mcp/ — optional (.[mcp])"]
140
+ SRV["server.py\nrun_demo_backtest · backtest_csv"]
141
+ end
142
+ BT -.callable via.-> SRV
143
+
144
+ PROTO ==same interface\n(backtest = live)==> HOST
145
+ ALGO --> BINANCE[("Binance USDT-M\nFutures Algo Order API")]
146
+ ```
147
+
148
+ - How to plug in a strategy and wire up live execution → [docs/USAGE.md](docs/USAGE.md)
149
+ - Design rationale for no-look-ahead, backtest/live parity, and the cost model → [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
150
+
151
+ ## License
152
+
153
+ MIT
154
+
155
+ ---
156
+
157
+ Bugs and questions → [Issues](https://github.com/younghwan91/binance-quant-engine/issues).
158
+
159
+ **Younghwan Chae** · [GitHub @younghwan91](https://github.com/younghwan91) · [LinkedIn](https://www.linkedin.com/in/younghwan-chae/) — other open-source quant projects (Korean equities, US equities, crypto) are on the [profile page](https://github.com/younghwan91).
@@ -0,0 +1,135 @@
1
+ # Binance Quant Engine ⚙️
2
+
3
+ [![CI](https://github.com/younghwan91/binance-quant-engine/actions/workflows/ci.yml/badge.svg)](https://github.com/younghwan91/binance-quant-engine/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/binance-quant-engine.svg)](https://pypi.org/project/binance-quant-engine/)
5
+ [![Python](https://img.shields.io/badge/python-3.10%2B-blue.svg)](https://www.python.org/downloads/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
+ [![LinkedIn](https://img.shields.io/badge/LinkedIn-younghwan--chae-0A66C2?logo=linkedin&logoColor=white)](https://www.linkedin.com/in/younghwan-chae/)
8
+
9
+ **A strategy-agnostic Binance USDT-M futures backtest & execution engine.** Backtest and live trading run through the *same* strategy code, so a look-ahead bug can't exist in one and not the other — and an [MCP server](#mcp-server-let-an-agent-run-your-backtests) lets any MCP-compatible agent run backtests without writing a line of Python.
10
+
11
+ Extracted from the infra layer of a real, currently-running Binance USDT-M futures bot — the alpha (strategy, parameters, live P&L) stays private; what's public is the part every retail algo trader rebuilds badly at least once: a backtester that can't cheat, and an execution layer that survives Binance's actual API quirks. The bundled demo strategy is textbook logic (Bollinger squeeze); it exists to exercise the engine end to end, not to make you money.
12
+
13
+ > This repository supersedes `quantbox-engine`, which is no longer maintained. If you have that repo cloned, switch to this one.
14
+
15
+ ## Why not just write your own backtest loop?
16
+
17
+ Because the failure modes here are the ones that quietly wreck a real account, not the ones a unit test catches:
18
+
19
+ | Problem | What usually happens | What this engine does |
20
+ |---|---|---|
21
+ | Look-ahead bias | A signal computed over a full array accidentally sees future bars | The backtester physically hands the strategy only `close[: t + 1]` each step — there is no future in the array to peek at ([enforced by test](tests/test_backtest.py)) |
22
+ | Backtest/live divergence | Backtest logic gets "ported" to the live bot and drifts | One `TradingStrategy` object, same method calls, same order, in both paths |
23
+ | Binance tick-size rejection | A price a float-division away from the tick grid gets silently rejected or mispriced | [`round_to_tick`](src/binance_quant_engine/execution/utils.py) snaps every stop/limit/activation price to the symbol's grid before it leaves the process |
24
+ | Dead-man's switch | Bot crashes → open position has no stop-loss | SL/TP/trailing stops are placed **on Binance's Algo Order API**, so the exchange — not your process — enforces the exit |
25
+
26
+ ## Quick start
27
+
28
+ ```bash
29
+ pip install binance-quant-engine
30
+ # or: uv add binance-quant-engine
31
+
32
+ bqe-backtest --demo # backtest the bundled demo strategy on synthetic data
33
+ ```
34
+
35
+ ![Demo backtest run](docs/images/demo-backtest.png)
36
+
37
+ > ⚠️ The numbers above are from a **demo strategy on synthetic data**. They prove the engine runs; they say nothing about profitability.
38
+
39
+ ## Backtest your own data
40
+
41
+ ```python
42
+ from binance_quant_engine.backtest.vectorized import run_backtest
43
+ from binance_quant_engine.data.klines import load_csv
44
+ from binance_quant_engine.strategy.demo_squeeze import SqueezeStrategy
45
+
46
+ high, low, close = load_csv("BTCUSDT_1h.csv") # columns: high,low,close
47
+ result = run_backtest(SqueezeStrategy(), high, low, close, fee=0.0004, slippage=0.0002)
48
+ print(result.summary())
49
+ # {'n_trades': ..., 'total_return': ..., 'win_rate': ..., 'profit_factor': ..., 'max_drawdown': ...}
50
+ ```
51
+
52
+ Bring your own strategy by implementing [`TradingStrategy`](src/binance_quant_engine/strategy/protocol.py) — a `typing.Protocol`, no base class required. Wiring it into live execution: [docs/USAGE.md](docs/USAGE.md).
53
+
54
+ ## MCP server — let an agent run your backtests
55
+
56
+ ```bash
57
+ pip install "binance-quant-engine[mcp]"
58
+ binance-quant-engine-mcp # stdio MCP server
59
+ ```
60
+
61
+ Exposes `run_demo_backtest`, `backtest_csv`, and `describe_strategy_protocol` to any MCP client (Claude Code, Claude Desktop, etc.) — point an agent at a CSV of OHLC data and it can backtest a strategy idea in the same turn, no local Python environment required on the agent's side.
62
+
63
+ **This is deliberately backtest-only.** Nothing under `execution/` (order placement, cancellation, bracket management) is reachable through the MCP server — there is no tool call that can touch a live Binance order. If you want an agent that also *trades*, that's a decision you wire yourself, explicitly, outside this server.
64
+
65
+ ## Design
66
+
67
+ | Design choice | How |
68
+ |---|---|
69
+ | **No-look-ahead by construction** | The backtester hands the strategy `close[:t+1]` (bars completed as of now) every step — there's no future in the array to see. |
70
+ | **Backtest = live, same code** | The same `TradingStrategy` object is driven by the backtester and the live bot, in the same call order. No reimplementation gap. |
71
+ | **Pluggable strategy** | The engine only ever talks to a strategy through [`TradingStrategy`](src/binance_quant_engine/strategy/protocol.py) (PEP 544). No inheritance required. |
72
+ | **Costs are net, not gross** | Taker fee + slippage charged on both entry and exit legs. |
73
+ | **Server-side exits** | SL/TP/trailing stops live on Binance's Algo Order API — the exchange enforces them even if your process dies. ([brackets.py](src/binance_quant_engine/execution/brackets.py)) |
74
+ | **Tick-safe prices** | Every price sent to the exchange is snapped to the symbol's tick grid first ([utils.py](src/binance_quant_engine/execution/utils.py)) — a category of Binance rejection this engine doesn't have. |
75
+
76
+ ## Structure
77
+
78
+ **Decide** (strategy) / **measure** (backtest) / **execute** (live) are kept apart and meet only at `TradingStrategy`.
79
+
80
+ ```mermaid
81
+ flowchart LR
82
+ subgraph Data["binance_quant_engine/data/"]
83
+ CSV[("OHLCV CSV\nload_csv()")]
84
+ SYN[["synth_ohlcv()\nsynthetic data"]]
85
+ CACHE["cache.py\nmemory + gzip cache"]
86
+ end
87
+
88
+ subgraph Decide["binance_quant_engine/strategy/ — decide"]
89
+ PROTO{{"TradingStrategy\n(PEP 544 protocol)"}}
90
+ SQZ["demo_squeeze.py\nSqueezeStrategy"]
91
+ PROTO -.implements.-> SQZ
92
+ end
93
+
94
+ CSV --> BT
95
+ SYN --> BT
96
+ CACHE -.caches.-> CSV
97
+
98
+ subgraph Measure["binance_quant_engine/backtest/ — measure"]
99
+ BT["vectorized.run_backtest()\nhands close[:t+1] only\n(no look-ahead)"]
100
+ RES["BacktestResult\ntrades · equity curve · MDD"]
101
+ BT --> RES
102
+ end
103
+
104
+ BT <-->|"update_market_data / on_bar\nopen·update·close_position"| PROTO
105
+
106
+ subgraph Execute["binance_quant_engine/execution/ — execute (.[live])"]
107
+ HOST["host.py\nScalperProtocol (live bot host)"]
108
+ BRACKET["brackets.py\nBracketMixin — SL/TP/trailing"]
109
+ UTIL["utils.py\nround_to_tick · dead-status set"]
110
+ ALGO["algo_api.py\nAlgoApiClient"]
111
+ HOST --> BRACKET --> ALGO
112
+ BRACKET -.-> UTIL
113
+ end
114
+
115
+ subgraph MCP["binance_quant_engine/mcp/ — optional (.[mcp])"]
116
+ SRV["server.py\nrun_demo_backtest · backtest_csv"]
117
+ end
118
+ BT -.callable via.-> SRV
119
+
120
+ PROTO ==same interface\n(backtest = live)==> HOST
121
+ ALGO --> BINANCE[("Binance USDT-M\nFutures Algo Order API")]
122
+ ```
123
+
124
+ - How to plug in a strategy and wire up live execution → [docs/USAGE.md](docs/USAGE.md)
125
+ - Design rationale for no-look-ahead, backtest/live parity, and the cost model → [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md)
126
+
127
+ ## License
128
+
129
+ MIT
130
+
131
+ ---
132
+
133
+ Bugs and questions → [Issues](https://github.com/younghwan91/binance-quant-engine/issues).
134
+
135
+ **Younghwan Chae** · [GitHub @younghwan91](https://github.com/younghwan91) · [LinkedIn](https://www.linkedin.com/in/younghwan-chae/) — other open-source quant projects (Korean equities, US equities, crypto) are on the [profile page](https://github.com/younghwan91).
@@ -0,0 +1,92 @@
1
+ # Architecture
2
+
3
+ Binance Quant Engine separates three concerns that are usually tangled in trading
4
+ code: **deciding** (strategy), **measuring** (backtest), and **executing**
5
+ (live). The engine owns measuring and executing; a strategy only decides. They
6
+ meet at one narrow interface, [`TradingStrategy`](../src/binance_quant_engine/strategy/protocol.py).
7
+
8
+ ```
9
+ ┌───────────────────────────────┐
10
+ OHLCV → │ Backtester / Live Scalper │ ← same driver, same calls
11
+ └───────────────┬───────────────┘
12
+ │ TradingStrategy protocol
13
+
14
+ ┌───────────────────────────────┐
15
+ │ Strategy (e.g. Squeeze) │ decides: -1 / 0 / +1, exits
16
+ └───────────────────────────────┘
17
+ ```
18
+
19
+ ## 1. No-look-ahead by construction
20
+
21
+ The cheapest way to fake a great backtest is to let a signal see the future.
22
+ We remove the possibility instead of policing it: the backtester only ever
23
+ hands a strategy `close[: t + 1]`, the prefix of *completed* bars up to the
24
+ current one. A strategy literally cannot index into the future because the
25
+ future isn't in the array it receives.
26
+
27
+ This invariant is asserted in
28
+ [`test_no_look_ahead_strategy_only_sees_past`](../tests/test_backtest.py): a spy
29
+ strategy records every window it is shown and verifies each is an exact past
30
+ prefix that grows by one bar per step.
31
+
32
+ ## 2. Backtest / live parity
33
+
34
+ A strategy is a *stateful object* implementing `TradingStrategy`, not a vector
35
+ of precomputed signals. The exact same object is stepped by the backtester and
36
+ by the live scalper, calling the same methods in the same order:
37
+
38
+ ```
39
+ update_market_data(...) # feed the latest completed bars
40
+ on_bar(...) # -> signal
41
+ open_position(...) # on entry
42
+ update_position(...) # -> exit reason or None, each subsequent bar
43
+ close_position(...)
44
+ ```
45
+
46
+ Because there is one implementation of the decision logic, a backtested edge and
47
+ a live edge cannot silently diverge from a reimplementation gap. The only
48
+ differences live in the *execution* layer (fills, latency, fees), which the
49
+ backtester models explicitly.
50
+
51
+ ## 3. Execution layer (live)
52
+
53
+ The live layer (in [`src/binance_quant_engine/execution/`](../src/binance_quant_engine/execution/)) is included as a
54
+ reference for how the decision logic reaches the exchange safely. Highlights:
55
+
56
+ - **Server-side brackets** ([`brackets.py`](../src/binance_quant_engine/execution/brackets.py)):
57
+ stop-loss / take-profit / trailing stops are placed on Binance's Algo Order
58
+ engine, so risk protection survives a bot crash, restart, or network outage —
59
+ the exchange enforces the exit even if the process is dead.
60
+ - **Ratcheting trailing stops**: as price advances, the trailing stop is moved
61
+ (ratcheted) server-side, never loosened, with a minimum step to avoid
62
+ rate-limit churn.
63
+ - **Host interface** ([`host.py`](../src/binance_quant_engine/execution/host.py)): execution is
64
+ composed from mixins typed against a `ScalperProtocol`, so each mixin
65
+ type-checks in isolation without importing a concrete, strategy-specific host.
66
+
67
+ > Trailing geometry here is driven by a generic `TrailConfig`; the proprietary
68
+ > per-strategy tuning from the private system is intentionally not included.
69
+
70
+ ## 4. Cost model
71
+
72
+ `Trade.net_return` charges `fee + slippage` on **both** the entry and exit legs,
73
+ so `BacktestResult.profit_factor`, `win_rate`, and `total_return` are all net of
74
+ trading costs. Gross return is available separately for attribution.
75
+
76
+ ## 5. MCP server (optional, `.[mcp]`)
77
+
78
+ [`src/binance_quant_engine/mcp/server.py`](../src/binance_quant_engine/mcp/server.py) exposes the
79
+ backtest engine — not the execution layer — over the Model Context Protocol,
80
+ so any MCP-compatible agent can run `run_demo_backtest` / `backtest_csv`
81
+ without writing Python. This boundary is deliberate: nothing under
82
+ `execution/` is reachable from the MCP process, so there is no tool call that
83
+ can place, modify, or cancel a live order. Wire `execution/` into your own bot
84
+ directly if you want live trading; don't expect (or ask for) an MCP tool that
85
+ does it.
86
+
87
+ ## What is intentionally omitted
88
+
89
+ This is the engine, not the alpha. The proprietary strategies, their parameters,
90
+ research notebooks, and live performance figures from the private system are not
91
+ part of this repository. The bundled `SqueezeStrategy` is public textbook logic
92
+ whose only job is to exercise the engine end to end.