PyAlgoEngine 0.8.0a17__tar.gz → 0.8.0.post2__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 (58) hide show
  1. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/PKG-INFO +1 -1
  2. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/PyAlgoEngine.egg-info/PKG-INFO +1 -1
  3. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/PyAlgoEngine.egg-info/SOURCES.txt +1 -0
  4. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/__init__.py +1 -1
  5. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/apps/backtest/tester.py +11 -9
  6. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/backtest/__init__.py +2 -2
  7. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/backtest/replay.py +119 -23
  8. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/base/__init__.py +5 -0
  9. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/base/market_data.pyi +3 -0
  10. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/base/market_data_buffer.pyi +12 -2
  11. pyalgoengine-0.8.0.post2/algo_engine/base/trade_utils_native.py +693 -0
  12. pyalgoengine-0.8.0.post2/algo_engine/profile/__init__.py +236 -0
  13. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/strategy/strategy_engine.py +6 -5
  14. pyalgoengine-0.8.0a17/algo_engine/profile/__init__.py +0 -121
  15. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/LICENSE +0 -0
  16. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/PyAlgoEngine.egg-info/dependency_links.txt +0 -0
  17. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/PyAlgoEngine.egg-info/requires.txt +0 -0
  18. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/PyAlgoEngine.egg-info/top_level.txt +0 -0
  19. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/README.md +0 -0
  20. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/apps/__init__.py +0 -0
  21. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/apps/backtest/__init__.py +0 -0
  22. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/apps/backtest/doc_server.py +0 -0
  23. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/apps/backtest/web_app.py +0 -0
  24. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/apps/bokeh_server.py +0 -0
  25. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/apps/demo/__init__.py +0 -0
  26. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/apps/demo/test.py +0 -0
  27. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/apps/sim_input/__init__.py +0 -0
  28. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/apps/sim_input/client.py +0 -0
  29. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/apps/sim_input/sim_keyboard.py +0 -0
  30. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/apps/sim_input/sim_mouse.py +0 -0
  31. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/apps/sim_input/window.py +0 -0
  32. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/backtest/__main__.py +0 -0
  33. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/backtest/metrics.py +0 -0
  34. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/backtest/sim_match.py +0 -0
  35. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/base/candlestick.pyi +0 -0
  36. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/base/console_utils.py +0 -0
  37. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/base/finance_decimal.py +0 -0
  38. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/base/market_utils_nt.py +0 -0
  39. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/base/market_utils_posix.py +0 -0
  40. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/base/technical_analysis.py +0 -0
  41. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/base/telemetrics.py +0 -0
  42. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/base/tick.pyi +0 -0
  43. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/base/trade_utils.pyi +0 -0
  44. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/base/transaction.pyi +0 -0
  45. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/engine/__init__.py +0 -0
  46. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/engine/algo_engine.py +0 -0
  47. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/engine/event_engine.py +0 -0
  48. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/engine/market_engine.py +0 -0
  49. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/engine/trade_engine.py +0 -0
  50. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/monitor/__init__.py +0 -0
  51. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/monitor/advanced_data_interface.py +0 -0
  52. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/profile/cn.py +0 -0
  53. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/strategy/__init__.py +0 -0
  54. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/utils/__init__.py +0 -0
  55. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/utils/commit_regularizer.py +0 -0
  56. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/algo_engine/utils/data_utils.py +0 -0
  57. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/setup.cfg +0 -0
  58. {pyalgoengine-0.8.0a17 → pyalgoengine-0.8.0.post2}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyAlgoEngine
3
- Version: 0.8.0a17
3
+ Version: 0.8.0.post2
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.0a17
3
+ Version: 0.8.0.post2
4
4
  Summary: Basic algo engine
5
5
  Home-page: https://github.com/BolunHan/PyAlgoEngine
6
6
  Author: Bolun.Han
@@ -37,6 +37,7 @@ algo_engine/base/technical_analysis.py
37
37
  algo_engine/base/telemetrics.py
38
38
  algo_engine/base/tick.pyi
39
39
  algo_engine/base/trade_utils.pyi
40
+ algo_engine/base/trade_utils_native.py
40
41
  algo_engine/base/transaction.pyi
41
42
  algo_engine/engine/__init__.py
42
43
  algo_engine/engine/algo_engine.py
@@ -1,4 +1,4 @@
1
- __version__ = "0.8.0.alpha17"
1
+ __version__ = "0.8.0.post2"
2
2
 
3
3
  import logging
4
4
  import os
@@ -8,7 +8,7 @@ import numpy as np
8
8
  from algo_engine.backtest.metrics import TradeMetrics
9
9
  from . import LOGGER
10
10
  from .web_app import WebApp
11
- from ...backtest import SimMatch, ProgressiveReplay
11
+ from ...backtest import SimMatch, ProgressReplay
12
12
  from ...base import MarketData, TradeReport, TradeInstruction
13
13
  from ...profile import Profile, PROFILE
14
14
 
@@ -109,17 +109,18 @@ class Tester(object, metaclass=abc.ABCMeta):
109
109
  pass
110
110
 
111
111
  def run(self, **kwargs):
112
- replay = ProgressiveReplay(
112
+ replay = ProgressReplay(
113
113
  loader=self.load_data,
114
- tickers=list(self.subscription),
115
- dtype=['TickData', 'TradeData'],
116
114
  start_date=self.start_date,
117
115
  end_date=self.end_date,
118
116
  bod=self.bod,
119
117
  eod=self.eod,
120
- tick_size=kwargs.get('progress_tick_size', 0.001),
121
118
  )
122
119
 
120
+ for ticker in self.subscription:
121
+ replay.add_subscription(ticker, dtype='TickData')
122
+ replay.add_subscription(ticker, dtype='TradeData')
123
+
123
124
  _start_ts = time.time()
124
125
 
125
126
  for market_data in replay:
@@ -222,17 +223,18 @@ class StrategyTester(Tester):
222
223
  if not self.event_engine.active:
223
224
  self.event_engine.start()
224
225
 
225
- replay = ProgressiveReplay(
226
+ replay = ProgressReplay(
226
227
  loader=self.load_data,
227
- tickers=list(self.subscription),
228
- dtype=['TickData', 'TradeData'],
229
228
  start_date=self.start_date,
230
229
  end_date=self.end_date,
231
230
  bod=self.bod,
232
231
  eod=self.eod,
233
- tick_size=kwargs.get('progress_tick_size', 0.001),
234
232
  )
235
233
 
234
+ for ticker in self.subscription:
235
+ replay.add_subscription(ticker, dtype='TickData')
236
+ replay.add_subscription(ticker, dtype='TradeData')
237
+
236
238
  _start_ts = time.time()
237
239
 
238
240
  for market_data in replay:
@@ -13,7 +13,7 @@ def set_logger(logger: logging.Logger):
13
13
  sim_match.LOGGER = LOGGER.getChild('SimMatch')
14
14
 
15
15
 
16
- from .replay import PyDataScope, MarketDateCallable, MarketDataLoader, MarketDataBulkLoader, Replay, SimpleReplay, ProgressReplay, ProgressiveReplay
16
+ from .replay import PyDataScope, MarketDateCallable, MarketDataLoader, MarketDataBulkLoader, Replay, SimpleReplay, ProgressReplay
17
17
  from .sim_match import SimMatch
18
18
 
19
- __all__ = ['PyDataScope', 'MarketDateCallable', 'MarketDataLoader', 'MarketDataBulkLoader', 'Replay', 'SimpleReplay', 'ProgressReplay', 'ProgressiveReplay', 'SimMatch']
19
+ __all__ = ['PyDataScope', 'MarketDateCallable', 'MarketDataLoader', 'MarketDataBulkLoader', 'Replay', 'SimpleReplay', 'ProgressReplay', 'SimMatch']
@@ -2,6 +2,7 @@ import abc
2
2
  import datetime
3
3
  import enum
4
4
  import inspect
5
+ import logging
5
6
  import operator
6
7
  import warnings
7
8
  from collections.abc import Sequence, Mapping, Iterable, Callable
@@ -244,7 +245,7 @@ class SimpleReplay(Replay):
244
245
 
245
246
  def __iter__(self):
246
247
  self._calendar = self.calendar or [self.start_date + datetime.timedelta(days=i) for i in range((self.end_date - self.start_date).days + 1)]
247
- self._market_date = self.market_date or sorted(_ for _ in self._calendar if _ >= self.market_date)[0]
248
+ self._market_date = sorted(_ for _ in self._calendar if _ >= self.market_date)[0]
248
249
  self._status = {market_date: 'skipped' if market_date < self.market_date else 'idle' for market_date in self._calendar}
249
250
  self._idx_buffer = 0
250
251
  self._idx_date = sum([1 for _ in self._calendar if _ < self.market_date])
@@ -353,7 +354,7 @@ class SimpleReplay(Replay):
353
354
  if not hasattr(self, '_buffer'):
354
355
  raise RuntimeError(f'{self.__class__.__name__} not started yet.')
355
356
 
356
- return (self._idx_date + (self._idx_buffer / self._buffer_size - 1)) / len(self._calendar)
357
+ return (self._idx_date + self._idx_buffer / self._buffer_size) / len(self._calendar)
357
358
 
358
359
  @property
359
360
  def tickers(self) -> list[str]:
@@ -387,7 +388,7 @@ class ProgressReplay(SimpleReplay):
387
388
  calendar: Sequence[datetime.date] = None,
388
389
  bod: MarketDateCallable = None,
389
390
  eod: MarketDateCallable = None,
390
- **tqdm_kwargs
391
+ **pbar_config
391
392
  ):
392
393
  super().__init__(
393
394
  loader=loader,
@@ -399,40 +400,135 @@ class ProgressReplay(SimpleReplay):
399
400
  eod=eod
400
401
  )
401
402
 
402
- self._tqdm_kwargs = {
403
- 'total': 1,
404
- 'unit_scale': True,
405
- 'unit': 'percent',
406
- 'mininterval': 0.1,
407
- 'miniters': 0.001,
408
- **tqdm_kwargs
403
+ self.pbar_config = {
404
+ 'backend': pbar_config.pop('backend', 'tqdm'), # tqdm or native
405
+ 'config': pbar_config,
409
406
  }
410
407
  self._pbar = None
411
- self.add_bod(self._update_progress_bar, priority=0)
408
+ self.add_bod(self._update_pbar_prefix, priority=0)
409
+
410
+ def _init_pbar(self):
411
+ pbar_backend = self.pbar_config['backend']
412
+ match pbar_backend:
413
+ case 'tqdm':
414
+ from tqdm.auto import tqdm
415
+ from tqdm.std import tqdm as tqdm_std
416
+ from tqdm.contrib.logging import _TqdmLoggingHandler, _get_first_found_console_logging_handler, _is_console_logging_handler
417
+
418
+ tqdm_secondary_config = {
419
+ 'total': 1,
420
+ 'unit_scale': True,
421
+ 'unit': 'percent',
422
+ 'mininterval': 0.1,
423
+ 'miniters': 0.001,
424
+ **self.pbar_config['config'],
425
+ }
426
+ self._pbar_secondary = tqdm(**tqdm_secondary_config)
427
+
428
+ tqdm_config = {
429
+ 'total': 1,
430
+ 'unit_scale': True,
431
+ 'unit': 'percent',
432
+ 'mininterval': 0.1,
433
+ 'miniters': 0.001,
434
+ **self.pbar_config['config'],
435
+ }
436
+ self._pbar = tqdm(**tqdm_config)
437
+
438
+ self._update_pbar_progress = self._update_tqdm_progress
439
+ self.pbar_config['loggers'] = loggers = [LOGGER.root] + [_ for _ in LOGGER.root.manager.loggerDict.values() if isinstance(_, logging.Logger) and _.handlers]
440
+ self.pbar_config['original_handlers_list'] = [logger.handlers for logger in loggers]
441
+ for logger in loggers:
442
+ tqdm_handler = _TqdmLoggingHandler(tqdm_std)
443
+ orig_handler = _get_first_found_console_logging_handler(logger.handlers)
444
+ if orig_handler is not None:
445
+ tqdm_handler.setFormatter(orig_handler.formatter)
446
+ tqdm_handler.stream = orig_handler.stream
447
+ logger.handlers = [handler for handler in logger.handlers if not _is_console_logging_handler(handler)] + [tqdm_handler]
448
+ case 'native':
449
+ from ..base import Progress
450
+
451
+ progress_config = dict(
452
+ tasks=1,
453
+ tick_size=0.001,
454
+ **self.pbar_config['config'],
455
+ )
456
+
457
+ self._pbar = Progress(**progress_config)
458
+ self._update_pbar_progress = self._update_native_progress
459
+ case _:
460
+ raise NotImplementedError(f'Invalid pbar backend {pbar_backend}')
461
+
462
+ def _update_pbar_prefix(self, market_date: datetime.date):
463
+ pbar_backend = self.pbar_config['backend']
464
+ match pbar_backend:
465
+ case 'tqdm':
466
+ prompt = f'Progress Total ({self._idx_date + 1} / {len(self._calendar)})'
467
+ prompt_secondary = f'Progress [{market_date:%Y-%m-%d}]'
468
+ prompt_length = max(len(prompt), len(prompt_secondary))
469
+ self._pbar.set_description(prompt.ljust(prompt_length))
470
+ self._pbar.refresh()
471
+ self._pbar_secondary.n = 0
472
+ self._pbar_secondary.set_description(prompt_secondary.ljust(prompt_length))
473
+ self._pbar_secondary.refresh()
474
+ case 'native':
475
+ self._pbar.prompt = f'Replay {market_date:%Y-%m-%d} ({self._idx_date + 1} / {len(self._calendar)}):'
476
+ self._pbar.output()
477
+ case _:
478
+ raise NotImplementedError(f'Invalid pbar backend {pbar_backend}')
479
+
480
+ def _close_pbar(self):
481
+ pbar_backend = self.pbar_config['backend']
482
+ match pbar_backend:
483
+ case 'tqdm':
484
+ for logger, original_handlers in zip(self.pbar_config['loggers'], self.pbar_config['original_handlers_list']):
485
+ logger.handlers = original_handlers
486
+
487
+ self._pbar_secondary.n = 1
488
+ # self._pbar_secondary.refresh()
489
+ self._pbar_secondary.close()
490
+ self._pbar_secondary = None
491
+
492
+ self._pbar.n = 1
493
+ # self._pbar.refresh()
494
+ self._pbar.close()
495
+ self._pbar = None
496
+ case 'native':
497
+ self._pbar.done_tasks = 1
498
+ self._pbar.output()
499
+ case _:
500
+ raise NotImplementedError(f'Invalid pbar backend {pbar_backend}')
501
+
502
+ def _update_tqdm_progress(self):
503
+ self._pbar.n = self.progress
504
+ self._pbar.update(0)
505
+
506
+ self._pbar_secondary.n = self._idx_buffer / self._buffer_size
507
+ self._pbar_secondary.update(0)
508
+
509
+ def _update_native_progress(self):
510
+ self._pbar.done_tasks = self.progress
511
+
512
+ if (not self._pbar.tick_size) \
513
+ or self._pbar.progress >= self._pbar.tick_size + self._pbar.last_output \
514
+ or self._pbar.is_done:
515
+ self._pbar.output()
412
516
 
413
517
  def __iter__(self):
414
- from tqdm.auto import tqdm
415
- self._pbar = tqdm(**self._tqdm_kwargs)
518
+ self._init_pbar()
416
519
  return super().__iter__()
417
520
 
418
521
  def __next__(self) -> MarketData:
419
522
  try:
420
523
  result = super().__next__()
421
- if self._pbar:
422
- self._pbar.update(self.progress)
423
- self._pbar.refresh()
524
+ if self._pbar is not None:
525
+ self._update_pbar_progress()
424
526
  return result
425
527
  except StopIteration:
426
528
  if self._pbar is not None:
427
- self._pbar.close()
428
- self._pbar = None
529
+ self._close_pbar()
429
530
  raise
430
531
 
431
- def _update_progress_bar(self, market_date: datetime.date):
432
- if self._pbar:
433
- self._pbar.set_description(f'Replay {market_date:%Y-%m-%d} ({self._idx_date + 1} / {len(self._calendar)})')
434
- self._pbar.refresh()
435
-
436
532
 
437
533
  class ProgressiveReplay(SimpleReplay):
438
534
  """
@@ -5,6 +5,8 @@ import pathlib
5
5
  from .telemetrics import LOGGER
6
6
  from ..profile import PROFILE
7
7
 
8
+ USE_CYTHON = True
9
+
8
10
 
9
11
  def set_logger(logger: logging.Logger):
10
12
  global LOGGER
@@ -15,6 +17,9 @@ def set_logger(logger: logging.Logger):
15
17
 
16
18
 
17
19
  def check_cython_module(cython_module) -> bool:
20
+ if not USE_CYTHON:
21
+ return False
22
+
18
23
  for name in cython_module:
19
24
  cython_ext = '.pyd' if os.name == 'nt' else '.so'
20
25
  for file in pathlib.Path(__file__).parent.glob(f'*{cython_ext}'):
@@ -19,6 +19,9 @@ class MarketData:
19
19
 
20
20
  def __init__(self, ticker: str, timestamp: float, **kwargs: Any) -> None: ...
21
21
 
22
+ def update(self, name: str, value: Any) -> None:
23
+ ...
24
+
22
25
  @classmethod
23
26
  def buffer_size(cls) -> int: ...
24
27
 
@@ -31,7 +31,7 @@ class MarketDataBuffer:
31
31
 
32
32
  def to_bytes(self) -> bytes: ...
33
33
 
34
- def update(self, dtype: int, **kwargs: dict[str, Any]) -> None: ...
34
+ def update(self, dtype: int, **kwargs: Any) -> None: ...
35
35
 
36
36
  def __getitem__(self, idx: int) -> MarketData | TransactionData | OrderData | TickDataLite | TickData | BarData: ...
37
37
 
@@ -79,6 +79,10 @@ class MarketDataConcurrentBuffer:
79
79
  capacity: int = ...
80
80
  ) -> None: ...
81
81
 
82
+ def get_head(self, worker_id: int) -> int: ...
83
+
84
+ def min_head(self) -> int: ...
85
+
82
86
  def is_empty(self, worker_id: int) -> bool: ...
83
87
 
84
88
  def is_empty_all(self) -> bool: ...
@@ -89,4 +93,10 @@ class MarketDataConcurrentBuffer:
89
93
 
90
94
  def get(self, idx: int) -> MarketData | TransactionData | OrderData | TickDataLite | TickData | BarData: ...
91
95
 
92
- def listen(self, worker_id: int, timeout: float = ...) -> MarketData | TransactionData | OrderData | TickDataLite | TickData | BarData: ...
96
+ def listen(self, worker_id: int, block: bool = True, timeout: float = ...) -> MarketData | TransactionData | OrderData | TickDataLite | TickData | BarData: ...
97
+
98
+ @property
99
+ def head(self) -> list[int]: ...
100
+
101
+ @property
102
+ def tail(self) -> int: ...