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,15 @@
|
|
|
1
|
+
from ..engine import MDS, MarketDataMonitor as Monitor
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
def add_synthetic_orderbook():
|
|
5
|
+
# init synthetic orderbook monitor
|
|
6
|
+
monitor = SyntheticOrderBookMonitor(mds=MDS)
|
|
7
|
+
MDS.add_monitor(monitor=monitor)
|
|
8
|
+
# override current orderbook
|
|
9
|
+
MDS.bid = monitor.bid
|
|
10
|
+
MDS.ask = monitor.ask
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
from .advanced_data_interface import *
|
|
14
|
+
|
|
15
|
+
__all__ = ['Monitor', 'SyntheticOrderBookMonitor', 'MinuteBarMonitor']
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
import datetime
|
|
2
|
+
import json
|
|
3
|
+
import pickle
|
|
4
|
+
from ctypes import Structure, c_double
|
|
5
|
+
from multiprocessing import shared_memory
|
|
6
|
+
from typing import Self
|
|
7
|
+
|
|
8
|
+
from . import Monitor
|
|
9
|
+
from ..base import TradeData, MarketData, OrderData, TransactionData, TransactionSide, TickData, TickDataLite, BarData
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Entry(Structure):
|
|
13
|
+
_fields_ = [
|
|
14
|
+
("price", c_double), # Double precision floating point for price
|
|
15
|
+
("volume", c_double), # Double precision floating point for volume
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
def __repr__(self):
|
|
19
|
+
return f"Entry(price={self.price}, volume={self.volume}, n_orders={self.n_orders})"
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class PyOrderBook(object):
|
|
23
|
+
def __init__(self, side: TransactionSide, data: dict[float, Entry] = None):
|
|
24
|
+
self.side = TransactionSide(side)
|
|
25
|
+
self.data: dict[float, Entry] = {}
|
|
26
|
+
self.timestamp: float = 0
|
|
27
|
+
|
|
28
|
+
if data:
|
|
29
|
+
self.data.update(data)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class SyntheticOrderBookMonitor(Monitor):
|
|
33
|
+
|
|
34
|
+
def __init__(self, **kwargs):
|
|
35
|
+
|
|
36
|
+
super().__init__(
|
|
37
|
+
name=kwargs.pop('name', 'Monitor.SyntheticOrderBook'),
|
|
38
|
+
monitor_id=kwargs.pop('monitor_id', None)
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
self.bid: dict[str, PyOrderBook] = {}
|
|
42
|
+
self.ask: dict[str, PyOrderBook] = {}
|
|
43
|
+
self.tick_data: dict[str, TickData | TickDataLite] = {}
|
|
44
|
+
|
|
45
|
+
def __call__(self, market_data: MarketData, **kwargs):
|
|
46
|
+
if isinstance(market_data, (TickData, TickDataLite)):
|
|
47
|
+
self._on_tick_data(market_data)
|
|
48
|
+
if isinstance(market_data, (TransactionData, TradeData)):
|
|
49
|
+
self._on_trade_data(market_data)
|
|
50
|
+
|
|
51
|
+
def _get_order_book(self, ticker: str) -> tuple[PyOrderBook, PyOrderBook]:
|
|
52
|
+
if ticker in self.bid:
|
|
53
|
+
bid = self.bid[ticker]
|
|
54
|
+
else:
|
|
55
|
+
bid = self.bid[ticker] = PyOrderBook(side=TransactionSide.SIDE_BID)
|
|
56
|
+
|
|
57
|
+
if ticker in self.ask:
|
|
58
|
+
ask = self.ask[ticker]
|
|
59
|
+
else:
|
|
60
|
+
ask = self.ask[ticker] = PyOrderBook(side=TransactionSide.SIDE_ASK)
|
|
61
|
+
|
|
62
|
+
return bid, ask
|
|
63
|
+
|
|
64
|
+
def _on_tick_data(self, tick_data: TickData):
|
|
65
|
+
if isinstance(tick_data, TickDataLite):
|
|
66
|
+
self.tick_data[tick_data.ticker] = tick_data
|
|
67
|
+
else:
|
|
68
|
+
self.tick_data[tick_data.ticker] = tick_data.lite
|
|
69
|
+
|
|
70
|
+
def _on_trade_data(self, trade_data: TransactionData | TradeData):
|
|
71
|
+
ticker = trade_data.ticker
|
|
72
|
+
bid, ask = self._get_order_book(ticker)
|
|
73
|
+
price = trade_data.price
|
|
74
|
+
volume = trade_data.volume
|
|
75
|
+
sign = trade_data.side.sign
|
|
76
|
+
|
|
77
|
+
if sign == 1:
|
|
78
|
+
entry = ask.data.get(price)
|
|
79
|
+
|
|
80
|
+
if entry is None:
|
|
81
|
+
return
|
|
82
|
+
|
|
83
|
+
entry.volume -= volume
|
|
84
|
+
if entry.volume <= 0:
|
|
85
|
+
ask.data.pop(price, None)
|
|
86
|
+
elif sign == -1:
|
|
87
|
+
entry = bid.data.get(price)
|
|
88
|
+
|
|
89
|
+
if entry is None:
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
entry.volume -= volume
|
|
93
|
+
if entry.volume <= 0:
|
|
94
|
+
bid.data.pop(price, None)
|
|
95
|
+
|
|
96
|
+
def on_order_data(self, order_data: OrderData):
|
|
97
|
+
ticker = order_data.ticker
|
|
98
|
+
bid, ask = self._get_order_book(ticker)
|
|
99
|
+
price = order_data.price
|
|
100
|
+
volume = order_data.volume
|
|
101
|
+
sign = order_data.side.sign
|
|
102
|
+
|
|
103
|
+
if sign == 1:
|
|
104
|
+
entry = ask.data.get(price)
|
|
105
|
+
|
|
106
|
+
if entry is None:
|
|
107
|
+
ask.data[price] = Entry(price=price, volume=volume)
|
|
108
|
+
else:
|
|
109
|
+
entry.volume += volume
|
|
110
|
+
elif sign == -1:
|
|
111
|
+
entry = bid.data.get(price)
|
|
112
|
+
|
|
113
|
+
if entry is None:
|
|
114
|
+
bid.data[price] = Entry(price=price, volume=volume)
|
|
115
|
+
else:
|
|
116
|
+
entry.volume += volume
|
|
117
|
+
|
|
118
|
+
def to_json(self, fmt='str', **kwargs) -> str | dict:
|
|
119
|
+
data_dict = dict(
|
|
120
|
+
name=self.name,
|
|
121
|
+
monitor_id=self.monitor_id,
|
|
122
|
+
bid={ticker: {price: order_book.data[price].volume for price in sorted(order_book.data)} for ticker, order_book in self.bid.items()},
|
|
123
|
+
ask={ticker: {price: order_book.data[price].volume for price in sorted(order_book.data)} for ticker, order_book in self.ask.items()},
|
|
124
|
+
)
|
|
125
|
+
|
|
126
|
+
if fmt == 'dict':
|
|
127
|
+
return data_dict
|
|
128
|
+
elif fmt == 'str':
|
|
129
|
+
return json.dumps(data_dict, **kwargs)
|
|
130
|
+
else:
|
|
131
|
+
raise ValueError(f'Invalid format {fmt}, except "dict" or "str".')
|
|
132
|
+
|
|
133
|
+
@classmethod
|
|
134
|
+
def from_json(cls, json_message: str | bytes | bytearray | dict) -> Self:
|
|
135
|
+
if isinstance(json_message, dict):
|
|
136
|
+
json_dict = json_message
|
|
137
|
+
else:
|
|
138
|
+
json_dict = json.loads(json_message)
|
|
139
|
+
|
|
140
|
+
self = cls(
|
|
141
|
+
name=json_dict['name'],
|
|
142
|
+
monitor_id=json_dict['monitor_id'],
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
self.bid = {ticker: (book := PyOrderBook(side=TransactionSide.SIDE_BID, data={price: Entry(price=price, volume=volume) for price, volume in order_book.items()})) for ticker, order_book in json_dict['bid'].items()}
|
|
146
|
+
self.bid = {ticker: (book := PyOrderBook(side=TransactionSide.SIDE_ASK, data={price: Entry(price=price, volume=volume) for price, volume in order_book.items()})) for ticker, order_book in json_dict['ask'].items()}
|
|
147
|
+
return self
|
|
148
|
+
|
|
149
|
+
def clear(self) -> None:
|
|
150
|
+
self.bid.clear()
|
|
151
|
+
self.ask.clear()
|
|
152
|
+
self.tick_data.clear()
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def value(self) -> dict[str, TickData]:
|
|
156
|
+
order_book = {}
|
|
157
|
+
for ticker, tick_lite in self.tick_data.items():
|
|
158
|
+
bid, ask = self._get_order_book(ticker)
|
|
159
|
+
|
|
160
|
+
bid_price, bid_volume, ask_price, ask_volume = {}, {}, {}, {}
|
|
161
|
+
|
|
162
|
+
for i, price in enumerate(sorted(bid.data, reverse=True), start=1):
|
|
163
|
+
bid_price[f'bid_price_{i}'] = price
|
|
164
|
+
bid_price[f'bid_volume_{i}'] = bid.data[price].volume
|
|
165
|
+
|
|
166
|
+
for i, price in enumerate(sorted(ask.data), start=1):
|
|
167
|
+
bid_price[f'ask_price_{i}'] = price
|
|
168
|
+
bid_price[f'ask_volume_{i}'] = ask.data[price].volume
|
|
169
|
+
|
|
170
|
+
data = TickData(
|
|
171
|
+
ticker=tick_lite.ticker,
|
|
172
|
+
timestamp=tick_lite.timestamp,
|
|
173
|
+
last_price=tick_lite.last_price,
|
|
174
|
+
total_traded_volume=tick_lite.total_traded_volume,
|
|
175
|
+
total_traded_notional=tick_lite.total_traded_notional,
|
|
176
|
+
total_trade_count=tick_lite.total_trade_count,
|
|
177
|
+
**bid_price, **bid_volume, **ask_price, **ask_volume
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
order_book[ticker] = data
|
|
181
|
+
|
|
182
|
+
return order_book
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
class MinuteBarMonitor(Monitor):
|
|
186
|
+
|
|
187
|
+
def __init__(self, interval: float = 60., **kwargs):
|
|
188
|
+
self.interval = interval
|
|
189
|
+
|
|
190
|
+
super().__init__(
|
|
191
|
+
name=kwargs.pop('name', 'Monitor.MinuteBarMonitor'),
|
|
192
|
+
monitor_id=kwargs.pop('monitor_id', None)
|
|
193
|
+
)
|
|
194
|
+
|
|
195
|
+
self._minute_bar_data: dict[str, BarData] = {}
|
|
196
|
+
self._last_bar_data: dict[str, BarData] = {}
|
|
197
|
+
|
|
198
|
+
def __call__(self, market_data: MarketData, **kwargs):
|
|
199
|
+
self._update_last_bar(market_data=market_data, interval=self.interval)
|
|
200
|
+
# self._update_active_bar(market_data=market_data, interval=self.interval)
|
|
201
|
+
|
|
202
|
+
def _update_last_bar(self, market_data: MarketData, interval: float):
|
|
203
|
+
ticker = market_data.ticker
|
|
204
|
+
market_price = market_data.market_price
|
|
205
|
+
market_time = market_data.market_time
|
|
206
|
+
timestamp = market_data.timestamp
|
|
207
|
+
|
|
208
|
+
if ticker not in self._minute_bar_data or market_time >= self._minute_bar_data[ticker].bar_end_time:
|
|
209
|
+
# update bar_data
|
|
210
|
+
if ticker in self._minute_bar_data:
|
|
211
|
+
self._last_bar_data[ticker] = self._minute_bar_data[ticker]
|
|
212
|
+
|
|
213
|
+
bar_data = self._minute_bar_data[ticker] = BarData(
|
|
214
|
+
ticker=ticker,
|
|
215
|
+
timestamp=int(timestamp // interval + 1) * interval,
|
|
216
|
+
start_timestamp=int(timestamp // interval) * interval,
|
|
217
|
+
bar_span=datetime.timedelta(seconds=interval),
|
|
218
|
+
high_price=market_price,
|
|
219
|
+
low_price=market_price,
|
|
220
|
+
open_price=market_price,
|
|
221
|
+
close_price=market_price,
|
|
222
|
+
volume=0.,
|
|
223
|
+
notional=0.,
|
|
224
|
+
trade_count=0
|
|
225
|
+
)
|
|
226
|
+
else:
|
|
227
|
+
bar_data = self._minute_bar_data[ticker]
|
|
228
|
+
|
|
229
|
+
if isinstance(market_data, TradeData):
|
|
230
|
+
bar_data['volume'] += market_data.volume
|
|
231
|
+
bar_data['notional'] += market_data.notional
|
|
232
|
+
bar_data['trade_count'] += 1
|
|
233
|
+
|
|
234
|
+
bar_data['close_price'] = market_price
|
|
235
|
+
bar_data['high_price'] = max(bar_data.high_price, market_price)
|
|
236
|
+
bar_data['low_price'] = min(bar_data.low_price, market_price)
|
|
237
|
+
|
|
238
|
+
def _update_active_bar(self, market_data: MarketData, interval: float):
|
|
239
|
+
ticker = market_data.ticker
|
|
240
|
+
market_price = market_data.market_price
|
|
241
|
+
market_time = market_data.market_time
|
|
242
|
+
timestamp = market_data.timestamp
|
|
243
|
+
|
|
244
|
+
if ticker not in self._minute_bar_data or market_time >= self._minute_bar_data[ticker].bar_end_time:
|
|
245
|
+
bar_data = self._minute_bar_data[ticker] = BarData(
|
|
246
|
+
ticker=ticker,
|
|
247
|
+
start_timestamp=timestamp - interval,
|
|
248
|
+
timestamp=timestamp,
|
|
249
|
+
bar_span=datetime.timedelta(seconds=interval),
|
|
250
|
+
high_price=market_price,
|
|
251
|
+
low_price=market_price,
|
|
252
|
+
open_price=market_price,
|
|
253
|
+
close_price=market_price,
|
|
254
|
+
volume=0.,
|
|
255
|
+
notional=0.,
|
|
256
|
+
trade_count=0
|
|
257
|
+
)
|
|
258
|
+
bar_data.history = []
|
|
259
|
+
|
|
260
|
+
else:
|
|
261
|
+
bar_data = self._minute_bar_data[ticker]
|
|
262
|
+
|
|
263
|
+
history: list[TradeData] = getattr(bar_data, 'history')
|
|
264
|
+
bar_data['start_timestamp'] = timestamp - interval
|
|
265
|
+
|
|
266
|
+
if isinstance(market_data, TradeData):
|
|
267
|
+
history.append(market_data)
|
|
268
|
+
|
|
269
|
+
while True:
|
|
270
|
+
if history[0].market_time >= bar_data.bar_start_time:
|
|
271
|
+
break
|
|
272
|
+
else:
|
|
273
|
+
history.pop(0)
|
|
274
|
+
|
|
275
|
+
bar_data['volume'] = sum([_.volume for _ in history])
|
|
276
|
+
bar_data['notional'] = sum([_.notional for _ in history])
|
|
277
|
+
bar_data['trade_count'] = len([_.notional for _ in history])
|
|
278
|
+
bar_data['close_price'] = market_price
|
|
279
|
+
bar_data['open_price'] = history[0].market_price
|
|
280
|
+
bar_data['high_price'] = max([_.market_price for _ in history])
|
|
281
|
+
bar_data['low_price'] = min([_.market_price for _ in history])
|
|
282
|
+
|
|
283
|
+
def to_json(self, fmt='str', **kwargs) -> str | dict:
|
|
284
|
+
data_dict = dict(
|
|
285
|
+
name=self.name,
|
|
286
|
+
monitor_id=self.monitor_id,
|
|
287
|
+
interval=self.interval,
|
|
288
|
+
minute_bar_data={k: v.to_bytes() for k, v in self._minute_bar_data.items()},
|
|
289
|
+
last_bar_data={k: v.to_bytes() for k, v in self._last_bar_data.items()},
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
if fmt == 'dict':
|
|
293
|
+
return data_dict
|
|
294
|
+
elif fmt == 'str':
|
|
295
|
+
return json.dumps(data_dict, **kwargs)
|
|
296
|
+
else:
|
|
297
|
+
raise ValueError(f'Invalid format {fmt}, except "dict" or "str".')
|
|
298
|
+
|
|
299
|
+
@classmethod
|
|
300
|
+
def from_json(cls, json_message: str | bytes | bytearray | dict) -> Self:
|
|
301
|
+
if isinstance(json_message, dict):
|
|
302
|
+
json_dict = json_message
|
|
303
|
+
else:
|
|
304
|
+
json_dict = json.loads(json_message)
|
|
305
|
+
|
|
306
|
+
self = cls(
|
|
307
|
+
name=json_dict['name'],
|
|
308
|
+
monitor_id=json_dict['monitor_id'],
|
|
309
|
+
interval=json_dict['interval'],
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
self._minute_bar_data = {k: BarData.from_bytes(v) for k, v in json_dict['minute_bar_data'].items()}
|
|
313
|
+
self._last_bar_data = {k: BarData.from_bytes(v) for k, v in json_dict['last_bar_data'].items()}
|
|
314
|
+
return self
|
|
315
|
+
|
|
316
|
+
def from_shm(self, name: str = None) -> None:
|
|
317
|
+
if name is None:
|
|
318
|
+
name = f'{self.monitor_id}.json'
|
|
319
|
+
|
|
320
|
+
shm = shared_memory.SharedMemory(name=name)
|
|
321
|
+
json_dict = pickle.loads(bytes(shm.buf))
|
|
322
|
+
|
|
323
|
+
self.clear()
|
|
324
|
+
|
|
325
|
+
self._minute_bar_data.update({k: BarData.from_bytes(v) for k, v in json_dict['minute_bar_data'].items()})
|
|
326
|
+
self._last_bar_data.update({k: BarData.from_bytes(v) for k, v in json_dict['last_bar_data'].items()})
|
|
327
|
+
|
|
328
|
+
def clear(self) -> None:
|
|
329
|
+
self._minute_bar_data.clear()
|
|
330
|
+
self._last_bar_data.clear()
|
|
331
|
+
|
|
332
|
+
@property
|
|
333
|
+
def value(self) -> dict[str, BarData]:
|
|
334
|
+
return self._last_bar_data
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import logging
|
|
2
|
+
from threading import Lock
|
|
3
|
+
|
|
4
|
+
from .. import LOGGER
|
|
5
|
+
from ..base import TradeInstruction
|
|
6
|
+
from ..engine import EVENT_ENGINE, TOPIC, MDS, MarketDataService, Balance, Inventory, DirectMarketAccess, RiskProfile, PositionManagementService
|
|
7
|
+
|
|
8
|
+
LOGGER = LOGGER.getChild('Strategy')
|
|
9
|
+
|
|
10
|
+
from .strategy_engine import StrategyEngine
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class EventDMA(DirectMarketAccess):
|
|
14
|
+
def __init__(self, mds: MarketDataService, risk_profile: RiskProfile, event_engine=None, cool_down: float = None):
|
|
15
|
+
self.event_engine = EVENT_ENGINE if event_engine is None else event_engine
|
|
16
|
+
super().__init__(mds=mds, risk_profile=risk_profile, cool_down=cool_down)
|
|
17
|
+
|
|
18
|
+
def _launch_order_handler(self, order: TradeInstruction, **kwargs):
|
|
19
|
+
self.event_engine.put(topic=TOPIC.launch_order(ticker=order.ticker), order=order, **kwargs)
|
|
20
|
+
|
|
21
|
+
def _cancel_order_handler(self, order: TradeInstruction, **kwargs):
|
|
22
|
+
self.event_engine.put(topic=TOPIC.cancel_order(ticker=order.ticker), order_id=order.order_id, **kwargs)
|
|
23
|
+
|
|
24
|
+
def _reject_order_handler(self, order: TradeInstruction, **kwargs):
|
|
25
|
+
raise NotImplementedError()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def set_logger(logger: logging.Logger):
|
|
29
|
+
global LOGGER
|
|
30
|
+
LOGGER = logger
|
|
31
|
+
|
|
32
|
+
strategy_engine.LOGGER = logger.getChild('Strategy')
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
REPLAY_LOCK = Lock()
|
|
36
|
+
INVENTORY = Inventory()
|
|
37
|
+
BALANCE = Balance(inventory=INVENTORY) # need to be registered
|
|
38
|
+
RISK_PROFILE = RiskProfile(mds=MDS, balance=BALANCE)
|
|
39
|
+
DMA = EventDMA(mds=MDS, risk_profile=RISK_PROFILE)
|
|
40
|
+
POSITION_TRACKER = PositionManagementService(dma=DMA)
|
|
41
|
+
STRATEGY_ENGINE = StrategyEngine(event_engine=EVENT_ENGINE, position_tracker=POSITION_TRACKER) # need to be registered, also register MDS
|
|
42
|
+
BALANCE.add(strategy=STRATEGY_ENGINE, position_tracker=POSITION_TRACKER)
|
|
43
|
+
|
|
44
|
+
__all__ = ['INVENTORY', 'BALANCE', 'RISK_PROFILE', 'DMA', 'POSITION_TRACKER', 'STRATEGY_ENGINE']
|