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.
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/PKG-INFO +13 -2
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/PyAlgoEngine.egg-info/PKG-INFO +13 -2
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/PyAlgoEngine.egg-info/SOURCES.txt +1 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/__init__.py +1 -1
- pyalgoengine-0.8.0a1/algo_engine/base/market_data_wrapper.py +547 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/base/market_utils_posix.py +8 -2
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/LICENSE +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/PyAlgoEngine.egg-info/dependency_links.txt +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/PyAlgoEngine.egg-info/requires.txt +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/PyAlgoEngine.egg-info/top_level.txt +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/README.md +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/apps/__init__.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/apps/backtest/__init__.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/apps/backtest/doc_server.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/apps/backtest/tester.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/apps/backtest/web_app.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/apps/bokeh_server.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/apps/demo/__init__.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/apps/demo/test.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/apps/sim_input/__init__.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/apps/sim_input/client.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/apps/sim_input/sim_keyboard.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/apps/sim_input/sim_mouse.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/apps/sim_input/window.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/backtest/__init__.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/backtest/__main__.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/backtest/metrics.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/backtest/replay.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/backtest/sim_match.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/base/__init__.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/base/console_utils.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/base/finance_decimal.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/base/market_buffer.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/base/market_utils.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/base/market_utils_nt.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/base/technical_analysis.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/base/telemetrics.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/base/trade_utils.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/engine/__init__.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/engine/algo_engine.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/engine/event_engine.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/engine/market_engine.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/engine/trade_engine.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/monitor/__init__.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/monitor/advanced_data_interface.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/profile/__init__.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/profile/cn.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/strategy/__init__.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/strategy/strategy_engine.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/utils/__init__.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/utils/commit_regularizer.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/algo_engine/utils/data_utils.py +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/setup.cfg +0 -0
- {pyalgoengine-0.7.7a5 → pyalgoengine-0.8.0a1}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: PyAlgoEngine
|
|
3
|
-
Version: 0.
|
|
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
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: PyAlgoEngine
|
|
3
|
-
Version: 0.
|
|
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
|
|
@@ -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
|
-
|
|
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
|
|
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.7.7a5 → pyalgoengine-0.8.0a1}/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
|