PyAlgoEngine 0.12.3__cp315-cp315-win_amd64.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 (156) hide show
  1. algo_engine/__infra__.pxd +3 -0
  2. algo_engine/__init__.pxd +3 -0
  3. algo_engine/__init__.py +107 -0
  4. algo_engine/apps/__init__.py +17 -0
  5. algo_engine/apps/backtest/__init__.py +20 -0
  6. algo_engine/apps/backtest/doc_server.py +328 -0
  7. algo_engine/apps/backtest/static/styles/dash.css +48 -0
  8. algo_engine/apps/backtest/templates/dash.html +20 -0
  9. algo_engine/apps/backtest/templates/index.html +40 -0
  10. algo_engine/apps/backtest/tester.py +252 -0
  11. algo_engine/apps/backtest/web_app.py +125 -0
  12. algo_engine/apps/bokeh_server.py +245 -0
  13. algo_engine/apps/demo/__init__.py +0 -0
  14. algo_engine/apps/demo/test.py +40 -0
  15. algo_engine/apps/sim_input/__init__.py +23 -0
  16. algo_engine/apps/sim_input/client.py +412 -0
  17. algo_engine/apps/sim_input/sim_keyboard.py +88 -0
  18. algo_engine/apps/sim_input/sim_mouse.py +137 -0
  19. algo_engine/apps/sim_input/window.py +162 -0
  20. algo_engine/backtest/__init__.py +19 -0
  21. algo_engine/backtest/__main__.py +51 -0
  22. algo_engine/backtest/metrics.py +179 -0
  23. algo_engine/backtest/replay.py +635 -0
  24. algo_engine/backtest/sim_match.py +506 -0
  25. algo_engine/base/__infra__.pxd +3 -0
  26. algo_engine/base/__init__.pxd +3 -0
  27. algo_engine/base/__init__.py +60 -0
  28. algo_engine/base/c_allocator_protocol.c +11608 -0
  29. algo_engine/base/c_allocator_protocol.cp315-win_amd64.pyd +0 -0
  30. algo_engine/base/c_allocator_protocol.pxd +24 -0
  31. algo_engine/base/c_allocator_protocol.pyi +68 -0
  32. algo_engine/base/c_allocator_protocol.pyx +111 -0
  33. algo_engine/base/c_intern_string.c +5908 -0
  34. algo_engine/base/c_intern_string.cp315-win_amd64.pyd +0 -0
  35. algo_engine/base/c_intern_string.pxd +14 -0
  36. algo_engine/base/c_intern_string.pyi +22 -0
  37. algo_engine/base/c_intern_string.pyx +17 -0
  38. algo_engine/base/c_market_data/__infra__.pxd +196 -0
  39. algo_engine/base/c_market_data/__init__.pxd +196 -0
  40. algo_engine/base/c_market_data/__init__.py +24 -0
  41. algo_engine/base/c_market_data/c_candlestick.c +18996 -0
  42. algo_engine/base/c_market_data/c_candlestick.cp315-win_amd64.pyd +0 -0
  43. algo_engine/base/c_market_data/c_candlestick.pxd +27 -0
  44. algo_engine/base/c_market_data/c_candlestick.pyi +217 -0
  45. algo_engine/base/c_market_data/c_candlestick.pyx +255 -0
  46. algo_engine/base/c_market_data/c_internal.c +14059 -0
  47. algo_engine/base/c_market_data/c_internal.cp315-win_amd64.pyd +0 -0
  48. algo_engine/base/c_market_data/c_internal.pxd +14 -0
  49. algo_engine/base/c_market_data/c_internal.pyi +47 -0
  50. algo_engine/base/c_market_data/c_internal.pyx +41 -0
  51. algo_engine/base/c_market_data/c_market_data.c +30420 -0
  52. algo_engine/base/c_market_data/c_market_data.cp315-win_amd64.pyd +0 -0
  53. algo_engine/base/c_market_data/c_market_data.h +1464 -0
  54. algo_engine/base/c_market_data/c_market_data.pxd +414 -0
  55. algo_engine/base/c_market_data/c_market_data.pyi +550 -0
  56. algo_engine/base/c_market_data/c_market_data.pyx +701 -0
  57. algo_engine/base/c_market_data/c_market_data_buffer.c +29407 -0
  58. algo_engine/base/c_market_data/c_market_data_buffer.cp315-win_amd64.pyd +0 -0
  59. algo_engine/base/c_market_data/c_market_data_buffer.h +941 -0
  60. algo_engine/base/c_market_data/c_market_data_buffer.pxd +143 -0
  61. algo_engine/base/c_market_data/c_market_data_buffer.pyi +355 -0
  62. algo_engine/base/c_market_data/c_market_data_buffer.pyx +571 -0
  63. algo_engine/base/c_market_data/c_market_data_config.h +29 -0
  64. algo_engine/base/c_market_data/c_tick.c +44303 -0
  65. algo_engine/base/c_market_data/c_tick.cp315-win_amd64.pyd +0 -0
  66. algo_engine/base/c_market_data/c_tick.pxd +53 -0
  67. algo_engine/base/c_market_data/c_tick.pyi +466 -0
  68. algo_engine/base/c_market_data/c_tick.pyx +673 -0
  69. algo_engine/base/c_market_data/c_trade_utils.c +28702 -0
  70. algo_engine/base/c_market_data/c_trade_utils.cp315-win_amd64.pyd +0 -0
  71. algo_engine/base/c_market_data/c_trade_utils.pxd +53 -0
  72. algo_engine/base/c_market_data/c_trade_utils.pyi +602 -0
  73. algo_engine/base/c_market_data/c_trade_utils.pyx +609 -0
  74. algo_engine/base/c_market_data/c_transaction.c +23558 -0
  75. algo_engine/base/c_market_data/c_transaction.cp315-win_amd64.pyd +0 -0
  76. algo_engine/base/c_market_data/c_transaction.pxd +27 -0
  77. algo_engine/base/c_market_data/c_transaction.pyi +433 -0
  78. algo_engine/base/c_market_data/c_transaction.pyx +460 -0
  79. algo_engine/base/console_utils.py +1070 -0
  80. algo_engine/base/finance_decimal.py +258 -0
  81. algo_engine/base/telemetrics.py +18 -0
  82. algo_engine/engine/__infra__.pxd +10 -0
  83. algo_engine/engine/__init__.pxd +10 -0
  84. algo_engine/engine/__init__.py +40 -0
  85. algo_engine/engine/algo_engine.py +904 -0
  86. algo_engine/engine/c_event_engine.c +16325 -0
  87. algo_engine/engine/c_event_engine.cp315-win_amd64.pyd +0 -0
  88. algo_engine/engine/c_event_engine.pxd +25 -0
  89. algo_engine/engine/c_event_engine.pyi +68 -0
  90. algo_engine/engine/c_market_engine.c +24258 -0
  91. algo_engine/engine/c_market_engine.cp315-win_amd64.pyd +0 -0
  92. algo_engine/engine/c_market_engine.pxd +87 -0
  93. algo_engine/engine/c_market_engine.pyi +357 -0
  94. algo_engine/engine/event_engine.py +53 -0
  95. algo_engine/engine/trade_engine.py +2037 -0
  96. algo_engine/exchange_profile/__infra__.pxd +100 -0
  97. algo_engine/exchange_profile/__init__.pxd +100 -0
  98. algo_engine/exchange_profile/__init__.py +53 -0
  99. algo_engine/exchange_profile/c_ex_profile_base.c +87 -0
  100. algo_engine/exchange_profile/c_ex_profile_base.h +1204 -0
  101. algo_engine/exchange_profile/c_ex_profile_cn.c +968 -0
  102. algo_engine/exchange_profile/c_ex_profile_cn.h +39 -0
  103. algo_engine/exchange_profile/c_exchange_profile.c +52375 -0
  104. algo_engine/exchange_profile/c_exchange_profile.cp315-win_amd64.pyd +0 -0
  105. algo_engine/exchange_profile/c_exchange_profile.pxd +336 -0
  106. algo_engine/exchange_profile/c_exchange_profile.pyi +883 -0
  107. algo_engine/exchange_profile/c_exchange_profile.pyx +1495 -0
  108. algo_engine/exchange_profile/c_profile_cn.c +7798 -0
  109. algo_engine/exchange_profile/c_profile_cn.cp315-win_amd64.pyd +0 -0
  110. algo_engine/exchange_profile/c_profile_cn.pxd +1 -0
  111. algo_engine/exchange_profile/c_profile_cn.pyi +3 -0
  112. algo_engine/exchange_profile/c_profile_cn.pyx +1 -0
  113. algo_engine/exchange_profile/c_profile_default.c +7798 -0
  114. algo_engine/exchange_profile/c_profile_default.cp315-win_amd64.pyd +0 -0
  115. algo_engine/exchange_profile/c_profile_default.pxd +1 -0
  116. algo_engine/exchange_profile/c_profile_default.pyi +3 -0
  117. algo_engine/exchange_profile/c_profile_default.pyx +1 -0
  118. algo_engine/exchange_profile/c_profile_dispatcher.c +7798 -0
  119. algo_engine/exchange_profile/c_profile_dispatcher.cp315-win_amd64.pyd +0 -0
  120. algo_engine/exchange_profile/c_profile_dispatcher.pxd +1 -0
  121. algo_engine/exchange_profile/c_profile_dispatcher.pyi +3 -0
  122. algo_engine/exchange_profile/c_profile_dispatcher.pyx +1 -0
  123. algo_engine/includes/algo_engine/base/c_allocator_protocol.c +11608 -0
  124. algo_engine/includes/algo_engine/base/c_intern_string.c +5908 -0
  125. algo_engine/includes/algo_engine/base/c_market_data/c_candlestick.c +18996 -0
  126. algo_engine/includes/algo_engine/base/c_market_data/c_internal.c +14059 -0
  127. algo_engine/includes/algo_engine/base/c_market_data/c_market_data.c +30420 -0
  128. algo_engine/includes/algo_engine/base/c_market_data/c_market_data.h +1464 -0
  129. algo_engine/includes/algo_engine/base/c_market_data/c_market_data_buffer.c +29407 -0
  130. algo_engine/includes/algo_engine/base/c_market_data/c_market_data_buffer.h +941 -0
  131. algo_engine/includes/algo_engine/base/c_market_data/c_market_data_config.h +29 -0
  132. algo_engine/includes/algo_engine/base/c_market_data/c_tick.c +44303 -0
  133. algo_engine/includes/algo_engine/base/c_market_data/c_trade_utils.c +28702 -0
  134. algo_engine/includes/algo_engine/base/c_market_data/c_transaction.c +23558 -0
  135. algo_engine/includes/algo_engine/engine/c_event_engine.c +16325 -0
  136. algo_engine/includes/algo_engine/engine/c_market_engine.c +24258 -0
  137. algo_engine/includes/algo_engine/exchange_profile/c_ex_profile_base.c +87 -0
  138. algo_engine/includes/algo_engine/exchange_profile/c_ex_profile_base.h +1204 -0
  139. algo_engine/includes/algo_engine/exchange_profile/c_ex_profile_cn.c +968 -0
  140. algo_engine/includes/algo_engine/exchange_profile/c_ex_profile_cn.h +39 -0
  141. algo_engine/includes/algo_engine/exchange_profile/c_exchange_profile.c +52375 -0
  142. algo_engine/includes/algo_engine/exchange_profile/c_profile_cn.c +7798 -0
  143. algo_engine/includes/algo_engine/exchange_profile/c_profile_default.c +7798 -0
  144. algo_engine/includes/algo_engine/exchange_profile/c_profile_dispatcher.c +7798 -0
  145. algo_engine/monitor/__init__.py +15 -0
  146. algo_engine/monitor/advanced_data_interface.py +334 -0
  147. algo_engine/strategy/__init__.py +44 -0
  148. algo_engine/strategy/strategy_engine.py +441 -0
  149. algo_engine/utils/__init__.py +3 -0
  150. algo_engine/utils/commit_regularizer.py +49 -0
  151. algo_engine/utils/data_utils.py +296 -0
  152. pyalgoengine-0.12.3.dist-info/METADATA +142 -0
  153. pyalgoengine-0.12.3.dist-info/RECORD +156 -0
  154. pyalgoengine-0.12.3.dist-info/WHEEL +5 -0
  155. pyalgoengine-0.12.3.dist-info/licenses/LICENSE +21 -0
  156. pyalgoengine-0.12.3.dist-info/top_level.txt +2 -0
@@ -0,0 +1,673 @@
1
+ from cpython.bytes cimport PyBytes_FromStringAndSize
2
+ from cpython.unicode cimport PyUnicode_AsUTF8
3
+ from libc.math cimport NAN
4
+ from libc.stdint cimport uint64_t, uintptr_t
5
+ from libc.stdlib cimport calloc, free
6
+ from libc.string cimport memcpy
7
+
8
+ from algo_engine.base.c_allocator_protocol cimport MD_DEFAULT_ALLOCATOR
9
+
10
+ from .c_market_data cimport (
11
+ md_ret_code, md_data_type, md_direction, MD_CFG_BOOK_SIZE,
12
+ md_orderbook_entry, c_md_orderbook_sort,
13
+ md_meta, md_tick_data_lite,
14
+ c_init_buffer, c_md_orderbook_new, c_md_orderbook_free
15
+ )
16
+ from .c_transaction import TransactionDirection, TransactionOffset
17
+
18
+
19
+ cdef class TickDataLite(MarketData):
20
+ def __init__(
21
+ self,
22
+ *,
23
+ str ticker,
24
+ double timestamp,
25
+ double last_price,
26
+ double bid_price,
27
+ double bid_volume,
28
+ double ask_price,
29
+ double ask_volume,
30
+ double open_price=NAN,
31
+ double prev_close=NAN,
32
+ double total_traded_volume=0.0,
33
+ double total_traded_notional=0.0,
34
+ uint64_t total_trade_count=0,
35
+ **kwargs
36
+ ):
37
+ cdef md_variant* header = c_init_buffer(
38
+ md_data_type.DTYPE_TICK_LITE,
39
+ PyUnicode_AsUTF8(ticker),
40
+ timestamp
41
+ )
42
+
43
+ # Set other fields
44
+ header.tick_data_lite.last_price = last_price
45
+ header.tick_data_lite.bid_price = bid_price
46
+ header.tick_data_lite.bid_volume = bid_volume
47
+ header.tick_data_lite.ask_price = ask_price
48
+ header.tick_data_lite.ask_volume = ask_volume
49
+ header.tick_data_lite.open_price = open_price
50
+ header.tick_data_lite.prev_close = prev_close
51
+ header.tick_data_lite.total_traded_volume = total_traded_volume
52
+ header.tick_data_lite.total_traded_notional = total_traded_notional
53
+ header.tick_data_lite.total_trade_count = total_trade_count
54
+
55
+ self.header = header
56
+ self.data_addr = <uintptr_t> header
57
+ self.owner = True
58
+
59
+ if kwargs:
60
+ self.__dict__.update(kwargs)
61
+
62
+ def __repr__(self):
63
+ if not self.header:
64
+ return f"<{self.__class__.__name__}>(Uninitialized)"
65
+ return f'<{self.__class__.__name__}>([{self.market_time:%Y-%m-%d %H:%M:%S}] {self.ticker}, bid={self.bid_price}, ask={self.ask_price})'
66
+
67
+ property last_price:
68
+ def __get__(self):
69
+ return self.header.tick_data_lite.last_price
70
+
71
+ property bid_price:
72
+ def __get__(self):
73
+ return self.header.tick_data_lite.bid_price
74
+
75
+ property bid_volume:
76
+ def __get__(self):
77
+ return self.header.tick_data_lite.bid_volume
78
+
79
+ property ask_price:
80
+ def __get__(self):
81
+ return self.header.tick_data_lite.ask_price
82
+
83
+ property ask_volume:
84
+ def __get__(self):
85
+ return self.header.tick_data_lite.ask_volume
86
+
87
+ property open_price:
88
+ def __get__(self):
89
+ return self.header.tick_data_lite.open_price
90
+
91
+ property prev_close:
92
+ def __get__(self):
93
+ return self.header.tick_data_lite.prev_close
94
+
95
+ property total_traded_volume:
96
+ def __get__(self):
97
+ return self.header.tick_data_lite.total_traded_volume
98
+
99
+ property total_traded_notional:
100
+ def __get__(self):
101
+ return self.header.tick_data_lite.total_traded_notional
102
+
103
+ property total_trade_count:
104
+ def __get__(self):
105
+ return self.header.tick_data_lite.total_trade_count
106
+
107
+ property mid_price:
108
+ def __get__(self):
109
+ cdef double bid_price = self.header.tick_data_lite.bid_price
110
+ cdef double ask_price = self.header.tick_data_lite.ask_price
111
+ return (bid_price + ask_price) / 2.0
112
+
113
+ property spread:
114
+ def __get__(self):
115
+ cdef double bid_price = self.header.tick_data_lite.bid_price
116
+ cdef double ask_price = self.header.tick_data_lite.ask_price
117
+ return ask_price - bid_price
118
+
119
+
120
+ cdef class OrderBook:
121
+ def __cinit__(
122
+ self,
123
+ *,
124
+ md_direction direction = md_direction.DIRECTION_UNKNOWN,
125
+ object price=None,
126
+ object volume=None,
127
+ object n_orders=None,
128
+ bint is_sorted=False,
129
+ **kwargs
130
+ ):
131
+ if direction == md_direction.DIRECTION_UNKNOWN:
132
+ return
133
+ elif direction == md_direction.DIRECTION_LONG or direction == md_direction.DIRECTION_SHORT:
134
+ pass
135
+ else:
136
+ raise ValueError(f'Invalid Direction {direction}. expecting {TransactionDirection.DIRECTION_SHORT} or {TransactionDirection.DIRECTION_LONG}')
137
+
138
+ cdef size_t book_size = MD_CFG_BOOK_SIZE
139
+
140
+ self.header = c_md_orderbook_new(book_size, MD_DEFAULT_ALLOCATOR)
141
+ if not self.header:
142
+ raise MemoryError('Failed to allocate memory for OrderBook')
143
+
144
+ self.header.direction = direction
145
+ self.header.size = 0
146
+ self.header.sorted = is_sorted
147
+
148
+ self.owner = True
149
+
150
+ # If prices, volumes, or n_orders are provided, populate them
151
+ if price is None and self.volume is None:
152
+ self.header.sorted = True
153
+ return
154
+
155
+ cdef size_t i
156
+ cdef size_t len_price = 0 if price is None else len(price)
157
+ cdef size_t len_volume = 0 if volume is None else len(volume)
158
+ cdef size_t len_n_orders = 0 if n_orders is None else len(n_orders)
159
+ cdef md_orderbook_entry* entry
160
+
161
+ for i in range(self.header.capacity):
162
+ entry = self.header.entries + i
163
+
164
+ # Update price if available
165
+ if i < len_price:
166
+ entry.price = price[i]
167
+ entry.n_orders = 1
168
+ else:
169
+ entry.price = NAN
170
+
171
+ # Update volume if available
172
+ if i < len_volume:
173
+ entry.volume = volume[i]
174
+ entry.n_orders = 1
175
+ else:
176
+ entry.volume = NAN
177
+
178
+ # Update n_orders if available
179
+ if i < len_n_orders:
180
+ entry.n_orders = n_orders[i]
181
+ else:
182
+ entry.n_orders = 0
183
+
184
+ if entry.n_orders:
185
+ self.header.size += 1
186
+
187
+ self.c_sort()
188
+
189
+ def __dealloc__(self):
190
+ if not self.owner:
191
+ return
192
+
193
+ if self.header:
194
+ c_md_orderbook_free(self.header)
195
+
196
+ def __getbuffer__(self, Py_buffer *view, int flags):
197
+ # Fill in the Py_buffer structure
198
+ view.buf = <void*> self.header.entries
199
+ view.obj = self
200
+ view.len = self.header.capacity * sizeof(md_orderbook_entry)
201
+ view.readonly = 0
202
+ view.itemsize = sizeof(double)
203
+ view.format = NULL
204
+ view.ndim = 2
205
+
206
+ # Allocate memory for shape and strides
207
+ view.shape = <Py_ssize_t*> calloc(2, sizeof(Py_ssize_t))
208
+ view.strides = <Py_ssize_t*> calloc(2, sizeof(Py_ssize_t))
209
+
210
+ if view.shape == NULL or view.strides == NULL:
211
+ if view.shape:
212
+ free(view.shape)
213
+ if view.strides:
214
+ free(view.strides)
215
+ raise MemoryError("Failed to allocate memory for shape and strides")
216
+
217
+ # Set shape and strides
218
+ view.shape[0] = self.header.capacity # Number of entries
219
+ view.shape[1] = 3 # Each entry has 3 fields (price, volume, n_orders)
220
+
221
+ view.strides[0] = sizeof(md_orderbook_entry) # Stride between entries
222
+ view.strides[1] = sizeof(double) # Stride between fields within an entry
223
+
224
+ view.suboffsets = NULL
225
+ view.internal = NULL
226
+
227
+ def __releasebuffer__(self, Py_buffer *view):
228
+ if view.shape:
229
+ free(view.shape)
230
+ view.shape = NULL
231
+ if view.strides:
232
+ free(view.strides)
233
+ view.strides = NULL
234
+
235
+ cdef void c_sort(self):
236
+ if not self.header:
237
+ raise RuntimeError('Uninitialized OrderBook cannot be sorted.')
238
+
239
+ cdef int ret_code = c_md_orderbook_sort(self.header)
240
+
241
+ if ret_code != md_ret_code.MD_OK:
242
+ raise RuntimeError(f'OrderBook sorting failed with error code {ret_code}.')
243
+
244
+ cdef double c_loc_volume(self, double p0, double p1):
245
+ self.c_sort()
246
+
247
+ cdef double volume = 0.0
248
+ cdef md_orderbook_entry* entry
249
+ cdef size_t i
250
+
251
+ for i in range(self.header.size):
252
+ entry = self.header.entries + i
253
+ if not entry.n_orders:
254
+ break
255
+
256
+ if p0 <= entry.price < p1:
257
+ volume += entry.volume
258
+
259
+ return volume
260
+
261
+ cdef bytes c_to_bytes(self):
262
+ if not self.header:
263
+ raise ValueError("Cannot convert uninitialized data to bytes")
264
+ cdef size_t buffer_size = sizeof(md_orderbook) + self.header.capacity * sizeof(md_orderbook_entry);
265
+ return PyBytes_FromStringAndSize(<char*> self.header, buffer_size)
266
+
267
+ @staticmethod
268
+ cdef OrderBook c_from_bytes(const char* data):
269
+ cdef OrderBook instance = OrderBook.__new__(OrderBook)
270
+ cdef const md_orderbook* borrowed = <const md_orderbook*> data
271
+ cdef size_t book_size = borrowed.capacity
272
+ cdef size_t buffer_size = sizeof(md_orderbook) + borrowed.capacity * sizeof(md_orderbook_entry);
273
+
274
+ cdef md_orderbook* header = c_md_orderbook_new(book_size, MD_DEFAULT_ALLOCATOR)
275
+ if not header:
276
+ raise MemoryError('Failed to allocate memory for OrderBook')
277
+
278
+ memcpy(<void*> header, <void*> borrowed, buffer_size)
279
+
280
+ instance.header = header
281
+ instance.owner = True
282
+ return instance
283
+
284
+ # === Python Interfaces ===
285
+
286
+ def __repr__(self):
287
+ if not self.header:
288
+ return f'<{self.__class__.__name__}>(Uninitialized)'
289
+ cdef str orderbook_type = 'bid' if self.header.direction == md_direction.DIRECTION_LONG \
290
+ else 'ask' if self.header.direction == md_direction.DIRECTION_SHORT \
291
+ else 'unknown'
292
+ return f'<{self.__class__.__name__} {orderbook_type}>(size={self.header.size}, capacity={self.header.capacity}, sorted={self.header.sorted})'
293
+
294
+ def __len__(self):
295
+ return self.header.size
296
+
297
+ def __iter__(self):
298
+ self.c_sort()
299
+ self.iter_index = 0
300
+ return self
301
+
302
+ def __next__(self):
303
+ cdef md_orderbook_entry* entry
304
+ while self.iter_index < self.header.size:
305
+ entry = self.header.entries + self.iter_index
306
+ self.iter_index += 1
307
+ return entry.price, entry.volume, entry.n_orders
308
+ raise StopIteration
309
+
310
+ def __getitem__(self, ssize_t idx):
311
+ self.c_sort()
312
+ cdef ssize_t ttl = self.header.size
313
+ if idx >= ttl or idx < -ttl:
314
+ raise IndexError('OrderBook index out of range')
315
+ if idx < 0:
316
+ idx += ttl
317
+ cdef md_orderbook_entry* entry = self.header.entries + idx
318
+ return entry.price, entry.volume, entry.n_orders
319
+
320
+ cpdef tuple at_price(self, double price):
321
+ cdef size_t n = self.header.size
322
+ cdef size_t i
323
+ cdef md_orderbook_entry* entry
324
+
325
+ for i in range(n):
326
+ entry = self.header.entries + i
327
+ if entry.price == price:
328
+ return entry.price, entry.volume, entry.n_orders
329
+
330
+ raise IndexError(f'price {price} not found!')
331
+
332
+ cpdef tuple at_level(self, ssize_t idx):
333
+ self.c_sort()
334
+ cdef ssize_t ttl = self.header.size
335
+
336
+ if idx >= ttl or idx < -ttl:
337
+ raise IndexError('OrderBook index out of range')
338
+ if idx < 0:
339
+ idx += ttl
340
+
341
+ cdef md_orderbook_entry* entry = self.header.entries + idx
342
+ return entry.price, entry.volume, entry.n_orders
343
+
344
+ @classmethod
345
+ def from_buffer(cls, const unsigned char[:] buffer):
346
+ cdef OrderBook instance = cls.__new__(cls)
347
+ instance.header = <md_orderbook*> &buffer[0]
348
+ instance.owner = False
349
+ return instance
350
+
351
+ def loc_volume(self, double p0, double p1):
352
+ return self.c_loc_volume(p0, p1)
353
+
354
+ def sort(self):
355
+ return self.c_sort()
356
+
357
+ def to_bytes(self):
358
+ return self.c_to_bytes()
359
+
360
+ @classmethod
361
+ def from_bytes(cls, bytes data):
362
+ return OrderBook.c_from_bytes(<const char*> data)
363
+
364
+ def to_numpy(self):
365
+ import numpy as np
366
+ dtype = [('price', np.float64), ('volume', np.float64), ('n_orders', np.uint64)]
367
+ arr = np.ndarray(
368
+ shape=(self.header.capacity,),
369
+ dtype=dtype,
370
+ buffer=self
371
+ )
372
+ return arr
373
+
374
+ property price:
375
+ def __get__(self):
376
+ if self.header.size == 0:
377
+ return None
378
+ elif self.header.size == self.header.capacity:
379
+ return self.to_numpy()['price']
380
+ else:
381
+ return self.to_numpy()['price'][:self.header.size]
382
+
383
+ property volume:
384
+ def __get__(self):
385
+ if self.header.size == 0:
386
+ return None
387
+ elif self.header.size == self.header.capacity:
388
+ return self.to_numpy()['volume']
389
+ else:
390
+ return self.to_numpy()['volume'][:self.header.size]
391
+
392
+ property n_orders:
393
+ def __get__(self):
394
+ if self.header.size == 0:
395
+ return None
396
+ elif self.header.size == self.header.capacity:
397
+ return self.to_numpy()['n_orders']
398
+ else:
399
+ return self.to_numpy()['n_orders'][:self.header.size]
400
+
401
+ property sorted:
402
+ def __get__(self):
403
+ return self.header.sorted
404
+
405
+ property side:
406
+ def __get__(self):
407
+ return TransactionDirection(self.header.direction) | TransactionOffset.OFFSET_ORDER
408
+
409
+ property direction:
410
+ def __get__(self):
411
+ return TransactionDirection(self.header.direction)
412
+
413
+ property size:
414
+ def __get__(self):
415
+ return self.header.size
416
+
417
+ property capacity:
418
+ def __get__(self):
419
+ return self.header.capacity
420
+
421
+
422
+ cdef class TickData(MarketData):
423
+ def __init__(
424
+ self,
425
+ *,
426
+ str ticker,
427
+ double timestamp,
428
+ double last_price,
429
+ double open_price=NAN,
430
+ double prev_close=NAN,
431
+ double total_traded_volume=0.0,
432
+ double total_traded_notional=0.0,
433
+ uint64_t total_trade_count=0,
434
+ double total_bid_volume=0.0,
435
+ double total_ask_volume=0.0,
436
+ double weighted_bid_price=NAN,
437
+ double weighted_ask_price=NAN,
438
+ **kwargs
439
+ ):
440
+ cdef md_variant* header = c_init_buffer(
441
+ md_data_type.DTYPE_TICK,
442
+ PyUnicode_AsUTF8(ticker),
443
+ timestamp
444
+ )
445
+
446
+ # Set TickDataLite fields
447
+ header.tick_data_full.lite.last_price = last_price
448
+ header.tick_data_full.lite.bid_price = kwargs.get('bid_price_1', NAN)
449
+ header.tick_data_full.lite.bid_volume = kwargs.get('bid_volume_1', NAN)
450
+ header.tick_data_full.lite.ask_price = kwargs.get('ask_price_1', NAN)
451
+ header.tick_data_full.lite.ask_volume = kwargs.get('ask_volume_1', NAN)
452
+ header.tick_data_full.lite.open_price = open_price
453
+ header.tick_data_full.lite.prev_close = prev_close
454
+ header.tick_data_full.lite.total_traded_volume = total_traded_volume
455
+ header.tick_data_full.lite.total_traded_notional = total_traded_notional
456
+ header.tick_data_full.lite.total_trade_count = total_trade_count
457
+ header.tick_data_full.total_bid_volume = total_bid_volume
458
+ header.tick_data_full.total_ask_volume = total_ask_volume
459
+ header.tick_data_full.weighted_bid_price = weighted_bid_price
460
+ header.tick_data_full.weighted_ask_price = weighted_ask_price
461
+
462
+ self.bid = OrderBook.__new__(OrderBook, direction=md_direction.DIRECTION_LONG, is_sorted=False)
463
+ self.ask = OrderBook.__new__(OrderBook, direction=md_direction.DIRECTION_SHORT, is_sorted=False)
464
+ header.tick_data_full.bid = self.bid.header
465
+ header.tick_data_full.ask = self.ask.header
466
+
467
+ self.header = header
468
+ self.data_addr = <uintptr_t> header
469
+ self.owner = True
470
+
471
+ if kwargs:
472
+ self.parse(kwargs)
473
+
474
+ def __repr__(self):
475
+ if not self.header:
476
+ return f"<{self.__class__.__name__}>(Uninitialized)"
477
+ return f'<{self.__class__.__name__}>([{self.market_time:%Y-%m-%d %H:%M:%S}] {self.ticker}, bid={self.bid_price}, ask={self.ask_price})'
478
+
479
+ def __copy__(self):
480
+ cdef TickData instance = super().__copy__()
481
+ cdef md_orderbook* bid_header = c_md_orderbook_new(self.header.tick_data_full.bid.capacity, MD_DEFAULT_ALLOCATOR)
482
+ cdef md_orderbook* ask_header = c_md_orderbook_new(self.header.tick_data_full.ask.capacity, MD_DEFAULT_ALLOCATOR)
483
+
484
+ memcpy(<void*> bid_header, <void*> self.header.tick_data_full.bid, sizeof(md_orderbook) + self.header.tick_data_full.bid.capacity * sizeof(md_orderbook_entry))
485
+ memcpy(<void*> ask_header, <void*> self.header.tick_data_full.ask, sizeof(md_orderbook) + self.header.tick_data_full.ask.capacity * sizeof(md_orderbook_entry))
486
+
487
+ cdef md_variant* header = <md_variant*> instance.header
488
+ header.tick_data_full.bid = bid_header
489
+ header.tick_data_full.ask = ask_header
490
+
491
+ instance.bid.header = bid_header
492
+ instance.ask.header = ask_header
493
+ return instance
494
+
495
+ cpdef void parse(self, dict kwargs):
496
+ cdef list parts
497
+ cdef str key
498
+ cdef object value
499
+ cdef str key_type, book_type
500
+ cdef size_t capacity
501
+ cdef size_t level
502
+ cdef md_orderbook* orderbook
503
+ cdef md_orderbook_entry* entry
504
+
505
+ for key, value in kwargs.items():
506
+ parts = key.split('_')
507
+ if len(parts) != 3:
508
+ self.__dict__[key] = value
509
+ continue
510
+
511
+ book_type = parts[0] # 'bid' or 'ask'
512
+ key_type = parts[1] # 'price', 'volume', or 'orders'
513
+
514
+ # Check if there's a level number
515
+ if not parts[2].isdigit():
516
+ self.__dict__[key] = value
517
+ continue
518
+
519
+ # Convert to 0-based index
520
+ # if level part is provided 0, it will be converted to SIZE_MAX, which will out of range and be saved by __dict__
521
+ level = int(parts[2]) - 1
522
+
523
+ if book_type == 'bid':
524
+ orderbook = self.header.tick_data_full.bid
525
+ elif book_type == 'ask':
526
+ orderbook = self.header.tick_data_full.ask
527
+ else:
528
+ self.__dict__[key] = value
529
+ continue
530
+
531
+ # Skip if level is out of range
532
+ capacity = orderbook.capacity
533
+ if level < 0 or level >= capacity:
534
+ self.__dict__[key] = value
535
+ continue
536
+
537
+ entry = orderbook.entries + level
538
+ if key_type == 'price':
539
+ entry.price = value
540
+ elif key_type == 'volume':
541
+ entry.volume = value
542
+ elif key_type == 'orders' or key_type == 'n_orders':
543
+ entry.n_orders = int(value)
544
+ else:
545
+ self.__dict__[key] = value
546
+ continue
547
+
548
+ if level >= orderbook.size:
549
+ orderbook.size = level + 1
550
+
551
+ if not entry.n_orders:
552
+ entry.n_orders = 1
553
+
554
+ # Sort the books
555
+ cdef int ret_code
556
+
557
+ ret_code = c_md_orderbook_sort(self.header.tick_data_full.bid)
558
+ if ret_code != md_ret_code.MD_OK:
559
+ raise RuntimeError(f'OrderBook.bid sorting failed with error code {ret_code}.')
560
+
561
+ ret_code = c_md_orderbook_sort(self.header.tick_data_full.ask)
562
+ if ret_code != md_ret_code.MD_OK:
563
+ raise RuntimeError(f'OrderBook.ask sorting failed with error code {ret_code}.')
564
+
565
+ cpdef TickDataLite lite(self, bint copy=True):
566
+ cdef TickDataLite instance = TickDataLite.__new__(TickDataLite)
567
+ if not copy:
568
+ instance.header = <const md_variant*> &self.header.tick_data_full.lite
569
+ instance.owner = False
570
+ return instance
571
+ cdef md_variant* tk_lite = c_init_buffer(
572
+ md_data_type.DTYPE_TICK_LITE,
573
+ self.header.meta_info.ticker,
574
+ self.header.meta_info.timestamp
575
+ )
576
+ memcpy(
577
+ <char*> tk_lite + sizeof(md_meta),
578
+ <char*> &self.header.tick_data_full.lite + sizeof(md_meta),
579
+ sizeof(md_tick_data_lite) - sizeof(md_meta)
580
+ )
581
+ instance.header = tk_lite
582
+ instance.owner = True
583
+ return instance
584
+
585
+ property best_bid_price:
586
+ def __get__(self):
587
+ return self.header.tick_data_full.bid.entries.price
588
+
589
+ property best_ask_price:
590
+ def __get__(self):
591
+ return self.header.tick_data_full.ask.entries.price
592
+
593
+ property best_bid_volume:
594
+ def __get__(self):
595
+ return self.header.tick_data_full.bid.entries.volume
596
+
597
+ property best_ask_volume:
598
+ def __get__(self):
599
+ return self.header.tick_data_full.ask.entries.volume
600
+
601
+ property last_price:
602
+ def __get__(self):
603
+ return self.header.tick_data_full.lite.last_price
604
+
605
+ property bid_price:
606
+ def __get__(self):
607
+ return self.header.tick_data_full.lite.bid_price
608
+
609
+ property bid_volume:
610
+ def __get__(self):
611
+ return self.header.tick_data_full.lite.bid_volume
612
+
613
+ property ask_price:
614
+ def __get__(self):
615
+ return self.header.tick_data_full.lite.ask_price
616
+
617
+ property ask_volume:
618
+ def __get__(self):
619
+ return self.header.tick_data_full.lite.ask_volume
620
+
621
+ property open_price:
622
+ def __get__(self):
623
+ return self.header.tick_data_full.lite.open_price
624
+
625
+ property prev_close:
626
+ def __get__(self):
627
+ return self.header.tick_data_full.lite.prev_close
628
+
629
+ property total_traded_volume:
630
+ def __get__(self):
631
+ return self.header.tick_data_full.lite.total_traded_volume
632
+
633
+ property total_traded_notional:
634
+ def __get__(self):
635
+ return self.header.tick_data_full.lite.total_traded_notional
636
+
637
+ property total_trade_count:
638
+ def __get__(self):
639
+ return self.header.tick_data_full.lite.total_trade_count
640
+
641
+ property total_bid_volume:
642
+ def __get__(self):
643
+ return self.header.tick_data_full.total_bid_volume
644
+
645
+ property total_ask_volume:
646
+ def __get__(self):
647
+ return self.header.tick_data_full.total_ask_volume
648
+
649
+ property weighted_bid_price:
650
+ def __get__(self):
651
+ return self.header.tick_data_full.weighted_bid_price
652
+
653
+ property weighted_ask_price:
654
+ def __get__(self):
655
+ return self.header.tick_data_full.weighted_ask_price
656
+
657
+ property mid_price:
658
+ def __get__(self):
659
+ cdef double bid = self.header.tick_data_full.lite.bid_price
660
+ cdef double ask = self.header.tick_data_full.lite.ask_price
661
+ return (bid + ask) / 2.0
662
+
663
+ property spread:
664
+ def __get__(self):
665
+ cdef double bid = self.header.tick_data_full.lite.bid_price
666
+ cdef double ask = self.header.tick_data_full.lite.ask_price
667
+ return ask - bid
668
+
669
+
670
+ from . cimport c_market_data
671
+
672
+ c_market_data.tick_lite_from_header = tick_lite_from_header
673
+ c_market_data.tick_from_header = tick_from_header