PyAlgoEngine 0.8.0a14__tar.gz → 0.8.0a16__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.8.0a14 → pyalgoengine-0.8.0a16}/PKG-INFO +1 -1
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/PyAlgoEngine.egg-info/PKG-INFO +1 -1
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/__init__.py +1 -1
- pyalgoengine-0.8.0a16/algo_engine/backtest/__init__.py +19 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/backtest/replay.py +113 -24
- pyalgoengine-0.8.0a14/algo_engine/backtest/__init__.py +0 -19
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/LICENSE +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/PyAlgoEngine.egg-info/SOURCES.txt +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/PyAlgoEngine.egg-info/dependency_links.txt +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/PyAlgoEngine.egg-info/requires.txt +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/PyAlgoEngine.egg-info/top_level.txt +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/README.md +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/apps/__init__.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/apps/backtest/__init__.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/apps/backtest/doc_server.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/apps/backtest/tester.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/apps/backtest/web_app.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/apps/bokeh_server.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/apps/demo/__init__.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/apps/demo/test.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/apps/sim_input/__init__.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/apps/sim_input/client.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/apps/sim_input/sim_keyboard.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/apps/sim_input/sim_mouse.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/apps/sim_input/window.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/backtest/__main__.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/backtest/metrics.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/backtest/sim_match.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/base/__init__.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/base/candlestick.pyi +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/base/console_utils.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/base/finance_decimal.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/base/market_data.pyi +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/base/market_data_buffer.pyi +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/base/market_utils_nt.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/base/market_utils_posix.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/base/technical_analysis.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/base/telemetrics.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/base/tick.pyi +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/base/trade_utils.pyi +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/base/transaction.pyi +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/engine/__init__.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/engine/algo_engine.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/engine/event_engine.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/engine/market_engine.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/engine/trade_engine.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/monitor/__init__.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/monitor/advanced_data_interface.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/profile/__init__.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/profile/cn.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/strategy/__init__.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/strategy/strategy_engine.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/utils/__init__.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/utils/commit_regularizer.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/utils/data_utils.py +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/setup.cfg +0 -0
- {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/setup.py +0 -0
|
@@ -0,0 +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 MarketDateCallable, MarketDataLoader, MarketDataBulkLoader, Replay, SimpleReplay, ProgressReplay, ProgressiveReplay
|
|
17
|
+
from .sim_match import SimMatch
|
|
18
|
+
|
|
19
|
+
__all__ = ['MarketDateCallable', 'MarketDataLoader', 'MarketDataBulkLoader', 'Replay', 'SimpleReplay', 'ProgressReplay', 'ProgressiveReplay', 'SimMatch']
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import abc
|
|
2
2
|
import datetime
|
|
3
|
+
import inspect
|
|
3
4
|
import operator
|
|
4
5
|
import warnings
|
|
5
|
-
from collections.abc import Sequence, Mapping, Iterable
|
|
6
|
-
from typing import Literal, Protocol, runtime_checkable
|
|
6
|
+
from collections.abc import Sequence, Mapping, Iterable, Callable
|
|
7
|
+
from typing import Literal, Protocol, runtime_checkable, get_type_hints
|
|
7
8
|
|
|
8
9
|
from . import LOGGER
|
|
9
10
|
from ..base import MarketData, DataType, MarketDataBuffer
|
|
@@ -30,6 +31,60 @@ class MarketDataBulkLoader(Protocol):
|
|
|
30
31
|
pass
|
|
31
32
|
|
|
32
33
|
|
|
34
|
+
def check_protocol_signature(func: Callable, protocol: type) -> bool:
|
|
35
|
+
if not callable(func):
|
|
36
|
+
raise TypeError(f"{func} is not callable")
|
|
37
|
+
|
|
38
|
+
proto_sig = inspect.signature(protocol.__call__)
|
|
39
|
+
func_sig = inspect.signature(func)
|
|
40
|
+
|
|
41
|
+
proto_params = list(proto_sig.parameters.values())[1:] # Skip 'self'
|
|
42
|
+
func_params = list(func_sig.parameters.values())
|
|
43
|
+
|
|
44
|
+
# Check for *args (VAR_POSITIONAL) — not allowed
|
|
45
|
+
for p in func_params:
|
|
46
|
+
if p.kind == inspect.Parameter.VAR_POSITIONAL:
|
|
47
|
+
raise TypeError(f"{func.__name__} uses *args, which is not allowed")
|
|
48
|
+
|
|
49
|
+
# Check number of required positional args (not counting **kwargs)
|
|
50
|
+
proto_arg_names = [p.name for p in proto_params if p.kind in (
|
|
51
|
+
inspect.Parameter.POSITIONAL_ONLY,
|
|
52
|
+
inspect.Parameter.POSITIONAL_OR_KEYWORD
|
|
53
|
+
)]
|
|
54
|
+
|
|
55
|
+
func_arg_names = [p.name for p in func_params if p.kind in (
|
|
56
|
+
inspect.Parameter.POSITIONAL_ONLY,
|
|
57
|
+
inspect.Parameter.POSITIONAL_OR_KEYWORD
|
|
58
|
+
)]
|
|
59
|
+
|
|
60
|
+
if proto_arg_names != func_arg_names:
|
|
61
|
+
raise TypeError(f"{func.__name__} argument names {func_arg_names} do not match protocol {proto_arg_names}")
|
|
62
|
+
|
|
63
|
+
# Type hint comparison (warn if mismatched, but allow)
|
|
64
|
+
proto_hints = get_type_hints(protocol.__call__)
|
|
65
|
+
func_hints = get_type_hints(func)
|
|
66
|
+
|
|
67
|
+
for pname in proto_arg_names:
|
|
68
|
+
expected = proto_hints.get(pname)
|
|
69
|
+
actual = func_hints.get(pname)
|
|
70
|
+
if expected and actual and expected != actual:
|
|
71
|
+
warnings.warn(
|
|
72
|
+
f"Type hint mismatch for parameter '{pname}': expected {expected}, got {actual}",
|
|
73
|
+
stacklevel=2
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
# Optional: check return type
|
|
77
|
+
expected_ret = proto_hints.get("return")
|
|
78
|
+
actual_ret = func_hints.get("return")
|
|
79
|
+
if expected_ret and actual_ret and expected_ret != actual_ret:
|
|
80
|
+
warnings.warn(
|
|
81
|
+
f"Return type mismatch: expected {expected_ret}, got {actual_ret}",
|
|
82
|
+
stacklevel=2
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
return True
|
|
86
|
+
|
|
87
|
+
|
|
33
88
|
class Replay(object, metaclass=abc.ABCMeta):
|
|
34
89
|
# __slots__ = ('start_date', 'end_date', 'market_date', 'calendar', 'bod', 'eod', 'subscription', '_calendar', '_market_date', '_status', '_progress')
|
|
35
90
|
|
|
@@ -115,27 +170,7 @@ class SimpleReplay(Replay):
|
|
|
115
170
|
for func in self.bod:
|
|
116
171
|
func(self._market_date)
|
|
117
172
|
|
|
118
|
-
|
|
119
|
-
assert hasattr(self, '_buffer') and isinstance(self._buffer, Iterable), f'Without assigning a data loader, the _buffer of {self.__class__.__name__} should be set in bod process.'
|
|
120
|
-
elif isinstance(self.loader, MarketDataLoader):
|
|
121
|
-
md_list = []
|
|
122
|
-
for topic, (_ticker, _dtype) in self.subscription.items():
|
|
123
|
-
LOGGER.info(f'{self} loading {self._market_date} {_ticker} {_dtype}')
|
|
124
|
-
data = self.loader(market_date=self._market_date, ticker=_ticker, dtype=_dtype)
|
|
125
|
-
if isinstance(data, Mapping):
|
|
126
|
-
md_list.extend(list(data.values()))
|
|
127
|
-
elif isinstance(data, Sequence):
|
|
128
|
-
md_list.extend(data)
|
|
129
|
-
else:
|
|
130
|
-
raise TypeError(f'The loader {self.loader} returned {type(data)}. Expect a sequence or mapping of MarketData')
|
|
131
|
-
md_list.sort(key=operator.attrgetter('timestamp', 'ticker', '_dtype'))
|
|
132
|
-
self._buffer = iter(md_list)
|
|
133
|
-
self._buffer_size = len(md_list)
|
|
134
|
-
elif isinstance(self.loader, MarketDataBulkLoader):
|
|
135
|
-
self._buffer = self.loader(market_date=self._market_date, tickers=self.tickers, dtypes=self.dtypes)
|
|
136
|
-
self._buffer_size = len(self._buffer)
|
|
137
|
-
else:
|
|
138
|
-
raise NotImplementedError()
|
|
173
|
+
self._safe_load()
|
|
139
174
|
|
|
140
175
|
return self
|
|
141
176
|
|
|
@@ -166,12 +201,66 @@ class SimpleReplay(Replay):
|
|
|
166
201
|
for func in self.bod:
|
|
167
202
|
func(self._market_date)
|
|
168
203
|
|
|
169
|
-
self.
|
|
204
|
+
self._safe_load()
|
|
170
205
|
return self.__next__()
|
|
171
206
|
|
|
172
207
|
def __repr__(self):
|
|
173
208
|
return f'{self.__class__.__name__}{{id={id(self)}, from={self.start_date}, to={self.end_date}}}'
|
|
174
209
|
|
|
210
|
+
def _bulk_load_protocol(self):
|
|
211
|
+
buffer = self.loader(market_date=self._market_date, tickers=self.tickers, dtypes=self.dtypes)
|
|
212
|
+
buffer.sort()
|
|
213
|
+
|
|
214
|
+
if isinstance(buffer, MarketDataBuffer):
|
|
215
|
+
self._buffer = buffer
|
|
216
|
+
self._buffer_size = len(self._buffer)
|
|
217
|
+
elif isinstance(buffer, Sequence):
|
|
218
|
+
self._buffer = iter(buffer)
|
|
219
|
+
self._buffer_size = len(buffer)
|
|
220
|
+
elif isinstance(buffer, Mapping):
|
|
221
|
+
self._buffer = iter(buffer.values())
|
|
222
|
+
self._buffer_size = len(buffer)
|
|
223
|
+
|
|
224
|
+
def _individual_load_protocol(self):
|
|
225
|
+
buffer = []
|
|
226
|
+
for topic, (_ticker, _dtype) in self.subscription.items():
|
|
227
|
+
LOGGER.info(f'{self} loading {self._market_date} {_ticker} {_dtype}')
|
|
228
|
+
data = self.loader(market_date=self._market_date, ticker=_ticker, dtype=_dtype)
|
|
229
|
+
if isinstance(data, Mapping):
|
|
230
|
+
buffer.extend(list(data.values()))
|
|
231
|
+
elif isinstance(data, Sequence):
|
|
232
|
+
buffer.extend(data)
|
|
233
|
+
else:
|
|
234
|
+
raise TypeError(f'The loader {self.loader} returned {type(data)}. Expect a sequence or mapping of MarketData')
|
|
235
|
+
buffer.sort(key=operator.attrgetter('timestamp', 'ticker', '_dtype'))
|
|
236
|
+
self._buffer = iter(buffer)
|
|
237
|
+
self._buffer_size = len(buffer)
|
|
238
|
+
|
|
239
|
+
def _safe_load(self):
|
|
240
|
+
if self.loader is None:
|
|
241
|
+
assert hasattr(self, '_buffer') and isinstance(self._buffer, Iterable), f'Without assigning a data loader, the _buffer of {self.__class__.__name__} should be set in bod process.'
|
|
242
|
+
return
|
|
243
|
+
|
|
244
|
+
is_bulk_loader = check_protocol_signature(self.loader, MarketDataBulkLoader)
|
|
245
|
+
is_individual_loader = check_protocol_signature(self.loader, MarketDataLoader)
|
|
246
|
+
|
|
247
|
+
if (is_bulk_loader and is_individual_loader) or (not is_bulk_loader and not is_individual_loader):
|
|
248
|
+
try:
|
|
249
|
+
return self._bulk_load_protocol()
|
|
250
|
+
except Exception as e:
|
|
251
|
+
LOGGER.info('Failed to load data using MarketDataBulkLoader protocol!')
|
|
252
|
+
|
|
253
|
+
try:
|
|
254
|
+
return self._individual_load_protocol()
|
|
255
|
+
except Exception as e:
|
|
256
|
+
LOGGER.info('Failed to load data using MarketDataLoader protocol!')
|
|
257
|
+
raise
|
|
258
|
+
|
|
259
|
+
if is_bulk_loader:
|
|
260
|
+
return self._bulk_load_protocol()
|
|
261
|
+
|
|
262
|
+
return self._individual_load_protocol()
|
|
263
|
+
|
|
175
264
|
@property
|
|
176
265
|
def progress(self) -> float:
|
|
177
266
|
if not hasattr(self, '_buffer'):
|
|
@@ -1,19 +0,0 @@
|
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/monitor/advanced_data_interface.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|