PyAlgoEngine 0.5.4.post10__tar.gz → 0.7.4__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 (50) hide show
  1. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/PKG-INFO +1 -1
  2. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/PyAlgoEngine.egg-info/PKG-INFO +1 -1
  3. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/PyAlgoEngine.egg-info/SOURCES.txt +3 -0
  4. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/__init__.py +1 -1
  5. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/apps/backtest/doc_server.py +24 -20
  6. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/apps/bokeh_server.py +57 -14
  7. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/backtest/replay.py +5 -2
  8. pyalgoengine-0.7.4/algo_engine/base/__init__.py +40 -0
  9. pyalgoengine-0.7.4/algo_engine/base/market_buffer.py +571 -0
  10. pyalgoengine-0.7.4/algo_engine/base/market_utils.py +3092 -0
  11. pyalgoengine-0.7.4/algo_engine/base/market_utils_nt.py +188 -0
  12. pyalgoengine-0.7.4/algo_engine/base/market_utils_posix.py +3004 -0
  13. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/engine/algo_engine.py +1 -1
  14. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/engine/trade_engine.py +1 -1
  15. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/utils/data_utils.py +2 -2
  16. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/setup.py +15 -2
  17. pyalgoengine-0.5.4.post10/algo_engine/base/__init__.py +0 -28
  18. pyalgoengine-0.5.4.post10/algo_engine/base/market_utils.py +0 -1686
  19. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/LICENSE +0 -0
  20. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/PyAlgoEngine.egg-info/dependency_links.txt +0 -0
  21. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/PyAlgoEngine.egg-info/requires.txt +0 -0
  22. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/PyAlgoEngine.egg-info/top_level.txt +0 -0
  23. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/README.md +0 -0
  24. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/apps/__init__.py +0 -0
  25. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/apps/backtest/__init__.py +0 -0
  26. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/apps/backtest/tester.py +0 -0
  27. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/apps/backtest/web_app.py +0 -0
  28. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/apps/demo/__init__.py +0 -0
  29. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/apps/demo/test.py +0 -0
  30. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/backtest/__init__.py +0 -0
  31. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/backtest/__main__.py +0 -0
  32. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/backtest/metrics.py +0 -0
  33. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/backtest/sim_match.py +0 -0
  34. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/base/console_utils.py +0 -0
  35. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/base/finance_decimal.py +0 -0
  36. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/base/technical_analysis.py +0 -0
  37. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/base/telemetrics.py +0 -0
  38. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/base/trade_utils.py +0 -0
  39. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/engine/__init__.py +0 -0
  40. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/engine/event_engine.py +0 -0
  41. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/engine/market_engine.py +0 -0
  42. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/monitor/__init__.py +0 -0
  43. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/monitor/advanced_data_interface.py +0 -0
  44. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/profile/__init__.py +0 -0
  45. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/profile/cn.py +0 -0
  46. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/strategy/__init__.py +0 -0
  47. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/strategy/strategy_engine.py +0 -0
  48. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/utils/__init__.py +0 -0
  49. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/algo_engine/utils/commit_regularizer.py +0 -0
  50. {pyalgoengine-0.5.4.post10 → pyalgoengine-0.7.4}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyAlgoEngine
3
- Version: 0.5.4.post10
3
+ Version: 0.7.4
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.1
2
2
  Name: PyAlgoEngine
3
- Version: 0.5.4.post10
3
+ Version: 0.7.4
4
4
  Summary: Basic algo engine
5
5
  Home-page: https://github.com/BolunHan/PyAlgoEngine
6
6
  Author: Bolun.Han
@@ -23,7 +23,10 @@ algo_engine/backtest/sim_match.py
23
23
  algo_engine/base/__init__.py
24
24
  algo_engine/base/console_utils.py
25
25
  algo_engine/base/finance_decimal.py
26
+ algo_engine/base/market_buffer.py
26
27
  algo_engine/base/market_utils.py
28
+ algo_engine/base/market_utils_nt.py
29
+ algo_engine/base/market_utils_posix.py
27
30
  algo_engine/base/technical_analysis.py
28
31
  algo_engine/base/telemetrics.py
29
32
  algo_engine/base/trade_utils.py
@@ -1,4 +1,4 @@
1
- __version__ = "0.5.4.post10"
1
+ __version__ = "0.7.4"
2
2
 
3
3
  import logging
4
4
  import os
@@ -71,18 +71,18 @@ class CandleStick(DocServer):
71
71
  self.theme = StickTheme(profile=self.profile) if self.theme is None else self.theme
72
72
  self.timestamp: float = 0.
73
73
  self.active_bar_data: CandleStick.ActiveBarData | None = None
74
- self.data.update(
75
- index=[],
76
- market_time=[],
77
- open_price=[],
78
- high_price=[],
79
- low_price=[],
80
- close_price=[],
81
- volume=[],
82
- _max_price=[],
83
- _min_price=[],
84
- stick_color=[]
85
- )
74
+ self._data = {
75
+ 'index': [],
76
+ 'market_time': [],
77
+ 'open_price': [],
78
+ 'cs.high_price': [],
79
+ 'cs.low_price': [],
80
+ 'close_price': [],
81
+ 'volume': [],
82
+ '_max_price': [],
83
+ '_min_price': [],
84
+ 'stick_color': []
85
+ }
86
86
 
87
87
  def ts_indices(self) -> list[float]:
88
88
  """generate integer indices
@@ -187,7 +187,7 @@ class CandleStick(DocServer):
187
187
 
188
188
  for doc_id in list(self.bokeh_documents):
189
189
  doc = self.bokeh_documents[doc_id]
190
- new_data = self.bokeh_data_queue[doc_id]
190
+ new_data = self.bokeh_data_pipe[doc_id]
191
191
 
192
192
  self.pipe(sequence=new_data)
193
193
 
@@ -209,8 +209,8 @@ class CandleStick(DocServer):
209
209
  sequence['market_time'].append(datetime.datetime.fromtimestamp(self.active_bar_data['ts_start'], tz=self.profile.time_zone))
210
210
  sequence['open_price'].append(self.active_bar_data['open_price'])
211
211
  sequence['close_price'].append(self.active_bar_data['close_price'])
212
- sequence['high_price'].append(self.active_bar_data['high_price'])
213
- sequence['low_price'].append(self.active_bar_data['low_price'])
212
+ sequence['cs.high_price'].append(self.active_bar_data['high_price'])
213
+ sequence['cs.low_price'].append(self.active_bar_data['low_price'])
214
214
  sequence['volume'].append(self.active_bar_data['volume'])
215
215
  sequence['_max_price'].append(max(self.active_bar_data['open_price'], self.active_bar_data['close_price']))
216
216
  sequence['_min_price'].append(min(self.active_bar_data['open_price'], self.active_bar_data['close_price']))
@@ -224,7 +224,7 @@ class CandleStick(DocServer):
224
224
  from bokeh.plotting import figure, gridplot
225
225
 
226
226
  doc = self.bokeh_documents[doc_id]
227
- source = self.bokeh_source[doc_id]
227
+ source = self.bokeh_data_source[doc_id]
228
228
 
229
229
  tools = [
230
230
  PanTool(dimensions="width", syncable=False),
@@ -242,8 +242,8 @@ class CandleStick(DocServer):
242
242
  ("market_time", "@market_time{%H:%M:%S}"),
243
243
  ("close_price", "@close_price"),
244
244
  ("open_price", "@open_price"),
245
- ("high_price", "@high_price"),
246
- ("low_price", "@low_price"),
245
+ ("high_price", "@{cs.high_price}"),
246
+ ("low_price", "@{cs.low_price}"),
247
247
  ]
248
248
 
249
249
  plot = figure(
@@ -261,8 +261,8 @@ class CandleStick(DocServer):
261
261
  name='candlestick.shade',
262
262
  x0='index',
263
263
  x1='index',
264
- y0='low_price',
265
- y1='high_price',
264
+ y0='cs.low_price',
265
+ y1='cs.high_price',
266
266
  line_width=1,
267
267
  color="black",
268
268
  alpha=0.8,
@@ -325,3 +325,7 @@ class CandleStick(DocServer):
325
325
  df = pd.DataFrame(self.data).set_index(keys='market_time')
326
326
  df = df[['open_price', 'high_price', 'low_price', 'close_price', 'volume']]
327
327
  df.to_csv(filename)
328
+
329
+ @property
330
+ def data(self) -> dict[str, list]:
331
+ return self._data
@@ -24,9 +24,10 @@ class DocServer(object, metaclass=abc.ABCMeta):
24
24
  self.lock = Lock() if lock is None else lock
25
25
 
26
26
  self.bokeh_documents: dict[int, Document] = {}
27
- self.bokeh_source: dict[int, ColumnDataSource] = {}
28
- self.bokeh_data_queue: dict[int, dict[str, list]] = {} # this is a dict of deepcopy of self.data, to update each documents
29
- self.data: dict[str, list] = dict()
27
+ # self.bokeh_source: dict[int, ColumnDataSource] = {}
28
+ self.bokeh_data_pipe: dict[int, dict[str, list[...]]] = {}
29
+ self.bokeh_data_patch: dict[int, dict[str, list[tuple[int, ...]]]] = {}
30
+ self.bokeh_data_source: dict[int, ColumnDataSource] = {}
30
31
 
31
32
  def __str__(self):
32
33
  return f'<{self.__class__.__name__}>(id={id(self.__class__)})'
@@ -63,15 +64,31 @@ class DocServer(object, metaclass=abc.ABCMeta):
63
64
  self.stream(doc_id=doc_id)
64
65
  return
65
66
 
66
- doc = self.bokeh_documents[doc_id]
67
- new_data = self.bokeh_data_queue[doc_id]
68
- source = self.bokeh_source[doc_id]
67
+ # doc = self.bokeh_documents[doc_id]
68
+ data_pipe = self.bokeh_data_pipe[doc_id]
69
+ source = self.bokeh_data_source[doc_id]
69
70
 
70
- source.stream(new_data=deepcopy(new_data), rollover=self.max_size)
71
- for key, seq in new_data.items():
71
+ source.stream(new_data=deepcopy(data_pipe), rollover=self.max_size)
72
+ for key, seq in data_pipe.items():
72
73
  seq.clear()
73
74
 
74
- LOGGER.debug(f'{self.__class__} stream updated!')
75
+ LOGGER.debug(f'{self.__class__} <stream> updated!')
76
+
77
+ def patch(self, doc_id: int = None):
78
+ if doc_id is None:
79
+ for doc_id in list(self.bokeh_documents):
80
+ self.patch(doc_id=doc_id)
81
+ return
82
+
83
+ # doc = self.bokeh_documents[doc_id]
84
+ data_patch = self.bokeh_data_patch[doc_id]
85
+ source = self.bokeh_data_source[doc_id]
86
+
87
+ source.patch(patches=deepcopy(data_patch))
88
+ for key, seq in data_patch.items():
89
+ seq.clear()
90
+
91
+ LOGGER.debug(f'{self.__class__} <patch> updated!')
75
92
 
76
93
  def register_document(self, doc):
77
94
  from bokeh.models import ColumnDataSource
@@ -80,14 +97,17 @@ class DocServer(object, metaclass=abc.ABCMeta):
80
97
 
81
98
  doc_id = uuid.uuid4().int
82
99
 
100
+ data = deepcopy(self.data)
83
101
  self.bokeh_documents[doc_id] = doc
84
- self.bokeh_data_queue[doc_id] = {key: [] for key in self.data}
85
- self.bokeh_source[doc_id] = ColumnDataSource(data=deepcopy(self.data))
102
+ self.bokeh_data_pipe[doc_id] = {key: [] for key in data}
103
+ self.bokeh_data_patch[doc_id] = {key: [] for key in data}
104
+ self.bokeh_data_source[doc_id] = ColumnDataSource(data=data)
86
105
 
87
106
  self.layout(doc_id=doc_id)
88
107
 
89
108
  if self.update_interval:
90
109
  doc.add_periodic_callback(callback=partial(self.stream, doc_id=doc_id), period_milliseconds=int(self.update_interval * 1000))
110
+ doc.add_periodic_callback(callback=partial(self.patch, doc_id=doc_id), period_milliseconds=int(self.update_interval * 1000))
91
111
 
92
112
  doc.on_session_destroyed(partial(self._unregister_document, doc_id=doc_id))
93
113
 
@@ -99,10 +119,19 @@ class DocServer(object, metaclass=abc.ABCMeta):
99
119
  LOGGER.info(f'Session {doc_id} disconnected!')
100
120
 
101
121
  self.bokeh_documents.pop(doc_id)
102
- self.bokeh_source.pop(doc_id)
103
- self.bokeh_data_queue.pop(doc_id)
122
+ self.bokeh_data_pipe.pop(doc_id)
123
+ self.bokeh_data_patch.pop(doc_id)
124
+ self.bokeh_data_source.pop(doc_id)
104
125
  self.lock.release()
105
126
 
127
+ @property
128
+ @abc.abstractmethod
129
+ def data(self) -> dict[str, list]:
130
+ """
131
+ the data used to provide initial values for new bokeh.ColumnDataSource.
132
+ """
133
+ ...
134
+
106
135
 
107
136
  class DocManager(object):
108
137
  def __init__(self, host: str = 'localhost', port: int = 21543, **kwargs):
@@ -136,13 +165,27 @@ class DocManager(object):
136
165
 
137
166
  def serve_bokeh(self):
138
167
  from bokeh.server.server import Server
168
+ import psutil
169
+ import socket
170
+
171
+ # Get all network interfaces and their IP addresses
172
+ addrs = psutil.net_if_addrs()
173
+ websocket_origin = [
174
+ f"{self.host}:{self.port}"
175
+ ]
176
+
177
+ for interface, addr_info in addrs.items():
178
+ for addr in addr_info:
179
+ if addr.family == socket.AF_INET: # Filter only IPv4 addresses
180
+ LOGGER.info(f"Binding network interface: {interface}, IP Address: {addr.address}")
181
+ websocket_origin.append(f"{addr.address}:{self.port}")
139
182
 
140
183
  server = Server(
141
184
  applications=self.doc_server,
142
185
  address=self.bokeh_host,
143
186
  port=self.bokeh_port,
144
187
  check_unused_sessions_milliseconds=(self.bokeh_check_unused_sessions * 1000),
145
- allow_websocket_origin=[f"{self.bokeh_host}:{self.bokeh_port}", f"{self.host}:{self.port}"],
188
+ allow_websocket_origin=[f"{self.bokeh_host}:{self.bokeh_port}"] + websocket_origin,
146
189
  # num_procs=1
147
190
  )
148
191
 
@@ -1,6 +1,7 @@
1
1
  import abc
2
2
  import datetime
3
3
  import inspect
4
+ import operator
4
5
  from typing import Iterable
5
6
 
6
7
  from . import LOGGER
@@ -196,17 +197,17 @@ class ProgressiveReplay(Replay):
196
197
  ticker, dtype = self.replay_subscription[topic]
197
198
  LOGGER.info(f'{self} loading {market_date} {ticker} {dtype}')
198
199
  data = self.loader(market_date=market_date, ticker=ticker, dtype=dtype)
199
-
200
200
  if isinstance(data, dict):
201
201
  self.replay_task.extend(list(data.values()))
202
202
  elif isinstance(data, (list, tuple)):
203
203
  self.replay_task.extend(data)
204
204
 
205
+ LOGGER.info(f'{market_date} data loaded! {len(self.replay_task):,} entries.')
205
206
  self.date_progress += 1
206
207
  else:
207
208
  raise StopIteration()
208
209
 
209
- self.replay_task.sort(key=lambda x: x.market_time)
210
+ self.replay_task.sort(key=operator.attrgetter('timestamp', 'ticker', '__class__.__name__'))
210
211
 
211
212
  def next_task(self):
212
213
  if self.task_progress < len(self.replay_task):
@@ -224,6 +225,8 @@ class ProgressiveReplay(Replay):
224
225
 
225
226
  self.next_trade_day()
226
227
 
228
+ # the bod process should be moved here!
229
+
227
230
  data = self.next_task()
228
231
 
229
232
  if self.replay_task and self.replay_calendar:
@@ -0,0 +1,40 @@
1
+ import logging
2
+ import os
3
+
4
+ from .telemetrics import LOGGER
5
+ from ..profile import PROFILE
6
+
7
+
8
+ def set_logger(logger: logging.Logger):
9
+ global LOGGER
10
+ LOGGER = logger
11
+
12
+ market_utils.LOGGER = logger.getChild('MarketUtils')
13
+ trade_utils.LOGGER = logger.getChild('TradeUtils')
14
+ technical_analysis.LOGGER = logger.getChild('TA')
15
+ console_utils.LOGGER = logger.getChild('Console')
16
+
17
+
18
+ from .finance_decimal import FinancialDecimal
19
+
20
+ if os.name == 'nt':
21
+ from .market_utils_nt import TransactionSide, OrderType, MarketData, OrderBook, BarData, DailyBar, CandleStick, TickData, TransactionData, TradeData, OrderData, MarketDataBuffer, MarketDataRingBuffer
22
+ else:
23
+ from .market_utils_posix import TransactionSide, OrderType, MarketData, OrderBook, BarData, DailyBar, CandleStick, TickData, TransactionData, TradeData, OrderData, MarketDataBuffer, MarketDataRingBuffer
24
+
25
+ # from .market_utils_posix import OrderType
26
+ # from .market_utils import TransactionSide, MarketData, OrderBook, BarData, DailyBar, CandleStick, TickData, TransactionData, TradeData
27
+ # from .market_buffer import MarketDataPointer, MarketDataMemoryBuffer, OrderBookPointer, OrderBookBuffer, BarDataPointer, BarDataBuffer, TickDataPointer, TickDataBuffer, TransactionDataPointer, TransactionDataBuffer
28
+
29
+ from .technical_analysis import TechnicalAnalysis
30
+ from .trade_utils import OrderState, TradeInstruction, TradeReport
31
+ from .console_utils import Progress, GetInput, GetArgs, count_ordinal, TerminalStyle, InteractiveShell, ShellTransfer
32
+
33
+ __all__ = ['PROFILE',
34
+ 'FinancialDecimal',
35
+ 'TransactionSide', 'OrderType', 'MarketData', 'OrderBook', 'BarData', 'DailyBar', 'CandleStick', 'TickData', 'TransactionData', 'TradeData', 'OrderData', 'MarketDataBuffer', 'MarketDataRingBuffer',
36
+ # 'MarketDataMemoryBuffer', 'OrderBookBuffer', 'BarDataBuffer', 'TickDataBuffer', 'TransactionDataBuffer',
37
+ # 'MarketDataPointer', 'OrderBookPointer', 'BarDataPointer', 'TickDataPointer', 'TransactionDataPointer',
38
+ 'TechnicalAnalysis',
39
+ 'OrderState', 'TradeInstruction', 'TradeReport',
40
+ 'Progress', 'GetInput', 'GetArgs', 'count_ordinal', 'TerminalStyle', 'InteractiveShell', 'ShellTransfer']