PyAlgoEngine 0.7.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. PyAlgoEngine-0.7.4.dist-info/LICENSE +21 -0
  2. PyAlgoEngine-0.7.4.dist-info/METADATA +27 -0
  3. PyAlgoEngine-0.7.4.dist-info/RECORD +43 -0
  4. PyAlgoEngine-0.7.4.dist-info/WHEEL +5 -0
  5. PyAlgoEngine-0.7.4.dist-info/top_level.txt +1 -0
  6. algo_engine/__init__.py +41 -0
  7. algo_engine/apps/__init__.py +17 -0
  8. algo_engine/apps/backtest/__init__.py +20 -0
  9. algo_engine/apps/backtest/doc_server.py +331 -0
  10. algo_engine/apps/backtest/tester.py +254 -0
  11. algo_engine/apps/backtest/web_app.py +127 -0
  12. algo_engine/apps/bokeh_server.py +205 -0
  13. algo_engine/apps/demo/__init__.py +0 -0
  14. algo_engine/apps/demo/test.py +39 -0
  15. algo_engine/backtest/__init__.py +19 -0
  16. algo_engine/backtest/__main__.py +51 -0
  17. algo_engine/backtest/metrics.py +179 -0
  18. algo_engine/backtest/replay.py +261 -0
  19. algo_engine/backtest/sim_match.py +295 -0
  20. algo_engine/base/__init__.py +40 -0
  21. algo_engine/base/console_utils.py +1070 -0
  22. algo_engine/base/finance_decimal.py +258 -0
  23. algo_engine/base/market_buffer.py +571 -0
  24. algo_engine/base/market_utils.py +3092 -0
  25. algo_engine/base/market_utils_nt.py +188 -0
  26. algo_engine/base/market_utils_posix.py +3004 -0
  27. algo_engine/base/technical_analysis.py +406 -0
  28. algo_engine/base/telemetrics.py +78 -0
  29. algo_engine/base/trade_utils.py +709 -0
  30. algo_engine/engine/__init__.py +28 -0
  31. algo_engine/engine/algo_engine.py +901 -0
  32. algo_engine/engine/event_engine.py +53 -0
  33. algo_engine/engine/market_engine.py +370 -0
  34. algo_engine/engine/trade_engine.py +2037 -0
  35. algo_engine/monitor/__init__.py +15 -0
  36. algo_engine/monitor/advanced_data_interface.py +239 -0
  37. algo_engine/profile/__init__.py +121 -0
  38. algo_engine/profile/cn.py +175 -0
  39. algo_engine/strategy/__init__.py +44 -0
  40. algo_engine/strategy/strategy_engine.py +440 -0
  41. algo_engine/utils/__init__.py +3 -0
  42. algo_engine/utils/commit_regularizer.py +49 -0
  43. algo_engine/utils/data_utils.py +251 -0
@@ -0,0 +1,15 @@
1
+ from ..engine import MDS
2
+ from ..engine.market_engine import MarketDataMonitor as Monitor
3
+
4
+
5
+ def add_synthetic_orderbook():
6
+ # init synthetic orderbook monitor
7
+ monitor = SyntheticOrderBookMonitor(mds=MDS)
8
+ MDS.add_monitor(monitor=monitor)
9
+ # override current orderbook
10
+ MDS._order_book = monitor.order_book
11
+
12
+
13
+ from .advanced_data_interface import *
14
+
15
+ __all__ = ['Monitor', 'SyntheticOrderBookMonitor', 'MinuteBarMonitor']
@@ -0,0 +1,239 @@
1
+ import datetime
2
+ import json
3
+ import pickle
4
+ from multiprocessing import shared_memory
5
+ from typing import Self
6
+
7
+ from . import Monitor
8
+ from ..base import TradeData, OrderBook, MarketData, BarData, TransactionData
9
+
10
+
11
+ class SyntheticOrderBookMonitor(Monitor):
12
+
13
+ def __init__(self, **kwargs):
14
+
15
+ super().__init__(
16
+ name=kwargs.pop('name', 'Monitor.SyntheticOrderBook'),
17
+ monitor_id=kwargs.pop('monitor_id', None)
18
+ )
19
+
20
+ self.order_book: dict[str, OrderBook] = {}
21
+
22
+ def __call__(self, market_data: MarketData, **kwargs):
23
+ if isinstance(market_data, TradeData):
24
+ self.on_trade_data(trade_data=market_data)
25
+
26
+ def on_trade_data(self, trade_data: TradeData):
27
+ ticker = trade_data.ticker
28
+
29
+ if order_book := self.order_book.get(ticker):
30
+ if order_book.market_time <= trade_data.market_time:
31
+ side = trade_data.side
32
+ price = trade_data.price
33
+ book = order_book.ask if side.sign > 0 else order_book.bid
34
+ listed_volume = book.at_price(price).volume if price in book else 0.
35
+ traded_volume = trade_data.volume
36
+ book.update(price=price, volume=max(0, listed_volume - traded_volume))
37
+
38
+ def on_transaction_data(self, transaction_data: TransactionData):
39
+ pass
40
+
41
+ def to_json(self, fmt='str', **kwargs) -> str | dict:
42
+ data_dict = dict(
43
+ name=self.name,
44
+ monitor_id=self.monitor_id,
45
+ order_book={k: v.to_json(fmt='dict') for k, v in self.order_book.items()},
46
+ )
47
+
48
+ if fmt == 'dict':
49
+ return data_dict
50
+ elif fmt == 'str':
51
+ return json.dumps(data_dict, **kwargs)
52
+ else:
53
+ raise ValueError(f'Invalid format {fmt}, except "dict" or "str".')
54
+
55
+ @classmethod
56
+ def from_json(cls, json_message: str | bytes | bytearray | dict) -> Self:
57
+ if isinstance(json_message, dict):
58
+ json_dict = json_message
59
+ else:
60
+ json_dict = json.loads(json_message)
61
+
62
+ self = cls(
63
+ name=json_dict['name'],
64
+ monitor_id=json_dict['monitor_id'],
65
+ keep_order_log=json_dict['keep_order_log']
66
+ )
67
+
68
+ self.order_book = {k: MarketData.from_json(v) for k, v in json_dict['order_book'].items()}
69
+ return self
70
+
71
+ def from_shm(self, name: str = None) -> None:
72
+ if name is None:
73
+ name = f'{self.monitor_id}.json'
74
+
75
+ shm = shared_memory.SharedMemory(name=name)
76
+ json_dict = pickle.loads(bytes(shm.buf))
77
+
78
+ self.clear()
79
+
80
+ self.order_book.update({k: MarketData.from_json(v) for k, v in json_dict['order_book'].items()})
81
+
82
+ def clear(self) -> None:
83
+ self.order_book.clear()
84
+
85
+ @property
86
+ def value(self) -> dict[str, OrderBook]:
87
+ return self.order_book
88
+
89
+
90
+ class MinuteBarMonitor(Monitor):
91
+
92
+ def __init__(self, interval: float = 60., **kwargs):
93
+ self.interval = interval
94
+
95
+ super().__init__(
96
+ name=kwargs.pop('name', 'Monitor.MinuteBarMonitor'),
97
+ monitor_id=kwargs.pop('monitor_id', None)
98
+ )
99
+
100
+ self._minute_bar_data: dict[str, BarData] = {}
101
+ self._last_bar_data: dict[str, BarData] = {}
102
+
103
+ def __call__(self, market_data: MarketData, **kwargs):
104
+ self._update_last_bar(market_data=market_data, interval=self.interval)
105
+ # self._update_active_bar(market_data=market_data, interval=self.interval)
106
+
107
+ def _update_last_bar(self, market_data: MarketData, interval: float):
108
+ ticker = market_data.ticker
109
+ market_price = market_data.market_price
110
+ market_time = market_data.market_time
111
+ timestamp = market_data.timestamp
112
+
113
+ if ticker not in self._minute_bar_data or market_time >= self._minute_bar_data[ticker].bar_end_time:
114
+ # update bar_data
115
+ if ticker in self._minute_bar_data:
116
+ self._last_bar_data[ticker] = self._minute_bar_data[ticker]
117
+
118
+ bar_data = self._minute_bar_data[ticker] = BarData(
119
+ ticker=ticker,
120
+ timestamp=int(timestamp // interval + 1) * interval,
121
+ start_timestamp=int(timestamp // interval) * interval,
122
+ bar_span=datetime.timedelta(seconds=interval),
123
+ high_price=market_price,
124
+ low_price=market_price,
125
+ open_price=market_price,
126
+ close_price=market_price,
127
+ volume=0.,
128
+ notional=0.,
129
+ trade_count=0
130
+ )
131
+ else:
132
+ bar_data = self._minute_bar_data[ticker]
133
+
134
+ if isinstance(market_data, TradeData):
135
+ bar_data['volume'] += market_data.volume
136
+ bar_data['notional'] += market_data.notional
137
+ bar_data['trade_count'] += 1
138
+
139
+ bar_data['close_price'] = market_price
140
+ bar_data['high_price'] = max(bar_data.high_price, market_price)
141
+ bar_data['low_price'] = min(bar_data.low_price, market_price)
142
+
143
+ def _update_active_bar(self, market_data: MarketData, interval: float):
144
+ ticker = market_data.ticker
145
+ market_price = market_data.market_price
146
+ market_time = market_data.market_time
147
+ timestamp = market_data.timestamp
148
+
149
+ if ticker not in self._minute_bar_data or market_time >= self._minute_bar_data[ticker].bar_end_time:
150
+ bar_data = self._minute_bar_data[ticker] = BarData(
151
+ ticker=ticker,
152
+ start_timestamp=timestamp - interval,
153
+ timestamp=timestamp,
154
+ bar_span=datetime.timedelta(seconds=interval),
155
+ high_price=market_price,
156
+ low_price=market_price,
157
+ open_price=market_price,
158
+ close_price=market_price,
159
+ volume=0.,
160
+ notional=0.,
161
+ trade_count=0
162
+ )
163
+ bar_data.history = []
164
+
165
+ else:
166
+ bar_data = self._minute_bar_data[ticker]
167
+
168
+ history: list[TradeData] = getattr(bar_data, 'history')
169
+ bar_data['start_timestamp'] = timestamp - interval
170
+
171
+ if isinstance(market_data, TradeData):
172
+ history.append(market_data)
173
+
174
+ while True:
175
+ if history[0].market_time >= bar_data.bar_start_time:
176
+ break
177
+ else:
178
+ history.pop(0)
179
+
180
+ bar_data['volume'] = sum([_.volume for _ in history])
181
+ bar_data['notional'] = sum([_.notional for _ in history])
182
+ bar_data['trade_count'] = len([_.notional for _ in history])
183
+ bar_data['close_price'] = market_price
184
+ bar_data['open_price'] = history[0].market_price
185
+ bar_data['high_price'] = max([_.market_price for _ in history])
186
+ bar_data['low_price'] = min([_.market_price for _ in history])
187
+
188
+ def to_json(self, fmt='str', **kwargs) -> str | dict:
189
+ data_dict = dict(
190
+ name=self.name,
191
+ monitor_id=self.monitor_id,
192
+ interval=self.interval,
193
+ minute_bar_data={k: v.to_json(fmt='dict') for k, v in self._minute_bar_data.items()},
194
+ last_bar_data={k: v.to_json(fmt='dict') for k, v in self._last_bar_data.items()},
195
+ )
196
+
197
+ if fmt == 'dict':
198
+ return data_dict
199
+ elif fmt == 'str':
200
+ return json.dumps(data_dict, **kwargs)
201
+ else:
202
+ raise ValueError(f'Invalid format {fmt}, except "dict" or "str".')
203
+
204
+ @classmethod
205
+ def from_json(cls, json_message: str | bytes | bytearray | dict) -> Self:
206
+ if isinstance(json_message, dict):
207
+ json_dict = json_message
208
+ else:
209
+ json_dict = json.loads(json_message)
210
+
211
+ self = cls(
212
+ name=json_dict['name'],
213
+ monitor_id=json_dict['monitor_id'],
214
+ interval=json_dict['interval'],
215
+ )
216
+
217
+ self._minute_bar_data = {k: MarketData.from_json(v) for k, v in json_dict['minute_bar_data'].items()}
218
+ self._last_bar_data = {k: MarketData.from_json(v) for k, v in json_dict['last_bar_data'].items()}
219
+ return self
220
+
221
+ def from_shm(self, name: str = None) -> None:
222
+ if name is None:
223
+ name = f'{self.monitor_id}.json'
224
+
225
+ shm = shared_memory.SharedMemory(name=name)
226
+ json_dict = pickle.loads(bytes(shm.buf))
227
+
228
+ self.clear()
229
+
230
+ self._minute_bar_data.update({k: MarketData.from_json(v) for k, v in json_dict['minute_bar_data'].items()})
231
+ self._last_bar_data.update({k: MarketData.from_json(v) for k, v in json_dict['last_bar_data'].items()})
232
+
233
+ def clear(self) -> None:
234
+ self._minute_bar_data.clear()
235
+ self._last_bar_data.clear()
236
+
237
+ @property
238
+ def value(self) -> dict[str, BarData]:
239
+ return self._last_bar_data
@@ -0,0 +1,121 @@
1
+ import abc
2
+ import datetime
3
+ from typing import Self
4
+
5
+
6
+ class Profile(object, metaclass=abc.ABCMeta):
7
+ def __init__(
8
+ self,
9
+ profile_id: str,
10
+ session_start: datetime.time | None = None,
11
+ session_end: datetime.time | None = None,
12
+ session_break: list[tuple[datetime.time, datetime.time]] = None
13
+ ):
14
+ self.profile_id = profile_id
15
+ self.session_start = session_start
16
+ self.session_end = session_end
17
+ self.session_break = [] if session_break is None else session_break
18
+
19
+ self.time_zone = None
20
+
21
+ def __repr__(self):
22
+ return f'<Profile {self.profile_id}>({id(self)})'
23
+
24
+ def override_profile(self, profile: Self = None) -> Self:
25
+ if profile is None:
26
+ profile = PROFILE
27
+
28
+ profile.profile_id = self.profile_id
29
+ profile.session_start = self.session_start
30
+ profile.session_end = self.session_end
31
+
32
+ if profile.session_break is None or self.session_break is None:
33
+ profile.session_break = self.session_break
34
+ else:
35
+ profile.session_break.clear()
36
+ profile.session_break.extend(self.session_break)
37
+
38
+ profile.trade_time_between = self.trade_time_between
39
+ profile.is_market_session = self.is_market_session
40
+ profile.trade_calendar = self.trade_calendar
41
+
42
+ return profile
43
+
44
+ @abc.abstractmethod
45
+ def trade_time_between(self, start_time: datetime.datetime | float, end_time: datetime.datetime | float, **kwargs) -> datetime.timedelta:
46
+ ...
47
+
48
+ @abc.abstractmethod
49
+ def is_market_session(self, timestamp: float | int | datetime.datetime, **kwargs) -> bool:
50
+ ...
51
+
52
+ @abc.abstractmethod
53
+ def trade_calendar(self, start_date: datetime.date, end_date: datetime.date, **kwargs) -> list[datetime.date]:
54
+ ...
55
+
56
+ @property
57
+ def range_break(self) -> list[dict]:
58
+ """
59
+ an range break designed for plotly.
60
+ """
61
+ range_break = []
62
+
63
+ if not self.session_break:
64
+ return range_break
65
+
66
+ # Convert session_break to range_break format
67
+ for start, end in self.session_break:
68
+ start_hour = start.hour + start.minute / 60
69
+ end_hour = end.hour + end.minute / 60
70
+ range_break.append(dict(bounds=[start_hour, end_hour], pattern="hour"))
71
+
72
+ # Add the additional fixed non-trading periods
73
+ if self.session_start is not None and self.session_start != datetime.time.min:
74
+ range_break.append(
75
+ dict(bounds=[0, self.session_start.hour + self.session_start.minute / 60], pattern="hour"),
76
+ )
77
+
78
+ if self.session_end is not None and self.session_end != datetime.time.max:
79
+ range_break.append(
80
+ dict(bounds=[self.session_end.hour + self.session_end.minute / 60, 24], pattern="hour"),
81
+ )
82
+
83
+ return range_break
84
+
85
+
86
+ class DefaultProfile(Profile):
87
+ def __init__(self):
88
+ super().__init__(
89
+ profile_id='non-stop',
90
+ session_start=datetime.time.min,
91
+ session_end=None,
92
+ session_break=None
93
+ )
94
+
95
+ def trade_time_between(self, start_time: datetime.datetime | float, end_time: datetime.datetime | float, **kwargs) -> datetime.timedelta:
96
+ if start_time is not None and isinstance(start_time, (float, int)):
97
+ start_time = datetime.datetime.fromtimestamp(start_time, tz=self.time_zone)
98
+
99
+ if end_time is not None and isinstance(end_time, (float, int)):
100
+ end_time = datetime.datetime.fromtimestamp(end_time, tz=self.time_zone)
101
+
102
+ if start_time is None or end_time is None:
103
+ return datetime.timedelta(seconds=0)
104
+
105
+ if start_time > end_time:
106
+ return datetime.timedelta(seconds=0)
107
+
108
+ return end_time - start_time
109
+
110
+ def is_market_session(self, timestamp: float | int | datetime.datetime, **kwargs) -> bool:
111
+ return True
112
+
113
+ def trade_calendar(self, start_date: datetime.date, end_date: datetime.date, **kwargs) -> list[datetime.date]:
114
+ return [start_date + datetime.timedelta(days=i) for i in range((end_date - start_date).days + 1)]
115
+
116
+
117
+ from .cn import PROFILE_CN
118
+
119
+ PROFILE = DefaultProfile()
120
+
121
+ __all__ = ['Profile', 'PROFILE', 'PROFILE_CN']
@@ -0,0 +1,175 @@
1
+ import datetime
2
+ import functools
3
+
4
+ from . import Profile
5
+
6
+
7
+ class ProfileCN(Profile):
8
+ def __init__(self):
9
+ super().__init__(
10
+ profile_id='cn',
11
+ session_start=datetime.time(9, 30),
12
+ session_end=datetime.time(15, 0),
13
+ session_break=[(datetime.time(11, 30), datetime.time(13, 0))]
14
+ )
15
+
16
+ self.cn_trade_calendar_cache = {}
17
+
18
+ def override_profile(self, profile: Profile = None):
19
+ profile = super().override_profile(profile=profile)
20
+
21
+ setattr(profile, 'cn_trade_calendar_cache', self.cn_trade_calendar_cache)
22
+
23
+ @functools.lru_cache
24
+ def trade_calendar(self, start_date: datetime.date, end_date: datetime.date, **kwargs) -> list[datetime.date]:
25
+ import pandas as pd
26
+ import exchange_calendars
27
+
28
+ market = kwargs.get('market', 'XSHG')
29
+ tz = kwargs.get('tz', 'UTC')
30
+
31
+ if market in self.cn_trade_calendar_cache:
32
+ trade_calendar = self.cn_trade_calendar_cache[market]
33
+ else:
34
+ trade_calendar = self.cn_trade_calendar_cache[market] = exchange_calendars.get_calendar(market)
35
+
36
+ calendar = trade_calendar.sessions_in_range(start_date, end_date)
37
+
38
+ # noinspection PyTypeChecker
39
+ result = list(pd.to_datetime(calendar).date)
40
+
41
+ return result
42
+
43
+ @functools.lru_cache
44
+ def is_trade_day(self, market_date: datetime.date, market='XSHG', tz='UTC') -> bool:
45
+ if market in self.cn_trade_calendar_cache:
46
+ trade_calendar = self.cn_trade_calendar_cache[market]
47
+ else:
48
+ import exchange_calendars
49
+ trade_calendar = self.cn_trade_calendar_cache[market] = exchange_calendars.get_calendar(market)
50
+
51
+ return trade_calendar.is_session(market_date)
52
+
53
+ def trade_days_between(self, start_date: datetime.date, end_date: datetime.date = datetime.date.today(), **kwargs) -> int:
54
+ """
55
+ Returns the number of trade days between the given date, which is the pre-open of the start_date to the pre-open of the end_date.
56
+ :param start_date: the given trade date
57
+ :param end_date: the given trade date
58
+ :return: integer number of days
59
+ """
60
+ assert start_date <= end_date, "The end date must not before the start date"
61
+
62
+ if start_date == end_date:
63
+ offset = 0
64
+ else:
65
+ market_date_list = self.trade_calendar(start_date=start_date, end_date=end_date, **kwargs)
66
+ if not market_date_list:
67
+ offset = 0
68
+ else:
69
+ last_trade_date = market_date_list[-1]
70
+ offset = len(market_date_list)
71
+
72
+ if last_trade_date == end_date:
73
+ offset -= 1
74
+
75
+ return offset
76
+
77
+ @classmethod
78
+ def time_to_seconds(cls, t: datetime.time):
79
+ return (t.hour * 60 + t.minute) * 60 + t.second + t.microsecond / 1000
80
+
81
+ def trade_time_between(self, start_time: datetime.datetime | datetime.time | float | int, end_time: datetime.datetime | datetime.time | float | int, fmt='timedelta', **kwargs):
82
+ if start_time is None or end_time is None:
83
+ if fmt == 'timestamp':
84
+ return 0.
85
+ elif fmt == 'timedelta':
86
+ return datetime.timedelta(0)
87
+ else:
88
+ raise NotImplementedError(f'Invalid fmt {fmt}, should be "timestamp" or "timedelta"')
89
+
90
+ session_start = kwargs.pop('session_start', self.session_start)
91
+ session_break = kwargs.pop('session_break', self.session_break)
92
+ session_end = kwargs.pop('session_end', self.session_end)
93
+ session_length_0 = datetime.timedelta(seconds=self.time_to_seconds(session_break[0]) - self.time_to_seconds(session_start))
94
+ session_length_1 = datetime.timedelta(seconds=self.time_to_seconds(session_end) - self.time_to_seconds(session_break[1]))
95
+ session_length = session_length_0 + session_length_1
96
+ implied_date = datetime.date.today()
97
+
98
+ if isinstance(start_time, (float, int)):
99
+ start_time = datetime.datetime.fromtimestamp(start_time, tz=self.time_zone)
100
+ implied_date = start_time.date()
101
+
102
+ if isinstance(end_time, (float, int)):
103
+ end_time = datetime.datetime.fromtimestamp(end_time, tz=self.time_zone)
104
+ implied_date = end_time.date()
105
+
106
+ if isinstance(start_time, datetime.time):
107
+ start_time = datetime.datetime.combine(implied_date, start_time)
108
+
109
+ if isinstance(end_time, datetime.time):
110
+ end_time = datetime.datetime.combine(implied_date, end_time)
111
+
112
+ offset = datetime.timedelta()
113
+
114
+ market_time = start_time.time()
115
+
116
+ # calculate the timespan from start_time to session_end
117
+ if market_time <= session_start:
118
+ offset += session_length
119
+ elif session_start < market_time <= session_break[0]:
120
+ offset += datetime.datetime.combine(start_time.date(), session_break[0]) - start_time
121
+ offset += session_length_1
122
+ elif session_break[0] < market_time <= session_break[1]:
123
+ offset += session_length_1
124
+ elif session_break[1] < market_time <= session_end:
125
+ offset += datetime.datetime.combine(start_time.date(), session_end) - start_time
126
+ else:
127
+ offset += datetime.timedelta(0)
128
+
129
+ offset -= session_length
130
+
131
+ market_time = end_time.time()
132
+
133
+ # calculate the timespan from session_start to end_time
134
+ if market_time <= session_start:
135
+ offset += datetime.timedelta(0)
136
+ elif session_start < market_time <= session_break[0]:
137
+ offset += end_time - datetime.datetime.combine(end_time.date(), session_start)
138
+ elif session_break[0] < market_time <= session_break[1]:
139
+ offset += session_length_0
140
+ elif session_break[1] < market_time <= session_end:
141
+ offset += end_time - datetime.datetime.combine(end_time.date(), session_break[1])
142
+ offset += session_length_0
143
+ else:
144
+ offset += session_length
145
+
146
+ # calculate market_date difference
147
+ if start_time.date() != end_time.date():
148
+ offset += session_length * self.trade_days_between(start_date=start_time.date(), end_date=end_time.date(), **kwargs)
149
+
150
+ if fmt == 'timestamp':
151
+ return offset.total_seconds()
152
+ elif fmt == 'timedelta':
153
+ return offset
154
+ else:
155
+ raise NotImplementedError(f'Invalid fmt {fmt}, should be "timestamp" or "timedelta"')
156
+
157
+ def is_market_session(self, timestamp: float | int | datetime.datetime, **kwargs) -> bool:
158
+ if isinstance(timestamp, (float, int)):
159
+ market_time = datetime.datetime.fromtimestamp(timestamp, tz=self.time_zone).time()
160
+ elif isinstance(timestamp, datetime.datetime):
161
+ market_time = timestamp.time()
162
+ elif isinstance(timestamp, datetime.time):
163
+ market_time = timestamp
164
+ else:
165
+ raise TypeError(f'Expect timestamp to be a float, int or datetime, got {type(timestamp)}!')
166
+
167
+ if (market_time < datetime.time(9, 30)
168
+ or datetime.time(11, 30) < market_time < datetime.time(13, 0)
169
+ or datetime.time(15, 0) < market_time):
170
+ return False
171
+
172
+ return True
173
+
174
+
175
+ PROFILE_CN = ProfileCN()
@@ -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']