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.
Files changed (57) hide show
  1. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/PKG-INFO +1 -1
  2. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/PyAlgoEngine.egg-info/PKG-INFO +1 -1
  3. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/__init__.py +1 -1
  4. pyalgoengine-0.8.0a16/algo_engine/backtest/__init__.py +19 -0
  5. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/backtest/replay.py +113 -24
  6. pyalgoengine-0.8.0a14/algo_engine/backtest/__init__.py +0 -19
  7. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/LICENSE +0 -0
  8. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/PyAlgoEngine.egg-info/SOURCES.txt +0 -0
  9. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/PyAlgoEngine.egg-info/dependency_links.txt +0 -0
  10. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/PyAlgoEngine.egg-info/requires.txt +0 -0
  11. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/PyAlgoEngine.egg-info/top_level.txt +0 -0
  12. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/README.md +0 -0
  13. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/apps/__init__.py +0 -0
  14. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/apps/backtest/__init__.py +0 -0
  15. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/apps/backtest/doc_server.py +0 -0
  16. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/apps/backtest/tester.py +0 -0
  17. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/apps/backtest/web_app.py +0 -0
  18. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/apps/bokeh_server.py +0 -0
  19. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/apps/demo/__init__.py +0 -0
  20. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/apps/demo/test.py +0 -0
  21. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/apps/sim_input/__init__.py +0 -0
  22. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/apps/sim_input/client.py +0 -0
  23. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/apps/sim_input/sim_keyboard.py +0 -0
  24. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/apps/sim_input/sim_mouse.py +0 -0
  25. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/apps/sim_input/window.py +0 -0
  26. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/backtest/__main__.py +0 -0
  27. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/backtest/metrics.py +0 -0
  28. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/backtest/sim_match.py +0 -0
  29. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/base/__init__.py +0 -0
  30. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/base/candlestick.pyi +0 -0
  31. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/base/console_utils.py +0 -0
  32. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/base/finance_decimal.py +0 -0
  33. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/base/market_data.pyi +0 -0
  34. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/base/market_data_buffer.pyi +0 -0
  35. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/base/market_utils_nt.py +0 -0
  36. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/base/market_utils_posix.py +0 -0
  37. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/base/technical_analysis.py +0 -0
  38. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/base/telemetrics.py +0 -0
  39. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/base/tick.pyi +0 -0
  40. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/base/trade_utils.pyi +0 -0
  41. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/base/transaction.pyi +0 -0
  42. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/engine/__init__.py +0 -0
  43. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/engine/algo_engine.py +0 -0
  44. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/engine/event_engine.py +0 -0
  45. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/engine/market_engine.py +0 -0
  46. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/engine/trade_engine.py +0 -0
  47. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/monitor/__init__.py +0 -0
  48. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/monitor/advanced_data_interface.py +0 -0
  49. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/profile/__init__.py +0 -0
  50. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/profile/cn.py +0 -0
  51. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/strategy/__init__.py +0 -0
  52. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/strategy/strategy_engine.py +0 -0
  53. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/utils/__init__.py +0 -0
  54. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/utils/commit_regularizer.py +0 -0
  55. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/algo_engine/utils/data_utils.py +0 -0
  56. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/setup.cfg +0 -0
  57. {pyalgoengine-0.8.0a14 → pyalgoengine-0.8.0a16}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyAlgoEngine
3
- Version: 0.8.0a14
3
+ Version: 0.8.0a16
4
4
  Summary: Basic algo engine
5
5
  Home-page: https://github.com/BolunHan/PyAlgoEngine
6
6
  Author: Bolun.Han
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyAlgoEngine
3
- Version: 0.8.0a14
3
+ Version: 0.8.0a16
4
4
  Summary: Basic algo engine
5
5
  Home-page: https://github.com/BolunHan/PyAlgoEngine
6
6
  Author: Bolun.Han
@@ -1,4 +1,4 @@
1
- __version__ = "0.8.0.alpha14"
1
+ __version__ = "0.8.0.alpha16"
2
2
 
3
3
  import logging
4
4
  import os
@@ -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
- if self.loader is None:
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._buffer = self.loader(market_date=self._market_date, tickers=self.tickers, dtypes=self.dtypes)
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