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.
- algo_engine/__infra__.pxd +3 -0
- algo_engine/__init__.pxd +3 -0
- algo_engine/__init__.py +107 -0
- algo_engine/apps/__init__.py +17 -0
- algo_engine/apps/backtest/__init__.py +20 -0
- algo_engine/apps/backtest/doc_server.py +328 -0
- algo_engine/apps/backtest/static/styles/dash.css +48 -0
- algo_engine/apps/backtest/templates/dash.html +20 -0
- algo_engine/apps/backtest/templates/index.html +40 -0
- algo_engine/apps/backtest/tester.py +252 -0
- algo_engine/apps/backtest/web_app.py +125 -0
- algo_engine/apps/bokeh_server.py +245 -0
- algo_engine/apps/demo/__init__.py +0 -0
- algo_engine/apps/demo/test.py +40 -0
- algo_engine/apps/sim_input/__init__.py +23 -0
- algo_engine/apps/sim_input/client.py +412 -0
- algo_engine/apps/sim_input/sim_keyboard.py +88 -0
- algo_engine/apps/sim_input/sim_mouse.py +137 -0
- algo_engine/apps/sim_input/window.py +162 -0
- algo_engine/backtest/__init__.py +19 -0
- algo_engine/backtest/__main__.py +51 -0
- algo_engine/backtest/metrics.py +179 -0
- algo_engine/backtest/replay.py +635 -0
- algo_engine/backtest/sim_match.py +506 -0
- algo_engine/base/__infra__.pxd +3 -0
- algo_engine/base/__init__.pxd +3 -0
- algo_engine/base/__init__.py +60 -0
- algo_engine/base/c_allocator_protocol.c +11608 -0
- algo_engine/base/c_allocator_protocol.cp315-win_amd64.pyd +0 -0
- algo_engine/base/c_allocator_protocol.pxd +24 -0
- algo_engine/base/c_allocator_protocol.pyi +68 -0
- algo_engine/base/c_allocator_protocol.pyx +111 -0
- algo_engine/base/c_intern_string.c +5908 -0
- algo_engine/base/c_intern_string.cp315-win_amd64.pyd +0 -0
- algo_engine/base/c_intern_string.pxd +14 -0
- algo_engine/base/c_intern_string.pyi +22 -0
- algo_engine/base/c_intern_string.pyx +17 -0
- algo_engine/base/c_market_data/__infra__.pxd +196 -0
- algo_engine/base/c_market_data/__init__.pxd +196 -0
- algo_engine/base/c_market_data/__init__.py +24 -0
- algo_engine/base/c_market_data/c_candlestick.c +18996 -0
- algo_engine/base/c_market_data/c_candlestick.cp315-win_amd64.pyd +0 -0
- algo_engine/base/c_market_data/c_candlestick.pxd +27 -0
- algo_engine/base/c_market_data/c_candlestick.pyi +217 -0
- algo_engine/base/c_market_data/c_candlestick.pyx +255 -0
- algo_engine/base/c_market_data/c_internal.c +14059 -0
- algo_engine/base/c_market_data/c_internal.cp315-win_amd64.pyd +0 -0
- algo_engine/base/c_market_data/c_internal.pxd +14 -0
- algo_engine/base/c_market_data/c_internal.pyi +47 -0
- algo_engine/base/c_market_data/c_internal.pyx +41 -0
- algo_engine/base/c_market_data/c_market_data.c +30420 -0
- algo_engine/base/c_market_data/c_market_data.cp315-win_amd64.pyd +0 -0
- algo_engine/base/c_market_data/c_market_data.h +1464 -0
- algo_engine/base/c_market_data/c_market_data.pxd +414 -0
- algo_engine/base/c_market_data/c_market_data.pyi +550 -0
- algo_engine/base/c_market_data/c_market_data.pyx +701 -0
- algo_engine/base/c_market_data/c_market_data_buffer.c +29407 -0
- algo_engine/base/c_market_data/c_market_data_buffer.cp315-win_amd64.pyd +0 -0
- algo_engine/base/c_market_data/c_market_data_buffer.h +941 -0
- algo_engine/base/c_market_data/c_market_data_buffer.pxd +143 -0
- algo_engine/base/c_market_data/c_market_data_buffer.pyi +355 -0
- algo_engine/base/c_market_data/c_market_data_buffer.pyx +571 -0
- algo_engine/base/c_market_data/c_market_data_config.h +29 -0
- algo_engine/base/c_market_data/c_tick.c +44303 -0
- algo_engine/base/c_market_data/c_tick.cp315-win_amd64.pyd +0 -0
- algo_engine/base/c_market_data/c_tick.pxd +53 -0
- algo_engine/base/c_market_data/c_tick.pyi +466 -0
- algo_engine/base/c_market_data/c_tick.pyx +673 -0
- algo_engine/base/c_market_data/c_trade_utils.c +28702 -0
- algo_engine/base/c_market_data/c_trade_utils.cp315-win_amd64.pyd +0 -0
- algo_engine/base/c_market_data/c_trade_utils.pxd +53 -0
- algo_engine/base/c_market_data/c_trade_utils.pyi +602 -0
- algo_engine/base/c_market_data/c_trade_utils.pyx +609 -0
- algo_engine/base/c_market_data/c_transaction.c +23558 -0
- algo_engine/base/c_market_data/c_transaction.cp315-win_amd64.pyd +0 -0
- algo_engine/base/c_market_data/c_transaction.pxd +27 -0
- algo_engine/base/c_market_data/c_transaction.pyi +433 -0
- algo_engine/base/c_market_data/c_transaction.pyx +460 -0
- algo_engine/base/console_utils.py +1070 -0
- algo_engine/base/finance_decimal.py +258 -0
- algo_engine/base/telemetrics.py +18 -0
- algo_engine/engine/__infra__.pxd +10 -0
- algo_engine/engine/__init__.pxd +10 -0
- algo_engine/engine/__init__.py +40 -0
- algo_engine/engine/algo_engine.py +904 -0
- algo_engine/engine/c_event_engine.c +16325 -0
- algo_engine/engine/c_event_engine.cp315-win_amd64.pyd +0 -0
- algo_engine/engine/c_event_engine.pxd +25 -0
- algo_engine/engine/c_event_engine.pyi +68 -0
- algo_engine/engine/c_market_engine.c +24258 -0
- algo_engine/engine/c_market_engine.cp315-win_amd64.pyd +0 -0
- algo_engine/engine/c_market_engine.pxd +87 -0
- algo_engine/engine/c_market_engine.pyi +357 -0
- algo_engine/engine/event_engine.py +53 -0
- algo_engine/engine/trade_engine.py +2037 -0
- algo_engine/exchange_profile/__infra__.pxd +100 -0
- algo_engine/exchange_profile/__init__.pxd +100 -0
- algo_engine/exchange_profile/__init__.py +53 -0
- algo_engine/exchange_profile/c_ex_profile_base.c +87 -0
- algo_engine/exchange_profile/c_ex_profile_base.h +1204 -0
- algo_engine/exchange_profile/c_ex_profile_cn.c +968 -0
- algo_engine/exchange_profile/c_ex_profile_cn.h +39 -0
- algo_engine/exchange_profile/c_exchange_profile.c +52375 -0
- algo_engine/exchange_profile/c_exchange_profile.cp315-win_amd64.pyd +0 -0
- algo_engine/exchange_profile/c_exchange_profile.pxd +336 -0
- algo_engine/exchange_profile/c_exchange_profile.pyi +883 -0
- algo_engine/exchange_profile/c_exchange_profile.pyx +1495 -0
- algo_engine/exchange_profile/c_profile_cn.c +7798 -0
- algo_engine/exchange_profile/c_profile_cn.cp315-win_amd64.pyd +0 -0
- algo_engine/exchange_profile/c_profile_cn.pxd +1 -0
- algo_engine/exchange_profile/c_profile_cn.pyi +3 -0
- algo_engine/exchange_profile/c_profile_cn.pyx +1 -0
- algo_engine/exchange_profile/c_profile_default.c +7798 -0
- algo_engine/exchange_profile/c_profile_default.cp315-win_amd64.pyd +0 -0
- algo_engine/exchange_profile/c_profile_default.pxd +1 -0
- algo_engine/exchange_profile/c_profile_default.pyi +3 -0
- algo_engine/exchange_profile/c_profile_default.pyx +1 -0
- algo_engine/exchange_profile/c_profile_dispatcher.c +7798 -0
- algo_engine/exchange_profile/c_profile_dispatcher.cp315-win_amd64.pyd +0 -0
- algo_engine/exchange_profile/c_profile_dispatcher.pxd +1 -0
- algo_engine/exchange_profile/c_profile_dispatcher.pyi +3 -0
- algo_engine/exchange_profile/c_profile_dispatcher.pyx +1 -0
- algo_engine/includes/algo_engine/base/c_allocator_protocol.c +11608 -0
- algo_engine/includes/algo_engine/base/c_intern_string.c +5908 -0
- algo_engine/includes/algo_engine/base/c_market_data/c_candlestick.c +18996 -0
- algo_engine/includes/algo_engine/base/c_market_data/c_internal.c +14059 -0
- algo_engine/includes/algo_engine/base/c_market_data/c_market_data.c +30420 -0
- algo_engine/includes/algo_engine/base/c_market_data/c_market_data.h +1464 -0
- algo_engine/includes/algo_engine/base/c_market_data/c_market_data_buffer.c +29407 -0
- algo_engine/includes/algo_engine/base/c_market_data/c_market_data_buffer.h +941 -0
- algo_engine/includes/algo_engine/base/c_market_data/c_market_data_config.h +29 -0
- algo_engine/includes/algo_engine/base/c_market_data/c_tick.c +44303 -0
- algo_engine/includes/algo_engine/base/c_market_data/c_trade_utils.c +28702 -0
- algo_engine/includes/algo_engine/base/c_market_data/c_transaction.c +23558 -0
- algo_engine/includes/algo_engine/engine/c_event_engine.c +16325 -0
- algo_engine/includes/algo_engine/engine/c_market_engine.c +24258 -0
- algo_engine/includes/algo_engine/exchange_profile/c_ex_profile_base.c +87 -0
- algo_engine/includes/algo_engine/exchange_profile/c_ex_profile_base.h +1204 -0
- algo_engine/includes/algo_engine/exchange_profile/c_ex_profile_cn.c +968 -0
- algo_engine/includes/algo_engine/exchange_profile/c_ex_profile_cn.h +39 -0
- algo_engine/includes/algo_engine/exchange_profile/c_exchange_profile.c +52375 -0
- algo_engine/includes/algo_engine/exchange_profile/c_profile_cn.c +7798 -0
- algo_engine/includes/algo_engine/exchange_profile/c_profile_default.c +7798 -0
- algo_engine/includes/algo_engine/exchange_profile/c_profile_dispatcher.c +7798 -0
- algo_engine/monitor/__init__.py +15 -0
- algo_engine/monitor/advanced_data_interface.py +334 -0
- algo_engine/strategy/__init__.py +44 -0
- algo_engine/strategy/strategy_engine.py +441 -0
- algo_engine/utils/__init__.py +3 -0
- algo_engine/utils/commit_regularizer.py +49 -0
- algo_engine/utils/data_utils.py +296 -0
- pyalgoengine-0.12.3.dist-info/METADATA +142 -0
- pyalgoengine-0.12.3.dist-info/RECORD +156 -0
- pyalgoengine-0.12.3.dist-info/WHEEL +5 -0
- pyalgoengine-0.12.3.dist-info/licenses/LICENSE +21 -0
- 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
|