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.
- quantbt/__init__.py +862 -0
- quantbt/adapters/__init__.py +5 -0
- quantbt/adapters/nautilus/__init__.py +42 -0
- quantbt/adapters/nautilus/_dependency.py +53 -0
- quantbt/adapters/nautilus/backend.py +639 -0
- quantbt/adapters/nautilus/instruments.py +275 -0
- quantbt/adapters/nautilus/options.py +465 -0
- quantbt/adapters/nautilus/reports.py +231 -0
- quantbt/backends/__init__.py +29 -0
- quantbt/backends/_native_event_rust.py +2168 -0
- quantbt/backends/native_event.py +5048 -0
- quantbt/backends/native_option.py +796 -0
- quantbt/backends/native_portfolio.py +928 -0
- quantbt/backends/native_vectorized.py +1299 -0
- quantbt/backtester.py +485 -0
- quantbt/benchmarks/__init__.py +1 -0
- quantbt/benchmarks/compare_phase9_parity.py +302 -0
- quantbt/benchmarks/gamma_scalping_backtestsample.py +802 -0
- quantbt/benchmarks/profile_phase7.py +415 -0
- quantbt/benchmarks/run_arbitrage_phase_e.py +160 -0
- quantbt/benchmarks/run_optimization_overhead.py +195 -0
- quantbt/benchmarks/run_options_engine.py +272 -0
- quantbt/benchmarks/run_pct_equity_nautilus_smoke.py +227 -0
- quantbt/benchmarks/run_phase12_arbitrage_cert.py +402 -0
- quantbt/benchmarks/run_phase12_benchmark_nautilus_cert.py +430 -0
- quantbt/benchmarks/run_phase13_portfolio_report.py +97 -0
- quantbt/benchmarks/run_phase13_wfo_cache.py +162 -0
- quantbt/benchmarks/run_phase14_service_loop.py +694 -0
- quantbt/benchmarks/run_phase15a_nautilus_certification.py +514 -0
- quantbt/benchmarks/run_phase15b_synthetic_depth.py +175 -0
- quantbt/benchmarks/run_phase16_performance_debt.py +389 -0
- quantbt/benchmarks/run_phase30e_reactive_runner.py +161 -0
- quantbt/benchmarks/run_phase31_intrabar.py +441 -0
- quantbt/benchmarks/run_phase34a_native_event_memory.py +179 -0
- quantbt/benchmarks/run_phase34b_native_event_prepared_score.py +173 -0
- quantbt/benchmarks/run_phase34c_native_event_single_pass.py +176 -0
- quantbt/benchmarks/run_phase7.py +654 -0
- quantbt/benchmarks/run_portfolio_real_parity.py +537 -0
- quantbt/core/__init__.py +323 -0
- quantbt/core/arbitrage.py +767 -0
- quantbt/core/basket.py +234 -0
- quantbt/core/certification.py +274 -0
- quantbt/core/constraints.py +155 -0
- quantbt/core/engine.py +1144 -0
- quantbt/core/event.py +869 -0
- quantbt/core/execution_contract.py +197 -0
- quantbt/core/execution_depth.py +613 -0
- quantbt/core/intrabar_kernel.py +1906 -0
- quantbt/core/intrabar_reference.py +907 -0
- quantbt/core/intrabar_session.py +156 -0
- quantbt/core/market_tape.py +480 -0
- quantbt/core/native_event_capabilities.py +121 -0
- quantbt/core/native_event_parity.py +336 -0
- quantbt/core/order_compiler.py +422 -0
- quantbt/core/orders.py +319 -0
- quantbt/core/portfolio.py +392 -0
- quantbt/core/preprocessor.py +256 -0
- quantbt/core/reactive.py +173 -0
- quantbt/core/results.py +341 -0
- quantbt/core/schema.py +211 -0
- quantbt/core/scopes.py +96 -0
- quantbt/core/structured_orders.py +378 -0
- quantbt/core/types.py +87 -0
- quantbt/core/vectorized.py +203 -0
- quantbt/endpoint.py +4465 -0
- quantbt/engines.py +1014 -0
- quantbt/metrics/__init__.py +45 -0
- quantbt/metrics/options_analytics.py +46 -0
- quantbt/metrics/performance.py +541 -0
- quantbt/optimization/__init__.py +94 -0
- quantbt/optimization/callbacks.py +116 -0
- quantbt/optimization/candidate_selection.py +397 -0
- quantbt/optimization/config.py +84 -0
- quantbt/optimization/constraints.py +22 -0
- quantbt/optimization/evaluator.py +21 -0
- quantbt/optimization/evaluators/__init__.py +32 -0
- quantbt/optimization/evaluators/arbitrage.py +22 -0
- quantbt/optimization/evaluators/generic.py +34 -0
- quantbt/optimization/evaluators/grid_dca.py +22 -0
- quantbt/optimization/evaluators/intrabar.py +54 -0
- quantbt/optimization/evaluators/native_event.py +51 -0
- quantbt/optimization/evaluators/options.py +22 -0
- quantbt/optimization/evaluators/portfolio.py +42 -0
- quantbt/optimization/evaluators/signal.py +43 -0
- quantbt/optimization/multiseed.py +176 -0
- quantbt/optimization/objectives.py +221 -0
- quantbt/optimization/optimizer.py +500 -0
- quantbt/optimization/result.py +80 -0
- quantbt/optimization/samplers.py +84 -0
- quantbt/optimization/space.py +223 -0
- quantbt/options/__init__.py +215 -0
- quantbt/options/cache.py +116 -0
- quantbt/options/conventions.py +158 -0
- quantbt/options/data.py +177 -0
- quantbt/options/execution.py +600 -0
- quantbt/options/fees.py +135 -0
- quantbt/options/greeks.py +173 -0
- quantbt/options/hedging.py +232 -0
- quantbt/options/iv.py +188 -0
- quantbt/options/ledger.py +263 -0
- quantbt/options/lifecycle.py +94 -0
- quantbt/options/margin.py +311 -0
- quantbt/options/packages.py +147 -0
- quantbt/options/pricing.py +191 -0
- quantbt/options/schema.py +227 -0
- quantbt/options/selectors.py +281 -0
- quantbt/options/strategy.py +249 -0
- quantbt/options/surface.py +142 -0
- quantbt/options/tape.py +226 -0
- quantbt/options/templates/__init__.py +33 -0
- quantbt/options/templates/packages.py +354 -0
- quantbt/portfolio.py +606 -0
- quantbt/py.typed +1 -0
- quantbt/reporting/__init__.py +38 -0
- quantbt/reporting/arbitrage_audit.py +211 -0
- quantbt/reporting/nautilus_bundle.py +758 -0
- quantbt/reporting/nautilus_certification.py +226 -0
- quantbt/reporting/nautilus_diagnostics.py +225 -0
- quantbt/reporting/parity.py +496 -0
- quantbt/reporting/portfolio_audit.py +206 -0
- quantbt/reporting/portfolio_nautilus.py +140 -0
- quantbt/sizing/__init__.py +3 -0
- quantbt/sizing/fast.py +67 -0
- quantbt/sizing/modes.py +162 -0
- quantbt/viz/__init__.py +4 -0
- quantbt/viz/plots.py +310 -0
- quantbt/viz/themes.py +102 -0
- quantbt/walkforward.py +3136 -0
- quantbt_engine-1.0.7.dist-info/METADATA +891 -0
- quantbt_engine-1.0.7.dist-info/RECORD +133 -0
- quantbt_engine-1.0.7.dist-info/WHEEL +5 -0
- quantbt_engine-1.0.7.dist-info/licenses/LICENSE +21 -0
- quantbt_engine-1.0.7.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
"""
|
|
2
|
+
OrderIntent compiler for native event kernels.
|
|
3
|
+
|
|
4
|
+
The compiler is an internal performance helper: it converts immutable order
|
|
5
|
+
intent objects into contiguous ndarray inputs while preserving the old event
|
|
6
|
+
kernel semantics.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
from dataclasses import dataclass, replace
|
|
12
|
+
import hashlib
|
|
13
|
+
from typing import Dict, Sequence, Tuple
|
|
14
|
+
|
|
15
|
+
import numpy as np
|
|
16
|
+
import pandas as pd
|
|
17
|
+
|
|
18
|
+
from .event import (
|
|
19
|
+
ORDER_TYPE_LIMIT,
|
|
20
|
+
ORDER_TYPE_MARKET,
|
|
21
|
+
ORDER_TYPE_STOP_LIMIT,
|
|
22
|
+
ORDER_TYPE_STOP_MARKET,
|
|
23
|
+
TIF_FOK,
|
|
24
|
+
TIF_GTC,
|
|
25
|
+
TIF_GTD,
|
|
26
|
+
TIF_IOC,
|
|
27
|
+
)
|
|
28
|
+
from .orders import OrderAction, OrderActivationPolicy, OrderCommand, OrderIntent
|
|
29
|
+
from .preprocessor import MarketDataSignature, market_data_signature
|
|
30
|
+
from .schema import OrderSide, OrderType, TimeInForce
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
COMMAND_ACTION_PLACE = 0
|
|
34
|
+
COMMAND_ACTION_CANCEL = 1
|
|
35
|
+
COMMAND_ACTION_REPLACE = 2
|
|
36
|
+
COMMAND_ACTION_AMEND = 3
|
|
37
|
+
COMMAND_ACTION_CANCEL_ALL = 4
|
|
38
|
+
|
|
39
|
+
ACTIVATION_IMMEDIATE = 0
|
|
40
|
+
ACTIVATION_ON_PARENT_FIRST_FILL = 1
|
|
41
|
+
ACTIVATION_ON_PARENT_FULL_FILL = 2
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True)
|
|
45
|
+
class CompiledOrderArrays:
|
|
46
|
+
index_signature: MarketDataSignature
|
|
47
|
+
symbols: Tuple[str, ...]
|
|
48
|
+
sorted_orders: Tuple[Tuple[int, OrderIntent], ...]
|
|
49
|
+
order_ptr: np.ndarray
|
|
50
|
+
order_symbol: np.ndarray
|
|
51
|
+
order_side: np.ndarray
|
|
52
|
+
order_type: np.ndarray
|
|
53
|
+
order_qty: np.ndarray
|
|
54
|
+
order_price: np.ndarray
|
|
55
|
+
order_tif: np.ndarray
|
|
56
|
+
original_index: np.ndarray
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def n_orders(self) -> int:
|
|
60
|
+
return int(len(self.original_index))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
@dataclass(frozen=True)
|
|
64
|
+
class CompiledOrderCommandArrays:
|
|
65
|
+
"""
|
|
66
|
+
Array contract for native-event lifecycle commands.
|
|
67
|
+
|
|
68
|
+
This v2 compiler is intentionally separate from `CompiledOrderArrays` so
|
|
69
|
+
the legacy v1 kernel remains byte-for-byte compatible with old endpoints.
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
index_signature: MarketDataSignature
|
|
73
|
+
symbols: Tuple[str, ...]
|
|
74
|
+
sorted_commands: Tuple[Tuple[int, OrderCommand], ...]
|
|
75
|
+
command_ptr: np.ndarray
|
|
76
|
+
command_bar: np.ndarray
|
|
77
|
+
command_action: np.ndarray
|
|
78
|
+
command_symbol: np.ndarray
|
|
79
|
+
command_side: np.ndarray
|
|
80
|
+
command_type: np.ndarray
|
|
81
|
+
command_qty: np.ndarray
|
|
82
|
+
command_price: np.ndarray
|
|
83
|
+
command_trigger_price: np.ndarray
|
|
84
|
+
command_tif: np.ndarray
|
|
85
|
+
command_reduce_only: np.ndarray
|
|
86
|
+
command_order_id: np.ndarray
|
|
87
|
+
command_target_order_id: np.ndarray
|
|
88
|
+
command_parent_order_id: np.ndarray
|
|
89
|
+
command_group_id: np.ndarray
|
|
90
|
+
command_oco_group_id: np.ndarray
|
|
91
|
+
command_activation: np.ndarray
|
|
92
|
+
command_expires_bar: np.ndarray
|
|
93
|
+
original_index: np.ndarray
|
|
94
|
+
id_values: Tuple[str, ...]
|
|
95
|
+
tape_fingerprint: str = ""
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def n_commands(self) -> int:
|
|
99
|
+
return int(len(self.original_index))
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def command_tape_fingerprint(compiled: CompiledOrderCommandArrays) -> str:
|
|
103
|
+
"""Return a complete identity for the immutable primitive command tape.
|
|
104
|
+
|
|
105
|
+
The digest includes fields used by Rust validation as well as execution.
|
|
106
|
+
It is computed when the compiler creates a tape so repeated score calls do
|
|
107
|
+
not hash every array on the measured execution path.
|
|
108
|
+
"""
|
|
109
|
+
|
|
110
|
+
digest = hashlib.blake2b(digest_size=16)
|
|
111
|
+
digest.update(repr(compiled.index_signature).encode("utf-8"))
|
|
112
|
+
digest.update(repr(tuple(compiled.symbols)).encode("utf-8"))
|
|
113
|
+
digest.update(repr(tuple(compiled.id_values)).encode("utf-8"))
|
|
114
|
+
for name in (
|
|
115
|
+
"command_ptr",
|
|
116
|
+
"command_bar",
|
|
117
|
+
"command_action",
|
|
118
|
+
"command_symbol",
|
|
119
|
+
"command_side",
|
|
120
|
+
"command_type",
|
|
121
|
+
"command_qty",
|
|
122
|
+
"command_price",
|
|
123
|
+
"command_trigger_price",
|
|
124
|
+
"command_tif",
|
|
125
|
+
"command_reduce_only",
|
|
126
|
+
"command_order_id",
|
|
127
|
+
"command_target_order_id",
|
|
128
|
+
"command_parent_order_id",
|
|
129
|
+
"command_group_id",
|
|
130
|
+
"command_oco_group_id",
|
|
131
|
+
"command_activation",
|
|
132
|
+
"command_expires_bar",
|
|
133
|
+
"original_index",
|
|
134
|
+
):
|
|
135
|
+
array = np.ascontiguousarray(getattr(compiled, name))
|
|
136
|
+
digest.update(name.encode("ascii"))
|
|
137
|
+
digest.update(str(array.dtype).encode("ascii"))
|
|
138
|
+
digest.update(str(array.shape).encode("ascii"))
|
|
139
|
+
digest.update(array.tobytes())
|
|
140
|
+
return digest.hexdigest()
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
def compile_order_intents(
|
|
144
|
+
idx: pd.DatetimeIndex,
|
|
145
|
+
orders: Sequence[OrderIntent],
|
|
146
|
+
symbol_to_col: Dict[str, int],
|
|
147
|
+
) -> CompiledOrderArrays:
|
|
148
|
+
"""
|
|
149
|
+
Compile order intents into the exact array contract expected by event v1.
|
|
150
|
+
|
|
151
|
+
The sort is stable by effective bar, matching Python's previous
|
|
152
|
+
`sorted(enumerate(orders), key=bar_index)` behavior.
|
|
153
|
+
"""
|
|
154
|
+
n_orders = len(orders)
|
|
155
|
+
order_bar_unsorted = np.zeros(n_orders, dtype=np.int64)
|
|
156
|
+
symbol_unsorted = np.zeros(n_orders, dtype=np.int64)
|
|
157
|
+
side_unsorted = np.zeros(n_orders, dtype=np.int64)
|
|
158
|
+
type_unsorted = np.zeros(n_orders, dtype=np.int64)
|
|
159
|
+
qty_unsorted = np.zeros(n_orders, dtype=np.float64)
|
|
160
|
+
price_unsorted = np.zeros(n_orders, dtype=np.float64)
|
|
161
|
+
tif_unsorted = np.zeros(n_orders, dtype=np.int64)
|
|
162
|
+
original_unsorted = np.arange(n_orders, dtype=np.int64)
|
|
163
|
+
|
|
164
|
+
idx_ns = idx.view("int64")
|
|
165
|
+
ts_ns = np.zeros(n_orders, dtype=np.int64)
|
|
166
|
+
for k, order in enumerate(orders):
|
|
167
|
+
if order.symbol not in symbol_to_col:
|
|
168
|
+
raise ValueError(f"order symbol {order.symbol!r} is not in symbols")
|
|
169
|
+
ts = pd.Timestamp(order.timestamp)
|
|
170
|
+
if ts.tz is None:
|
|
171
|
+
ts = ts.tz_localize("UTC")
|
|
172
|
+
else:
|
|
173
|
+
ts = ts.tz_convert("UTC")
|
|
174
|
+
ts_ns[k] = ts.value
|
|
175
|
+
symbol_unsorted[k] = symbol_to_col[order.symbol]
|
|
176
|
+
side_unsorted[k] = _side_code(order.side)
|
|
177
|
+
type_unsorted[k] = _order_type_code(order.order_type)
|
|
178
|
+
qty_unsorted[k] = float(order.qty)
|
|
179
|
+
price_unsorted[k] = 0.0 if order.price is None else float(order.price)
|
|
180
|
+
tif_unsorted[k] = _tif_code(order.tif)
|
|
181
|
+
|
|
182
|
+
order_bar_unsorted = np.searchsorted(idx_ns, ts_ns, side="left").astype(np.int64)
|
|
183
|
+
if n_orders > 0 and int(order_bar_unsorted.max()) >= len(idx):
|
|
184
|
+
raise ValueError("order timestamp is after the available data")
|
|
185
|
+
order_sort = np.argsort(order_bar_unsorted, kind="stable")
|
|
186
|
+
|
|
187
|
+
order_bar = np.ascontiguousarray(order_bar_unsorted[order_sort], dtype=np.int64)
|
|
188
|
+
order_symbol = np.ascontiguousarray(symbol_unsorted[order_sort], dtype=np.int64)
|
|
189
|
+
order_side = np.ascontiguousarray(side_unsorted[order_sort], dtype=np.int64)
|
|
190
|
+
order_type = np.ascontiguousarray(type_unsorted[order_sort], dtype=np.int64)
|
|
191
|
+
order_qty = np.ascontiguousarray(qty_unsorted[order_sort], dtype=np.float64)
|
|
192
|
+
order_price = np.ascontiguousarray(price_unsorted[order_sort], dtype=np.float64)
|
|
193
|
+
order_tif = np.ascontiguousarray(tif_unsorted[order_sort], dtype=np.int64)
|
|
194
|
+
original_index = np.ascontiguousarray(original_unsorted[order_sort], dtype=np.int64)
|
|
195
|
+
|
|
196
|
+
order_ptr = np.zeros(len(idx) + 1, dtype=np.int64)
|
|
197
|
+
if n_orders > 0:
|
|
198
|
+
counts = np.bincount(order_bar + 1, minlength=len(idx) + 1)
|
|
199
|
+
order_ptr[:] = np.cumsum(counts, dtype=np.int64)
|
|
200
|
+
|
|
201
|
+
sorted_orders = tuple((int(orig_idx), orders[int(orig_idx)]) for orig_idx in original_index)
|
|
202
|
+
return CompiledOrderArrays(
|
|
203
|
+
index_signature=market_data_signature(idx, list(symbol_to_col.keys())),
|
|
204
|
+
symbols=tuple(symbol_to_col.keys()),
|
|
205
|
+
sorted_orders=sorted_orders,
|
|
206
|
+
order_ptr=order_ptr,
|
|
207
|
+
order_symbol=order_symbol,
|
|
208
|
+
order_side=order_side,
|
|
209
|
+
order_type=order_type,
|
|
210
|
+
order_qty=order_qty,
|
|
211
|
+
order_price=order_price,
|
|
212
|
+
order_tif=order_tif,
|
|
213
|
+
original_index=original_index,
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
def compile_order_commands(
|
|
218
|
+
idx: pd.DatetimeIndex,
|
|
219
|
+
commands: Sequence[OrderCommand],
|
|
220
|
+
symbol_to_col: Dict[str, int],
|
|
221
|
+
) -> CompiledOrderCommandArrays:
|
|
222
|
+
"""
|
|
223
|
+
Compile lifecycle commands into contiguous arrays for native-event v2.
|
|
224
|
+
|
|
225
|
+
The compiler validates timestamps/symbols, keeps a stable command order
|
|
226
|
+
within each bar, and maps sparse string IDs to dense integer codes. No fill
|
|
227
|
+
or accounting logic is performed here; this is only the deterministic input
|
|
228
|
+
contract for a lifecycle kernel or adapter.
|
|
229
|
+
"""
|
|
230
|
+
n_commands = len(commands)
|
|
231
|
+
command_bar_unsorted = np.zeros(n_commands, dtype=np.int64)
|
|
232
|
+
action_unsorted = np.zeros(n_commands, dtype=np.int64)
|
|
233
|
+
symbol_unsorted = np.full(n_commands, -1, dtype=np.int64)
|
|
234
|
+
side_unsorted = np.zeros(n_commands, dtype=np.int64)
|
|
235
|
+
type_unsorted = np.full(n_commands, -1, dtype=np.int64)
|
|
236
|
+
qty_unsorted = np.zeros(n_commands, dtype=np.float64)
|
|
237
|
+
price_unsorted = np.zeros(n_commands, dtype=np.float64)
|
|
238
|
+
trigger_unsorted = np.zeros(n_commands, dtype=np.float64)
|
|
239
|
+
tif_unsorted = np.full(n_commands, TIF_GTC, dtype=np.int64)
|
|
240
|
+
reduce_only_unsorted = np.zeros(n_commands, dtype=np.int64)
|
|
241
|
+
order_id_unsorted = np.full(n_commands, -1, dtype=np.int64)
|
|
242
|
+
target_id_unsorted = np.full(n_commands, -1, dtype=np.int64)
|
|
243
|
+
parent_id_unsorted = np.full(n_commands, -1, dtype=np.int64)
|
|
244
|
+
group_id_unsorted = np.full(n_commands, -1, dtype=np.int64)
|
|
245
|
+
oco_id_unsorted = np.full(n_commands, -1, dtype=np.int64)
|
|
246
|
+
activation_unsorted = np.zeros(n_commands, dtype=np.int64)
|
|
247
|
+
expires_bar_unsorted = np.full(n_commands, -1, dtype=np.int64)
|
|
248
|
+
original_unsorted = np.arange(n_commands, dtype=np.int64)
|
|
249
|
+
|
|
250
|
+
id_map: Dict[str, int] = {}
|
|
251
|
+
idx_ns = idx.view("int64")
|
|
252
|
+
ts_ns = np.zeros(n_commands, dtype=np.int64)
|
|
253
|
+
for k, command in enumerate(commands):
|
|
254
|
+
ts_ns[k] = _timestamp_ns(command.timestamp)
|
|
255
|
+
action_unsorted[k] = _action_code(command.action)
|
|
256
|
+
if command.symbol is not None:
|
|
257
|
+
if command.symbol not in symbol_to_col:
|
|
258
|
+
raise ValueError(f"command symbol {command.symbol!r} is not in symbols")
|
|
259
|
+
symbol_unsorted[k] = symbol_to_col[command.symbol]
|
|
260
|
+
if command.side is not None:
|
|
261
|
+
side_unsorted[k] = _side_code(command.side)
|
|
262
|
+
if command.order_type is not None:
|
|
263
|
+
type_unsorted[k] = _command_order_type_code(command.order_type)
|
|
264
|
+
if command.qty is not None:
|
|
265
|
+
qty_unsorted[k] = float(command.qty)
|
|
266
|
+
price_unsorted[k] = 0.0 if command.price is None else float(command.price)
|
|
267
|
+
trigger_unsorted[k] = 0.0 if command.trigger_price is None else float(command.trigger_price)
|
|
268
|
+
tif_unsorted[k] = _tif_code(command.tif)
|
|
269
|
+
reduce_only_unsorted[k] = 1 if command.reduce_only else 0
|
|
270
|
+
order_id_unsorted[k] = _id_code(command.order_id, id_map)
|
|
271
|
+
target_id_unsorted[k] = _id_code(command.target_order_id, id_map)
|
|
272
|
+
parent_id_unsorted[k] = _id_code(command.parent_order_id, id_map)
|
|
273
|
+
group_id_unsorted[k] = _id_code(command.group_id, id_map)
|
|
274
|
+
oco_id_unsorted[k] = _id_code(command.oco_group_id, id_map)
|
|
275
|
+
activation_unsorted[k] = _activation_code(command.activation_policy)
|
|
276
|
+
if command.expires_at is not None:
|
|
277
|
+
expires_bar_unsorted[k] = int(np.searchsorted(idx_ns, _timestamp_ns(command.expires_at), side="left"))
|
|
278
|
+
|
|
279
|
+
command_bar_unsorted = np.searchsorted(idx_ns, ts_ns, side="left").astype(np.int64)
|
|
280
|
+
if n_commands > 0 and int(command_bar_unsorted.max()) >= len(idx):
|
|
281
|
+
raise ValueError("command timestamp is after the available data")
|
|
282
|
+
order_sort = np.argsort(command_bar_unsorted, kind="stable")
|
|
283
|
+
|
|
284
|
+
command_bar = np.ascontiguousarray(command_bar_unsorted[order_sort], dtype=np.int64)
|
|
285
|
+
command_ptr = np.zeros(len(idx) + 1, dtype=np.int64)
|
|
286
|
+
if n_commands > 0:
|
|
287
|
+
counts = np.bincount(command_bar + 1, minlength=len(idx) + 1)
|
|
288
|
+
command_ptr[:] = np.cumsum(counts, dtype=np.int64)
|
|
289
|
+
|
|
290
|
+
original_index = np.ascontiguousarray(original_unsorted[order_sort], dtype=np.int64)
|
|
291
|
+
sorted_commands = tuple((int(orig_idx), commands[int(orig_idx)]) for orig_idx in original_index)
|
|
292
|
+
id_values = tuple(sorted(id_map, key=id_map.get))
|
|
293
|
+
compiled = CompiledOrderCommandArrays(
|
|
294
|
+
index_signature=market_data_signature(idx, list(symbol_to_col.keys())),
|
|
295
|
+
symbols=tuple(symbol_to_col.keys()),
|
|
296
|
+
sorted_commands=sorted_commands,
|
|
297
|
+
command_ptr=command_ptr,
|
|
298
|
+
command_bar=np.ascontiguousarray(command_bar, dtype=np.int64),
|
|
299
|
+
command_action=np.ascontiguousarray(action_unsorted[order_sort], dtype=np.int64),
|
|
300
|
+
command_symbol=np.ascontiguousarray(symbol_unsorted[order_sort], dtype=np.int64),
|
|
301
|
+
command_side=np.ascontiguousarray(side_unsorted[order_sort], dtype=np.int64),
|
|
302
|
+
command_type=np.ascontiguousarray(type_unsorted[order_sort], dtype=np.int64),
|
|
303
|
+
command_qty=np.ascontiguousarray(qty_unsorted[order_sort], dtype=np.float64),
|
|
304
|
+
command_price=np.ascontiguousarray(price_unsorted[order_sort], dtype=np.float64),
|
|
305
|
+
command_trigger_price=np.ascontiguousarray(trigger_unsorted[order_sort], dtype=np.float64),
|
|
306
|
+
command_tif=np.ascontiguousarray(tif_unsorted[order_sort], dtype=np.int64),
|
|
307
|
+
command_reduce_only=np.ascontiguousarray(reduce_only_unsorted[order_sort], dtype=np.int64),
|
|
308
|
+
command_order_id=np.ascontiguousarray(order_id_unsorted[order_sort], dtype=np.int64),
|
|
309
|
+
command_target_order_id=np.ascontiguousarray(target_id_unsorted[order_sort], dtype=np.int64),
|
|
310
|
+
command_parent_order_id=np.ascontiguousarray(parent_id_unsorted[order_sort], dtype=np.int64),
|
|
311
|
+
command_group_id=np.ascontiguousarray(group_id_unsorted[order_sort], dtype=np.int64),
|
|
312
|
+
command_oco_group_id=np.ascontiguousarray(oco_id_unsorted[order_sort], dtype=np.int64),
|
|
313
|
+
command_activation=np.ascontiguousarray(activation_unsorted[order_sort], dtype=np.int64),
|
|
314
|
+
command_expires_bar=np.ascontiguousarray(expires_bar_unsorted[order_sort], dtype=np.int64),
|
|
315
|
+
original_index=original_index,
|
|
316
|
+
id_values=id_values,
|
|
317
|
+
)
|
|
318
|
+
for name in (
|
|
319
|
+
"command_ptr",
|
|
320
|
+
"command_bar",
|
|
321
|
+
"command_action",
|
|
322
|
+
"command_symbol",
|
|
323
|
+
"command_side",
|
|
324
|
+
"command_type",
|
|
325
|
+
"command_qty",
|
|
326
|
+
"command_price",
|
|
327
|
+
"command_trigger_price",
|
|
328
|
+
"command_tif",
|
|
329
|
+
"command_reduce_only",
|
|
330
|
+
"command_order_id",
|
|
331
|
+
"command_target_order_id",
|
|
332
|
+
"command_parent_order_id",
|
|
333
|
+
"command_group_id",
|
|
334
|
+
"command_oco_group_id",
|
|
335
|
+
"command_activation",
|
|
336
|
+
"command_expires_bar",
|
|
337
|
+
"original_index",
|
|
338
|
+
):
|
|
339
|
+
getattr(compiled, name).flags.writeable = False
|
|
340
|
+
return replace(compiled, tape_fingerprint=command_tape_fingerprint(compiled))
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def order_intents_to_commands(orders: Sequence[OrderIntent]) -> Tuple[OrderCommand, ...]:
|
|
344
|
+
"""Convert legacy intents to immediate PLACE lifecycle commands."""
|
|
345
|
+
return tuple(OrderCommand.from_intent(order) for order in orders)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def _side_code(side: OrderSide) -> int:
|
|
349
|
+
return 1 if side is OrderSide.BUY else -1
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def _order_type_code(order_type: OrderType) -> int:
|
|
353
|
+
if order_type is OrderType.MARKET:
|
|
354
|
+
return ORDER_TYPE_MARKET
|
|
355
|
+
if order_type is OrderType.LIMIT:
|
|
356
|
+
return ORDER_TYPE_LIMIT
|
|
357
|
+
raise NotImplementedError(f"unsupported order_type={order_type!r}")
|
|
358
|
+
|
|
359
|
+
|
|
360
|
+
def _command_order_type_code(order_type: OrderType) -> int:
|
|
361
|
+
if order_type is OrderType.MARKET:
|
|
362
|
+
return ORDER_TYPE_MARKET
|
|
363
|
+
if order_type is OrderType.LIMIT:
|
|
364
|
+
return ORDER_TYPE_LIMIT
|
|
365
|
+
if order_type is OrderType.STOP_MARKET:
|
|
366
|
+
return ORDER_TYPE_STOP_MARKET
|
|
367
|
+
if order_type is OrderType.STOP_LIMIT:
|
|
368
|
+
return ORDER_TYPE_STOP_LIMIT
|
|
369
|
+
raise NotImplementedError(f"unsupported order_type={order_type!r}")
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
def _tif_code(tif: TimeInForce) -> int:
|
|
373
|
+
if tif is TimeInForce.GTC:
|
|
374
|
+
return TIF_GTC
|
|
375
|
+
if tif is TimeInForce.IOC:
|
|
376
|
+
return TIF_IOC
|
|
377
|
+
if tif is TimeInForce.FOK:
|
|
378
|
+
return TIF_FOK
|
|
379
|
+
if tif is TimeInForce.GTD:
|
|
380
|
+
return TIF_GTD
|
|
381
|
+
raise NotImplementedError(f"unsupported tif={tif!r}")
|
|
382
|
+
|
|
383
|
+
|
|
384
|
+
def _action_code(action: OrderAction) -> int:
|
|
385
|
+
if action is OrderAction.PLACE:
|
|
386
|
+
return COMMAND_ACTION_PLACE
|
|
387
|
+
if action is OrderAction.CANCEL:
|
|
388
|
+
return COMMAND_ACTION_CANCEL
|
|
389
|
+
if action is OrderAction.REPLACE:
|
|
390
|
+
return COMMAND_ACTION_REPLACE
|
|
391
|
+
if action is OrderAction.AMEND:
|
|
392
|
+
return COMMAND_ACTION_AMEND
|
|
393
|
+
if action is OrderAction.CANCEL_ALL:
|
|
394
|
+
return COMMAND_ACTION_CANCEL_ALL
|
|
395
|
+
raise NotImplementedError(f"unsupported action={action!r}")
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def _activation_code(policy: OrderActivationPolicy) -> int:
|
|
399
|
+
if policy is OrderActivationPolicy.IMMEDIATE:
|
|
400
|
+
return ACTIVATION_IMMEDIATE
|
|
401
|
+
if policy is OrderActivationPolicy.ON_PARENT_FIRST_FILL:
|
|
402
|
+
return ACTIVATION_ON_PARENT_FIRST_FILL
|
|
403
|
+
if policy is OrderActivationPolicy.ON_PARENT_FULL_FILL:
|
|
404
|
+
return ACTIVATION_ON_PARENT_FULL_FILL
|
|
405
|
+
raise NotImplementedError(f"unsupported activation_policy={policy!r}")
|
|
406
|
+
|
|
407
|
+
|
|
408
|
+
def _id_code(value: str | None, id_map: Dict[str, int]) -> int:
|
|
409
|
+
if value is None or value == "":
|
|
410
|
+
return -1
|
|
411
|
+
if value not in id_map:
|
|
412
|
+
id_map[value] = len(id_map)
|
|
413
|
+
return id_map[value]
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def _timestamp_ns(value: object) -> int:
|
|
417
|
+
ts = pd.Timestamp(value)
|
|
418
|
+
if ts.tz is None:
|
|
419
|
+
ts = ts.tz_localize("UTC")
|
|
420
|
+
else:
|
|
421
|
+
ts = ts.tz_convert("UTC")
|
|
422
|
+
return int(ts.value)
|