binance-quant-engine 0.1.1__tar.gz → 0.1.2__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 (34) hide show
  1. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/PKG-INFO +21 -7
  2. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/README.md +4 -4
  3. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/pyproject.toml +25 -4
  4. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/src/binance_quant_engine/__init__.py +1 -1
  5. binance_quant_engine-0.1.2/tests/test_brackets.py +161 -0
  6. binance_quant_engine-0.1.2/tests/test_mcp_stdio_e2e.py +35 -0
  7. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/.env.example +0 -0
  8. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/.github/workflows/ci.yml +0 -0
  9. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/.github/workflows/commit-identity.yml +0 -0
  10. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/.github/workflows/publish.yml +0 -0
  11. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/.gitignore +0 -0
  12. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/LICENSE +0 -0
  13. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/docs/ARCHITECTURE.md +0 -0
  14. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/docs/USAGE.md +0 -0
  15. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/docs/images/demo-backtest.png +0 -0
  16. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/src/binance_quant_engine/backtest/__init__.py +0 -0
  17. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/src/binance_quant_engine/backtest/vectorized.py +0 -0
  18. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/src/binance_quant_engine/data/__init__.py +0 -0
  19. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/src/binance_quant_engine/data/cache.py +0 -0
  20. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/src/binance_quant_engine/data/klines.py +0 -0
  21. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/src/binance_quant_engine/execution/__init__.py +0 -0
  22. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/src/binance_quant_engine/execution/algo_api.py +0 -0
  23. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/src/binance_quant_engine/execution/brackets.py +0 -0
  24. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/src/binance_quant_engine/execution/host.py +0 -0
  25. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/src/binance_quant_engine/execution/utils.py +0 -0
  26. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/src/binance_quant_engine/mcp/__init__.py +0 -0
  27. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/src/binance_quant_engine/mcp/server.py +0 -0
  28. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/src/binance_quant_engine/strategy/__init__.py +0 -0
  29. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/src/binance_quant_engine/strategy/demo_squeeze.py +0 -0
  30. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/src/binance_quant_engine/strategy/protocol.py +0 -0
  31. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/tests/test_backtest.py +0 -0
  32. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/tests/test_demo_squeeze.py +0 -0
  33. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/tests/test_execution_utils.py +0 -0
  34. {binance_quant_engine-0.1.1 → binance_quant_engine-0.1.2}/tests/test_mcp_server.py +0 -0
@@ -1,18 +1,32 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: binance-quant-engine
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
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/
5
+ Project-URL: Homepage, https://github.com/younghwan91/binance-quant-engine
6
6
  Project-URL: Repository, https://github.com/younghwan91/binance-quant-engine
7
+ Project-URL: Documentation, https://github.com/younghwan91/binance-quant-engine/blob/main/docs/USAGE.md
8
+ Project-URL: Bug Tracker, https://github.com/younghwan91/binance-quant-engine/issues
9
+ Project-URL: Changelog, https://github.com/younghwan91/binance-quant-engine/releases
7
10
  Author-email: Younghwan Chae <chyohw97@gmail.com>
8
11
  License-Expression: MIT
9
12
  License-File: LICENSE
10
- Keywords: algo-trading,backtest,binance,execution,futures,mcp,quant,trading
13
+ Keywords: algo-trading,algorithmic-trading,backtest,backtesting,binance,binance-futures,crypto,cryptocurrency,execution,futures,mcp,model-context-protocol,quant,quantitative-trading,trading,trading-bot
14
+ Classifier: Development Status :: 4 - Beta
15
+ Classifier: Intended Audience :: Developers
16
+ Classifier: Intended Audience :: Financial and Insurance Industry
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Office/Business :: Financial :: Investment
23
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
11
24
  Requires-Python: >=3.10
12
25
  Requires-Dist: numpy>=1.26
13
26
  Requires-Dist: pandas>=2.0
14
27
  Provides-Extra: dev
15
28
  Requires-Dist: mcp>=1.0; extra == 'dev'
29
+ Requires-Dist: pytest-asyncio>=0.24; extra == 'dev'
16
30
  Requires-Dist: pytest>=8.0; extra == 'dev'
17
31
  Requires-Dist: python-binance>=1.0.29; extra == 'dev'
18
32
  Requires-Dist: ruff>=0.6; extra == 'dev'
@@ -112,12 +126,12 @@ flowchart LR
112
126
  subgraph Decide["binance_quant_engine/strategy/ — decide"]
113
127
  PROTO{{"TradingStrategy\n(PEP 544 protocol)"}}
114
128
  SQZ["demo_squeeze.py\nSqueezeStrategy"]
115
- PROTO -.implements.-> SQZ
129
+ PROTO -.->|"implements"| SQZ
116
130
  end
117
131
 
118
132
  CSV --> BT
119
133
  SYN --> BT
120
- CACHE -.caches.-> CSV
134
+ CACHE -.->|"caches"| CSV
121
135
 
122
136
  subgraph Measure["binance_quant_engine/backtest/ — measure"]
123
137
  BT["vectorized.run_backtest()\nhands close[:t+1] only\n(no look-ahead)"]
@@ -139,9 +153,9 @@ flowchart LR
139
153
  subgraph MCP["binance_quant_engine/mcp/ — optional (.[mcp])"]
140
154
  SRV["server.py\nrun_demo_backtest · backtest_csv"]
141
155
  end
142
- BT -.callable via.-> SRV
156
+ BT -.->|"callable via"| SRV
143
157
 
144
- PROTO ==same interface\n(backtest = live)==> HOST
158
+ PROTO ==>|"same interface<br/>(backtest = live)"| HOST
145
159
  ALGO --> BINANCE[("Binance USDT-M\nFutures Algo Order API")]
146
160
  ```
147
161
 
@@ -88,12 +88,12 @@ flowchart LR
88
88
  subgraph Decide["binance_quant_engine/strategy/ — decide"]
89
89
  PROTO{{"TradingStrategy\n(PEP 544 protocol)"}}
90
90
  SQZ["demo_squeeze.py\nSqueezeStrategy"]
91
- PROTO -.implements.-> SQZ
91
+ PROTO -.->|"implements"| SQZ
92
92
  end
93
93
 
94
94
  CSV --> BT
95
95
  SYN --> BT
96
- CACHE -.caches.-> CSV
96
+ CACHE -.->|"caches"| CSV
97
97
 
98
98
  subgraph Measure["binance_quant_engine/backtest/ — measure"]
99
99
  BT["vectorized.run_backtest()\nhands close[:t+1] only\n(no look-ahead)"]
@@ -115,9 +115,9 @@ flowchart LR
115
115
  subgraph MCP["binance_quant_engine/mcp/ — optional (.[mcp])"]
116
116
  SRV["server.py\nrun_demo_backtest · backtest_csv"]
117
117
  end
118
- BT -.callable via.-> SRV
118
+ BT -.->|"callable via"| SRV
119
119
 
120
- PROTO ==same interface\n(backtest = live)==> HOST
120
+ PROTO ==>|"same interface<br/>(backtest = live)"| HOST
121
121
  ALGO --> BINANCE[("Binance USDT-M\nFutures Algo Order API")]
122
122
  ```
123
123
 
@@ -4,13 +4,30 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "binance-quant-engine"
7
- version = "0.1.1"
7
+ version = "0.1.2"
8
8
  description = "Strategy-agnostic Binance USDT-M futures backtest & execution engine — no-look-ahead, backtest/live parity, optional MCP server"
9
9
  readme = "README.md"
10
10
  license = "MIT"
11
11
  requires-python = ">=3.10"
12
- keywords = ["quant", "backtest", "trading", "binance", "futures", "execution", "algo-trading", "mcp"]
12
+ keywords = [
13
+ "quant", "quantitative-trading", "backtest", "backtesting", "trading",
14
+ "binance", "binance-futures", "crypto", "cryptocurrency", "futures",
15
+ "algo-trading", "algorithmic-trading", "trading-bot", "execution", "mcp",
16
+ "model-context-protocol",
17
+ ]
13
18
  authors = [{ name = "Younghwan Chae", email = "chyohw97@gmail.com" }]
19
+ classifiers = [
20
+ "Development Status :: 4 - Beta",
21
+ "Intended Audience :: Developers",
22
+ "Intended Audience :: Financial and Insurance Industry",
23
+ "Topic :: Office/Business :: Financial :: Investment",
24
+ "Topic :: Software Development :: Libraries :: Python Modules",
25
+ "Operating System :: OS Independent",
26
+ "Programming Language :: Python :: 3.10",
27
+ "Programming Language :: Python :: 3.11",
28
+ "Programming Language :: Python :: 3.12",
29
+ "Programming Language :: Python :: 3.13",
30
+ ]
14
31
  dependencies = [
15
32
  "numpy>=1.26",
16
33
  "pandas>=2.0",
@@ -20,21 +37,25 @@ dependencies = [
20
37
  # Live execution talks to Binance USDT-M Futures; not needed for backtests.
21
38
  live = ["python-binance>=1.0.29"]
22
39
  mcp = ["mcp>=1.0"]
23
- dev = ["pytest>=8.0", "ruff>=0.6", "python-binance>=1.0.29", "mcp>=1.0"]
40
+ dev = ["pytest>=8.0", "pytest-asyncio>=0.24", "ruff>=0.6", "python-binance>=1.0.29", "mcp>=1.0"]
24
41
 
25
42
  [project.scripts]
26
43
  bqe-backtest = "binance_quant_engine.backtest.vectorized:main"
27
44
  binance-quant-engine-mcp = "binance_quant_engine.mcp.server:main"
28
45
 
29
46
  [project.urls]
30
- Homepage = "https://pypi.org/project/binance-quant-engine/"
47
+ Homepage = "https://github.com/younghwan91/binance-quant-engine"
31
48
  Repository = "https://github.com/younghwan91/binance-quant-engine"
49
+ Documentation = "https://github.com/younghwan91/binance-quant-engine/blob/main/docs/USAGE.md"
50
+ "Bug Tracker" = "https://github.com/younghwan91/binance-quant-engine/issues"
51
+ Changelog = "https://github.com/younghwan91/binance-quant-engine/releases"
32
52
 
33
53
  [tool.hatch.build.targets.wheel]
34
54
  packages = ["src/binance_quant_engine"]
35
55
 
36
56
  [tool.pytest.ini_options]
37
57
  testpaths = ["tests"]
58
+ asyncio_mode = "strict"
38
59
 
39
60
  [tool.ruff]
40
61
  line-length = 100
@@ -1,3 +1,3 @@
1
1
  """Binance Quant Engine — a strategy-agnostic crypto-futures backtest & execution engine."""
2
2
 
3
- __version__ = "0.1.1"
3
+ __version__ = "0.1.2"
@@ -0,0 +1,161 @@
1
+ """Integration tests for BracketMixin against a fake host.
2
+
3
+ Exercises the mixin the way the live bot actually drives it — through
4
+ ``self`` — rather than testing the extracted :mod:`utils` functions in
5
+ isolation. This is what proves the two correctness fixes upstreamed from
6
+ the private system (tick-safe activationPrice, REJECTED as terminal) are
7
+ actually wired into the code path a real bot calls, not just correct as
8
+ standalone functions.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from binance_quant_engine.execution.brackets import BracketMixin
14
+ from binance_quant_engine.execution.host import TrailConfig
15
+
16
+
17
+ class FakeAlgoApi:
18
+ def __init__(self):
19
+ self.placed: list[dict] = []
20
+ self.next_algo_id = 1
21
+ self.raise_on_place: Exception | None = None
22
+ self.get_order_response: dict | None = None
23
+
24
+ def place_order(self, params: dict) -> dict:
25
+ if self.raise_on_place is not None:
26
+ raise self.raise_on_place
27
+ self.placed.append(params)
28
+ algo_id = self.next_algo_id
29
+ self.next_algo_id += 1
30
+ return {"algoId": algo_id}
31
+
32
+ def cancel_order(self, algo_id: int) -> dict:
33
+ return {"algoId": algo_id, "status": "CANCELLED"}
34
+
35
+ def get_order(self, algo_id: int) -> dict:
36
+ return self.get_order_response or {}
37
+
38
+
39
+ class FakeDiscord:
40
+ def __init__(self):
41
+ self.messages: list[str] = []
42
+
43
+ def send(self, content: str) -> None:
44
+ self.messages.append(content)
45
+
46
+
47
+ class FakeHost(BracketMixin):
48
+ """Minimal concrete implementation of ScalperProtocol for tests."""
49
+
50
+ def __init__(self, *, paper_mode: bool = False, tick_size: float = 0.01, price_precision: int = 2):
51
+ self.paper_mode = paper_mode
52
+ self.client = None
53
+ self.discord = FakeDiscord()
54
+ self.strategy = None
55
+ self.trail_config = TrailConfig()
56
+ self._algo_api = FakeAlgoApi()
57
+ self._tick_size = tick_size
58
+ self._price_precision = price_precision
59
+
60
+ def get_symbol_info(self, symbol: str) -> dict:
61
+ return {"tick_size": self._tick_size, "price_precision": self._price_precision}
62
+
63
+ def _round_qty(self, qty: float, symbol: str) -> float:
64
+ return qty
65
+
66
+ def _save_state(self) -> None:
67
+ pass
68
+
69
+
70
+ def test_paper_mode_stop_loss_never_calls_algo_api():
71
+ host = FakeHost(paper_mode=True)
72
+ algo_id = host._place_server_stop_loss(
73
+ "BTCUSDT", signal=1, quantity=1.0, entry_price=100.0, move_size=0.05, stop_mult=1.0
74
+ )
75
+ assert algo_id.startswith("paper_sl_")
76
+ assert host._algo_api.placed == []
77
+
78
+
79
+ def test_stop_loss_prices_are_tick_rounded():
80
+ # tick_size=0.01 forces a price like 95.037 to round to 95.04.
81
+ host = FakeHost(tick_size=0.01, price_precision=2)
82
+ host._place_server_stop_loss(
83
+ "BTCUSDT", signal=1, quantity=1.0, entry_price=100.037, move_size=0.05, stop_mult=1.0
84
+ )
85
+ assert len(host._algo_api.placed) == 1
86
+ params = host._algo_api.placed[0]
87
+ trigger = float(params["triggerPrice"])
88
+ limit = float(params["price"])
89
+ assert round(trigger / 0.01) * 0.01 == round(trigger, 10) # exactly on the tick grid
90
+ assert round(limit / 0.01) * 0.01 == round(limit, 10)
91
+
92
+
93
+ def test_stop_loss_negative_stop_mult_aborts_without_calling_api():
94
+ host = FakeHost()
95
+ algo_id = host._place_server_stop_loss(
96
+ "BTCUSDT", signal=1, quantity=1.0, entry_price=100.0, move_size=0.05, stop_mult=-1.0
97
+ )
98
+ assert algo_id is None
99
+ assert host._algo_api.placed == []
100
+
101
+
102
+ def test_take_profit_invariant_guards_reject_bad_inputs():
103
+ host = FakeHost()
104
+ assert host._place_server_take_profit(
105
+ "BTCUSDT", signal=1, quantity=1.0, entry_price=100.0, move_size=0.05, target_retrace=0.0
106
+ ) is None
107
+ assert host._algo_api.placed == []
108
+
109
+
110
+ def test_trailing_stop_activation_price_is_tick_rounded():
111
+ # An activation price that lands off the 0.1 tick grid must be snapped
112
+ # before it reaches params — this is the exact bug fixed vs. the
113
+ # quantbox-engine predecessor.
114
+ host = FakeHost(tick_size=0.1, price_precision=1)
115
+ host._place_trailing_stop_market(
116
+ "BTCUSDT", side="SELL", quantity=1.0, callback_rate=1.0, activation_price=100.037
117
+ )
118
+ assert len(host._algo_api.placed) == 1
119
+ activation = float(host._algo_api.placed[0]["activationPrice"])
120
+ assert activation == 100.0 # 100.037 snapped to the nearest 0.1 tick
121
+
122
+
123
+ def test_algo_order_would_trigger_returns_sentinel():
124
+ host = FakeHost()
125
+ host._algo_api.raise_on_place = Exception("APIError(code=-2021): Order would immediately trigger")
126
+ result = host._place_algo_order(
127
+ "BTCUSDT", side="SELL", order_type="STOP", quantity=1.0, trigger_price=100.0
128
+ )
129
+ assert result == "WOULD_TRIGGER"
130
+
131
+
132
+ def test_cancel_server_order_treats_unknown_order_as_success():
133
+ import json
134
+
135
+ from binance.exceptions import BinanceAPIException
136
+
137
+ class _FakeResponse:
138
+ status_code = 400
139
+
140
+ class _Raiser:
141
+ def cancel_order(self, algo_id):
142
+ body = json.dumps({"code": -2011, "msg": "Unknown order sent."})
143
+ raise BinanceAPIException(_FakeResponse(), 400, body)
144
+
145
+ host = FakeHost()
146
+ host._algo_api = _Raiser()
147
+ assert host._cancel_server_stop_loss("BTCUSDT", "12345") is True
148
+
149
+
150
+ def test_check_order_filled_recognizes_rejected_as_dead():
151
+ # The exact upstreamed fix: quantbox-engine's predecessor set didn't
152
+ # include REJECTED, so a rejected algo order stayed "tracked" forever.
153
+ host = FakeHost()
154
+ host._algo_api.get_order_response = {"algoStatus": "REJECTED"}
155
+ result = host._check_order_filled("BTCUSDT", "999")
156
+ assert result == {"status": "CANCELLED", "algoId": "999"}
157
+
158
+
159
+ def test_verify_algo_order_active_paper_mode_is_always_working():
160
+ host = FakeHost(paper_mode=True)
161
+ assert host._verify_algo_order_active("999", "BTCUSDT") == "WORKING"
@@ -0,0 +1,35 @@
1
+ """End-to-end smoke test: spawn the actual `binance-quant-engine-mcp` process
2
+ and speak real MCP-over-stdio to it, instead of calling the tool functions
3
+ in-process. This is what proves the packaged entry point (not just the
4
+ Python module) works for a real MCP client.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import shutil
10
+ import sys
11
+
12
+ import pytest
13
+
14
+ pytest.importorskip("mcp")
15
+
16
+ from mcp import ClientSession, StdioServerParameters # noqa: E402
17
+ from mcp.client.stdio import stdio_client # noqa: E402
18
+
19
+
20
+ @pytest.mark.asyncio
21
+ async def test_mcp_server_entry_point_lists_and_calls_tools():
22
+ exe = shutil.which("binance-quant-engine-mcp") or sys.executable
23
+ args = [] if exe != sys.executable else ["-m", "binance_quant_engine.mcp.server"]
24
+
25
+ params = StdioServerParameters(command=exe, args=args)
26
+ async with stdio_client(params) as (read, write):
27
+ async with ClientSession(read, write) as session:
28
+ await session.initialize()
29
+
30
+ tools = await session.list_tools()
31
+ names = {t.name for t in tools.tools}
32
+ assert {"run_demo_backtest", "backtest_csv", "describe_strategy_protocol"} <= names
33
+
34
+ result = await session.call_tool("run_demo_backtest", {"n_bars": 400, "seed": 3})
35
+ assert not result.is_error