quantbt-engine 1.0.7__py3-none-any.whl

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 (133) hide show
  1. quantbt/__init__.py +862 -0
  2. quantbt/adapters/__init__.py +5 -0
  3. quantbt/adapters/nautilus/__init__.py +42 -0
  4. quantbt/adapters/nautilus/_dependency.py +53 -0
  5. quantbt/adapters/nautilus/backend.py +639 -0
  6. quantbt/adapters/nautilus/instruments.py +275 -0
  7. quantbt/adapters/nautilus/options.py +465 -0
  8. quantbt/adapters/nautilus/reports.py +231 -0
  9. quantbt/backends/__init__.py +29 -0
  10. quantbt/backends/_native_event_rust.py +2168 -0
  11. quantbt/backends/native_event.py +5048 -0
  12. quantbt/backends/native_option.py +796 -0
  13. quantbt/backends/native_portfolio.py +928 -0
  14. quantbt/backends/native_vectorized.py +1299 -0
  15. quantbt/backtester.py +485 -0
  16. quantbt/benchmarks/__init__.py +1 -0
  17. quantbt/benchmarks/compare_phase9_parity.py +302 -0
  18. quantbt/benchmarks/gamma_scalping_backtestsample.py +802 -0
  19. quantbt/benchmarks/profile_phase7.py +415 -0
  20. quantbt/benchmarks/run_arbitrage_phase_e.py +160 -0
  21. quantbt/benchmarks/run_optimization_overhead.py +195 -0
  22. quantbt/benchmarks/run_options_engine.py +272 -0
  23. quantbt/benchmarks/run_pct_equity_nautilus_smoke.py +227 -0
  24. quantbt/benchmarks/run_phase12_arbitrage_cert.py +402 -0
  25. quantbt/benchmarks/run_phase12_benchmark_nautilus_cert.py +430 -0
  26. quantbt/benchmarks/run_phase13_portfolio_report.py +97 -0
  27. quantbt/benchmarks/run_phase13_wfo_cache.py +162 -0
  28. quantbt/benchmarks/run_phase14_service_loop.py +694 -0
  29. quantbt/benchmarks/run_phase15a_nautilus_certification.py +514 -0
  30. quantbt/benchmarks/run_phase15b_synthetic_depth.py +175 -0
  31. quantbt/benchmarks/run_phase16_performance_debt.py +389 -0
  32. quantbt/benchmarks/run_phase30e_reactive_runner.py +161 -0
  33. quantbt/benchmarks/run_phase31_intrabar.py +441 -0
  34. quantbt/benchmarks/run_phase34a_native_event_memory.py +179 -0
  35. quantbt/benchmarks/run_phase34b_native_event_prepared_score.py +173 -0
  36. quantbt/benchmarks/run_phase34c_native_event_single_pass.py +176 -0
  37. quantbt/benchmarks/run_phase7.py +654 -0
  38. quantbt/benchmarks/run_portfolio_real_parity.py +537 -0
  39. quantbt/core/__init__.py +323 -0
  40. quantbt/core/arbitrage.py +767 -0
  41. quantbt/core/basket.py +234 -0
  42. quantbt/core/certification.py +274 -0
  43. quantbt/core/constraints.py +155 -0
  44. quantbt/core/engine.py +1144 -0
  45. quantbt/core/event.py +869 -0
  46. quantbt/core/execution_contract.py +197 -0
  47. quantbt/core/execution_depth.py +613 -0
  48. quantbt/core/intrabar_kernel.py +1906 -0
  49. quantbt/core/intrabar_reference.py +907 -0
  50. quantbt/core/intrabar_session.py +156 -0
  51. quantbt/core/market_tape.py +480 -0
  52. quantbt/core/native_event_capabilities.py +121 -0
  53. quantbt/core/native_event_parity.py +336 -0
  54. quantbt/core/order_compiler.py +422 -0
  55. quantbt/core/orders.py +319 -0
  56. quantbt/core/portfolio.py +392 -0
  57. quantbt/core/preprocessor.py +256 -0
  58. quantbt/core/reactive.py +173 -0
  59. quantbt/core/results.py +341 -0
  60. quantbt/core/schema.py +211 -0
  61. quantbt/core/scopes.py +96 -0
  62. quantbt/core/structured_orders.py +378 -0
  63. quantbt/core/types.py +87 -0
  64. quantbt/core/vectorized.py +203 -0
  65. quantbt/endpoint.py +4465 -0
  66. quantbt/engines.py +1014 -0
  67. quantbt/metrics/__init__.py +45 -0
  68. quantbt/metrics/options_analytics.py +46 -0
  69. quantbt/metrics/performance.py +541 -0
  70. quantbt/optimization/__init__.py +94 -0
  71. quantbt/optimization/callbacks.py +116 -0
  72. quantbt/optimization/candidate_selection.py +397 -0
  73. quantbt/optimization/config.py +84 -0
  74. quantbt/optimization/constraints.py +22 -0
  75. quantbt/optimization/evaluator.py +21 -0
  76. quantbt/optimization/evaluators/__init__.py +32 -0
  77. quantbt/optimization/evaluators/arbitrage.py +22 -0
  78. quantbt/optimization/evaluators/generic.py +34 -0
  79. quantbt/optimization/evaluators/grid_dca.py +22 -0
  80. quantbt/optimization/evaluators/intrabar.py +54 -0
  81. quantbt/optimization/evaluators/native_event.py +51 -0
  82. quantbt/optimization/evaluators/options.py +22 -0
  83. quantbt/optimization/evaluators/portfolio.py +42 -0
  84. quantbt/optimization/evaluators/signal.py +43 -0
  85. quantbt/optimization/multiseed.py +176 -0
  86. quantbt/optimization/objectives.py +221 -0
  87. quantbt/optimization/optimizer.py +500 -0
  88. quantbt/optimization/result.py +80 -0
  89. quantbt/optimization/samplers.py +84 -0
  90. quantbt/optimization/space.py +223 -0
  91. quantbt/options/__init__.py +215 -0
  92. quantbt/options/cache.py +116 -0
  93. quantbt/options/conventions.py +158 -0
  94. quantbt/options/data.py +177 -0
  95. quantbt/options/execution.py +600 -0
  96. quantbt/options/fees.py +135 -0
  97. quantbt/options/greeks.py +173 -0
  98. quantbt/options/hedging.py +232 -0
  99. quantbt/options/iv.py +188 -0
  100. quantbt/options/ledger.py +263 -0
  101. quantbt/options/lifecycle.py +94 -0
  102. quantbt/options/margin.py +311 -0
  103. quantbt/options/packages.py +147 -0
  104. quantbt/options/pricing.py +191 -0
  105. quantbt/options/schema.py +227 -0
  106. quantbt/options/selectors.py +281 -0
  107. quantbt/options/strategy.py +249 -0
  108. quantbt/options/surface.py +142 -0
  109. quantbt/options/tape.py +226 -0
  110. quantbt/options/templates/__init__.py +33 -0
  111. quantbt/options/templates/packages.py +354 -0
  112. quantbt/portfolio.py +606 -0
  113. quantbt/py.typed +1 -0
  114. quantbt/reporting/__init__.py +38 -0
  115. quantbt/reporting/arbitrage_audit.py +211 -0
  116. quantbt/reporting/nautilus_bundle.py +758 -0
  117. quantbt/reporting/nautilus_certification.py +226 -0
  118. quantbt/reporting/nautilus_diagnostics.py +225 -0
  119. quantbt/reporting/parity.py +496 -0
  120. quantbt/reporting/portfolio_audit.py +206 -0
  121. quantbt/reporting/portfolio_nautilus.py +140 -0
  122. quantbt/sizing/__init__.py +3 -0
  123. quantbt/sizing/fast.py +67 -0
  124. quantbt/sizing/modes.py +162 -0
  125. quantbt/viz/__init__.py +4 -0
  126. quantbt/viz/plots.py +310 -0
  127. quantbt/viz/themes.py +102 -0
  128. quantbt/walkforward.py +3136 -0
  129. quantbt_engine-1.0.7.dist-info/METADATA +891 -0
  130. quantbt_engine-1.0.7.dist-info/RECORD +133 -0
  131. quantbt_engine-1.0.7.dist-info/WHEEL +5 -0
  132. quantbt_engine-1.0.7.dist-info/licenses/LICENSE +21 -0
  133. quantbt_engine-1.0.7.dist-info/top_level.txt +1 -0
quantbt/core/orders.py ADDED
@@ -0,0 +1,319 @@
1
+ """
2
+ quantbt.core.orders
3
+ -------------------
4
+ Order, fill, and trade records used by event-driven backends and result V2.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass, field
10
+ from enum import Enum
11
+ from typing import Dict, Optional, Sequence, Tuple
12
+
13
+ from .schema import LiquiditySide, OrderSide, OrderType, TimeInForce
14
+
15
+
16
+ class OrderAction(str, Enum):
17
+ """Lifecycle command consumed by the native-event v2 compiler."""
18
+
19
+ PLACE = "place"
20
+ CANCEL = "cancel"
21
+ REPLACE = "replace"
22
+ AMEND = "amend"
23
+ CANCEL_ALL = "cancel_all"
24
+
25
+
26
+ class OrderActivationPolicy(str, Enum):
27
+ """When a placed child order becomes eligible for matching."""
28
+
29
+ IMMEDIATE = "immediate"
30
+ ON_PARENT_FIRST_FILL = "on_parent_first_fill"
31
+ ON_PARENT_FULL_FILL = "on_parent_full_fill"
32
+
33
+
34
+ @dataclass(frozen=True)
35
+ class OrderIntent:
36
+ timestamp: object
37
+ symbol: str
38
+ side: OrderSide
39
+ order_type: OrderType
40
+ qty: float
41
+ price: Optional[float] = None
42
+ trigger_price: Optional[float] = None
43
+ tif: TimeInForce = TimeInForce.GTC
44
+ reduce_only: bool = False
45
+ order_id: Optional[str] = None
46
+ tag: Optional[str] = None
47
+ metadata: Dict = field(default_factory=dict)
48
+
49
+ def __post_init__(self) -> None:
50
+ if not self.symbol:
51
+ raise ValueError("symbol is required")
52
+ if self.qty <= 0.0:
53
+ raise ValueError("qty must be > 0")
54
+ if self.order_type in (OrderType.LIMIT, OrderType.STOP_LIMIT):
55
+ if self.price is None or self.price <= 0.0:
56
+ raise ValueError("limit orders require price > 0")
57
+ if self.order_type in (OrderType.STOP_MARKET, OrderType.STOP_LIMIT):
58
+ if self.trigger_price is None or self.trigger_price <= 0.0:
59
+ raise ValueError("stop orders require trigger_price > 0")
60
+
61
+ @property
62
+ def signed_qty(self) -> float:
63
+ return self.qty * self.side.sign
64
+
65
+
66
+ @dataclass(frozen=True)
67
+ class OrderCommand:
68
+ """
69
+ Canonical order-lifecycle command for native-event v2 and adapters.
70
+
71
+ `OrderIntent` remains the backwards-compatible shorthand for an immediate
72
+ PLACE command. Phase 30A only defines and compiles this contract; lifecycle
73
+ matching is wired into a dedicated v2 engine phase.
74
+ """
75
+
76
+ timestamp: object
77
+ action: OrderAction = OrderAction.PLACE
78
+ symbol: Optional[str] = None
79
+ side: Optional[OrderSide] = None
80
+ order_type: Optional[OrderType] = None
81
+ qty: Optional[float] = None
82
+ price: Optional[float] = None
83
+ trigger_price: Optional[float] = None
84
+ tif: TimeInForce = TimeInForce.GTC
85
+ reduce_only: bool = False
86
+ order_id: Optional[str] = None
87
+ target_order_id: Optional[str] = None
88
+ parent_order_id: Optional[str] = None
89
+ group_id: Optional[str] = None
90
+ oco_group_id: Optional[str] = None
91
+ activation_policy: OrderActivationPolicy = OrderActivationPolicy.IMMEDIATE
92
+ expires_at: Optional[object] = None
93
+ tag: Optional[str] = None
94
+ metadata: Dict = field(default_factory=dict)
95
+ tag_prefix: Optional[str] = None
96
+
97
+ def __post_init__(self) -> None:
98
+ action = _normalize_order_action(self.action)
99
+ object.__setattr__(self, "action", action)
100
+
101
+ activation = _normalize_activation_policy(self.activation_policy)
102
+ object.__setattr__(self, "activation_policy", activation)
103
+
104
+ if action in (OrderAction.PLACE, OrderAction.REPLACE):
105
+ if not self.symbol:
106
+ raise ValueError(f"{action.value} command requires symbol")
107
+ if self.side is None:
108
+ raise ValueError(f"{action.value} command requires side")
109
+ if self.order_type is None:
110
+ raise ValueError(f"{action.value} command requires order_type")
111
+ if self.qty is None or self.qty <= 0.0:
112
+ raise ValueError(f"{action.value} command requires qty > 0")
113
+ if self.order_type in (OrderType.LIMIT, OrderType.STOP_LIMIT):
114
+ if self.price is None or self.price <= 0.0:
115
+ raise ValueError("limit commands require price > 0")
116
+ if self.order_type in (OrderType.STOP_MARKET, OrderType.STOP_LIMIT):
117
+ if self.trigger_price is None or self.trigger_price <= 0.0:
118
+ raise ValueError("stop commands require trigger_price > 0")
119
+ if action is OrderAction.REPLACE and not self.target_order_id:
120
+ raise ValueError("replace command requires target_order_id")
121
+ elif action in (OrderAction.CANCEL, OrderAction.AMEND):
122
+ if not self.target_order_id:
123
+ raise ValueError(f"{action.value} command requires target_order_id")
124
+ if action is OrderAction.AMEND:
125
+ if self.qty is not None and self.qty <= 0.0:
126
+ raise ValueError("amend qty must be > 0")
127
+ if self.price is not None and self.price <= 0.0:
128
+ raise ValueError("amend price must be > 0")
129
+ if self.trigger_price is not None and self.trigger_price <= 0.0:
130
+ raise ValueError("amend trigger_price must be > 0")
131
+ elif action is OrderAction.CANCEL_ALL:
132
+ pass
133
+ else:
134
+ raise NotImplementedError(f"unsupported order action={action!r}")
135
+
136
+ @classmethod
137
+ def from_intent(cls, intent: OrderIntent) -> "OrderCommand":
138
+ return cls(
139
+ timestamp=intent.timestamp,
140
+ action=OrderAction.PLACE,
141
+ symbol=intent.symbol,
142
+ side=intent.side,
143
+ order_type=intent.order_type,
144
+ qty=float(intent.qty),
145
+ price=intent.price,
146
+ trigger_price=intent.trigger_price,
147
+ tif=intent.tif,
148
+ reduce_only=intent.reduce_only,
149
+ order_id=intent.order_id,
150
+ tag=intent.tag,
151
+ metadata=dict(intent.metadata),
152
+ )
153
+
154
+ def to_intent(self) -> OrderIntent:
155
+ if self.action is not OrderAction.PLACE:
156
+ raise ValueError("only place commands can be converted to OrderIntent")
157
+ if self.symbol is None or self.side is None or self.order_type is None or self.qty is None:
158
+ raise ValueError("place command is incomplete")
159
+ return OrderIntent(
160
+ timestamp=self.timestamp,
161
+ symbol=self.symbol,
162
+ side=self.side,
163
+ order_type=self.order_type,
164
+ qty=float(self.qty),
165
+ price=self.price,
166
+ trigger_price=self.trigger_price,
167
+ tif=self.tif,
168
+ reduce_only=self.reduce_only,
169
+ order_id=self.order_id,
170
+ tag=self.tag,
171
+ metadata=dict(self.metadata),
172
+ )
173
+
174
+ @property
175
+ def signed_qty(self) -> float:
176
+ if self.side is None or self.qty is None:
177
+ return 0.0
178
+ return float(self.qty) * self.side.sign
179
+
180
+
181
+ @dataclass(frozen=True)
182
+ class BasketIntent:
183
+ timestamp: object
184
+ basket_id: str
185
+ signal: float
186
+ gross_notional: Optional[float] = None
187
+ tag: Optional[str] = None
188
+ metadata: Dict = field(default_factory=dict)
189
+
190
+ def __post_init__(self) -> None:
191
+ if not self.basket_id:
192
+ raise ValueError("basket_id is required")
193
+
194
+
195
+ @dataclass(frozen=True)
196
+ class Fill:
197
+ timestamp: object
198
+ symbol: str
199
+ side: OrderSide
200
+ qty: float
201
+ price: float
202
+ fee: float = 0.0
203
+ liquidity: LiquiditySide = LiquiditySide.TAKER
204
+ order_id: Optional[str] = None
205
+ trade_id: Optional[str] = None
206
+ metadata: Dict = field(default_factory=dict)
207
+
208
+ def __post_init__(self) -> None:
209
+ if not self.symbol:
210
+ raise ValueError("symbol is required")
211
+ if self.qty <= 0.0:
212
+ raise ValueError("qty must be > 0")
213
+ if self.price <= 0.0:
214
+ raise ValueError("price must be > 0")
215
+ if self.fee < 0.0:
216
+ raise ValueError("fee must be >= 0")
217
+
218
+ @property
219
+ def signed_qty(self) -> float:
220
+ return self.qty * self.side.sign
221
+
222
+ @property
223
+ def notional(self) -> float:
224
+ return self.qty * self.price
225
+
226
+
227
+ @dataclass(frozen=True)
228
+ class Trade:
229
+ symbol: str
230
+ qty: float
231
+ side: OrderSide
232
+ opened_at: object
233
+ closed_at: object
234
+ avg_entry: float
235
+ avg_exit: float
236
+ realized_pnl: float
237
+ fees: float = 0.0
238
+ trade_id: Optional[str] = None
239
+ metadata: Dict = field(default_factory=dict)
240
+
241
+ def __post_init__(self) -> None:
242
+ if not self.symbol:
243
+ raise ValueError("symbol is required")
244
+ if self.qty <= 0.0:
245
+ raise ValueError("qty must be > 0")
246
+ if self.avg_entry <= 0.0 or self.avg_exit <= 0.0:
247
+ raise ValueError("avg_entry and avg_exit must be > 0")
248
+ if self.fees < 0.0:
249
+ raise ValueError("fees must be >= 0")
250
+
251
+
252
+ def _normalize_order_action(action: OrderAction | str) -> OrderAction:
253
+ if isinstance(action, OrderAction):
254
+ return action
255
+ return OrderAction(str(action))
256
+
257
+
258
+ def _normalize_activation_policy(policy: OrderActivationPolicy | str) -> OrderActivationPolicy:
259
+ if isinstance(policy, OrderActivationPolicy):
260
+ return policy
261
+ return OrderActivationPolicy(str(policy))
262
+
263
+
264
+ def order_intents_to_lifecycle_commands(
265
+ orders: Sequence[OrderIntent],
266
+ *,
267
+ linked_metadata: bool = True,
268
+ ) -> Tuple[OrderCommand, ...]:
269
+ """
270
+ Convert `OrderIntent` records into lifecycle-v2 `OrderCommand` records.
271
+
272
+ Structured package builders already carry parent/OCO information in
273
+ metadata. This helper lifts those fields into the explicit command contract
274
+ while preserving all old order intent fields for compatibility.
275
+ """
276
+ commands = []
277
+ tag_to_id = {}
278
+ for idx, order in enumerate(orders):
279
+ order_id = order.order_id or order.tag or f"order-{idx}"
280
+ tag_to_id[order.tag] = order_id
281
+
282
+ for idx, order in enumerate(orders):
283
+ metadata = dict(order.metadata)
284
+ order_id = order.order_id or order.tag or f"order-{idx}"
285
+ parent_id = None
286
+ oco_group_id = None
287
+ activation = OrderActivationPolicy.IMMEDIATE
288
+ group_id = None
289
+ if linked_metadata:
290
+ group_id = metadata.get("group_id") or metadata.get("package_id") or metadata.get("arb_id")
291
+ leg_role = str(metadata.get("leg_role", "")).lower().strip()
292
+ if order.reduce_only or leg_role in {"take_profit", "stop_loss", "exit"}:
293
+ oco_group_id = metadata.get("oco_group_id")
294
+ parent_ref = metadata.get("parent_order_id") or metadata.get("parent_tag")
295
+ if parent_ref is not None:
296
+ parent_id = tag_to_id.get(parent_ref, str(parent_ref))
297
+ activation = OrderActivationPolicy.ON_PARENT_FIRST_FILL
298
+ commands.append(
299
+ OrderCommand(
300
+ timestamp=order.timestamp,
301
+ action=OrderAction.PLACE,
302
+ symbol=order.symbol,
303
+ side=order.side,
304
+ order_type=order.order_type,
305
+ qty=float(order.qty),
306
+ price=order.price,
307
+ trigger_price=order.trigger_price,
308
+ tif=order.tif,
309
+ reduce_only=order.reduce_only,
310
+ order_id=order_id,
311
+ parent_order_id=parent_id,
312
+ group_id=None if group_id is None else str(group_id),
313
+ oco_group_id=None if oco_group_id is None else str(oco_group_id),
314
+ activation_policy=activation,
315
+ tag=order.tag,
316
+ metadata=metadata,
317
+ )
318
+ )
319
+ return tuple(commands)