PyAlgoEngine 0.7.7a5__tar.gz → 0.8.0a1__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 (54) hide show
  1. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/PKG-INFO +13 -2
  2. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/PyAlgoEngine.egg-info/PKG-INFO +13 -2
  3. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/PyAlgoEngine.egg-info/SOURCES.txt +1 -0
  4. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/__init__.py +1 -1
  5. pyalgoengine-0.8.0a1/algo_engine/base/market_data_wrapper.py +547 -0
  6. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/base/market_utils_posix.py +8 -2
  7. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/LICENSE +0 -0
  8. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/PyAlgoEngine.egg-info/dependency_links.txt +0 -0
  9. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/PyAlgoEngine.egg-info/requires.txt +0 -0
  10. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/PyAlgoEngine.egg-info/top_level.txt +0 -0
  11. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/README.md +0 -0
  12. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/apps/__init__.py +0 -0
  13. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/apps/backtest/__init__.py +0 -0
  14. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/apps/backtest/doc_server.py +0 -0
  15. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/apps/backtest/tester.py +0 -0
  16. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/apps/backtest/web_app.py +0 -0
  17. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/apps/bokeh_server.py +0 -0
  18. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/apps/demo/__init__.py +0 -0
  19. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/apps/demo/test.py +0 -0
  20. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/apps/sim_input/__init__.py +0 -0
  21. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/apps/sim_input/client.py +0 -0
  22. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/apps/sim_input/sim_keyboard.py +0 -0
  23. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/apps/sim_input/sim_mouse.py +0 -0
  24. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/apps/sim_input/window.py +0 -0
  25. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/backtest/__init__.py +0 -0
  26. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/backtest/__main__.py +0 -0
  27. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/backtest/metrics.py +0 -0
  28. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/backtest/replay.py +0 -0
  29. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/backtest/sim_match.py +0 -0
  30. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/base/__init__.py +0 -0
  31. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/base/console_utils.py +0 -0
  32. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/base/finance_decimal.py +0 -0
  33. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/base/market_buffer.py +0 -0
  34. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/base/market_utils.py +0 -0
  35. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/base/market_utils_nt.py +0 -0
  36. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/base/technical_analysis.py +0 -0
  37. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/base/telemetrics.py +0 -0
  38. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/base/trade_utils.py +0 -0
  39. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/engine/__init__.py +0 -0
  40. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/engine/algo_engine.py +0 -0
  41. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/engine/event_engine.py +0 -0
  42. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/engine/market_engine.py +0 -0
  43. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/engine/trade_engine.py +0 -0
  44. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/monitor/__init__.py +0 -0
  45. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/monitor/advanced_data_interface.py +0 -0
  46. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/profile/__init__.py +0 -0
  47. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/profile/cn.py +0 -0
  48. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/strategy/__init__.py +0 -0
  49. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/strategy/strategy_engine.py +0 -0
  50. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/utils/__init__.py +0 -0
  51. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/utils/commit_regularizer.py +0 -0
  52. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/utils/data_utils.py +0 -0
  53. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/setup.cfg +0 -0
  54. {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: PyAlgoEngine
3
- Version: 0.7.7a5
3
+ Version: 0.8.0a1
4
4
  Summary: Basic algo engine
5
5
  Home-page: https://github.com/BolunHan/PyAlgoEngine
6
6
  Author: Bolun.Han
@@ -22,6 +22,17 @@ Provides-Extra: webapps
22
22
  Requires-Dist: flask; extra == "webapps"
23
23
  Requires-Dist: waitress; extra == "webapps"
24
24
  Requires-Dist: bokeh; extra == "webapps"
25
+ Dynamic: author
26
+ Dynamic: author-email
27
+ Dynamic: classifier
28
+ Dynamic: description
29
+ Dynamic: description-content-type
30
+ Dynamic: home-page
31
+ Dynamic: license-file
32
+ Dynamic: provides-extra
33
+ Dynamic: requires-dist
34
+ Dynamic: requires-python
35
+ Dynamic: summary
25
36
 
26
37
  # PyAlgoEngine
27
38
  python algo trading engine
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.1
1
+ Metadata-Version: 2.4
2
2
  Name: PyAlgoEngine
3
- Version: 0.7.7a5
3
+ Version: 0.8.0a1
4
4
  Summary: Basic algo engine
5
5
  Home-page: https://github.com/BolunHan/PyAlgoEngine
6
6
  Author: Bolun.Han
@@ -22,6 +22,17 @@ Provides-Extra: webapps
22
22
  Requires-Dist: flask; extra == "webapps"
23
23
  Requires-Dist: waitress; extra == "webapps"
24
24
  Requires-Dist: bokeh; extra == "webapps"
25
+ Dynamic: author
26
+ Dynamic: author-email
27
+ Dynamic: classifier
28
+ Dynamic: description
29
+ Dynamic: description-content-type
30
+ Dynamic: home-page
31
+ Dynamic: license-file
32
+ Dynamic: provides-extra
33
+ Dynamic: requires-dist
34
+ Dynamic: requires-python
35
+ Dynamic: summary
25
36
 
26
37
  # PyAlgoEngine
27
38
  python algo trading engine
@@ -29,6 +29,7 @@ algo_engine/base/__init__.py
29
29
  algo_engine/base/console_utils.py
30
30
  algo_engine/base/finance_decimal.py
31
31
  algo_engine/base/market_buffer.py
32
+ algo_engine/base/market_data_wrapper.py
32
33
  algo_engine/base/market_utils.py
33
34
  algo_engine/base/market_utils_nt.py
34
35
  algo_engine/base/market_utils_posix.py
@@ -1,4 +1,4 @@
1
- __version__ = "0.7.7.alpha5"
1
+ __version__ = "0.8.0.alpha1"
2
2
 
3
3
  import logging
4
4
  import os
@@ -0,0 +1,547 @@
1
+ import abc
2
+ import datetime
3
+ import enum
4
+ import inspect
5
+ from typing import TypeVar, Any, Self, Literal
6
+ import logging
7
+ import market_data as market_data_cython
8
+
9
+ LOGGER = logging.getLogger(__name__)
10
+ from algo_engine.profile import PROFILE
11
+
12
+
13
+ class TransactionSide(enum.IntEnum):
14
+ ShortOrder = AskOrder = Offer_to_Short = -3
15
+ ShortOpen = Sell_to_Short = -2
16
+ ShortFilled = LongClose = Sell_to_Unwind = ask = -1
17
+ UNKNOWN = CANCEL = 0
18
+ LongFilled = LongOpen = Buy_to_Long = bid = 1
19
+ ShortClose = Buy_to_Cover = 2
20
+ LongOrder = BidOrder = Bid_to_Long = 3
21
+
22
+ def __neg__(self) -> Self:
23
+ """
24
+ Get the opposite transaction side.
25
+
26
+ Returns:
27
+ TransactionSide: The opposite transaction side.
28
+ """
29
+ if self is self.LongOpen:
30
+ return self.LongClose
31
+ elif self is self.LongClose:
32
+ return self.LongOpen
33
+ elif self is self.ShortOpen:
34
+ return self.ShortClose
35
+ elif self is self.ShortClose:
36
+ return self.ShortOpen
37
+ elif self is self.BidOrder:
38
+ return self.AskOrder
39
+ elif self is self.AskOrder:
40
+ return self.BidOrder
41
+ else:
42
+ LOGGER.warning('No valid registered opposite trade side for {}'.format(self))
43
+ return self.UNKNOWN
44
+
45
+ @classmethod
46
+ def from_offset(cls, direction: str, offset: str) -> Self:
47
+ """
48
+ Determine the transaction side from direction and offset.
49
+
50
+ Args:
51
+ direction (str): The trade direction (e.g., 'buy', 'sell').
52
+ offset (str): The trade offset (e.g., 'open', 'close').
53
+
54
+ Returns:
55
+ TransactionSide: The corresponding transaction side.
56
+
57
+ Raises:
58
+ ValueError: If the direction or offset is not recognized.
59
+ """
60
+ direction = direction.lower()
61
+ offset = offset.lower()
62
+
63
+ if direction in ['buy', 'long', 'b']:
64
+ if offset in ['open', 'wind']:
65
+ return cls.LongOpen
66
+ elif offset in ['close', 'cover', 'unwind']:
67
+ return cls.ShortOpen
68
+ else:
69
+ raise ValueError(f'Not recognized {direction} {offset}')
70
+ elif direction in ['sell', 'short', 's']:
71
+ if offset in ['open', 'wind']:
72
+ return cls.ShortOpen
73
+ elif offset in ['close', 'cover', 'unwind']:
74
+ return cls.LongClose
75
+ else:
76
+ raise ValueError(f'Not recognized {direction} {offset}')
77
+ else:
78
+ raise ValueError(f'Not recognized {direction} {offset}')
79
+
80
+ @classmethod
81
+ def _missing_(cls, value: str | int):
82
+ """
83
+ Handle missing values in the enumeration.
84
+
85
+ Args:
86
+ value (str | int): The value to resolve.
87
+
88
+ Returns:
89
+ TransactionSide: The resolved transaction side, or UNKNOWN if not recognized.
90
+ """
91
+ side_str = str(value).lower()
92
+
93
+ match side_str:
94
+ case 'long' | 'buy' | 'b':
95
+ trade_side = cls.LongOpen
96
+ case 'short' | 'sell' | 's':
97
+ trade_side = cls.LongClose
98
+ case 'short' | 'ss':
99
+ trade_side = cls.ShortOpen
100
+ case 'cover' | 'bc':
101
+ trade_side = cls.ShortClose
102
+ case 'ask':
103
+ trade_side = cls.AskOrder
104
+ case 'bid':
105
+ trade_side = cls.BidOrder
106
+ case _:
107
+ try:
108
+ trade_side = cls.__getitem__(value)
109
+ except Exception as _:
110
+ trade_side = cls.UNKNOWN
111
+ LOGGER.warning('{} is not recognized, return TransactionSide.UNKNOWN'.format(value))
112
+
113
+ return trade_side
114
+
115
+ @property
116
+ def sign(self) -> int:
117
+ """
118
+ Get the sign of the transaction side.
119
+
120
+ Returns:
121
+ int: 1 for buy/long, -1 for sell/short, 0 for unknown.
122
+ """
123
+ if self.value == self.Buy_to_Long.value or self.value == self.Buy_to_Cover.value:
124
+ return 1
125
+ elif self.value == self.Sell_to_Unwind.value or self.value == self.Sell_to_Short.value:
126
+ return -1
127
+ elif self.value == 0:
128
+ return 0
129
+ else:
130
+ frame = inspect.currentframe()
131
+ caller = inspect.getframeinfo(frame.f_back)
132
+ LOGGER.warning(f"Requesting .sign of {self.name} is not recommended, use .order_sign instead.\nCalled from {caller.filename}, line {caller.lineno}.")
133
+ return self.order_sign
134
+
135
+ @property
136
+ def order_sign(self) -> int:
137
+ """
138
+ Get the order sign of the transaction side.
139
+
140
+ Returns:
141
+ int: 1 for long orders, -1 for short orders, 0 for unknown.
142
+ """
143
+ if self.value == self.LongOrder.value:
144
+ return 1
145
+ elif self.value == self.ShortOrder.value:
146
+ return -1
147
+ elif self.value == 0:
148
+ return 0
149
+ else:
150
+ LOGGER.warning(f'Requesting .order_sign of {self.name} is not recommended, use .sign instead')
151
+ return self.sign
152
+
153
+ @property
154
+ def offset(self) -> int:
155
+ """
156
+ Get the offset of the transaction side.
157
+
158
+ Returns:
159
+ int: The offset value, equivalent to the sign.
160
+ """
161
+ return self.sign
162
+
163
+ @property
164
+ def side_name(self) -> str:
165
+ """
166
+ Get the name of the transaction side.
167
+
168
+ Returns:
169
+ str: 'Long', 'Short', 'ask', 'bid', or 'Unknown'.
170
+ """
171
+ if self.value == self.Buy_to_Long.value or self.value == self.Buy_to_Cover.value:
172
+ return 'Long'
173
+ elif self.value == self.Sell_to_Unwind.value or self.value == self.Sell_to_Short.value:
174
+ return 'Short'
175
+ elif self.value == self.Offer_to_Short.value:
176
+ return 'ask'
177
+ elif self.value == self.Bid_to_Long.value:
178
+ return 'bid'
179
+ else:
180
+ return 'Unknown'
181
+
182
+ @property
183
+ def offset_name(self) -> str:
184
+ """
185
+ Get the offset name of the transaction side.
186
+
187
+ Returns:
188
+ str: 'Open', 'Close', 'ask', 'bid', or 'Unknown'.
189
+ """
190
+ if self.value == self.Buy_to_Long.value or self.value == self.Sell_to_Short.value:
191
+ return 'Open'
192
+ elif self.value == self.Buy_to_Cover.value or self.value == self.Sell_to_Unwind.value:
193
+ return 'Close'
194
+ elif self.value == self.Offer_to_Short.value or self.value == self.Bid_to_Long.value:
195
+ LOGGER.warning(f'Requesting offset of {self.name} is not supported, returns {self.side_name}')
196
+ return self.side_name
197
+ else:
198
+ return 'Unknown'
199
+
200
+
201
+ class OrderType(enum.IntEnum):
202
+ UNKNOWN = -20
203
+ CancelOrder = -10
204
+ Generic = 0
205
+ LimitOrder = 10
206
+ LimitMarketMaking = 11
207
+ MarketOrder = 2
208
+ FOK = 21
209
+ FAK = 22
210
+ IOC = 23
211
+
212
+
213
+ class MarketData(market_data_cython.MarketData, metaclass=abc.ABCMeta):
214
+ """
215
+ Python wrapper for MarketData Cython class.
216
+ Provides pickle serialization support.
217
+ """
218
+
219
+ @classmethod
220
+ def from_buffer(cls, buffer, **kwargs):
221
+ self = super().from_buffer(buffer)
222
+ self.__dict__.update(kwargs)
223
+ return self
224
+
225
+ @abc.abstractmethod
226
+ def __reduce__(self):
227
+ ...
228
+
229
+ def __copy__(self):
230
+ return self.__class__.from_buffer(memoryview(self), **self.__dict__)
231
+
232
+ @property
233
+ def topic(self) -> str:
234
+ return f'{self.ticker}.{self.__class__.__name__}'
235
+
236
+ @property
237
+ def market_time(self) -> datetime.datetime | datetime.date:
238
+ return datetime.datetime.fromtimestamp(self.timestamp, tz=PROFILE.time_zone)
239
+
240
+
241
+ class TransactionData(market_data_cython.TransactionData, MarketData):
242
+ def __init__(
243
+ self,
244
+ ticker: str,
245
+ timestamp: float,
246
+ price: float,
247
+ volume: float,
248
+ side: int | TransactionSide,
249
+ multiplier: float = 1.0,
250
+ notional: float = 0.0,
251
+ transaction_id: int | str | bytes = None,
252
+ buy_id: int | str | bytes = None,
253
+ sell_id: int | str | bytes = None,
254
+ **kwargs
255
+ ):
256
+ super().__init__(ticker=ticker, timestamp=timestamp, price=price, volume=volume, side=side, multiplier=multiplier, notional=notional, transaction_id=transaction_id, buy_id=buy_id, sell_id=sell_id)
257
+ self.__dict__.update(kwargs)
258
+
259
+ def __reduce__(self):
260
+ """Support for pickle serialization"""
261
+ return self.__class__.from_bytes, (self.to_bytes(),), self.__dict__
262
+
263
+ def __setstate__(self, state):
264
+ """Restore state from pickle"""
265
+ self.__dict__.update(state)
266
+
267
+ @property
268
+ def side_int(self) -> int:
269
+ return super().side
270
+
271
+ @property
272
+ def side(self) -> TransactionSide:
273
+ return TransactionSide(super().side)
274
+
275
+
276
+ class BarData(market_data_cython.BarData, MarketData):
277
+ def __init__(
278
+ self,
279
+ ticker: str,
280
+ timestamp: float,
281
+ high_price: float,
282
+ low_price: float,
283
+ open_price: float,
284
+ close_price: float,
285
+ volume: float = 0.0,
286
+ notional: float = 0.0,
287
+ trade_count: int = 0,
288
+ start_timestamp: float = None,
289
+ bar_span: float = None,
290
+ **kwargs: object
291
+ ) -> None:
292
+ if bar_span is None:
293
+ if start_timestamp is None:
294
+ raise ValueError('Must assign either start_timestamp or bar_span or both.')
295
+ else:
296
+ bar_span = timestamp - start_timestamp
297
+ else:
298
+ if isinstance(bar_span, datetime.timedelta):
299
+ bar_span = bar_span.total_seconds()
300
+ else:
301
+ bar_span = float(bar_span)
302
+
303
+ super().__init__(ticker=ticker, timestamp=timestamp, high_price=high_price, low_price=low_price, open_price=open_price, close_price=close_price, volume=volume, notional=notional, trade_count=trade_count, bar_span=bar_span)
304
+ self.__dict__.update(kwargs)
305
+
306
+ def __reduce__(self):
307
+ """Support for pickle serialization"""
308
+ return self.__class__.from_bytes, (self.to_bytes(),), self.__dict__
309
+
310
+ def __setstate__(self, state):
311
+ """Restore state from pickle"""
312
+ self.__dict__.update(state)
313
+
314
+ @property
315
+ def bar_type(self) -> Literal['Hourly-Plus', 'Hourly', 'Minute-Plus', 'Minute', 'Sub-Minute']:
316
+ """
317
+ Determines the type of the bar based on its span.
318
+
319
+ Returns:
320
+ Literal['Hourly-Plus', 'Hourly', 'Minute-Plus', 'Minute', 'Sub-Minute']: The type of the bar.
321
+ """
322
+ bar_span = super().bar_span
323
+
324
+ if bar_span > 3600:
325
+ return 'Hourly-Plus'
326
+ elif bar_span == 3600:
327
+ return 'Hourly'
328
+ elif bar_span > 60:
329
+ return 'Minute-Plus'
330
+ elif bar_span == 60:
331
+ return 'Minute'
332
+ else:
333
+ return 'Sub-Minute'
334
+
335
+ @property
336
+ def bar_end_time(self) -> datetime.datetime | datetime.date:
337
+ """
338
+ The end time of the bar.
339
+
340
+ Returns:
341
+ datetime.datetime | datetime.date: The end time of the bar.
342
+ """
343
+ return self.market_time
344
+
345
+ @property
346
+ def bar_start_time(self) -> datetime.datetime:
347
+ """
348
+ The start time of the bar.
349
+
350
+ Returns:
351
+ datetime.datetime: The start time of the bar.
352
+ """
353
+ return datetime.datetime.fromtimestamp(super().start_timestamp, tz=PROFILE.time_zone)
354
+
355
+ @property
356
+ def bar_span(self) -> datetime.timedelta:
357
+ return datetime.timedelta(seconds=super().bar_span)
358
+
359
+
360
+ class DailyBar(market_data_cython.BarData):
361
+ def __init__(
362
+ self,
363
+ ticker: str,
364
+ market_date: datetime.date, # The market date of the bar, if with 1D data, or the END date of the bar.
365
+ high_price: float,
366
+ low_price: float,
367
+ open_price: float,
368
+ close_price: float,
369
+ volume: float = 0.0,
370
+ notional: float = 0.0,
371
+ trade_count: int = 0,
372
+ bar_span: datetime.timedelta | int = None, # Expect to be a timedelta for several days, or the number of days
373
+ start_date: datetime.date = None,
374
+ **kwargs
375
+ ):
376
+ if bar_span is None:
377
+ if start_date is None:
378
+ bar_span = 1
379
+ else:
380
+ bar_span = (market_date - start_date).days
381
+ else:
382
+ if isinstance(bar_span, datetime.timedelta):
383
+ bar_span = bar_span.days()
384
+ else:
385
+ bar_span = float(bar_span)
386
+
387
+ timestamp = 10000 * market_date.year + 100 * market_date.month + market_date.day
388
+
389
+ super().__init__(ticker=ticker, timestamp=timestamp, high_price=high_price, low_price=low_price, open_price=open_price, close_price=close_price, volume=volume, notional=notional, trade_count=trade_count, bar_span=bar_span)
390
+ self.__dict__.update(kwargs)
391
+
392
+ def __repr__(self) -> str:
393
+ if (bar_span := super().bar_span) == 1:
394
+ return f"DailyBar(ticker='{self.ticker}', date={self.market_date}, open={self.open_price}, high={self.high_price}, low={self.low_price}, close={self.close_price}, volume={self.volume})"
395
+ else:
396
+ return f"DailyBar(ticker='{self.ticker}', date={self.market_date}, span={bar_span}d, open={self.open_price}, high={self.high_price}, low={self.low_price}, close={self.close_price}, volume={self.volume})"
397
+
398
+ def __reduce__(self):
399
+ """Support for pickle serialization"""
400
+ return self.__class__.from_bytes, (self.to_bytes(),), self.__dict__
401
+
402
+ def __setstate__(self, state):
403
+ """Restore state from pickle"""
404
+ self.__dict__.update(state)
405
+
406
+ @property
407
+ def market_date(self) -> datetime.date:
408
+ """
409
+ The market date of the bar.
410
+
411
+ Returns:
412
+ datetime.date: The market date of the bar.
413
+ """
414
+
415
+ int_date = int(self.timestamp)
416
+ y, _m = divmod(int_date, 10000)
417
+ m, d = divmod(_m, 100)
418
+
419
+ return datetime.date(year=y, month=m, day=d)
420
+
421
+ @property
422
+ def market_time(self) -> datetime.date:
423
+ """
424
+ The market date of the bar (same as `market_date`).
425
+
426
+ Returns:
427
+ datetime.date: The market date of the bar.
428
+ """
429
+ return self.market_date
430
+
431
+ @property
432
+ def bar_start_time(self) -> datetime.date:
433
+ """
434
+ The start date of the bar period.
435
+
436
+ Returns:
437
+ datetime.date: The start date of the bar.
438
+ """
439
+ return self.market_date - self.bar_span
440
+
441
+ @property
442
+ def bar_end_time(self) -> datetime.date:
443
+ """
444
+ The end date of the bar period.
445
+
446
+ Returns:
447
+ datetime.date: The end date of the bar.
448
+ """
449
+ return self.market_date
450
+
451
+ @property
452
+ def bar_span(self) -> datetime.timedelta:
453
+ return datetime.timedelta(days=super().bar_span)
454
+
455
+ @property
456
+ def bar_type(self) -> Literal['Daily', 'Daily-Plus']:
457
+ """
458
+ Determines the type of the bar based on its span.
459
+
460
+ Returns:
461
+ Literal['Daily', 'Daily-Plus']: The type of the bar.
462
+
463
+ Raises:
464
+ ValueError: If `bar_span` is not valid for a daily bar.
465
+ """
466
+ if super().bar_span == 1:
467
+ return 'Daily'
468
+ elif super().bar_span > 1:
469
+ return 'Daily-Plus'
470
+ else:
471
+ raise ValueError(f'Invalid bar_span for {self.__class__.__name__}! Expect an int greater or equal to 1, got {super().bar_span}.')
472
+
473
+
474
+
475
+ class TickDataLite(market_data_cython.TickDataLite, MarketData):
476
+ """
477
+ Python wrapper for TickDataLite Cython class.
478
+ Represents tick data for a specific ticker without the order_book field.
479
+ """
480
+
481
+ def __init__(
482
+ self,
483
+ ticker: str,
484
+ timestamp: float,
485
+ last_price: float,
486
+ bid_price: float = float('nan'),
487
+ bid_volume: float = float('nan'),
488
+ ask_price: float = float('nan'),
489
+ ask_volume: float = float('nan'),
490
+ total_traded_volume: float = 0.0,
491
+ total_traded_notional: float = 0.0,
492
+ total_trade_count: int = 0,
493
+ **kwargs
494
+ ):
495
+ """
496
+ Initialize a new instance of TickDataLite.
497
+
498
+ Args:
499
+ ticker (str): The ticker symbol for the market data.
500
+ timestamp (float): The timestamp of the tick data.
501
+ last_price (float): The last traded price.
502
+ bid_price (float, optional): The bid price. Defaults to None.
503
+ bid_volume (float, optional): The bid volume. Defaults to None.
504
+ ask_price (float, optional): The ask price. Defaults to None.
505
+ ask_volume (float, optional): The ask volume. Defaults to None.
506
+ total_traded_volume (float, optional): The total traded volume. Defaults to 0.0.
507
+ total_traded_notional (float, optional): The total traded notional value. Defaults to 0.0.
508
+ total_trade_count (int, optional): The total number of trades. Defaults to 0.
509
+ **kwargs: Additional keyword arguments.
510
+ """
511
+ # Create the Cython object
512
+ super().__init__(
513
+ ticker=ticker,
514
+ timestamp=timestamp,
515
+ last_price=last_price,
516
+ bid_price=bid_price,
517
+ bid_volume=bid_volume,
518
+ ask_price=ask_price,
519
+ ask_volume=ask_volume,
520
+ total_traded_volume=total_traded_volume,
521
+ total_traded_notional=total_traded_notional,
522
+ total_trade_count=total_trade_count
523
+ )
524
+ self.__dict__.update(kwargs)
525
+
526
+ def __reduce__(self):
527
+ """Support for pickle serialization"""
528
+ return self.__class__.from_bytes, (self.to_bytes(),), self.__dict__
529
+
530
+ def __setstate__(self, state):
531
+ """Restore state from pickle"""
532
+ self.__dict__.update(state)
533
+
534
+ def __repr__(self) -> str:
535
+ """
536
+ Returns a string representation of the TickDataLite instance.
537
+
538
+ Returns:
539
+ str: A string representation of the TickDataLite instance.
540
+ """
541
+ return f'<TickDataLite>([{self.market_time:%Y-%m-%d %H:%M:%S}] {self.ticker}, bid={self.bid_price}, ask={self.ask_price})'
542
+
543
+ @classmethod
544
+ def from_buffer(cls, buffer, **kwargs):
545
+ self = super().from_buffer(buffer)
546
+ self.__dict__.update(kwargs)
547
+ return self
@@ -2,6 +2,7 @@ import abc
2
2
  import ctypes
3
3
  import datetime
4
4
  import enum
5
+ import inspect
5
6
  import json
6
7
  import math
7
8
  import re
@@ -159,7 +160,12 @@ class TransactionSide(enum.IntEnum):
159
160
  elif self.value == 0:
160
161
  return 0
161
162
  else:
162
- LOGGER.warning(f'Requesting .sign of {self.name} is not recommended, use .order_sign instead')
163
+ frame = inspect.currentframe()
164
+ caller = inspect.getframeinfo(frame.f_back)
165
+ LOGGER.warning(
166
+ f"Requesting .sign of {self.name} is not recommended, use .order_sign instead. "
167
+ f"Called from {caller.filename}, line {caller.lineno}."
168
+ )
163
169
  return self.order_sign
164
170
 
165
171
  @property
@@ -1949,7 +1955,7 @@ class DailyBar(BarData):
1949
1955
  datetime.date: The market date of the bar.
1950
1956
  """
1951
1957
 
1952
- int_date = self._buffer.timestamp
1958
+ int_date = int(self._buffer.timestamp)
1953
1959
  y, _m = divmod(int_date, 10000)
1954
1960
  m, d = divmod(_m, 100)
1955
1961
 
File without changes
File without changes
File without changes
File without changes