PyAlgoEngine 0.3.11.post1__tar.gz → 0.3.12.post3__tar.gz
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.
- {PyAlgoEngine-0.3.11.post1 → PyAlgoEngine-0.3.12.post3}/AlgoEngine/Engine/MarketEngine.py +287 -124
- {PyAlgoEngine-0.3.11.post1 → PyAlgoEngine-0.3.12.post3}/AlgoEngine/Engine/TradeEngine.py +3 -3
- {PyAlgoEngine-0.3.11.post1 → PyAlgoEngine-0.3.12.post3}/AlgoEngine/Engine/__init__.py +2 -2
- {PyAlgoEngine-0.3.11.post1 → PyAlgoEngine-0.3.12.post3}/AlgoEngine/__init__.py +1 -1
- {PyAlgoEngine-0.3.11.post1 → PyAlgoEngine-0.3.12.post3}/PKG-INFO +1 -1
- {PyAlgoEngine-0.3.11.post1 → PyAlgoEngine-0.3.12.post3}/PyAlgoEngine.egg-info/PKG-INFO +1 -1
- {PyAlgoEngine-0.3.11.post1 → PyAlgoEngine-0.3.12.post3}/AlgoEngine/Engine/AlgoEngine.py +0 -0
- {PyAlgoEngine-0.3.11.post1 → PyAlgoEngine-0.3.12.post3}/AlgoEngine/Engine/EventEngine.py +0 -0
- {PyAlgoEngine-0.3.11.post1 → PyAlgoEngine-0.3.12.post3}/AlgoEngine/Strategies/BackTest.py +0 -0
- {PyAlgoEngine-0.3.11.post1 → PyAlgoEngine-0.3.12.post3}/AlgoEngine/Strategies/_StrategyEngine.py +0 -0
- {PyAlgoEngine-0.3.11.post1 → PyAlgoEngine-0.3.12.post3}/AlgoEngine/Strategies/__init__.py +0 -0
- {PyAlgoEngine-0.3.11.post1 → PyAlgoEngine-0.3.12.post3}/LICENSE +0 -0
- {PyAlgoEngine-0.3.11.post1 → PyAlgoEngine-0.3.12.post3}/PyAlgoEngine.egg-info/SOURCES.txt +0 -0
- {PyAlgoEngine-0.3.11.post1 → PyAlgoEngine-0.3.12.post3}/PyAlgoEngine.egg-info/dependency_links.txt +0 -0
- {PyAlgoEngine-0.3.11.post1 → PyAlgoEngine-0.3.12.post3}/PyAlgoEngine.egg-info/requires.txt +0 -0
- {PyAlgoEngine-0.3.11.post1 → PyAlgoEngine-0.3.12.post3}/PyAlgoEngine.egg-info/top_level.txt +0 -0
- {PyAlgoEngine-0.3.11.post1 → PyAlgoEngine-0.3.12.post3}/README.md +0 -0
- {PyAlgoEngine-0.3.11.post1 → PyAlgoEngine-0.3.12.post3}/setup.cfg +0 -0
- {PyAlgoEngine-0.3.11.post1 → PyAlgoEngine-0.3.12.post3}/setup.py +0 -0
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
1
|
import abc
|
|
4
2
|
import datetime
|
|
5
3
|
import functools
|
|
6
|
-
import
|
|
4
|
+
import inspect
|
|
5
|
+
import json
|
|
6
|
+
import pickle
|
|
7
7
|
import uuid
|
|
8
8
|
from collections import defaultdict
|
|
9
|
-
from
|
|
9
|
+
from multiprocessing import shared_memory
|
|
10
|
+
from typing import Iterable, Self
|
|
10
11
|
|
|
11
12
|
from PyQuantKit import TickData, TradeData, OrderBook, MarketData, Progress, TransactionSide, BarData, TransactionData
|
|
12
13
|
|
|
13
14
|
from . import LOGGER
|
|
14
15
|
|
|
15
|
-
__all__ = ['MDS', 'MarketDataService', 'MarketDataMonitor', 'SyntheticOrderBookMonitor', 'MinuteBarMonitor', 'Profile', 'ProgressiveReplay', 'SimpleReplay', 'Replay']
|
|
16
|
+
__all__ = ['MDS', 'MarketDataService', 'MarketDataMonitor', 'MonitorManager', 'SyntheticOrderBookMonitor', 'MinuteBarMonitor', 'Profile', 'ProgressiveReplay', 'SimpleReplay', 'Replay']
|
|
16
17
|
LOGGER = LOGGER.getChild('MarketEngine')
|
|
17
18
|
|
|
18
19
|
|
|
@@ -32,41 +33,139 @@ class MarketDataMonitor(object, metaclass=abc.ABCMeta):
|
|
|
32
33
|
The implemented monitor should be initialized and use `MDS.add_monitor(monitor)` to attach onto the engine
|
|
33
34
|
"""
|
|
34
35
|
|
|
35
|
-
def __init__(self, name: str, monitor_id: str = None
|
|
36
|
-
self.name = name
|
|
37
|
-
self.monitor_id = uuid.uuid4().hex if monitor_id is None else monitor_id
|
|
38
|
-
self.
|
|
39
|
-
self.enabled = True
|
|
36
|
+
def __init__(self, name: str, monitor_id: str = None):
|
|
37
|
+
self.name: str = name
|
|
38
|
+
self.monitor_id: str = uuid.uuid4().hex if monitor_id is None else monitor_id
|
|
39
|
+
self.enabled: bool = True
|
|
40
40
|
|
|
41
41
|
@abc.abstractmethod
|
|
42
|
-
def __call__(self, market_data: MarketData, **kwargs):
|
|
42
|
+
def __call__(self, market_data: MarketData, **kwargs):
|
|
43
|
+
...
|
|
44
|
+
|
|
45
|
+
def __reduce__(self):
|
|
46
|
+
return self.__class__.from_json, (self.to_json(),)
|
|
43
47
|
|
|
44
48
|
@abc.abstractmethod
|
|
45
|
-
def
|
|
49
|
+
def to_json(self, fmt='str') -> dict | str:
|
|
50
|
+
...
|
|
51
|
+
|
|
52
|
+
@classmethod
|
|
53
|
+
@abc.abstractmethod
|
|
54
|
+
def from_json(cls, json_message: str | bytes | bytearray | dict) -> Self:
|
|
55
|
+
...
|
|
56
|
+
|
|
57
|
+
def to_shm(self, name: str = None) -> str:
|
|
58
|
+
"""
|
|
59
|
+
Put the data of the monitor into python shared memory.
|
|
60
|
+
This function is designed to facilitate multiprocessing.
|
|
61
|
+
Some monitor is not advised to be handled concurrently,
|
|
62
|
+
In which case, raise a NotImplementedError.
|
|
63
|
+
|
|
64
|
+
The function is expected to put all data into a sharable list,
|
|
65
|
+
and return the name of the list, which can be set by the given name.
|
|
66
|
+
Default name = self.monitor_id
|
|
67
|
+
|
|
68
|
+
Note that this method HAVE NO LOCK, use with caution.
|
|
69
|
+
"""
|
|
70
|
+
if name is None:
|
|
71
|
+
name = f'{self.monitor_id}.json'
|
|
72
|
+
|
|
73
|
+
data = pickle.dumps(self.to_json(fmt='dict'))
|
|
74
|
+
size = len(data)
|
|
75
|
+
|
|
76
|
+
try:
|
|
77
|
+
shm = shared_memory.SharedMemory(name=name)
|
|
78
|
+
|
|
79
|
+
if shm.size != size:
|
|
80
|
+
shm.close()
|
|
81
|
+
shm.unlink()
|
|
82
|
+
shm = shared_memory.SharedMemory(create=True, size=size, name=name)
|
|
83
|
+
except FileNotFoundError as _:
|
|
84
|
+
shm = shared_memory.SharedMemory(create=True, size=size, name=name)
|
|
85
|
+
|
|
86
|
+
shm.buf[:size] = data
|
|
87
|
+
shm.close()
|
|
88
|
+
return name
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def from_shm(cls, monitor_id: str):
|
|
92
|
+
"""
|
|
93
|
+
retrieve the data and update the monitor from shared memory.
|
|
94
|
+
This function is designed to facilitate multiprocessing.
|
|
95
|
+
"""
|
|
96
|
+
return
|
|
46
97
|
|
|
47
|
-
@property
|
|
48
98
|
@abc.abstractmethod
|
|
49
|
-
def
|
|
99
|
+
def clear(self) -> None:
|
|
100
|
+
...
|
|
50
101
|
|
|
51
102
|
@property
|
|
52
103
|
@abc.abstractmethod
|
|
53
|
-
def
|
|
104
|
+
def value(self) -> dict[str, float] | float:
|
|
105
|
+
...
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def is_ready(self) -> bool:
|
|
109
|
+
return True
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class MonitorManager(object):
|
|
113
|
+
"""
|
|
114
|
+
manage market data monitor
|
|
115
|
+
|
|
116
|
+
state codes for the manager
|
|
117
|
+
0: idle
|
|
118
|
+
1: working
|
|
119
|
+
-1: terminating
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
def __init__(self):
|
|
123
|
+
self.monitor: dict[str, MarketDataMonitor] = {}
|
|
124
|
+
|
|
125
|
+
def __call__(self, market_data: MarketData):
|
|
126
|
+
for monitor_id in self.monitor:
|
|
127
|
+
self._work(monitor_id=monitor_id, market_data=market_data)
|
|
128
|
+
|
|
129
|
+
def add_monitor(self, monitor: MarketDataMonitor):
|
|
130
|
+
self.monitor[monitor.monitor_id] = monitor
|
|
131
|
+
|
|
132
|
+
def pop_monitor(self, monitor_id: str) -> MarketDataMonitor:
|
|
133
|
+
return self.monitor.pop(monitor_id)
|
|
134
|
+
|
|
135
|
+
def _work(self, monitor_id: str, market_data: MarketData):
|
|
136
|
+
monitor = self.monitor.get(monitor_id)
|
|
137
|
+
if monitor is not None and monitor.enabled:
|
|
138
|
+
monitor.__call__(market_data)
|
|
139
|
+
|
|
140
|
+
def start(self):
|
|
141
|
+
pass
|
|
142
|
+
|
|
143
|
+
def stop(self):
|
|
144
|
+
pass
|
|
145
|
+
|
|
146
|
+
def clear(self):
|
|
147
|
+
self.monitor.clear()
|
|
148
|
+
|
|
149
|
+
@property
|
|
150
|
+
def values(self) -> dict[str, float]:
|
|
151
|
+
values = {}
|
|
152
|
+
|
|
153
|
+
for monitor in self.monitor.values():
|
|
154
|
+
values.update(monitor.value)
|
|
155
|
+
|
|
156
|
+
return values
|
|
54
157
|
|
|
55
158
|
|
|
56
159
|
class SyntheticOrderBookMonitor(MarketDataMonitor):
|
|
57
|
-
|
|
58
|
-
|
|
160
|
+
|
|
161
|
+
def __init__(self, **kwargs):
|
|
59
162
|
|
|
60
163
|
super().__init__(
|
|
61
164
|
name=kwargs.pop('name', 'Monitor.SyntheticOrderBook'),
|
|
62
|
-
monitor_id=kwargs.pop('monitor_id', None)
|
|
63
|
-
mds=kwargs.pop('mds', None),
|
|
165
|
+
monitor_id=kwargs.pop('monitor_id', None)
|
|
64
166
|
)
|
|
65
167
|
|
|
66
|
-
self.
|
|
67
|
-
self._value = {}
|
|
68
|
-
self.order_book = {}
|
|
69
|
-
self.order_log = {}
|
|
168
|
+
self.order_book: dict[str, OrderBook] = {}
|
|
70
169
|
|
|
71
170
|
def __call__(self, market_data: MarketData, **kwargs):
|
|
72
171
|
if isinstance(market_data, TradeData):
|
|
@@ -77,55 +176,59 @@ class SyntheticOrderBookMonitor(MarketDataMonitor):
|
|
|
77
176
|
|
|
78
177
|
if order_book := self.order_book.get(ticker):
|
|
79
178
|
if order_book.market_time <= trade_data.market_time:
|
|
80
|
-
side
|
|
179
|
+
side = trade_data.side
|
|
81
180
|
price = trade_data.price
|
|
82
181
|
book = order_book.ask if side.sign > 0 else order_book.bid
|
|
83
182
|
listed_volume = book.at_price(price).volume if price in book else 0.
|
|
84
183
|
traded_volume = trade_data.volume
|
|
85
|
-
book.
|
|
86
|
-
|
|
87
|
-
if self.keep_order_log:
|
|
88
|
-
self._update_order_log(trade_data=trade_data)
|
|
184
|
+
book.update(price=price, volume=max(0, listed_volume - traded_volume))
|
|
89
185
|
|
|
90
186
|
def on_transaction_data(self, transaction_data: TransactionData):
|
|
91
187
|
pass
|
|
92
188
|
|
|
93
|
-
def
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
order_log = self.order_log.get(order_id)
|
|
100
|
-
|
|
101
|
-
if order_log is None:
|
|
102
|
-
continue
|
|
103
|
-
|
|
104
|
-
if side.sign > 0:
|
|
105
|
-
if order_log.side.sign < 0:
|
|
106
|
-
if price == order_log.price:
|
|
107
|
-
order_log.volume -= traded_volume
|
|
108
|
-
elif price > order_log.price:
|
|
109
|
-
order_log.volume = 0.
|
|
110
|
-
else:
|
|
111
|
-
if price <= order_log.price:
|
|
112
|
-
order_log.volume = 0.
|
|
113
|
-
elif side.sign < 0:
|
|
114
|
-
if order_log.side.sign > 0:
|
|
115
|
-
if price == order_log.price:
|
|
116
|
-
order_log.volume -= traded_volume
|
|
117
|
-
elif price < order_log.price:
|
|
118
|
-
order_log.volume = 0.
|
|
119
|
-
else:
|
|
120
|
-
if price >= order_log.price:
|
|
121
|
-
order_log.volume = 0.
|
|
122
|
-
|
|
123
|
-
if order_log.volume <= 0:
|
|
124
|
-
self.order_log.pop(order_id)
|
|
189
|
+
def to_json(self, fmt='str', **kwargs) -> str | dict:
|
|
190
|
+
data_dict = dict(
|
|
191
|
+
name=self.name,
|
|
192
|
+
monitor_id=self.monitor_id,
|
|
193
|
+
order_book={k: v.to_json(fmt='dict') for k, v in self.order_book.items()},
|
|
194
|
+
)
|
|
125
195
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
196
|
+
if fmt == 'dict':
|
|
197
|
+
return data_dict
|
|
198
|
+
elif fmt == 'str':
|
|
199
|
+
return json.dumps(data_dict, **kwargs)
|
|
200
|
+
else:
|
|
201
|
+
raise ValueError(f'Invalid format {fmt}, except "dict" or "str".')
|
|
202
|
+
|
|
203
|
+
@classmethod
|
|
204
|
+
def from_json(cls, json_message: str | bytes | bytearray | dict) -> Self:
|
|
205
|
+
if isinstance(json_message, dict):
|
|
206
|
+
json_dict = json_message
|
|
207
|
+
else:
|
|
208
|
+
json_dict = json.loads(json_message)
|
|
209
|
+
|
|
210
|
+
self = cls(
|
|
211
|
+
name=json_dict['name'],
|
|
212
|
+
monitor_id=json_dict['monitor_id'],
|
|
213
|
+
keep_order_log=json_dict['keep_order_log']
|
|
214
|
+
)
|
|
215
|
+
|
|
216
|
+
self.order_book = {k: MarketData.from_json(v) for k, v in json_dict['order_book'].items()}
|
|
217
|
+
return self
|
|
218
|
+
|
|
219
|
+
def from_shm(self, name: str = None) -> None:
|
|
220
|
+
if name is None:
|
|
221
|
+
name = f'{self.monitor_id}.json'
|
|
222
|
+
|
|
223
|
+
shm = shared_memory.SharedMemory(name=name)
|
|
224
|
+
json_dict = pickle.loads(bytes(shm.buf))
|
|
225
|
+
|
|
226
|
+
self.clear()
|
|
227
|
+
|
|
228
|
+
self.order_book.update({k: MarketData.from_json(v) for k, v in json_dict['order_book'].items()})
|
|
229
|
+
|
|
230
|
+
def clear(self) -> None:
|
|
231
|
+
self.order_book.clear()
|
|
129
232
|
|
|
130
233
|
@property
|
|
131
234
|
def value(self) -> dict[str, OrderBook]:
|
|
@@ -133,21 +236,18 @@ class SyntheticOrderBookMonitor(MarketDataMonitor):
|
|
|
133
236
|
|
|
134
237
|
|
|
135
238
|
class MinuteBarMonitor(MarketDataMonitor):
|
|
239
|
+
|
|
136
240
|
def __init__(self, interval: float = 60., **kwargs):
|
|
137
241
|
self.interval = interval
|
|
138
242
|
|
|
139
243
|
super().__init__(
|
|
140
244
|
name=kwargs.pop('name', 'Monitor.MinuteBarMonitor'),
|
|
141
|
-
monitor_id=kwargs.pop('monitor_id', None)
|
|
142
|
-
mds=kwargs.pop('mds', None),
|
|
245
|
+
monitor_id=kwargs.pop('monitor_id', None)
|
|
143
246
|
)
|
|
144
247
|
|
|
145
248
|
self._minute_bar_data: dict[str, BarData] = {}
|
|
146
249
|
self._last_bar_data: dict[str, BarData] = {}
|
|
147
250
|
|
|
148
|
-
self._is_ready = True
|
|
149
|
-
self._value = {}
|
|
150
|
-
|
|
151
251
|
def __call__(self, market_data: MarketData, **kwargs):
|
|
152
252
|
self._update_last_bar(market_data=market_data, interval=self.interval)
|
|
153
253
|
# self._update_active_bar(market_data=market_data, interval=self.interval)
|
|
@@ -156,6 +256,7 @@ class MinuteBarMonitor(MarketDataMonitor):
|
|
|
156
256
|
ticker = market_data.ticker
|
|
157
257
|
market_price = market_data.market_price
|
|
158
258
|
market_time = market_data.market_time
|
|
259
|
+
timestamp = market_data.timestamp
|
|
159
260
|
|
|
160
261
|
if ticker not in self._minute_bar_data or market_time >= self._minute_bar_data[ticker].bar_end_time:
|
|
161
262
|
# update bar_data
|
|
@@ -164,7 +265,8 @@ class MinuteBarMonitor(MarketDataMonitor):
|
|
|
164
265
|
|
|
165
266
|
bar_data = self._minute_bar_data[ticker] = BarData(
|
|
166
267
|
ticker=ticker,
|
|
167
|
-
|
|
268
|
+
timestamp=int(timestamp // interval + 1) * interval,
|
|
269
|
+
start_timestamp=int(timestamp // interval) * interval,
|
|
168
270
|
bar_span=datetime.timedelta(seconds=interval),
|
|
169
271
|
high_price=market_price,
|
|
170
272
|
low_price=market_price,
|
|
@@ -178,23 +280,25 @@ class MinuteBarMonitor(MarketDataMonitor):
|
|
|
178
280
|
bar_data = self._minute_bar_data[ticker]
|
|
179
281
|
|
|
180
282
|
if isinstance(market_data, TradeData):
|
|
181
|
-
bar_data
|
|
182
|
-
bar_data
|
|
183
|
-
bar_data
|
|
283
|
+
bar_data['volume'] += market_data.volume
|
|
284
|
+
bar_data['notional'] += market_data.notional
|
|
285
|
+
bar_data['trade_count'] += 1
|
|
184
286
|
|
|
185
|
-
bar_data
|
|
186
|
-
bar_data
|
|
187
|
-
bar_data
|
|
287
|
+
bar_data['close_price'] = market_price
|
|
288
|
+
bar_data['high_price'] = max(bar_data.high_price, market_price)
|
|
289
|
+
bar_data['low_price'] = min(bar_data.low_price, market_price)
|
|
188
290
|
|
|
189
291
|
def _update_active_bar(self, market_data: MarketData, interval: float):
|
|
190
292
|
ticker = market_data.ticker
|
|
191
293
|
market_price = market_data.market_price
|
|
192
|
-
market_time =
|
|
294
|
+
market_time = market_data.market_time
|
|
295
|
+
timestamp = market_data.timestamp
|
|
193
296
|
|
|
194
297
|
if ticker not in self._minute_bar_data or market_time >= self._minute_bar_data[ticker].bar_end_time:
|
|
195
298
|
bar_data = self._minute_bar_data[ticker] = BarData(
|
|
196
299
|
ticker=ticker,
|
|
197
|
-
|
|
300
|
+
start_timestamp=timestamp - interval,
|
|
301
|
+
timestamp=timestamp,
|
|
198
302
|
bar_span=datetime.timedelta(seconds=interval),
|
|
199
303
|
high_price=market_price,
|
|
200
304
|
low_price=market_price,
|
|
@@ -210,7 +314,7 @@ class MinuteBarMonitor(MarketDataMonitor):
|
|
|
210
314
|
bar_data = self._minute_bar_data[ticker]
|
|
211
315
|
|
|
212
316
|
history: list[TradeData] = getattr(bar_data, 'history')
|
|
213
|
-
bar_data
|
|
317
|
+
bar_data['start_timestamp'] = timestamp - interval
|
|
214
318
|
|
|
215
319
|
if isinstance(market_data, TradeData):
|
|
216
320
|
history.append(market_data)
|
|
@@ -221,17 +325,62 @@ class MinuteBarMonitor(MarketDataMonitor):
|
|
|
221
325
|
else:
|
|
222
326
|
history.pop(0)
|
|
223
327
|
|
|
224
|
-
bar_data
|
|
225
|
-
bar_data
|
|
226
|
-
bar_data
|
|
227
|
-
bar_data
|
|
228
|
-
bar_data
|
|
229
|
-
bar_data
|
|
230
|
-
bar_data
|
|
328
|
+
bar_data['volume'] = sum([_.volume for _ in history])
|
|
329
|
+
bar_data['notional'] = sum([_.notional for _ in history])
|
|
330
|
+
bar_data['trade_count'] = len([_.notional for _ in history])
|
|
331
|
+
bar_data['close_price'] = market_price
|
|
332
|
+
bar_data['open_price'] = history[0].market_price
|
|
333
|
+
bar_data['high_price'] = max([_.market_price for _ in history])
|
|
334
|
+
bar_data['low_price'] = min([_.market_price for _ in history])
|
|
335
|
+
|
|
336
|
+
def to_json(self, fmt='str', **kwargs) -> str | dict:
|
|
337
|
+
data_dict = dict(
|
|
338
|
+
name=self.name,
|
|
339
|
+
monitor_id=self.monitor_id,
|
|
340
|
+
interval=self.interval,
|
|
341
|
+
minute_bar_data={k: v.to_json(fmt='dict') for k, v in self._minute_bar_data.items()},
|
|
342
|
+
last_bar_data={k: v.to_json(fmt='dict') for k, v in self._last_bar_data.items()},
|
|
343
|
+
)
|
|
231
344
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
345
|
+
if fmt == 'dict':
|
|
346
|
+
return data_dict
|
|
347
|
+
elif fmt == 'str':
|
|
348
|
+
return json.dumps(data_dict, **kwargs)
|
|
349
|
+
else:
|
|
350
|
+
raise ValueError(f'Invalid format {fmt}, except "dict" or "str".')
|
|
351
|
+
|
|
352
|
+
@classmethod
|
|
353
|
+
def from_json(cls, json_message: str | bytes | bytearray | dict) -> Self:
|
|
354
|
+
if isinstance(json_message, dict):
|
|
355
|
+
json_dict = json_message
|
|
356
|
+
else:
|
|
357
|
+
json_dict = json.loads(json_message)
|
|
358
|
+
|
|
359
|
+
self = cls(
|
|
360
|
+
name=json_dict['name'],
|
|
361
|
+
monitor_id=json_dict['monitor_id'],
|
|
362
|
+
interval=json_dict['interval'],
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
self._minute_bar_data = {k: MarketData.from_json(v) for k, v in json_dict['minute_bar_data'].items()}
|
|
366
|
+
self._last_bar_data = {k: MarketData.from_json(v) for k, v in json_dict['last_bar_data'].items()}
|
|
367
|
+
return self
|
|
368
|
+
|
|
369
|
+
def from_shm(self, name: str = None) -> None:
|
|
370
|
+
if name is None:
|
|
371
|
+
name = f'{self.monitor_id}.json'
|
|
372
|
+
|
|
373
|
+
shm = shared_memory.SharedMemory(name=name)
|
|
374
|
+
json_dict = pickle.loads(bytes(shm.buf))
|
|
375
|
+
|
|
376
|
+
self.clear()
|
|
377
|
+
|
|
378
|
+
self._minute_bar_data.update({k: MarketData.from_json(v) for k, v in json_dict['minute_bar_data'].items()})
|
|
379
|
+
self._last_bar_data.update({k: MarketData.from_json(v) for k, v in json_dict['last_bar_data'].items()})
|
|
380
|
+
|
|
381
|
+
def clear(self) -> None:
|
|
382
|
+
self._minute_bar_data.clear()
|
|
383
|
+
self._last_bar_data.clear()
|
|
235
384
|
|
|
236
385
|
@property
|
|
237
386
|
def value(self) -> dict[str, BarData]:
|
|
@@ -469,8 +618,7 @@ class MarketDataService(object):
|
|
|
469
618
|
self._tick_data: dict[str, TickData] = {}
|
|
470
619
|
self._trade_data: dict[str, TradeData] = {}
|
|
471
620
|
self._monitor: dict[str, MarketDataMonitor] = {}
|
|
472
|
-
|
|
473
|
-
self.lock = threading.Lock()
|
|
621
|
+
self._monitor_manager = MonitorManager()
|
|
474
622
|
|
|
475
623
|
if self.synthetic_orderbook:
|
|
476
624
|
# init synthetic orderbook monitor
|
|
@@ -484,24 +632,30 @@ class MarketDataService(object):
|
|
|
484
632
|
self.on_market_data(market_data=kwargs['market_data'])
|
|
485
633
|
|
|
486
634
|
def __getitem__(self, monitor_id: str) -> MarketDataMonitor:
|
|
487
|
-
return self.
|
|
635
|
+
return self.monitor[monitor_id]
|
|
488
636
|
|
|
489
637
|
def add_monitor(self, monitor: MarketDataMonitor):
|
|
490
|
-
self.
|
|
638
|
+
self.monitor[monitor.monitor_id] = monitor
|
|
639
|
+
self.monitor_manager.add_monitor(monitor)
|
|
491
640
|
|
|
492
641
|
def pop_monitor(self, monitor: MarketDataMonitor = None, monitor_id: str = None, monitor_name: str = None):
|
|
493
642
|
if monitor_id is not None:
|
|
494
|
-
|
|
643
|
+
pass
|
|
495
644
|
elif monitor_name is not None:
|
|
496
|
-
for _ in list(self.
|
|
645
|
+
for _ in list(self.monitor.values()):
|
|
497
646
|
if _.name == monitor_name:
|
|
498
|
-
|
|
647
|
+
monitor_id = _.monitor_id
|
|
648
|
+
if monitor is None:
|
|
649
|
+
LOGGER.error(f'monitor_name {monitor_name} not registered.')
|
|
499
650
|
elif monitor is not None:
|
|
500
|
-
|
|
651
|
+
monitor_id = monitor.monitor_id
|
|
501
652
|
else:
|
|
502
653
|
LOGGER.error('must assign a monitor, or monitor_id, or monitor_name to pop.')
|
|
503
654
|
return None
|
|
504
655
|
|
|
656
|
+
self.monitor.pop(monitor_id)
|
|
657
|
+
self.monitor_manager.pop_monitor(monitor_id)
|
|
658
|
+
|
|
505
659
|
def init_cn_override(self):
|
|
506
660
|
self.profile = CN_Profile()
|
|
507
661
|
|
|
@@ -520,7 +674,7 @@ class MarketDataService(object):
|
|
|
520
674
|
LOGGER.info(f'MDS confirmed {ticker} TickData subscribed!')
|
|
521
675
|
|
|
522
676
|
self._tick_data[ticker] = tick_data
|
|
523
|
-
self._order_book[ticker] = tick_data.order_book
|
|
677
|
+
# self._order_book[ticker] = tick_data.order_book
|
|
524
678
|
|
|
525
679
|
def _on_order_book(self, order_book):
|
|
526
680
|
ticker = order_book.ticker
|
|
@@ -531,7 +685,6 @@ class MarketDataService(object):
|
|
|
531
685
|
self._order_book[ticker] = order_book
|
|
532
686
|
|
|
533
687
|
def on_market_data(self, market_data: MarketData):
|
|
534
|
-
self.lock.acquire()
|
|
535
688
|
ticker = market_data.ticker
|
|
536
689
|
market_time = market_data.market_time
|
|
537
690
|
timestamp = market_data.timestamp
|
|
@@ -551,18 +704,7 @@ class MarketDataService(object):
|
|
|
551
704
|
elif isinstance(market_data, OrderBook):
|
|
552
705
|
self._on_order_book(order_book=market_data)
|
|
553
706
|
|
|
554
|
-
|
|
555
|
-
monitor = self._monitor.get(monitor_id)
|
|
556
|
-
|
|
557
|
-
if monitor is None:
|
|
558
|
-
continue
|
|
559
|
-
|
|
560
|
-
if not monitor.enabled:
|
|
561
|
-
continue
|
|
562
|
-
|
|
563
|
-
monitor.__call__(market_data)
|
|
564
|
-
|
|
565
|
-
self.lock.release()
|
|
707
|
+
self.monitor_manager.__call__(market_data=market_data)
|
|
566
708
|
|
|
567
709
|
def get_order_book(self, ticker: str) -> OrderBook | None:
|
|
568
710
|
return self._order_book.get(ticker, None)
|
|
@@ -590,7 +732,7 @@ class MarketDataService(object):
|
|
|
590
732
|
else:
|
|
591
733
|
raise ValueError(f'Invalid side {side}')
|
|
592
734
|
|
|
593
|
-
queued_volume = book.
|
|
735
|
+
queued_volume = book.loc_volume(p0=prior, p1=posterior)
|
|
594
736
|
return queued_volume
|
|
595
737
|
|
|
596
738
|
def trade_time_between(self, start_time: datetime.datetime | float, end_time: datetime.datetime | float, **kwargs) -> datetime.timedelta:
|
|
@@ -600,28 +742,23 @@ class MarketDataService(object):
|
|
|
600
742
|
return self.profile.in_trade_session(market_time=market_time)
|
|
601
743
|
|
|
602
744
|
def clear(self):
|
|
603
|
-
self.lock.acquire()
|
|
604
745
|
# self._market_price.clear()
|
|
605
746
|
# self._market_time = None
|
|
606
747
|
# self._timestamp = None
|
|
607
748
|
|
|
608
749
|
self._market_history.clear()
|
|
609
750
|
self._order_book.clear()
|
|
610
|
-
self.
|
|
611
|
-
self.
|
|
751
|
+
self.monitor.clear()
|
|
752
|
+
self.monitor_manager.clear()
|
|
612
753
|
|
|
613
754
|
@property
|
|
614
755
|
def market_price(self) -> dict[str, float]:
|
|
615
|
-
self.lock.acquire()
|
|
616
756
|
result = self._market_price
|
|
617
|
-
self.lock.release()
|
|
618
757
|
return result
|
|
619
758
|
|
|
620
759
|
@property
|
|
621
760
|
def market_history(self) -> dict[str, dict[datetime.datetime, float]]:
|
|
622
|
-
self.lock.acquire()
|
|
623
761
|
result = self._market_history
|
|
624
|
-
self.lock.release()
|
|
625
762
|
return result
|
|
626
763
|
|
|
627
764
|
@property
|
|
@@ -663,6 +800,23 @@ class MarketDataService(object):
|
|
|
663
800
|
def session_break(self) -> tuple[datetime.time, datetime.time] | None:
|
|
664
801
|
return self.profile.session_break
|
|
665
802
|
|
|
803
|
+
@property
|
|
804
|
+
def monitor(self) -> dict[str, MarketDataMonitor]:
|
|
805
|
+
return self._monitor
|
|
806
|
+
|
|
807
|
+
@property
|
|
808
|
+
def monitor_manager(self) -> MonitorManager:
|
|
809
|
+
return self._monitor_manager
|
|
810
|
+
|
|
811
|
+
@monitor_manager.setter
|
|
812
|
+
def monitor_manager(self, manager: MonitorManager):
|
|
813
|
+
self._monitor_manager.clear()
|
|
814
|
+
|
|
815
|
+
self._monitor_manager = manager
|
|
816
|
+
|
|
817
|
+
for monitor in self.monitor.values():
|
|
818
|
+
self._monitor_manager.add_monitor(monitor=monitor)
|
|
819
|
+
|
|
666
820
|
|
|
667
821
|
class Replay(object, metaclass=abc.ABCMeta):
|
|
668
822
|
@abc.abstractmethod
|
|
@@ -779,15 +933,22 @@ class ProgressiveReplay(Replay):
|
|
|
779
933
|
tickers: list[str] = kwargs.pop('ticker', kwargs.pop('tickers', []))
|
|
780
934
|
dtypes: list[str | type] = kwargs.pop('dtype', kwargs.pop('dtypes', [TradeData, OrderBook, TickData]))
|
|
781
935
|
|
|
782
|
-
if
|
|
936
|
+
if not all([arg_name in inspect.getfullargspec(loader).args for arg_name in ['market_date', 'ticker', 'dtype']]):
|
|
937
|
+
raise TypeError('loader function has 3 requires args, market_date, ticker and dtype.')
|
|
938
|
+
|
|
939
|
+
if isinstance(tickers, str):
|
|
940
|
+
tickers = [tickers]
|
|
941
|
+
elif isinstance(tickers, Iterable):
|
|
783
942
|
tickers = list(tickers)
|
|
784
943
|
else:
|
|
785
|
-
tickers
|
|
944
|
+
raise TypeError(f'Invalid ticker {tickers}, expect str or list[str]')
|
|
786
945
|
|
|
787
|
-
if isinstance(dtypes,
|
|
946
|
+
if isinstance(dtypes, str) or inspect.isclass(dtypes):
|
|
947
|
+
dtypes = [dtypes]
|
|
948
|
+
elif isinstance(dtypes, Iterable):
|
|
788
949
|
dtypes = list(dtypes)
|
|
789
950
|
else:
|
|
790
|
-
dtypes
|
|
951
|
+
raise TypeError(f'Invalid dtype {dtypes}, expect str or list[str]')
|
|
791
952
|
|
|
792
953
|
for ticker in tickers:
|
|
793
954
|
for dtype in dtypes:
|
|
@@ -795,7 +956,7 @@ class ProgressiveReplay(Replay):
|
|
|
795
956
|
|
|
796
957
|
subscription = kwargs.pop('subscription', kwargs.pop('subscribe', []))
|
|
797
958
|
|
|
798
|
-
if
|
|
959
|
+
if isinstance(subscription, dict):
|
|
799
960
|
subscription = [subscription]
|
|
800
961
|
|
|
801
962
|
for sub in subscription:
|
|
@@ -806,8 +967,10 @@ class ProgressiveReplay(Replay):
|
|
|
806
967
|
def add_subscription(self, ticker: str, dtype: type | str):
|
|
807
968
|
if isinstance(dtype, str):
|
|
808
969
|
pass
|
|
809
|
-
|
|
970
|
+
elif inspect.isclass(dtype):
|
|
810
971
|
dtype = dtype.__name__
|
|
972
|
+
else:
|
|
973
|
+
raise ValueError(f'Invalid dtype {dtype}, expect str or class.')
|
|
811
974
|
|
|
812
975
|
topic = f'{ticker}.{dtype}'
|
|
813
976
|
self.replay_subscription[topic] = (ticker, dtype)
|
|
@@ -2000,7 +2000,7 @@ class SimMatch(object):
|
|
|
2000
2000
|
self.market_time = datetime.datetime.min
|
|
2001
2001
|
|
|
2002
2002
|
def __call__(self, **kwargs):
|
|
2003
|
-
order = kwargs.pop('order', None)
|
|
2003
|
+
order: TradeInstruction = kwargs.pop('order', None)
|
|
2004
2004
|
market_data = kwargs.pop('market_data', None)
|
|
2005
2005
|
|
|
2006
2006
|
if order is not None:
|
|
@@ -2084,7 +2084,7 @@ class SimMatch(object):
|
|
|
2084
2084
|
if order.side.sign > 0:
|
|
2085
2085
|
# match order based on worst offer
|
|
2086
2086
|
if order.limit_price is None:
|
|
2087
|
-
self._match(order=order, match_price=market_data.
|
|
2087
|
+
self._match(order=order, match_price=market_data.vwap)
|
|
2088
2088
|
elif market_data.high_price < order.limit_price:
|
|
2089
2089
|
self._match(order=order, match_price=market_data.high_price)
|
|
2090
2090
|
# match order based on limit price
|
|
@@ -2096,7 +2096,7 @@ class SimMatch(object):
|
|
|
2096
2096
|
elif order.side.sign < 0:
|
|
2097
2097
|
# match order based on worst offer
|
|
2098
2098
|
if order.limit_price is None:
|
|
2099
|
-
self._match(order=order, match_price=market_data.
|
|
2099
|
+
self._match(order=order, match_price=market_data.vwap)
|
|
2100
2100
|
elif market_data.low_price > order.limit_price:
|
|
2101
2101
|
self._match(order=order, match_price=market_data.low_price)
|
|
2102
2102
|
# match order based on limit price
|
|
@@ -92,10 +92,10 @@ _ = get_logger()
|
|
|
92
92
|
|
|
93
93
|
from .EventEngine import EVENT_ENGINE, TOPIC
|
|
94
94
|
from .AlgoEngine import AlgoTemplate, ALGO_ENGINE, ALGO_REGISTRY
|
|
95
|
-
from .MarketEngine import MDS, MarketDataService, MarketDataMonitor, SyntheticOrderBookMonitor, MinuteBarMonitor, ProgressiveReplay, SimpleReplay, Replay
|
|
95
|
+
from .MarketEngine import MDS, MarketDataService, MarketDataMonitor, MonitorManager, SyntheticOrderBookMonitor, MinuteBarMonitor, ProgressiveReplay, SimpleReplay, Replay
|
|
96
96
|
from .TradeEngine import DirectMarketAccess, Balance, PositionManagementService, Inventory, RiskProfile, SimMatch
|
|
97
97
|
|
|
98
98
|
__all__ = ['set_logger', 'LOGGER', 'EVENT_ENGINE', 'TOPIC',
|
|
99
99
|
'AlgoTemplate', 'ALGO_ENGINE', 'ALGO_REGISTRY',
|
|
100
|
-
'MDS', 'MarketDataService', 'MarketDataMonitor', 'SyntheticOrderBookMonitor', 'MinuteBarMonitor', 'ProgressiveReplay', 'SimpleReplay', 'Replay',
|
|
100
|
+
'MDS', 'MarketDataService', 'MarketDataMonitor', 'MonitorManager', 'SyntheticOrderBookMonitor', 'MinuteBarMonitor', 'ProgressiveReplay', 'SimpleReplay', 'Replay',
|
|
101
101
|
'DirectMarketAccess', 'Balance', 'PositionManagementService', 'Inventory', 'RiskProfile', 'SimMatch']
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{PyAlgoEngine-0.3.11.post1 → PyAlgoEngine-0.3.12.post3}/AlgoEngine/Strategies/_StrategyEngine.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{PyAlgoEngine-0.3.11.post1 → PyAlgoEngine-0.3.12.post3}/PyAlgoEngine.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|