PyAlgoEngine 0.3.13.post1__tar.gz → 0.4.0.post1__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.
Files changed (32) hide show
  1. {pyalgoengine-0.3.13.post1/PyAlgoEngine.egg-info → pyalgoengine-0.4.0.post1}/PKG-INFO +6 -4
  2. {pyalgoengine-0.3.13.post1 → pyalgoengine-0.4.0.post1/PyAlgoEngine.egg-info}/PKG-INFO +6 -4
  3. pyalgoengine-0.4.0.post1/PyAlgoEngine.egg-info/SOURCES.txt +24 -0
  4. pyalgoengine-0.4.0.post1/PyAlgoEngine.egg-info/requires.txt +5 -0
  5. pyalgoengine-0.4.0.post1/PyAlgoEngine.egg-info/top_level.txt +1 -0
  6. pyalgoengine-0.4.0.post1/algo_engine/__init__.py +74 -0
  7. pyalgoengine-0.4.0.post1/algo_engine/back_test/__init__.py +8 -0
  8. pyalgoengine-0.3.13.post1/AlgoEngine/Strategies/BackTest.py → pyalgoengine-0.4.0.post1/algo_engine/back_test/__main__.py +7 -8
  9. pyalgoengine-0.4.0.post1/algo_engine/back_test/replay.py +257 -0
  10. pyalgoengine-0.4.0.post1/algo_engine/back_test/sim_match.py +290 -0
  11. pyalgoengine-0.4.0.post1/algo_engine/engine/__init__.py +15 -0
  12. pyalgoengine-0.3.13.post1/AlgoEngine/Engine/AlgoEngine.py → pyalgoengine-0.4.0.post1/algo_engine/engine/algo_engine.py +63 -62
  13. pyalgoengine-0.3.13.post1/AlgoEngine/Engine/EventEngine.py → pyalgoengine-0.4.0.post1/algo_engine/engine/event_engine.py +15 -14
  14. pyalgoengine-0.4.0.post1/algo_engine/engine/market_engine.py +363 -0
  15. pyalgoengine-0.3.13.post1/AlgoEngine/Engine/TradeEngine.py → pyalgoengine-0.4.0.post1/algo_engine/engine/trade_engine.py +157 -402
  16. pyalgoengine-0.4.0.post1/algo_engine/monitor/__init__.py +15 -0
  17. pyalgoengine-0.4.0.post1/algo_engine/monitor/advanced_data_interface.py +240 -0
  18. pyalgoengine-0.4.0.post1/algo_engine/profile/__init__.py +54 -0
  19. pyalgoengine-0.4.0.post1/algo_engine/profile/cn.py +174 -0
  20. {pyalgoengine-0.3.13.post1/AlgoEngine/Strategies → pyalgoengine-0.4.0.post1/algo_engine/strategie}/__init__.py +6 -9
  21. pyalgoengine-0.3.13.post1/AlgoEngine/Strategies/_StrategyEngine.py → pyalgoengine-0.4.0.post1/algo_engine/strategie/strategy_engine.py +6 -9
  22. {pyalgoengine-0.3.13.post1 → pyalgoengine-0.4.0.post1}/setup.py +6 -4
  23. pyalgoengine-0.3.13.post1/AlgoEngine/Engine/MarketEngine.py +0 -1070
  24. pyalgoengine-0.3.13.post1/AlgoEngine/Engine/__init__.py +0 -101
  25. pyalgoengine-0.3.13.post1/AlgoEngine/__init__.py +0 -16
  26. pyalgoengine-0.3.13.post1/PyAlgoEngine.egg-info/SOURCES.txt +0 -17
  27. pyalgoengine-0.3.13.post1/PyAlgoEngine.egg-info/requires.txt +0 -5
  28. pyalgoengine-0.3.13.post1/PyAlgoEngine.egg-info/top_level.txt +0 -1
  29. {pyalgoengine-0.3.13.post1 → pyalgoengine-0.4.0.post1}/LICENSE +0 -0
  30. {pyalgoengine-0.3.13.post1 → pyalgoengine-0.4.0.post1}/PyAlgoEngine.egg-info/dependency_links.txt +0 -0
  31. {pyalgoengine-0.3.13.post1 → pyalgoengine-0.4.0.post1}/README.md +0 -0
  32. {pyalgoengine-0.3.13.post1 → pyalgoengine-0.4.0.post1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyAlgoEngine
3
- Version: 0.3.13.post1
3
+ Version: 0.4.0.post1
4
4
  Summary: Basic algo engine
5
5
  Home-page: https://github.com/BolunHan/PyAlgoEngine
6
6
  Author: Bolun.Han
@@ -8,15 +8,17 @@ Author-email: Bolun.Han@outlook.com
8
8
  Classifier: Programming Language :: Python :: 3.8
9
9
  Classifier: Programming Language :: Python :: 3.9
10
10
  Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
11
13
  Classifier: Operating System :: OS Independent
12
- Requires-Python: >=3.8
14
+ Requires-Python: >=3.1
13
15
  Description-Content-Type: text/markdown
14
16
  License-File: LICENSE
15
17
  Requires-Dist: numpy
16
18
  Requires-Dist: pandas
17
19
  Requires-Dist: exchange_calendars
18
- Requires-Dist: PyQuantKit
19
- Requires-Dist: PyEventEngine
20
+ Requires-Dist: PyQuantKit>=0.3.0
21
+ Requires-Dist: PyEventEngine>=0.3.0.post3
20
22
 
21
23
  # PyAlgoEngine
22
24
  python algo trading engine
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyAlgoEngine
3
- Version: 0.3.13.post1
3
+ Version: 0.4.0.post1
4
4
  Summary: Basic algo engine
5
5
  Home-page: https://github.com/BolunHan/PyAlgoEngine
6
6
  Author: Bolun.Han
@@ -8,15 +8,17 @@ Author-email: Bolun.Han@outlook.com
8
8
  Classifier: Programming Language :: Python :: 3.8
9
9
  Classifier: Programming Language :: Python :: 3.9
10
10
  Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
11
13
  Classifier: Operating System :: OS Independent
12
- Requires-Python: >=3.8
14
+ Requires-Python: >=3.1
13
15
  Description-Content-Type: text/markdown
14
16
  License-File: LICENSE
15
17
  Requires-Dist: numpy
16
18
  Requires-Dist: pandas
17
19
  Requires-Dist: exchange_calendars
18
- Requires-Dist: PyQuantKit
19
- Requires-Dist: PyEventEngine
20
+ Requires-Dist: PyQuantKit>=0.3.0
21
+ Requires-Dist: PyEventEngine>=0.3.0.post3
20
22
 
21
23
  # PyAlgoEngine
22
24
  python algo trading engine
@@ -0,0 +1,24 @@
1
+ LICENSE
2
+ README.md
3
+ setup.py
4
+ PyAlgoEngine.egg-info/PKG-INFO
5
+ PyAlgoEngine.egg-info/SOURCES.txt
6
+ PyAlgoEngine.egg-info/dependency_links.txt
7
+ PyAlgoEngine.egg-info/requires.txt
8
+ PyAlgoEngine.egg-info/top_level.txt
9
+ algo_engine/__init__.py
10
+ algo_engine/back_test/__init__.py
11
+ algo_engine/back_test/__main__.py
12
+ algo_engine/back_test/replay.py
13
+ algo_engine/back_test/sim_match.py
14
+ algo_engine/engine/__init__.py
15
+ algo_engine/engine/algo_engine.py
16
+ algo_engine/engine/event_engine.py
17
+ algo_engine/engine/market_engine.py
18
+ algo_engine/engine/trade_engine.py
19
+ algo_engine/monitor/__init__.py
20
+ algo_engine/monitor/advanced_data_interface.py
21
+ algo_engine/profile/__init__.py
22
+ algo_engine/profile/cn.py
23
+ algo_engine/strategie/__init__.py
24
+ algo_engine/strategie/strategy_engine.py
@@ -0,0 +1,5 @@
1
+ numpy
2
+ pandas
3
+ exchange_calendars
4
+ PyQuantKit>=0.3.0
5
+ PyEventEngine>=0.3.0.post3
@@ -0,0 +1 @@
1
+ algo_engine
@@ -0,0 +1,74 @@
1
+ __version__ = "0.4.0.post1"
2
+
3
+ import logging
4
+ import os
5
+ import sys
6
+ import time
7
+ import traceback
8
+
9
+ from PyQuantKit import ColoredFormatter
10
+
11
+ LOGGER: logging.Logger | None = None
12
+ LOG_LEVEL = logging.INFO
13
+
14
+ if 'ALGO_DIR' in os.environ:
15
+ WORKING_DIRECTORY = os.path.realpath(os.environ['ALGO_DIR'])
16
+ else:
17
+ WORKING_DIRECTORY = str(os.getcwd())
18
+
19
+
20
+ def get_logger(**kwargs) -> logging.Logger:
21
+ level = kwargs.get('level', LOG_LEVEL)
22
+ stream_io = kwargs.get('stream_io', sys.stdout)
23
+ formatter = kwargs.get('formatter', ColoredFormatter())
24
+ global LOGGER
25
+
26
+ if LOGGER is not None:
27
+ return LOGGER
28
+
29
+ LOGGER = logging.getLogger('PyAlgoEngine')
30
+ LOGGER.setLevel(level)
31
+ logging.Formatter.converter = time.gmtime
32
+
33
+ if stream_io:
34
+ have_handler = False
35
+ for handler in LOGGER.handlers:
36
+ # noinspection PyUnresolvedReferences
37
+ if type(handler) == logging.StreamHandler and handler.stream == stream_io:
38
+ have_handler = True
39
+ break
40
+
41
+ if not have_handler:
42
+ logger_ch = logging.StreamHandler(stream=stream_io)
43
+ logger_ch.setLevel(level=level)
44
+ logger_ch.setFormatter(fmt=formatter)
45
+ LOGGER.addHandler(logger_ch)
46
+
47
+ return LOGGER
48
+
49
+
50
+ def set_logger(logger: logging.Logger):
51
+ global LOGGER
52
+ LOGGER = logger
53
+
54
+ engine.LOGGER = LOGGER.getChild('Engine')
55
+ back_test.LOGGER = LOGGER.getChild('BackTest')
56
+ strategie.LOGGER = LOGGER.getChild('Strategies')
57
+
58
+
59
+ _ = get_logger()
60
+
61
+ from . import engine
62
+ from . import back_test
63
+ from . import profile
64
+ from . import strategie
65
+
66
+ engine.LOGGER.info(f'AlgoEngine version {__version__}')
67
+
68
+ # import addon module
69
+ try:
70
+ from . import EngineAddon
71
+
72
+ engine.LOGGER.info(f'AlgoEngine_Addons import successful, version {EngineAddon.__version__}')
73
+ except ImportError:
74
+ engine.LOGGER.debug(f'Install AlgoEngine_Addons to use Statistics module\n{traceback.format_exc()}')
@@ -0,0 +1,8 @@
1
+ from .. import LOGGER
2
+
3
+ LOGGER = LOGGER.getChild('BackTest')
4
+
5
+ from .replay import Replay, SimpleReplay, ProgressiveReplay
6
+ from .sim_match import SimMatch
7
+
8
+ __all__ = ['Replay', 'SimpleReplay', 'ProgressiveReplay', 'SimMatch']
@@ -1,15 +1,14 @@
1
- from __future__ import annotations
1
+ __package__ = 'algo_engine.back_test'
2
2
 
3
3
  import datetime
4
+ from typing import Callable
4
5
 
5
6
  import EventEngine
6
7
 
7
- from . import EventDMA
8
- from ._StrategyEngine import StrategyEngine
9
- from ..Engine import LOGGER, TOPIC, MarketDataService, Balance, RiskProfile, PositionManagementService
10
- from ..Engine.AlgoEngine import AlgoRegistry, AlgoEngine
11
-
12
- LOGGER = LOGGER.getChild('BackTest')
8
+ from ..engine import TOPIC, MarketDataService, Balance, RiskProfile, PositionManagementService
9
+ from ..engine.algo_engine import AlgoRegistry, AlgoEngine
10
+ from ..strategie import EventDMA
11
+ from ..strategie.strategy_engine import StrategyEngine
13
12
 
14
13
 
15
14
  def test_stop(code=0):
@@ -19,7 +18,7 @@ def test_stop(code=0):
19
18
  # `os._exit(code)`
20
19
 
21
20
 
22
- def test_start(start_date: datetime.date, end_date: datetime.date, data_loader: callable, **kwargs):
21
+ def test_start(start_date: datetime.date, end_date: datetime.date, data_loader: Callable, **kwargs):
23
22
  EVENT_ENGINE.start()
24
23
  STRATEGY_ENGINE.back_test(
25
24
  start_date=start_date,
@@ -0,0 +1,257 @@
1
+ import abc
2
+ import datetime
3
+ import inspect
4
+ from typing import Iterable
5
+
6
+ from PyQuantKit import Progress, TickData, TransactionData, TradeData, OrderBook
7
+
8
+ from . import LOGGER
9
+
10
+
11
+ class Replay(object, metaclass=abc.ABCMeta):
12
+ @abc.abstractmethod
13
+ def __next__(self): ...
14
+
15
+ @abc.abstractmethod
16
+ def __iter__(self): ...
17
+
18
+
19
+ class SimpleReplay(Replay):
20
+ def __init__(self, **kwargs):
21
+ self.eod = kwargs.pop('eod', None)
22
+ self.bod = kwargs.pop('bod', None)
23
+
24
+ self.replay_task = []
25
+ self.task_progress = 0
26
+ self.task_date = None
27
+ self.progress = Progress(tasks=1, **kwargs)
28
+
29
+ def load(self, data):
30
+ if isinstance(data, dict):
31
+ self.replay_task.extend(list(data.values()))
32
+ else:
33
+ self.replay_task.extend(data)
34
+
35
+ def reset(self):
36
+ self.replay_task.clear()
37
+ self.task_progress = 0
38
+ self.task_date = None
39
+ self.progress.reset()
40
+
41
+ def next_task(self):
42
+ if self.task_progress < len(self.replay_task):
43
+ market_data = self.replay_task[self.task_progress]
44
+ market_time = market_data.market_time
45
+
46
+ if isinstance(market_time, datetime.datetime):
47
+ market_date = market_time.date()
48
+ else:
49
+ market_date = market_time
50
+
51
+ if market_date != self.task_date:
52
+ if callable(self.eod) and self.task_date:
53
+ self.eod(self.task_date)
54
+
55
+ self.task_date = market_date
56
+ self.progress.prompt = f'Replay {market_date:%Y-%m-%d}:'
57
+
58
+ if callable(self.bod):
59
+ self.bod(market_date)
60
+
61
+ self.progress.done_tasks = self.task_progress / len(self.replay_task)
62
+
63
+ if (not self.progress.tick_size) or self.progress.progress >= self.progress.tick_size + self.progress.last_output:
64
+ self.progress.output()
65
+
66
+ self.task_progress += 1
67
+ else:
68
+ raise StopIteration()
69
+
70
+ return market_data
71
+
72
+ def __next__(self):
73
+ try:
74
+ return self.next_task()
75
+ except StopIteration:
76
+ if not self.progress.is_done:
77
+ self.progress.done_tasks = 1
78
+ self.progress.output()
79
+
80
+ self.reset()
81
+ raise StopIteration()
82
+
83
+ def __iter__(self):
84
+ return self
85
+
86
+
87
+ class ProgressiveReplay(Replay):
88
+ """
89
+ progressively loading and replaying market data
90
+
91
+ requires arguments
92
+ loader: a data loading function. Expect loader = Callable(market_date: datetime.date, ticker: str, dtype: str| type) -> dict[any, MarketData]
93
+ start_date & end_date: the given replay period
94
+ or calendar: the given replay calendar.
95
+
96
+ accepts kwargs:
97
+ ticker / tickers: the given symbols to replay, expect a str| list[str]
98
+ dtype / dtypes: the given dtype(s) of symbol to replay, expect a str | type, list[str | type]. default = all, which is (TradeData, TickData, OrderBook)
99
+ subscription / subscribe: the given ticker-dtype pair to replay, expect a list[dict[str, str | type]]
100
+ """
101
+
102
+ def __init__(
103
+ self,
104
+ loader,
105
+ **kwargs
106
+ ):
107
+ self.loader = loader
108
+ self.start_date: datetime.date | None = kwargs.pop('start_date', None)
109
+ self.end_date: datetime.date | None = kwargs.pop('end_date', None)
110
+ self.calendar: list[datetime.date] | None = kwargs.pop('calendar', None)
111
+
112
+ self.eod = kwargs.pop('eod', None)
113
+ self.bod = kwargs.pop('bod', None)
114
+
115
+ self.replay_subscription = {}
116
+ self.replay_calendar = []
117
+ self.replay_task = []
118
+
119
+ self.date_progress = 0
120
+ self.task_progress = 0
121
+ self.progress = Progress(tasks=1, **kwargs)
122
+
123
+ tickers: list[str] = kwargs.pop('ticker', kwargs.pop('tickers', []))
124
+ dtypes: list[str | type] = kwargs.pop('dtype', kwargs.pop('dtypes', [TradeData, TransactionData, OrderBook, TickData]))
125
+
126
+ if not all([arg_name in inspect.getfullargspec(loader).args for arg_name in ['market_date', 'ticker', 'dtype']]):
127
+ raise TypeError('loader function has 3 requires args, market_date, ticker and dtype.')
128
+
129
+ if isinstance(tickers, str):
130
+ tickers = [tickers]
131
+ elif isinstance(tickers, Iterable):
132
+ tickers = list(tickers)
133
+ else:
134
+ raise TypeError(f'Invalid ticker {tickers}, expect str or list[str]')
135
+
136
+ if isinstance(dtypes, str) or inspect.isclass(dtypes):
137
+ dtypes = [dtypes]
138
+ elif isinstance(dtypes, Iterable):
139
+ dtypes = list(dtypes)
140
+ else:
141
+ raise TypeError(f'Invalid dtype {dtypes}, expect str or list[str]')
142
+
143
+ for ticker in tickers:
144
+ for dtype in dtypes:
145
+ self.add_subscription(ticker=ticker, dtype=dtype)
146
+
147
+ subscription = kwargs.pop('subscription', kwargs.pop('subscribe', []))
148
+
149
+ if isinstance(subscription, dict):
150
+ subscription = [subscription]
151
+
152
+ for sub in subscription:
153
+ self.add_subscription(**sub)
154
+
155
+ self.reset()
156
+
157
+ def add_subscription(self, ticker: str, dtype: type | str):
158
+ if isinstance(dtype, str):
159
+ pass
160
+ elif inspect.isclass(dtype):
161
+ dtype = dtype.__name__
162
+ else:
163
+ raise ValueError(f'Invalid dtype {dtype}, expect str or class.')
164
+
165
+ topic = f'{ticker}.{dtype}'
166
+ self.replay_subscription[topic] = (ticker, dtype)
167
+
168
+ def remove_subscription(self, ticker: str, dtype: type | str):
169
+ if isinstance(dtype, str):
170
+ pass
171
+ else:
172
+ dtype = dtype.__name__
173
+
174
+ topic = f'{ticker}.{dtype}'
175
+ self.replay_subscription.pop(topic, None)
176
+
177
+ def reset(self):
178
+ if self.calendar is None:
179
+ self.replay_calendar = [self.start_date + datetime.timedelta(days=i) for i in range((self.end_date - self.start_date).days + 1)]
180
+ else:
181
+ self.replay_calendar = self.calendar
182
+
183
+ self.task_progress = 0
184
+ self.date_progress = sum([1 for _ in self.replay_calendar if _ < self.start_date])
185
+ self.progress.reset()
186
+
187
+ if self.date_progress:
188
+ self.progress.done_tasks = self.date_progress / len(self.replay_calendar)
189
+
190
+ def next_trade_day(self):
191
+ if self.date_progress < len(self.replay_calendar):
192
+ market_date = self.replay_calendar[self.date_progress]
193
+ self.progress.prompt = f'Replay {market_date:%Y-%m-%d} ({self.date_progress + 1} / {len(self.replay_calendar)}):'
194
+ for topic in self.replay_subscription:
195
+ ticker, dtype = self.replay_subscription[topic]
196
+ LOGGER.info(f'{self} loading {market_date} {ticker} {dtype}')
197
+ data = self.loader(market_date=market_date, ticker=ticker, dtype=dtype)
198
+
199
+ if isinstance(data, dict):
200
+ self.replay_task.extend(list(data.values()))
201
+ elif isinstance(data, (list, tuple)):
202
+ self.replay_task.extend(data)
203
+
204
+ self.date_progress += 1
205
+ else:
206
+ raise StopIteration()
207
+
208
+ self.replay_task.sort(key=lambda x: x.market_time)
209
+
210
+ def next_task(self):
211
+ if self.task_progress < len(self.replay_task):
212
+ data = self.replay_task[self.task_progress]
213
+ self.task_progress += 1
214
+ else:
215
+ 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):
216
+ self.eod(market_date=self.replay_calendar[self.date_progress - 1], replay=self)
217
+
218
+ self.replay_task.clear()
219
+ self.task_progress = 0
220
+
221
+ if self.bod is not None and self.date_progress < len(self.replay_calendar):
222
+ self.bod(market_date=self.replay_calendar[self.date_progress], replay=self)
223
+
224
+ self.next_trade_day()
225
+
226
+ data = self.next_task()
227
+
228
+ if self.replay_task and self.replay_calendar:
229
+ current_progress = (self.date_progress - 1 + (self.task_progress / len(self.replay_task))) / len(self.replay_calendar)
230
+ self.progress.done_tasks = current_progress
231
+ else:
232
+ self.progress.done_tasks = 1
233
+
234
+ if (not self.progress.tick_size) \
235
+ or self.progress.progress >= self.progress.tick_size + self.progress.last_output \
236
+ or self.progress.is_done:
237
+ self.progress.output()
238
+
239
+ return data
240
+
241
+ def __next__(self):
242
+ try:
243
+ return self.next_task()
244
+ except StopIteration:
245
+ if not self.progress.is_done:
246
+ self.progress.done_tasks = 1
247
+ self.progress.output()
248
+
249
+ self.reset()
250
+ raise StopIteration()
251
+
252
+ def __iter__(self):
253
+ self.reset()
254
+ return self
255
+
256
+ def __repr__(self):
257
+ return f'{self.__class__.__name__}{{id={id(self)}, from={self.start_date}, to={self.end_date}}}'