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
@@ -0,0 +1,441 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Phase 31D intrabar execution benchmark and certification summary.
4
+ """
5
+
6
+ from __future__ import annotations
7
+
8
+ import argparse
9
+ import gc
10
+ import json
11
+ import statistics
12
+ import sys
13
+ import time
14
+ from dataclasses import asdict, dataclass
15
+ from pathlib import Path
16
+ from typing import Dict, List
17
+
18
+ import numpy as np
19
+ import pandas as pd
20
+
21
+
22
+ PACKAGE_DIR = Path(__file__).resolve().parents[1]
23
+ PROJECT_DIR = PACKAGE_DIR.parent
24
+ if str(PROJECT_DIR) not in sys.path:
25
+ sys.path.insert(0, str(PROJECT_DIR))
26
+
27
+ from quantbt import ( # noqa: E402
28
+ AccountConfig,
29
+ BacktestEngineV2,
30
+ ExecutionContract,
31
+ FillReplayTape,
32
+ IntrabarIntentTape,
33
+ IntrabarSessionTape,
34
+ OrderIntent,
35
+ OrderSide,
36
+ OrderType,
37
+ SessionExecutionPolicy,
38
+ prepare_market_tape,
39
+ run_fill_replay_kernel,
40
+ run_intrabar_kernel,
41
+ run_intrabar_reference,
42
+ run_intrabar_session_kernel,
43
+ )
44
+ from quantbt.core.vectorized import _engine_units_v2 # noqa: E402
45
+
46
+
47
+ @dataclass
48
+ class Phase31BenchmarkRecord:
49
+ route: str
50
+ rows: int
51
+ symbols: int
52
+ fills_or_orders: int
53
+ warmup_seconds: float
54
+ runtime_seconds: float
55
+ runtime_min_seconds: float
56
+ runtime_max_seconds: float
57
+ bars_per_second: float
58
+ ratio_vs_close_target: float | None = None
59
+ ratio_vs_intrabar_minimal: float | None = None
60
+ speedup_vs_reference: float | None = None
61
+ parity: str = "n/a"
62
+ notes: str = ""
63
+
64
+
65
+ def run_benchmark(*, rows: int = 25_000, repeats: int = 3, seed: int = 31) -> Dict:
66
+ df, intent = _make_intrabar_fixture(rows=rows, seed=seed)
67
+ tape = prepare_market_tape(data=df, symbols=["BTC"], use_funding=False)
68
+ account = AccountConfig(initial_capital=100_000.0, leverage=10.0)
69
+ contract = ExecutionContract.intrabar_bracket(close_on_last_bar=True)
70
+
71
+ records: list[Phase31BenchmarkRecord] = []
72
+ close_stats = _measure(lambda: _run_close_target_kernel(tape, intent, account), repeats=repeats)
73
+ records.append(_record("close_target_v2_pure_kernel", rows, 1, 0, close_stats, baseline=close_stats["best"], parity="baseline"))
74
+
75
+ minimal_stats = _measure(
76
+ lambda: run_intrabar_kernel(tape=tape, intent=intent, account=account, contract=contract, report_level="minimal"),
77
+ repeats=repeats,
78
+ )
79
+ minimal_result = run_intrabar_kernel(tape=tape, intent=intent, account=account, contract=contract, report_level="minimal")
80
+ records.append(
81
+ _record(
82
+ "intrabar_bracket_v1_minimal",
83
+ rows,
84
+ 1,
85
+ minimal_result.fill_count,
86
+ minimal_stats,
87
+ baseline=close_stats["best"],
88
+ parity="oracle_checked_in_tests",
89
+ )
90
+ )
91
+
92
+ audit_stats = _measure(
93
+ lambda: run_intrabar_kernel(tape=tape, intent=intent, account=account, contract=contract, report_level="audit"),
94
+ repeats=repeats,
95
+ )
96
+ audit_result = run_intrabar_kernel(tape=tape, intent=intent, account=account, contract=contract, report_level="audit")
97
+ records.append(
98
+ _record(
99
+ "intrabar_bracket_v1_audit",
100
+ rows,
101
+ 1,
102
+ audit_result.fill_count,
103
+ audit_stats,
104
+ baseline=close_stats["best"],
105
+ intrabar_minimal=minimal_stats["best"],
106
+ parity="pass" if np.allclose(audit_result.equity, minimal_result.equity, atol=1e-9, rtol=0.0) else "fail",
107
+ notes="two_pass_sparse_fills",
108
+ )
109
+ )
110
+
111
+ session_tape = IntrabarSessionTape(
112
+ session_id=np.arange(rows, dtype=np.int64) // 24,
113
+ entry_allowed_at_open=np.ones(rows, dtype=np.bool_),
114
+ force_flat_at_open=(np.arange(rows, dtype=np.int64) % 24) == 23,
115
+ )
116
+ session_policy = SessionExecutionPolicy(max_long_entries_per_session=2)
117
+ session_minimal_stats = _measure(
118
+ lambda: run_intrabar_session_kernel(
119
+ tape=tape,
120
+ intent=intent,
121
+ account=account,
122
+ contract=contract,
123
+ session_policy=session_policy,
124
+ session_tape=session_tape,
125
+ report_level="minimal",
126
+ ),
127
+ repeats=repeats,
128
+ )
129
+ session_minimal = run_intrabar_session_kernel(
130
+ tape=tape,
131
+ intent=intent,
132
+ account=account,
133
+ contract=contract,
134
+ session_policy=session_policy,
135
+ session_tape=session_tape,
136
+ report_level="minimal",
137
+ )
138
+ records.append(
139
+ _record(
140
+ "intrabar_session_bracket_v1_minimal",
141
+ rows,
142
+ 1,
143
+ session_minimal.fill_count,
144
+ session_minimal_stats,
145
+ baseline=close_stats["best"],
146
+ intrabar_minimal=minimal_stats["best"],
147
+ parity="reference_checked_in_tests",
148
+ notes="session_state_kernel",
149
+ )
150
+ )
151
+
152
+ session_audit_stats = _measure(
153
+ lambda: run_intrabar_session_kernel(
154
+ tape=tape,
155
+ intent=intent,
156
+ account=account,
157
+ contract=contract,
158
+ session_policy=session_policy,
159
+ session_tape=session_tape,
160
+ report_level="audit",
161
+ ),
162
+ repeats=repeats,
163
+ )
164
+ session_audit = run_intrabar_session_kernel(
165
+ tape=tape,
166
+ intent=intent,
167
+ account=account,
168
+ contract=contract,
169
+ session_policy=session_policy,
170
+ session_tape=session_tape,
171
+ report_level="audit",
172
+ )
173
+ records.append(
174
+ _record(
175
+ "intrabar_session_bracket_v1_audit",
176
+ rows,
177
+ 1,
178
+ session_audit.fill_count,
179
+ session_audit_stats,
180
+ baseline=close_stats["best"],
181
+ intrabar_minimal=minimal_stats["best"],
182
+ parity="pass" if np.allclose(session_audit.equity, session_minimal.equity, atol=1e-9, rtol=0.0) else "fail",
183
+ notes="session_two_pass_sparse_fills",
184
+ )
185
+ )
186
+
187
+ reference_stats = _measure(
188
+ lambda: run_intrabar_reference(tape=tape, intent=intent, account=account, contract=contract),
189
+ repeats=max(1, min(2, repeats)),
190
+ )
191
+ records.append(
192
+ _record(
193
+ "intrabar_reference_python",
194
+ rows,
195
+ 1,
196
+ audit_result.fill_count,
197
+ reference_stats,
198
+ baseline=close_stats["best"],
199
+ intrabar_minimal=minimal_stats["best"],
200
+ parity="truth_model",
201
+ )
202
+ )
203
+
204
+ fill_tape = FillReplayTape.from_frame(audit_result.fills_report)
205
+ fill_replay_stats = _measure(
206
+ lambda: run_fill_replay_kernel(tape=tape, fill_tape=fill_tape, account=account),
207
+ repeats=repeats,
208
+ )
209
+ records.append(
210
+ _record(
211
+ "fill_replay_v1_kernel",
212
+ rows,
213
+ 1,
214
+ len(fill_tape.bar_index),
215
+ fill_replay_stats,
216
+ baseline=close_stats["best"],
217
+ intrabar_minimal=minimal_stats["best"],
218
+ parity="accounting_only",
219
+ )
220
+ )
221
+
222
+ native_event_stats = _measure(lambda: _run_native_event_orders(df, audit_result.fills_report), repeats=max(1, min(2, repeats)))
223
+ records.append(
224
+ _record(
225
+ "native_event_explicit_orders_facade",
226
+ rows,
227
+ 1,
228
+ int(len(audit_result.fills_report)),
229
+ native_event_stats,
230
+ baseline=close_stats["best"],
231
+ intrabar_minimal=minimal_stats["best"],
232
+ parity="speed_reference_not_semantic_claim",
233
+ notes="full_facade_order_replay",
234
+ )
235
+ )
236
+
237
+ reference = next(r for r in records if r.route == "intrabar_reference_python")
238
+ for record in records:
239
+ if record.route.startswith("intrabar_bracket_v1") or record.route.startswith("intrabar_session_bracket_v1"):
240
+ record.speedup_vs_reference = reference.runtime_seconds / record.runtime_seconds
241
+
242
+ return {
243
+ "rows": rows,
244
+ "repeats": repeats,
245
+ "seed": seed,
246
+ "records": [asdict(record) for record in records],
247
+ "summary": _summary(records),
248
+ }
249
+
250
+
251
+ def make_markdown(report: Dict) -> str:
252
+ lines = [
253
+ "# Phase 31 Intrabar Benchmark",
254
+ "",
255
+ f"- Rows: `{report['rows']}`",
256
+ f"- Repeats: `{report['repeats']}`",
257
+ f"- Seed: `{report['seed']}`",
258
+ "",
259
+ "| Route | Runtime | Bars/s | Ratio vs close-target | Ratio vs intrabar minimal | Speedup vs Python oracle | Fills/orders | Parity | Notes |",
260
+ "|---|---:|---:|---:|---:|---:|---:|---|---|",
261
+ ]
262
+ for record in report["records"]:
263
+ lines.append(
264
+ "| `{route}` | {runtime:.6f}s | {bps:,.0f} | {rclose} | {rmin} | {speedup} | {fills} | {parity} | {notes} |".format(
265
+ route=record["route"],
266
+ runtime=record["runtime_seconds"],
267
+ bps=record["bars_per_second"],
268
+ rclose=_fmt_ratio(record["ratio_vs_close_target"]),
269
+ rmin=_fmt_ratio(record["ratio_vs_intrabar_minimal"]),
270
+ speedup=_fmt_ratio(record["speedup_vs_reference"]),
271
+ fills=record["fills_or_orders"],
272
+ parity=record["parity"],
273
+ notes=record["notes"] or "",
274
+ )
275
+ )
276
+ lines.extend(
277
+ [
278
+ "",
279
+ "## Summary",
280
+ "",
281
+ f"- Fast intrabar minimal vs Python oracle: `{_fmt_ratio(report['summary']['intrabar_minimal_speedup_vs_reference'])}` faster.",
282
+ f"- Fast intrabar audit vs minimal: `{_fmt_ratio(report['summary']['intrabar_audit_ratio_vs_minimal'])}` runtime ratio.",
283
+ f"- Fast intrabar minimal vs close-target pure kernel: `{_fmt_ratio(report['summary']['intrabar_minimal_ratio_vs_close_target'])}` runtime ratio.",
284
+ "",
285
+ "Interpretation: close-target remains the fastest narrow contract. The new intrabar kernel is the fast path for alpha logic that needs next-open entry, intrabar SL/TP/trailing, and audit fills without falling back to Python event loops.",
286
+ ]
287
+ )
288
+ return "\n".join(lines) + "\n"
289
+
290
+
291
+ def _make_intrabar_fixture(*, rows: int, seed: int):
292
+ rng = np.random.default_rng(seed)
293
+ idx = pd.date_range("2020-01-01", periods=rows, freq="1h", tz="UTC")
294
+ ret = rng.normal(0.0, 0.0015, size=rows)
295
+ close = 100.0 * np.exp(np.cumsum(ret))
296
+ open_ = np.r_[close[0], close[:-1] * (1.0 + rng.normal(0.0, 0.0002, size=rows - 1))]
297
+ high = np.maximum(open_, close) * (1.0 + rng.uniform(0.0005, 0.006, size=rows))
298
+ low = np.minimum(open_, close) * (1.0 - rng.uniform(0.0005, 0.006, size=rows))
299
+ df = pd.DataFrame({"open": open_, "high": high, "low": low, "close": close, "volume": 100.0}, index=idx)
300
+ entry_side = np.zeros(rows, dtype=np.int8)
301
+ entry_size = np.zeros(rows, dtype=np.float64)
302
+ entry_side[5::50] = 1
303
+ entry_size[5::50] = 1.0
304
+ entry_side[30::50] = -1
305
+ entry_size[30::50] = 1.0
306
+ stop = np.full(rows, 0.012, dtype=np.float64)
307
+ tp = np.full(rows, 0.018, dtype=np.float64)
308
+ trailing = np.full(rows, 0.010, dtype=np.float64)
309
+ technical_exit = np.zeros(rows, dtype=np.bool_)
310
+ technical_exit[45::50] = True
311
+ intent = IntrabarIntentTape.from_arrays(
312
+ entry_side=entry_side,
313
+ entry_size=entry_size,
314
+ stop_value=stop,
315
+ take_profit_value=tp,
316
+ trailing_value=trailing,
317
+ technical_exit=technical_exit,
318
+ )
319
+ return df, intent
320
+
321
+
322
+ def _run_close_target_kernel(tape, intent, account):
323
+ target = np.zeros((tape.n_bars, 1), dtype=np.float64)
324
+ current = 0.0
325
+ for i in range(tape.n_bars):
326
+ if intent.entry_side[i] != 0 and intent.entry_size[i] > 0.0:
327
+ current = float(intent.entry_side[i]) * float(intent.entry_size[i])
328
+ target[i, 0] = current
329
+ return _engine_units_v2(
330
+ tape.n_bars,
331
+ 1,
332
+ tape.highs,
333
+ tape.lows,
334
+ tape.closes,
335
+ target,
336
+ tape.funding_rates,
337
+ tape.funding_event_mask,
338
+ account.initial_capital,
339
+ np.array([account.leverage], dtype=np.float64),
340
+ account.maintenance_ratio,
341
+ np.array([0.0], dtype=np.float64),
342
+ np.array([1.0], dtype=np.float64),
343
+ 0.0,
344
+ False,
345
+ )[0][-1]
346
+
347
+
348
+ def _run_native_event_orders(df: pd.DataFrame, fills: pd.DataFrame):
349
+ orders = []
350
+ idx = df.index
351
+ for row in fills.itertuples(index=False):
352
+ bar = int(row.bar_index)
353
+ side = OrderSide.BUY if int(row.side) > 0 else OrderSide.SELL
354
+ orders.append(OrderIntent(idx[bar], "BTC", side, OrderType.MARKET, qty=float(row.qty)))
355
+ engine = BacktestEngineV2(
356
+ data=df,
357
+ symbols=["BTC"],
358
+ backend="native_event",
359
+ orders=orders,
360
+ account=AccountConfig(initial_capital=100_000.0, leverage=10.0),
361
+ use_funding=False,
362
+ fee_rate=0.0,
363
+ )
364
+ return engine.result.equity.iloc[-1]
365
+
366
+
367
+ def _measure(workload, *, repeats: int) -> Dict[str, float]:
368
+ gc.collect()
369
+ start = time.perf_counter()
370
+ workload()
371
+ warmup = time.perf_counter() - start
372
+ runtimes = []
373
+ for _ in range(max(1, repeats)):
374
+ gc.collect()
375
+ start = time.perf_counter()
376
+ workload()
377
+ runtimes.append(time.perf_counter() - start)
378
+ return {
379
+ "best": float(min(runtimes)),
380
+ "worst": float(max(runtimes)),
381
+ "median": float(statistics.median(runtimes)),
382
+ "warmup": float(warmup),
383
+ }
384
+
385
+
386
+ def _record(route, rows, symbols, fills, stats, *, baseline, intrabar_minimal=None, parity="n/a", notes=""):
387
+ runtime = stats["best"]
388
+ return Phase31BenchmarkRecord(
389
+ route=route,
390
+ rows=rows,
391
+ symbols=symbols,
392
+ fills_or_orders=int(fills),
393
+ warmup_seconds=float(stats["warmup"]),
394
+ runtime_seconds=float(runtime),
395
+ runtime_min_seconds=float(stats["best"]),
396
+ runtime_max_seconds=float(stats["worst"]),
397
+ bars_per_second=float(rows / runtime) if runtime > 0 else float("inf"),
398
+ ratio_vs_close_target=float(runtime / baseline) if baseline and runtime else None,
399
+ ratio_vs_intrabar_minimal=float(runtime / intrabar_minimal) if intrabar_minimal and runtime else None,
400
+ parity=parity,
401
+ notes=notes,
402
+ )
403
+
404
+
405
+ def _summary(records: List[Phase31BenchmarkRecord]) -> Dict:
406
+ lookup = {record.route: record for record in records}
407
+ minimal = lookup["intrabar_bracket_v1_minimal"]
408
+ audit = lookup["intrabar_bracket_v1_audit"]
409
+ reference = lookup["intrabar_reference_python"]
410
+ close_target = lookup["close_target_v2_pure_kernel"]
411
+ return {
412
+ "intrabar_minimal_speedup_vs_reference": reference.runtime_seconds / minimal.runtime_seconds,
413
+ "intrabar_audit_ratio_vs_minimal": audit.runtime_seconds / minimal.runtime_seconds,
414
+ "intrabar_minimal_ratio_vs_close_target": minimal.runtime_seconds / close_target.runtime_seconds,
415
+ }
416
+
417
+
418
+ def _fmt_ratio(value) -> str:
419
+ if value is None:
420
+ return "-"
421
+ return f"{float(value):.2f}x"
422
+
423
+
424
+ def main(argv=None) -> int:
425
+ parser = argparse.ArgumentParser(description="Run Phase 31 intrabar benchmark.")
426
+ parser.add_argument("--rows", type=int, default=25_000)
427
+ parser.add_argument("--repeats", type=int, default=3)
428
+ parser.add_argument("--seed", type=int, default=31)
429
+ parser.add_argument("--json-out", type=Path, default=PACKAGE_DIR / "benchmarks" / "phase31_intrabar_benchmark.json")
430
+ parser.add_argument("--md-out", type=Path, default=PACKAGE_DIR / "benchmarks" / "phase31_intrabar_benchmark.md")
431
+ args = parser.parse_args(argv)
432
+
433
+ report = run_benchmark(rows=args.rows, repeats=args.repeats, seed=args.seed)
434
+ args.json_out.write_text(json.dumps(report, indent=2), encoding="utf-8")
435
+ args.md_out.write_text(make_markdown(report), encoding="utf-8")
436
+ print(make_markdown(report))
437
+ return 0
438
+
439
+
440
+ if __name__ == "__main__":
441
+ raise SystemExit(main())
@@ -0,0 +1,179 @@
1
+ from __future__ import annotations
2
+
3
+ import argparse
4
+ import json
5
+ import resource
6
+ import subprocess
7
+ import sys
8
+ import time
9
+ from pathlib import Path
10
+
11
+ import numpy as np
12
+ import pandas as pd
13
+
14
+ from quantbt import AccountConfig, ExecutionConfig, NativeEventBackend, NativeEventConfig
15
+ from quantbt.core.orders import OrderAction, OrderCommand
16
+ from quantbt.core.schema import OrderSide, OrderType, TimeInForce
17
+
18
+
19
+ def _rss_mb() -> float:
20
+ value = float(resource.getrusage(resource.RUSAGE_SELF).ru_maxrss)
21
+ if sys.platform == "darwin":
22
+ return value / (1024.0 * 1024.0)
23
+ return value / 1024.0
24
+
25
+
26
+ def _market(rows: int):
27
+ idx = pd.date_range("2020-01-01", periods=rows, freq="15min", tz="UTC")
28
+ x = np.arange(rows, dtype=np.float64)
29
+ close = pd.Series(100.0 + np.sin(x / 17.0) * 2.0 + x * 0.0001, index=idx)
30
+ high = close + 1.2
31
+ low = close - 1.2
32
+ return idx, {"BTC": close}, {"BTC": high}, {"BTC": low}
33
+
34
+
35
+ def _commands(idx: pd.DatetimeIndex, levels: int, cycle: int):
36
+ commands = []
37
+ order_id = 0
38
+ for bar in range(1, len(idx), cycle):
39
+ commands.append(OrderCommand(timestamp=idx[bar], action=OrderAction.CANCEL_ALL, symbol="BTC"))
40
+ anchor = 100.0 + np.sin(bar / 17.0) * 2.0 + bar * 0.0001
41
+ for level in range(1, levels + 1):
42
+ commands.append(
43
+ OrderCommand(
44
+ timestamp=idx[bar],
45
+ symbol="BTC",
46
+ side=OrderSide.BUY,
47
+ order_type=OrderType.LIMIT,
48
+ qty=0.01,
49
+ price=float(anchor - 0.08 * level),
50
+ tif=TimeInForce.GTC,
51
+ order_id=f"entry-{order_id}",
52
+ tag=f"GRID-C{bar}-L{level}",
53
+ metadata={"campaign_id": f"C{bar}", "level_id": str(level)},
54
+ )
55
+ )
56
+ order_id += 1
57
+ commands.append(
58
+ OrderCommand(
59
+ timestamp=idx[bar],
60
+ symbol="BTC",
61
+ side=OrderSide.SELL,
62
+ order_type=OrderType.LIMIT,
63
+ qty=0.01,
64
+ price=float(anchor + 0.08 * level),
65
+ tif=TimeInForce.GTC,
66
+ reduce_only=True,
67
+ order_id=f"exit-{order_id}",
68
+ tag=f"GRID-C{bar}-X{level}",
69
+ metadata={"campaign_id": f"C{bar}", "level_id": str(level), "leg_role": "take_profit"},
70
+ )
71
+ )
72
+ order_id += 1
73
+ return tuple(commands)
74
+
75
+
76
+ def _run_child(args) -> dict:
77
+ idx, close, high, low = _market(args.rows)
78
+ commands = _commands(idx, levels=args.levels, cycle=args.cycle)
79
+ backend = NativeEventBackend(
80
+ NativeEventConfig(
81
+ account=AccountConfig(initial_capital=100_000.0, leverage=10.0),
82
+ execution=ExecutionConfig(slippage_bps=0.0),
83
+ fee_rate=0.0,
84
+ use_funding=False,
85
+ report_level=args.report_level,
86
+ audit_sink=args.audit_sink,
87
+ audit_sink_path=args.audit_sink_path,
88
+ )
89
+ )
90
+ start = time.perf_counter()
91
+ result = backend.run_order_commands(idx, commands, close, high, low, symbols=["BTC"])
92
+ elapsed = time.perf_counter() - start
93
+ payload = {
94
+ "report_level": result.metadata["report_level"],
95
+ "audit_sink": result.metadata["audit_sink"],
96
+ "rows": int(args.rows),
97
+ "levels": int(args.levels),
98
+ "commands": int(len(commands)),
99
+ "fills": int(result.metadata["lifecycle_counters"]["fill_count"]),
100
+ "events": int(result.metadata["lifecycle_counters"]["event_count"]),
101
+ "seconds": float(elapsed),
102
+ "peak_rss_mb": float(_rss_mb()),
103
+ "command_report_rows": int(len(result.metadata["command_report"])),
104
+ "order_event_rows": int(len(result.metadata["order_events"])),
105
+ "fills_materialized": int(len(result.fills)),
106
+ "orders_materialized": int(len(result.orders)),
107
+ "final_equity": float(result.equity.iloc[-1]),
108
+ }
109
+ print(json.dumps(payload, sort_keys=True))
110
+ return payload
111
+
112
+
113
+ def _run_parent(args) -> list[dict]:
114
+ rows = []
115
+ for level in ("minimal", "standard", "audit"):
116
+ cmd = [
117
+ sys.executable,
118
+ __file__,
119
+ "--child",
120
+ "--rows",
121
+ str(args.rows),
122
+ "--levels",
123
+ str(args.levels),
124
+ "--cycle",
125
+ str(args.cycle),
126
+ "--report-level",
127
+ level,
128
+ ]
129
+ completed = subprocess.run(cmd, check=True, capture_output=True, text=True)
130
+ rows.append(json.loads(completed.stdout.strip().splitlines()[-1]))
131
+ if args.json_out:
132
+ Path(args.json_out).write_text(json.dumps(rows, indent=2, sort_keys=True) + "\n")
133
+ if args.md_out:
134
+ lines = [
135
+ "# Phase 34A Native Event Artifact Memory Benchmark",
136
+ "",
137
+ "| report_level | seconds | peak RSS MB | commands | fills | events | command rows | event rows | fills obj | orders obj |",
138
+ "|---|---:|---:|---:|---:|---:|---:|---:|---:|---:|",
139
+ ]
140
+ for row in rows:
141
+ lines.append(
142
+ "| {report_level} | {seconds:.6f} | {peak_rss_mb:.3f} | {commands} | {fills} | {events} | "
143
+ "{command_report_rows} | {order_event_rows} | {fills_materialized} | {orders_materialized} |".format(**row)
144
+ )
145
+ lines.extend(
146
+ [
147
+ "",
148
+ "Notes:",
149
+ "",
150
+ "- Each row runs in a fresh subprocess.",
151
+ "- Peak RSS includes Python import, pandas, and Numba/cache overhead; on small workloads it is not expected to be monotonic by artifact level.",
152
+ "- The artifact contract is verified by command/event row counts and materialized Python object counts; larger command-heavy runs are needed for stable RSS deltas.",
153
+ ]
154
+ )
155
+ Path(args.md_out).write_text("\n".join(lines) + "\n")
156
+ print(json.dumps(rows, indent=2, sort_keys=True))
157
+ return rows
158
+
159
+
160
+ def main() -> None:
161
+ parser = argparse.ArgumentParser()
162
+ parser.add_argument("--child", action="store_true")
163
+ parser.add_argument("--rows", type=int, default=5_000)
164
+ parser.add_argument("--levels", type=int, default=15)
165
+ parser.add_argument("--cycle", type=int, default=50)
166
+ parser.add_argument("--report-level", default="audit")
167
+ parser.add_argument("--audit-sink", default="memory")
168
+ parser.add_argument("--audit-sink-path", default=None)
169
+ parser.add_argument("--json-out", default="benchmarks/phase34a_native_event_memory.json")
170
+ parser.add_argument("--md-out", default="benchmarks/phase34a_native_event_memory.md")
171
+ args = parser.parse_args()
172
+ if args.child:
173
+ _run_child(args)
174
+ else:
175
+ _run_parent(args)
176
+
177
+
178
+ if __name__ == "__main__":
179
+ main()