PyAlgoEngine 0.4.2__tar.gz → 0.4.3__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.4.2 → PyAlgoEngine-0.4.3}/LICENSE +0 -0
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/PKG-INFO +1 -1
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/PyAlgoEngine.egg-info/PKG-INFO +1 -1
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/PyAlgoEngine.egg-info/SOURCES.txt +0 -0
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/PyAlgoEngine.egg-info/dependency_links.txt +0 -0
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/PyAlgoEngine.egg-info/requires.txt +0 -0
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/PyAlgoEngine.egg-info/top_level.txt +0 -0
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/README.md +0 -0
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/algo_engine/__init__.py +1 -1
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/algo_engine/back_test/__init__.py +19 -19
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/algo_engine/back_test/__main__.py +0 -0
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/algo_engine/back_test/replay.py +258 -258
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/algo_engine/back_test/sim_match.py +295 -295
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/algo_engine/base/__init__.py +28 -28
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/algo_engine/base/console_utils.py +0 -0
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/algo_engine/base/finance_decimal.py +0 -0
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/algo_engine/base/market_utils.py +0 -0
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/algo_engine/base/technical_analysis.py +0 -0
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/algo_engine/base/telemetrics.py +78 -78
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/algo_engine/base/trade_utils.py +0 -0
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/algo_engine/engine/__init__.py +0 -0
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/algo_engine/engine/algo_engine.py +0 -0
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/algo_engine/engine/event_engine.py +0 -0
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/algo_engine/engine/market_engine.py +0 -0
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/algo_engine/engine/trade_engine.py +0 -0
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/algo_engine/monitor/__init__.py +15 -15
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/algo_engine/monitor/advanced_data_interface.py +239 -239
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/algo_engine/profile/__init__.py +72 -72
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/algo_engine/profile/cn.py +187 -187
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/algo_engine/strategy/__init__.py +0 -0
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/algo_engine/strategy/strategy_engine.py +0 -0
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/setup.cfg +0 -0
- {PyAlgoEngine-0.4.2 → PyAlgoEngine-0.4.3}/setup.py +0 -0
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
|
|
3
|
-
from .. import LOGGER
|
|
4
|
-
|
|
5
|
-
LOGGER = LOGGER.getChild('BackTest')
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def set_logger(logger: logging.Logger):
|
|
9
|
-
global LOGGER
|
|
10
|
-
LOGGER = logger
|
|
11
|
-
|
|
12
|
-
replay.LOGGER = LOGGER.getChild('Replay')
|
|
13
|
-
sim_match.LOGGER = LOGGER.getChild('SimMatch')
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
from .replay import Replay, SimpleReplay, ProgressiveReplay
|
|
17
|
-
from .sim_match import SimMatch
|
|
18
|
-
|
|
19
|
-
__all__ = ['Replay', 'SimpleReplay', 'ProgressiveReplay', 'SimMatch']
|
|
1
|
+
import logging
|
|
2
|
+
|
|
3
|
+
from .. import LOGGER
|
|
4
|
+
|
|
5
|
+
LOGGER = LOGGER.getChild('BackTest')
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def set_logger(logger: logging.Logger):
|
|
9
|
+
global LOGGER
|
|
10
|
+
LOGGER = logger
|
|
11
|
+
|
|
12
|
+
replay.LOGGER = LOGGER.getChild('Replay')
|
|
13
|
+
sim_match.LOGGER = LOGGER.getChild('SimMatch')
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
from .replay import Replay, SimpleReplay, ProgressiveReplay
|
|
17
|
+
from .sim_match import SimMatch
|
|
18
|
+
|
|
19
|
+
__all__ = ['Replay', 'SimpleReplay', 'ProgressiveReplay', 'SimMatch']
|
|
File without changes
|
|
@@ -1,258 +1,258 @@
|
|
|
1
|
-
import abc
|
|
2
|
-
import datetime
|
|
3
|
-
import inspect
|
|
4
|
-
from typing import Iterable
|
|
5
|
-
|
|
6
|
-
from . import LOGGER
|
|
7
|
-
from ..base import Progress, TickData, TransactionData, TradeData, OrderBook
|
|
8
|
-
|
|
9
|
-
LOGGER = LOGGER.getChild('Replay')
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class Replay(object, metaclass=abc.ABCMeta):
|
|
13
|
-
@abc.abstractmethod
|
|
14
|
-
def __next__(self): ...
|
|
15
|
-
|
|
16
|
-
@abc.abstractmethod
|
|
17
|
-
def __iter__(self): ...
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
class SimpleReplay(Replay):
|
|
21
|
-
def __init__(self, **kwargs):
|
|
22
|
-
self.eod = kwargs.pop('eod', None)
|
|
23
|
-
self.bod = kwargs.pop('bod', None)
|
|
24
|
-
|
|
25
|
-
self.replay_task = []
|
|
26
|
-
self.task_progress = 0
|
|
27
|
-
self.task_date = None
|
|
28
|
-
self.progress = Progress(tasks=1, **kwargs)
|
|
29
|
-
|
|
30
|
-
def load(self, data):
|
|
31
|
-
if isinstance(data, dict):
|
|
32
|
-
self.replay_task.extend(list(data.values()))
|
|
33
|
-
else:
|
|
34
|
-
self.replay_task.extend(data)
|
|
35
|
-
|
|
36
|
-
def reset(self):
|
|
37
|
-
self.replay_task.clear()
|
|
38
|
-
self.task_progress = 0
|
|
39
|
-
self.task_date = None
|
|
40
|
-
self.progress.reset()
|
|
41
|
-
|
|
42
|
-
def next_task(self):
|
|
43
|
-
if self.task_progress < len(self.replay_task):
|
|
44
|
-
market_data = self.replay_task[self.task_progress]
|
|
45
|
-
market_time = market_data.market_time
|
|
46
|
-
|
|
47
|
-
if isinstance(market_time, datetime.datetime):
|
|
48
|
-
market_date = market_time.date()
|
|
49
|
-
else:
|
|
50
|
-
market_date = market_time
|
|
51
|
-
|
|
52
|
-
if market_date != self.task_date:
|
|
53
|
-
if callable(self.eod) and self.task_date:
|
|
54
|
-
self.eod(self.task_date)
|
|
55
|
-
|
|
56
|
-
self.task_date = market_date
|
|
57
|
-
self.progress.prompt = f'Replay {market_date:%Y-%m-%d}:'
|
|
58
|
-
|
|
59
|
-
if callable(self.bod):
|
|
60
|
-
self.bod(market_date)
|
|
61
|
-
|
|
62
|
-
self.progress.done_tasks = self.task_progress / len(self.replay_task)
|
|
63
|
-
|
|
64
|
-
if (not self.progress.tick_size) or self.progress.progress >= self.progress.tick_size + self.progress.last_output:
|
|
65
|
-
self.progress.output()
|
|
66
|
-
|
|
67
|
-
self.task_progress += 1
|
|
68
|
-
else:
|
|
69
|
-
raise StopIteration()
|
|
70
|
-
|
|
71
|
-
return market_data
|
|
72
|
-
|
|
73
|
-
def __next__(self):
|
|
74
|
-
try:
|
|
75
|
-
return self.next_task()
|
|
76
|
-
except StopIteration:
|
|
77
|
-
if not self.progress.is_done:
|
|
78
|
-
self.progress.done_tasks = 1
|
|
79
|
-
self.progress.output()
|
|
80
|
-
|
|
81
|
-
self.reset()
|
|
82
|
-
raise StopIteration()
|
|
83
|
-
|
|
84
|
-
def __iter__(self):
|
|
85
|
-
return self
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
class ProgressiveReplay(Replay):
|
|
89
|
-
"""
|
|
90
|
-
progressively loading and replaying market data
|
|
91
|
-
|
|
92
|
-
requires arguments
|
|
93
|
-
loader: a data loading function. Expect loader = Callable(market_date: datetime.date, ticker: str, dtype: str| type) -> dict[any, MarketData]
|
|
94
|
-
start_date & end_date: the given replay period
|
|
95
|
-
or calendar: the given replay calendar.
|
|
96
|
-
|
|
97
|
-
accepts kwargs:
|
|
98
|
-
ticker / tickers: the given symbols to replay, expect a str| list[str]
|
|
99
|
-
dtype / dtypes: the given dtype(s) of symbol to replay, expect a str | type, list[str | type]. default = all, which is (TradeData, TickData, OrderBook)
|
|
100
|
-
subscription / subscribe: the given ticker-dtype pair to replay, expect a list[dict[str, str | type]]
|
|
101
|
-
"""
|
|
102
|
-
|
|
103
|
-
def __init__(
|
|
104
|
-
self,
|
|
105
|
-
loader,
|
|
106
|
-
**kwargs
|
|
107
|
-
):
|
|
108
|
-
self.loader = loader
|
|
109
|
-
self.start_date: datetime.date | None = kwargs.pop('start_date', None)
|
|
110
|
-
self.end_date: datetime.date | None = kwargs.pop('end_date', None)
|
|
111
|
-
self.calendar: list[datetime.date] | None = kwargs.pop('calendar', None)
|
|
112
|
-
|
|
113
|
-
self.eod = kwargs.pop('eod', None)
|
|
114
|
-
self.bod = kwargs.pop('bod', None)
|
|
115
|
-
|
|
116
|
-
self.replay_subscription = {}
|
|
117
|
-
self.replay_calendar = []
|
|
118
|
-
self.replay_task = []
|
|
119
|
-
|
|
120
|
-
self.date_progress = 0
|
|
121
|
-
self.task_progress = 0
|
|
122
|
-
self.progress = Progress(tasks=1, **kwargs)
|
|
123
|
-
|
|
124
|
-
tickers: list[str] = kwargs.pop('ticker', kwargs.pop('tickers', []))
|
|
125
|
-
dtypes: list[str | type] = kwargs.pop('dtype', kwargs.pop('dtypes', [TradeData, TransactionData, OrderBook, TickData]))
|
|
126
|
-
|
|
127
|
-
if not all([arg_name in inspect.getfullargspec(loader).args for arg_name in ['market_date', 'ticker', 'dtype']]):
|
|
128
|
-
raise TypeError('loader function has 3 requires args, market_date, ticker and dtype.')
|
|
129
|
-
|
|
130
|
-
if isinstance(tickers, str):
|
|
131
|
-
tickers = [tickers]
|
|
132
|
-
elif isinstance(tickers, Iterable):
|
|
133
|
-
tickers = list(tickers)
|
|
134
|
-
else:
|
|
135
|
-
raise TypeError(f'Invalid ticker {tickers}, expect str or list[str]')
|
|
136
|
-
|
|
137
|
-
if isinstance(dtypes, str) or inspect.isclass(dtypes):
|
|
138
|
-
dtypes = [dtypes]
|
|
139
|
-
elif isinstance(dtypes, Iterable):
|
|
140
|
-
dtypes = list(dtypes)
|
|
141
|
-
else:
|
|
142
|
-
raise TypeError(f'Invalid dtype {dtypes}, expect str or list[str]')
|
|
143
|
-
|
|
144
|
-
for ticker in tickers:
|
|
145
|
-
for dtype in dtypes:
|
|
146
|
-
self.add_subscription(ticker=ticker, dtype=dtype)
|
|
147
|
-
|
|
148
|
-
subscription = kwargs.pop('subscription', kwargs.pop('subscribe', []))
|
|
149
|
-
|
|
150
|
-
if isinstance(subscription, dict):
|
|
151
|
-
subscription = [subscription]
|
|
152
|
-
|
|
153
|
-
for sub in subscription:
|
|
154
|
-
self.add_subscription(**sub)
|
|
155
|
-
|
|
156
|
-
self.reset()
|
|
157
|
-
|
|
158
|
-
def add_subscription(self, ticker: str, dtype: type | str):
|
|
159
|
-
if isinstance(dtype, str):
|
|
160
|
-
pass
|
|
161
|
-
elif inspect.isclass(dtype):
|
|
162
|
-
dtype = dtype.__name__
|
|
163
|
-
else:
|
|
164
|
-
raise ValueError(f'Invalid dtype {dtype}, expect str or class.')
|
|
165
|
-
|
|
166
|
-
topic = f'{ticker}.{dtype}'
|
|
167
|
-
self.replay_subscription[topic] = (ticker, dtype)
|
|
168
|
-
|
|
169
|
-
def remove_subscription(self, ticker: str, dtype: type | str):
|
|
170
|
-
if isinstance(dtype, str):
|
|
171
|
-
pass
|
|
172
|
-
else:
|
|
173
|
-
dtype = dtype.__name__
|
|
174
|
-
|
|
175
|
-
topic = f'{ticker}.{dtype}'
|
|
176
|
-
self.replay_subscription.pop(topic, None)
|
|
177
|
-
|
|
178
|
-
def reset(self):
|
|
179
|
-
if self.calendar is None:
|
|
180
|
-
self.replay_calendar = [self.start_date + datetime.timedelta(days=i) for i in range((self.end_date - self.start_date).days + 1)]
|
|
181
|
-
else:
|
|
182
|
-
self.replay_calendar = self.calendar
|
|
183
|
-
|
|
184
|
-
self.task_progress = 0
|
|
185
|
-
self.date_progress = sum([1 for _ in self.replay_calendar if _ < self.start_date])
|
|
186
|
-
self.progress.reset()
|
|
187
|
-
|
|
188
|
-
if self.date_progress:
|
|
189
|
-
self.progress.done_tasks = self.date_progress / len(self.replay_calendar)
|
|
190
|
-
|
|
191
|
-
def next_trade_day(self):
|
|
192
|
-
if self.date_progress < len(self.replay_calendar):
|
|
193
|
-
market_date = self.replay_calendar[self.date_progress]
|
|
194
|
-
self.progress.prompt = f'Replay {market_date:%Y-%m-%d} ({self.date_progress + 1} / {len(self.replay_calendar)}):'
|
|
195
|
-
for topic in self.replay_subscription:
|
|
196
|
-
ticker, dtype = self.replay_subscription[topic]
|
|
197
|
-
LOGGER.info(f'{self} loading {market_date} {ticker} {dtype}')
|
|
198
|
-
data = self.loader(market_date=market_date, ticker=ticker, dtype=dtype)
|
|
199
|
-
|
|
200
|
-
if isinstance(data, dict):
|
|
201
|
-
self.replay_task.extend(list(data.values()))
|
|
202
|
-
elif isinstance(data, (list, tuple)):
|
|
203
|
-
self.replay_task.extend(data)
|
|
204
|
-
|
|
205
|
-
self.date_progress += 1
|
|
206
|
-
else:
|
|
207
|
-
raise StopIteration()
|
|
208
|
-
|
|
209
|
-
self.replay_task.sort(key=lambda x: x.market_time)
|
|
210
|
-
|
|
211
|
-
def next_task(self):
|
|
212
|
-
if self.task_progress < len(self.replay_task):
|
|
213
|
-
data = self.replay_task[self.task_progress]
|
|
214
|
-
self.task_progress += 1
|
|
215
|
-
else:
|
|
216
|
-
if self.eod is not None and self.date_progress and (self.date_progress >= len(self.replay_calendar) or self.replay_calendar[self.date_progress] > self.start_date):
|
|
217
|
-
self.eod(market_date=self.replay_calendar[self.date_progress - 1], replay=self)
|
|
218
|
-
|
|
219
|
-
self.replay_task.clear()
|
|
220
|
-
self.task_progress = 0
|
|
221
|
-
|
|
222
|
-
if self.bod is not None and self.date_progress < len(self.replay_calendar):
|
|
223
|
-
self.bod(market_date=self.replay_calendar[self.date_progress], replay=self)
|
|
224
|
-
|
|
225
|
-
self.next_trade_day()
|
|
226
|
-
|
|
227
|
-
data = self.next_task()
|
|
228
|
-
|
|
229
|
-
if self.replay_task and self.replay_calendar:
|
|
230
|
-
current_progress = (self.date_progress - 1 + (self.task_progress / len(self.replay_task))) / len(self.replay_calendar)
|
|
231
|
-
self.progress.done_tasks = current_progress
|
|
232
|
-
else:
|
|
233
|
-
self.progress.done_tasks = 1
|
|
234
|
-
|
|
235
|
-
if (not self.progress.tick_size) \
|
|
236
|
-
or self.progress.progress >= self.progress.tick_size + self.progress.last_output \
|
|
237
|
-
or self.progress.is_done:
|
|
238
|
-
self.progress.output()
|
|
239
|
-
|
|
240
|
-
return data
|
|
241
|
-
|
|
242
|
-
def __next__(self):
|
|
243
|
-
try:
|
|
244
|
-
return self.next_task()
|
|
245
|
-
except StopIteration:
|
|
246
|
-
if not self.progress.is_done:
|
|
247
|
-
self.progress.done_tasks = 1
|
|
248
|
-
self.progress.output()
|
|
249
|
-
|
|
250
|
-
self.reset()
|
|
251
|
-
raise StopIteration()
|
|
252
|
-
|
|
253
|
-
def __iter__(self):
|
|
254
|
-
self.reset()
|
|
255
|
-
return self
|
|
256
|
-
|
|
257
|
-
def __repr__(self):
|
|
258
|
-
return f'{self.__class__.__name__}{{id={id(self)}, from={self.start_date}, to={self.end_date}}}'
|
|
1
|
+
import abc
|
|
2
|
+
import datetime
|
|
3
|
+
import inspect
|
|
4
|
+
from typing import Iterable
|
|
5
|
+
|
|
6
|
+
from . import LOGGER
|
|
7
|
+
from ..base import Progress, TickData, TransactionData, TradeData, OrderBook
|
|
8
|
+
|
|
9
|
+
LOGGER = LOGGER.getChild('Replay')
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Replay(object, metaclass=abc.ABCMeta):
|
|
13
|
+
@abc.abstractmethod
|
|
14
|
+
def __next__(self): ...
|
|
15
|
+
|
|
16
|
+
@abc.abstractmethod
|
|
17
|
+
def __iter__(self): ...
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SimpleReplay(Replay):
|
|
21
|
+
def __init__(self, **kwargs):
|
|
22
|
+
self.eod = kwargs.pop('eod', None)
|
|
23
|
+
self.bod = kwargs.pop('bod', None)
|
|
24
|
+
|
|
25
|
+
self.replay_task = []
|
|
26
|
+
self.task_progress = 0
|
|
27
|
+
self.task_date = None
|
|
28
|
+
self.progress = Progress(tasks=1, **kwargs)
|
|
29
|
+
|
|
30
|
+
def load(self, data):
|
|
31
|
+
if isinstance(data, dict):
|
|
32
|
+
self.replay_task.extend(list(data.values()))
|
|
33
|
+
else:
|
|
34
|
+
self.replay_task.extend(data)
|
|
35
|
+
|
|
36
|
+
def reset(self):
|
|
37
|
+
self.replay_task.clear()
|
|
38
|
+
self.task_progress = 0
|
|
39
|
+
self.task_date = None
|
|
40
|
+
self.progress.reset()
|
|
41
|
+
|
|
42
|
+
def next_task(self):
|
|
43
|
+
if self.task_progress < len(self.replay_task):
|
|
44
|
+
market_data = self.replay_task[self.task_progress]
|
|
45
|
+
market_time = market_data.market_time
|
|
46
|
+
|
|
47
|
+
if isinstance(market_time, datetime.datetime):
|
|
48
|
+
market_date = market_time.date()
|
|
49
|
+
else:
|
|
50
|
+
market_date = market_time
|
|
51
|
+
|
|
52
|
+
if market_date != self.task_date:
|
|
53
|
+
if callable(self.eod) and self.task_date:
|
|
54
|
+
self.eod(self.task_date)
|
|
55
|
+
|
|
56
|
+
self.task_date = market_date
|
|
57
|
+
self.progress.prompt = f'Replay {market_date:%Y-%m-%d}:'
|
|
58
|
+
|
|
59
|
+
if callable(self.bod):
|
|
60
|
+
self.bod(market_date)
|
|
61
|
+
|
|
62
|
+
self.progress.done_tasks = self.task_progress / len(self.replay_task)
|
|
63
|
+
|
|
64
|
+
if (not self.progress.tick_size) or self.progress.progress >= self.progress.tick_size + self.progress.last_output:
|
|
65
|
+
self.progress.output()
|
|
66
|
+
|
|
67
|
+
self.task_progress += 1
|
|
68
|
+
else:
|
|
69
|
+
raise StopIteration()
|
|
70
|
+
|
|
71
|
+
return market_data
|
|
72
|
+
|
|
73
|
+
def __next__(self):
|
|
74
|
+
try:
|
|
75
|
+
return self.next_task()
|
|
76
|
+
except StopIteration:
|
|
77
|
+
if not self.progress.is_done:
|
|
78
|
+
self.progress.done_tasks = 1
|
|
79
|
+
self.progress.output()
|
|
80
|
+
|
|
81
|
+
self.reset()
|
|
82
|
+
raise StopIteration()
|
|
83
|
+
|
|
84
|
+
def __iter__(self):
|
|
85
|
+
return self
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
class ProgressiveReplay(Replay):
|
|
89
|
+
"""
|
|
90
|
+
progressively loading and replaying market data
|
|
91
|
+
|
|
92
|
+
requires arguments
|
|
93
|
+
loader: a data loading function. Expect loader = Callable(market_date: datetime.date, ticker: str, dtype: str| type) -> dict[any, MarketData]
|
|
94
|
+
start_date & end_date: the given replay period
|
|
95
|
+
or calendar: the given replay calendar.
|
|
96
|
+
|
|
97
|
+
accepts kwargs:
|
|
98
|
+
ticker / tickers: the given symbols to replay, expect a str| list[str]
|
|
99
|
+
dtype / dtypes: the given dtype(s) of symbol to replay, expect a str | type, list[str | type]. default = all, which is (TradeData, TickData, OrderBook)
|
|
100
|
+
subscription / subscribe: the given ticker-dtype pair to replay, expect a list[dict[str, str | type]]
|
|
101
|
+
"""
|
|
102
|
+
|
|
103
|
+
def __init__(
|
|
104
|
+
self,
|
|
105
|
+
loader,
|
|
106
|
+
**kwargs
|
|
107
|
+
):
|
|
108
|
+
self.loader = loader
|
|
109
|
+
self.start_date: datetime.date | None = kwargs.pop('start_date', None)
|
|
110
|
+
self.end_date: datetime.date | None = kwargs.pop('end_date', None)
|
|
111
|
+
self.calendar: list[datetime.date] | None = kwargs.pop('calendar', None)
|
|
112
|
+
|
|
113
|
+
self.eod = kwargs.pop('eod', None)
|
|
114
|
+
self.bod = kwargs.pop('bod', None)
|
|
115
|
+
|
|
116
|
+
self.replay_subscription = {}
|
|
117
|
+
self.replay_calendar = []
|
|
118
|
+
self.replay_task = []
|
|
119
|
+
|
|
120
|
+
self.date_progress = 0
|
|
121
|
+
self.task_progress = 0
|
|
122
|
+
self.progress = Progress(tasks=1, **kwargs)
|
|
123
|
+
|
|
124
|
+
tickers: list[str] = kwargs.pop('ticker', kwargs.pop('tickers', []))
|
|
125
|
+
dtypes: list[str | type] = kwargs.pop('dtype', kwargs.pop('dtypes', [TradeData, TransactionData, OrderBook, TickData]))
|
|
126
|
+
|
|
127
|
+
if not all([arg_name in inspect.getfullargspec(loader).args for arg_name in ['market_date', 'ticker', 'dtype']]):
|
|
128
|
+
raise TypeError('loader function has 3 requires args, market_date, ticker and dtype.')
|
|
129
|
+
|
|
130
|
+
if isinstance(tickers, str):
|
|
131
|
+
tickers = [tickers]
|
|
132
|
+
elif isinstance(tickers, Iterable):
|
|
133
|
+
tickers = list(tickers)
|
|
134
|
+
else:
|
|
135
|
+
raise TypeError(f'Invalid ticker {tickers}, expect str or list[str]')
|
|
136
|
+
|
|
137
|
+
if isinstance(dtypes, str) or inspect.isclass(dtypes):
|
|
138
|
+
dtypes = [dtypes]
|
|
139
|
+
elif isinstance(dtypes, Iterable):
|
|
140
|
+
dtypes = list(dtypes)
|
|
141
|
+
else:
|
|
142
|
+
raise TypeError(f'Invalid dtype {dtypes}, expect str or list[str]')
|
|
143
|
+
|
|
144
|
+
for ticker in tickers:
|
|
145
|
+
for dtype in dtypes:
|
|
146
|
+
self.add_subscription(ticker=ticker, dtype=dtype)
|
|
147
|
+
|
|
148
|
+
subscription = kwargs.pop('subscription', kwargs.pop('subscribe', []))
|
|
149
|
+
|
|
150
|
+
if isinstance(subscription, dict):
|
|
151
|
+
subscription = [subscription]
|
|
152
|
+
|
|
153
|
+
for sub in subscription:
|
|
154
|
+
self.add_subscription(**sub)
|
|
155
|
+
|
|
156
|
+
self.reset()
|
|
157
|
+
|
|
158
|
+
def add_subscription(self, ticker: str, dtype: type | str):
|
|
159
|
+
if isinstance(dtype, str):
|
|
160
|
+
pass
|
|
161
|
+
elif inspect.isclass(dtype):
|
|
162
|
+
dtype = dtype.__name__
|
|
163
|
+
else:
|
|
164
|
+
raise ValueError(f'Invalid dtype {dtype}, expect str or class.')
|
|
165
|
+
|
|
166
|
+
topic = f'{ticker}.{dtype}'
|
|
167
|
+
self.replay_subscription[topic] = (ticker, dtype)
|
|
168
|
+
|
|
169
|
+
def remove_subscription(self, ticker: str, dtype: type | str):
|
|
170
|
+
if isinstance(dtype, str):
|
|
171
|
+
pass
|
|
172
|
+
else:
|
|
173
|
+
dtype = dtype.__name__
|
|
174
|
+
|
|
175
|
+
topic = f'{ticker}.{dtype}'
|
|
176
|
+
self.replay_subscription.pop(topic, None)
|
|
177
|
+
|
|
178
|
+
def reset(self):
|
|
179
|
+
if self.calendar is None:
|
|
180
|
+
self.replay_calendar = [self.start_date + datetime.timedelta(days=i) for i in range((self.end_date - self.start_date).days + 1)]
|
|
181
|
+
else:
|
|
182
|
+
self.replay_calendar = self.calendar
|
|
183
|
+
|
|
184
|
+
self.task_progress = 0
|
|
185
|
+
self.date_progress = sum([1 for _ in self.replay_calendar if _ < self.start_date])
|
|
186
|
+
self.progress.reset()
|
|
187
|
+
|
|
188
|
+
if self.date_progress:
|
|
189
|
+
self.progress.done_tasks = self.date_progress / len(self.replay_calendar)
|
|
190
|
+
|
|
191
|
+
def next_trade_day(self):
|
|
192
|
+
if self.date_progress < len(self.replay_calendar):
|
|
193
|
+
market_date = self.replay_calendar[self.date_progress]
|
|
194
|
+
self.progress.prompt = f'Replay {market_date:%Y-%m-%d} ({self.date_progress + 1} / {len(self.replay_calendar)}):'
|
|
195
|
+
for topic in self.replay_subscription:
|
|
196
|
+
ticker, dtype = self.replay_subscription[topic]
|
|
197
|
+
LOGGER.info(f'{self} loading {market_date} {ticker} {dtype}')
|
|
198
|
+
data = self.loader(market_date=market_date, ticker=ticker, dtype=dtype)
|
|
199
|
+
|
|
200
|
+
if isinstance(data, dict):
|
|
201
|
+
self.replay_task.extend(list(data.values()))
|
|
202
|
+
elif isinstance(data, (list, tuple)):
|
|
203
|
+
self.replay_task.extend(data)
|
|
204
|
+
|
|
205
|
+
self.date_progress += 1
|
|
206
|
+
else:
|
|
207
|
+
raise StopIteration()
|
|
208
|
+
|
|
209
|
+
self.replay_task.sort(key=lambda x: x.market_time)
|
|
210
|
+
|
|
211
|
+
def next_task(self):
|
|
212
|
+
if self.task_progress < len(self.replay_task):
|
|
213
|
+
data = self.replay_task[self.task_progress]
|
|
214
|
+
self.task_progress += 1
|
|
215
|
+
else:
|
|
216
|
+
if self.eod is not None and self.date_progress and (self.date_progress >= len(self.replay_calendar) or self.replay_calendar[self.date_progress] > self.start_date):
|
|
217
|
+
self.eod(market_date=self.replay_calendar[self.date_progress - 1], replay=self)
|
|
218
|
+
|
|
219
|
+
self.replay_task.clear()
|
|
220
|
+
self.task_progress = 0
|
|
221
|
+
|
|
222
|
+
if self.bod is not None and self.date_progress < len(self.replay_calendar):
|
|
223
|
+
self.bod(market_date=self.replay_calendar[self.date_progress], replay=self)
|
|
224
|
+
|
|
225
|
+
self.next_trade_day()
|
|
226
|
+
|
|
227
|
+
data = self.next_task()
|
|
228
|
+
|
|
229
|
+
if self.replay_task and self.replay_calendar:
|
|
230
|
+
current_progress = (self.date_progress - 1 + (self.task_progress / len(self.replay_task))) / len(self.replay_calendar)
|
|
231
|
+
self.progress.done_tasks = current_progress
|
|
232
|
+
else:
|
|
233
|
+
self.progress.done_tasks = 1
|
|
234
|
+
|
|
235
|
+
if (not self.progress.tick_size) \
|
|
236
|
+
or self.progress.progress >= self.progress.tick_size + self.progress.last_output \
|
|
237
|
+
or self.progress.is_done:
|
|
238
|
+
self.progress.output()
|
|
239
|
+
|
|
240
|
+
return data
|
|
241
|
+
|
|
242
|
+
def __next__(self):
|
|
243
|
+
try:
|
|
244
|
+
return self.next_task()
|
|
245
|
+
except StopIteration:
|
|
246
|
+
if not self.progress.is_done:
|
|
247
|
+
self.progress.done_tasks = 1
|
|
248
|
+
self.progress.output()
|
|
249
|
+
|
|
250
|
+
self.reset()
|
|
251
|
+
raise StopIteration()
|
|
252
|
+
|
|
253
|
+
def __iter__(self):
|
|
254
|
+
self.reset()
|
|
255
|
+
return self
|
|
256
|
+
|
|
257
|
+
def __repr__(self):
|
|
258
|
+
return f'{self.__class__.__name__}{{id={id(self)}, from={self.start_date}, to={self.end_date}}}'
|