PyAlgoEngine 0.7.4__py3-none-any.whl

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 (43) hide show
  1. PyAlgoEngine-0.7.4.dist-info/LICENSE +21 -0
  2. PyAlgoEngine-0.7.4.dist-info/METADATA +27 -0
  3. PyAlgoEngine-0.7.4.dist-info/RECORD +43 -0
  4. PyAlgoEngine-0.7.4.dist-info/WHEEL +5 -0
  5. PyAlgoEngine-0.7.4.dist-info/top_level.txt +1 -0
  6. algo_engine/__init__.py +41 -0
  7. algo_engine/apps/__init__.py +17 -0
  8. algo_engine/apps/backtest/__init__.py +20 -0
  9. algo_engine/apps/backtest/doc_server.py +331 -0
  10. algo_engine/apps/backtest/tester.py +254 -0
  11. algo_engine/apps/backtest/web_app.py +127 -0
  12. algo_engine/apps/bokeh_server.py +205 -0
  13. algo_engine/apps/demo/__init__.py +0 -0
  14. algo_engine/apps/demo/test.py +39 -0
  15. algo_engine/backtest/__init__.py +19 -0
  16. algo_engine/backtest/__main__.py +51 -0
  17. algo_engine/backtest/metrics.py +179 -0
  18. algo_engine/backtest/replay.py +261 -0
  19. algo_engine/backtest/sim_match.py +295 -0
  20. algo_engine/base/__init__.py +40 -0
  21. algo_engine/base/console_utils.py +1070 -0
  22. algo_engine/base/finance_decimal.py +258 -0
  23. algo_engine/base/market_buffer.py +571 -0
  24. algo_engine/base/market_utils.py +3092 -0
  25. algo_engine/base/market_utils_nt.py +188 -0
  26. algo_engine/base/market_utils_posix.py +3004 -0
  27. algo_engine/base/technical_analysis.py +406 -0
  28. algo_engine/base/telemetrics.py +78 -0
  29. algo_engine/base/trade_utils.py +709 -0
  30. algo_engine/engine/__init__.py +28 -0
  31. algo_engine/engine/algo_engine.py +901 -0
  32. algo_engine/engine/event_engine.py +53 -0
  33. algo_engine/engine/market_engine.py +370 -0
  34. algo_engine/engine/trade_engine.py +2037 -0
  35. algo_engine/monitor/__init__.py +15 -0
  36. algo_engine/monitor/advanced_data_interface.py +239 -0
  37. algo_engine/profile/__init__.py +121 -0
  38. algo_engine/profile/cn.py +175 -0
  39. algo_engine/strategy/__init__.py +44 -0
  40. algo_engine/strategy/strategy_engine.py +440 -0
  41. algo_engine/utils/__init__.py +3 -0
  42. algo_engine/utils/commit_regularizer.py +49 -0
  43. algo_engine/utils/data_utils.py +251 -0
@@ -0,0 +1,406 @@
1
+ import numpy as np
2
+ import pandas as pd
3
+
4
+ from . import LOGGER
5
+
6
+ LOGGER = LOGGER.getChild('TA')
7
+ __all__ = ['TechnicalAnalysis']
8
+
9
+
10
+ # noinspection PyPep8Naming
11
+ class TechnicalAnalysis(object):
12
+
13
+ def __getattribute__(self, name):
14
+ LOGGER.warning(DeprecationWarning('[TechnicalAnalysis] depreciated! Use [Factors.Common_Factors] instead!'), stacklevel=2)
15
+ super().__getattribute__(name)
16
+
17
+ @classmethod
18
+ def BOLL_value(
19
+ cls,
20
+ close_price: pd.DataFrame | pd.Series,
21
+ interval: int = 1,
22
+ window: int = 26
23
+ ) -> pd.DataFrame | pd.Series:
24
+ """
25
+ Calculate BOLL value of given serial or data frame
26
+ :param close_price: close price DataFrame or Serial. Each column are the serial of close price
27
+ :param interval: calculate interval
28
+ :param window: window parameter
29
+ :return: serial or data frame
30
+ """
31
+ close_index = close_price.index
32
+ close_df = close_price.reset_index(drop=True)
33
+ # noinspection SpellCheckingInspection
34
+ close_df = close_df.fillna(method='ffill')
35
+
36
+ boll_value_list = []
37
+
38
+ for i in range(interval):
39
+ target_df = close_df.iloc[i::interval]
40
+
41
+ mean = target_df.rolling(window=window).mean()
42
+ std = target_df.rolling(window=window).std()
43
+ boll = (target_df - mean) / std
44
+
45
+ boll_value_list.append(boll)
46
+
47
+ boll_value = pd.concat(boll_value_list, axis=0, sort=False).sort_index()
48
+ boll_value.index = close_index
49
+
50
+ return boll_value
51
+
52
+ # noinspection PyPep8Naming,DuplicatedCode
53
+ @staticmethod
54
+ def MACD_value(
55
+ close_price: pd.DataFrame | pd.Series,
56
+ interval: int = 1,
57
+ window: dict[str, int] = None,
58
+ flag: str = 'bar'
59
+ ) -> pd.DataFrame | pd.Series | tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame] | tuple[pd.Series, pd.Series, pd.Series]:
60
+ """
61
+ Calculate MACD value of given serial or data frame
62
+ :param close_price: close price DataFrame or Serial. Each column is the serial of close price
63
+ :param interval: calculate interval
64
+ :param window: window parameter, Default is {'short': 12, 'long': 26, 'diff': 9}
65
+ :param flag: return which value of MACD
66
+ :return:
67
+ """
68
+ if window is None:
69
+ window = {'short': 12, 'long': 26, 'diff': 9}
70
+
71
+ close_index = close_price.index
72
+ close_df = close_price.reset_index(drop=True)
73
+ # noinspection SpellCheckingInspection
74
+ close_df = close_df.fillna(method='ffill')
75
+
76
+ dif_list = []
77
+ dea_list = []
78
+ bar_list = []
79
+
80
+ for i in range(interval):
81
+ target_df: pd.DataFrame | pd.Series = close_df.iloc[i::interval]
82
+
83
+ ema_short = target_df.ewm(span=window.get('short', 12), adjust=False).mean()
84
+ ema_long = target_df.ewm(span=window.get('long', 26), adjust=False).mean()
85
+ dif = ema_short - ema_long
86
+ dea = dif.ewm(span=window.get('diff', 9), adjust=False).mean()
87
+ bar = 2 * (dif - dea)
88
+
89
+ dif_list.append(dif)
90
+ dea_list.append(dea)
91
+ bar_list.append(bar)
92
+
93
+ dif = pd.concat(dif_list, axis=0, sort=False).sort_index()
94
+ dea = pd.concat(dea_list, axis=0, sort=False).sort_index()
95
+ bar = pd.concat(bar_list, axis=0, sort=False).sort_index()
96
+
97
+ dif.index = close_index
98
+ dea.index = close_index
99
+ bar.index = close_index
100
+
101
+ if flag.upper() == 'BAR':
102
+ return bar
103
+ elif flag.upper() == 'DIF':
104
+ return dif
105
+ elif flag.upper() == 'DEA':
106
+ return dea
107
+ elif flag.upper() == 'FULL':
108
+ return dif, dea, bar
109
+ else:
110
+ return bar
111
+
112
+ # noinspection PyPep8Naming, DuplicatedCode
113
+ @staticmethod
114
+ def KDJ_value(
115
+ close_price: pd.DataFrame | pd.Series,
116
+ high_price: pd.DataFrame | pd.Series,
117
+ low_price: pd.DataFrame | pd.Series,
118
+ interval: int = 1,
119
+ window: dict[str, int] = None,
120
+ flag: str = 'J'
121
+ ) -> pd.DataFrame | pd.Series | tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame] | tuple[pd.Series, pd.Series, pd.Series]:
122
+ """
123
+ Calculate KDJ value of given serial or data frame
124
+ :param close_price: close price DataFrame or Serial. Each column is the serial of close price
125
+ :param high_price: HIGH price DataFrame or Serial. Each column is the serial of close price
126
+ :param low_price: LOW price DataFrame or Serial. Each column is the serial of close price
127
+ :param interval: calculate interval
128
+ :param window: window parameter, Default is {'RSV_Window': 9, 'K_Window': 3, 'D_Window': 3}
129
+ :param flag: return which value of KDJ
130
+ :return:
131
+ """
132
+ assert len(close_price) == len(high_price) == len(low_price), 'Alignment error!'
133
+ assert close_price.index[0] == high_price.index[0] == low_price.index[0], 'Alignment error!'
134
+
135
+ if window is None:
136
+ window = {'RSV_Window': 9, 'K_Window': 3, 'D_Window': 3}
137
+
138
+ close_index = close_price.index
139
+ close_df = close_price.reset_index(drop=True)
140
+ high_df = high_price.reset_index(drop=True)
141
+ low_df = low_price.reset_index(drop=True)
142
+
143
+ # noinspection SpellCheckingInspection
144
+ close_df = close_df.fillna(method='ffill')
145
+ # noinspection SpellCheckingInspection
146
+ high_df = high_df.fillna(method='ffill')
147
+ # noinspection SpellCheckingInspection
148
+ low_df = low_df.fillna(method='ffill')
149
+
150
+ k_list = []
151
+ d_list = []
152
+ j_list = []
153
+
154
+ for i in range(interval):
155
+ target_close_df: pd.DataFrame | pd.Series = close_df.iloc[i::interval]
156
+ target_high_df: pd.DataFrame | pd.Series = high_df.rolling(window=interval).max().iloc[i::interval]
157
+ target_low_df: pd.DataFrame | pd.Series = low_df.rolling(window=interval).min().iloc[i::interval]
158
+
159
+ window_high = target_high_df.rolling(window=window.get('RSV_Window', 9)).max()
160
+ window_low = target_low_df.rolling(window=window.get('RSV_Window', 9)).min()
161
+
162
+ rsv = (target_close_df - window_low) / (window_high - window_low) * 100
163
+
164
+ df_k = rsv.ewm(com=window.get('K_Window', 3) - 1).mean()
165
+ df_d = df_k.ewm(com=window.get('D_Window', 3) - 1).mean()
166
+ df_j = 3 * df_k - 2 * df_d
167
+
168
+ k_list.append(df_k)
169
+ d_list.append(df_d)
170
+ j_list.append(df_j)
171
+
172
+ k = pd.concat(k_list, axis=0, sort=False).sort_index()
173
+ d = pd.concat(d_list, axis=0, sort=False).sort_index()
174
+ j = pd.concat(j_list, axis=0, sort=False).sort_index()
175
+
176
+ k.index = close_index
177
+ d.index = close_index
178
+ j.index = close_index
179
+
180
+ if flag.upper() == 'K':
181
+ return k
182
+ elif flag.upper() == 'D':
183
+ return d
184
+ elif flag.upper() == 'J':
185
+ return j
186
+ elif flag.upper() == 'FULL':
187
+ return k, d, j
188
+ else:
189
+ return j
190
+
191
+ # noinspection PyPep8Naming, DuplicatedCode
192
+ @staticmethod
193
+ def CCI_value(
194
+ close_price: pd.DataFrame | pd.Series,
195
+ high_price: pd.DataFrame | pd.Series,
196
+ low_price: pd.DataFrame | pd.Series,
197
+ interval: int = 1,
198
+ window: dict[str, int] = None
199
+ ) -> pd.DataFrame | pd.Series | tuple[pd.DataFrame, pd.DataFrame, pd.DataFrame] | tuple[pd.Series, pd.Series, pd.Series]:
200
+ """
201
+ Calculate CCI value of given serial or data frame
202
+ :param close_price: close price DataFrame or Serial. Each column is the serial of close price
203
+ :param high_price: HIGH price DataFrame or Serial. Each column is the serial of close price
204
+ :param low_price: LOW price DataFrame or Serial. Each column is the serial of close price
205
+ :param interval: calculate interval
206
+ :param window: window parameter, Default is {'constant': 0.015, 'span': 14}
207
+ :return:
208
+ """
209
+ assert len(close_price) == len(high_price) == len(low_price), 'Alignment error!'
210
+ assert close_price.index[0] == high_price.index[0] == low_price.index[0], 'Alignment error!'
211
+
212
+ if window is None:
213
+ window = {'constant': 0.015, 'span': 14}
214
+
215
+ close_index = close_price.index
216
+ close_df = close_price.reset_index(drop=True)
217
+ high_df = high_price.reset_index(drop=True)
218
+ low_df = low_price.reset_index(drop=True)
219
+
220
+ # noinspection SpellCheckingInspection
221
+ close_df = close_df.fillna(method='ffill')
222
+ # noinspection SpellCheckingInspection
223
+ high_df = high_df.fillna(method='ffill')
224
+ # noinspection SpellCheckingInspection
225
+ low_df = low_df.fillna(method='ffill')
226
+
227
+ cci_list = []
228
+
229
+ for i in range(interval):
230
+ target_close_df: pd.DataFrame | pd.Series = close_df.iloc[i::interval]
231
+ target_high_df: pd.DataFrame | pd.Series = high_df.rolling(window=interval).max().iloc[i::interval]
232
+ target_low_df: pd.DataFrame | pd.Series = low_df.rolling(window=interval).min().iloc[i::interval]
233
+
234
+ tp = (target_high_df + target_low_df + target_close_df) / 3
235
+ ma = tp.rolling(window=window.get('span', 0.015)).mean()
236
+ md = tp.rolling(window=window.get('span', 0.015)).std()
237
+ target_cci = (tp - ma) / (window.get('constant', 0.015) * md)
238
+
239
+ cci_list.append(target_cci)
240
+
241
+ cci = pd.concat(cci_list, axis=0, sort=False).sort_index()
242
+ cci.index = close_index
243
+
244
+ return cci
245
+
246
+ # noinspection DuplicatedCode
247
+ @staticmethod
248
+ def dispersion(
249
+ trade_notional: pd.DataFrame,
250
+ calculation_period: int = 5,
251
+ alignment_method: str = 'rank'
252
+ ) -> pd.DataFrame:
253
+ from scipy import stats
254
+ notional_proportion_data_frame = trade_notional.div(np.sum(trade_notional, axis=1), axis=0)
255
+
256
+ b = {}
257
+ i = 0
258
+ while i < len(notional_proportion_data_frame):
259
+
260
+ x_s = []
261
+ y_s = []
262
+
263
+ if alignment_method == 'rank':
264
+ for j in range(min(calculation_period, i + 1)):
265
+ y = [np.log(k) for k in notional_proportion_data_frame.iloc[i + j - min(calculation_period, i + 1) + 1] if k > 0]
266
+ y.sort(reverse=True)
267
+
268
+ x = [k + 1 for k in range(len(y))]
269
+
270
+ x_s.extend(x)
271
+ y_s.extend(y)
272
+ elif alignment_method == 'ticker':
273
+ proportion = notional_proportion_data_frame.iloc[i - min(calculation_period, i + 1) + 1: i].sum()
274
+ y_s = [np.log(k) for k in proportion if k > 0]
275
+ y_s.sort(reverse=True)
276
+ x_s = [k + 1 for k in range(len(y_s))]
277
+ # noinspection PyBroadException
278
+ try:
279
+ slope, intercept, r_value, p_value, std_err = stats.linregress(x_s, y_s)
280
+
281
+ b[notional_proportion_data_frame.index[i]] = slope
282
+ except Exception as _:
283
+ b[notional_proportion_data_frame.index[i]] = float('nan')
284
+
285
+ i += 1
286
+
287
+ result = pd.DataFrame(data={'Dispersion': b})
288
+
289
+ return result
290
+
291
+ @staticmethod
292
+ def moving_average(
293
+ close_price: pd.DataFrame | pd.Series,
294
+ interval: int = 1,
295
+ window: int = 5
296
+ ):
297
+ """
298
+ Calculate MA value of given serial or data frame
299
+ :param close_price: close price DataFrame or Serial. Each column are the serial of close price
300
+ :param interval: calculate interval
301
+ :param window: window parameter
302
+ :return: serial or data frame
303
+ """
304
+ close_index = close_price.index
305
+ close_df = close_price.reset_index(drop=True)
306
+ # noinspection SpellCheckingInspection
307
+ close_df = close_df.fillna(method='ffill')
308
+
309
+ ma_value_list = []
310
+
311
+ for i in range(interval):
312
+ target_df = close_df.iloc[i::interval]
313
+
314
+ mean = target_df.rolling(window=window).mean()
315
+
316
+ ma_value_list.append(mean)
317
+
318
+ ma_value = pd.concat(ma_value_list, axis=0, sort=False).sort_index()
319
+ ma_value.index = close_index
320
+
321
+ return ma_value
322
+
323
+ # noinspection PyPep8Naming, DuplicatedCode
324
+ @staticmethod
325
+ def ASI_value(
326
+ open_price: pd.DataFrame | pd.Series,
327
+ close_price: pd.DataFrame | pd.Series,
328
+ high_price: pd.DataFrame | pd.Series,
329
+ low_price: pd.DataFrame | pd.Series,
330
+ interval: int = 11
331
+ ) -> pd.DataFrame:
332
+
333
+ assert len(open_price) == len(close_price) == len(high_price) == len(low_price), 'Alignment error!'
334
+ assert open_price.index[0] == close_price.index[0] == high_price.index[0] == low_price.index[0], 'Alignment error!'
335
+
336
+ close_index = close_price.index
337
+ open_df = open_price.reset_index(drop=True)
338
+ close_df = close_price.reset_index(drop=True)
339
+ high_df = high_price.reset_index(drop=True)
340
+ low_df = low_price.reset_index(drop=True)
341
+
342
+ # noinspection SpellCheckingInspection
343
+ open_df = open_df.fillna(method='ffill')
344
+ # noinspection SpellCheckingInspection
345
+ close_df = close_df.fillna(method='ffill')
346
+ # noinspection SpellCheckingInspection
347
+ high_df = high_df.fillna(method='ffill')
348
+ # noinspection SpellCheckingInspection
349
+ low_df = low_df.fillna(method='ffill')
350
+
351
+ asi_list = []
352
+
353
+ for i in range(interval):
354
+ target_open_df: pd.DataFrame | pd.Series = open_df.iloc[i::interval]
355
+ target_close_df: pd.DataFrame | pd.Series = close_df.iloc[i::interval]
356
+ target_high_df: pd.DataFrame | pd.Series = high_df.rolling(window=interval).max().iloc[i::interval]
357
+ target_low_df: pd.DataFrame | pd.Series = low_df.rolling(window=interval).min().iloc[i::interval]
358
+
359
+ target_si = 50 * (
360
+ target_close_df.shift(periods=1) - target_close_df
361
+ + 0.5 * (target_close_df.shift(periods=1) - target_open_df.shift(periods=1))
362
+ + 0.25 * (target_close_df - target_open_df)
363
+ ) * pd.concat((target_high_df.shift(periods=1) - target_close_df), (target_low_df.shift(periods=1) - target_close_df)).max(level=0) / (target_high_df - target_low_df)
364
+
365
+ r_1 = target_high_df - target_close_df.shift(periods=1) - 0.5 * (target_low_df - target_close_df.shift(periods=1)) + 0.25 * (target_close_df.shift(periods=1) - target_open_df.shift(periods=1))
366
+ r_2 = target_low_df - target_close_df.shift(periods=1) - 0.5 * (target_high_df - target_close_df.shift(periods=1)) + 0.25 * (target_close_df.shift(periods=1) - target_open_df.shift(periods=1))
367
+ r_3 = target_high_df - target_low_df + 0.25 * (target_close_df.shift(periods=1) - target_open_df.shift(periods=1))
368
+
369
+ r_1 = r_1.where(target_close_df.shift(periods=1) < target_low_df)
370
+ r_2 = r_2.where(target_close_df.shift(periods=1) > target_high_df)
371
+ r = r_1.fillna(r_2).fillna(r_3)
372
+
373
+ target_si = target_si / r
374
+
375
+ asi_list.append(target_si.cumsum())
376
+
377
+ asi = pd.concat(asi_list, axis=0, sort=False).sort_index()
378
+ asi.index = close_index
379
+
380
+ return asi
381
+
382
+ @staticmethod
383
+ def Volatility(
384
+ close_price: pd.DataFrame | pd.Series,
385
+ interval: int = 1,
386
+ window=5,
387
+ multiplier=244
388
+ ):
389
+ close_index = close_price.index
390
+ close_df = close_price.reset_index(drop=True)
391
+ # noinspection SpellCheckingInspection
392
+ close_df = close_df.fillna(method='ffill')
393
+
394
+ std_value_list = []
395
+
396
+ for i in range(interval):
397
+ target_df = close_df.iloc[i::interval]
398
+
399
+ std = target_df.pct_change().rolling(window=window).std() * np.sqrt(multiplier / interval)
400
+
401
+ std_value_list.append(std)
402
+
403
+ std_value = pd.concat(std_value_list, axis=0, sort=False).sort_index()
404
+ std_value.index = close_index
405
+
406
+ return std_value
@@ -0,0 +1,78 @@
1
+ import logging
2
+ import sys
3
+ import time
4
+
5
+ LOGGER: logging.Logger | None = None
6
+ LOG_LEVEL = logging.INFO
7
+
8
+
9
+ class ColoredFormatter(logging.Formatter):
10
+ """Logging Formatter to add colors and count warning / errors"""
11
+
12
+ def __init__(self, fmt=None, datefmt=None, style='{', validate=True):
13
+ self.format_str = '[{asctime} {name} - {threadName} - {module}:{lineno} - {levelname}] {message}' if fmt is None else fmt
14
+ self.date_fmt = '%Y-%m-%d %H:%M:%S' if datefmt is None else datefmt
15
+ self.style = style
16
+
17
+ super().__init__(fmt=fmt, datefmt=datefmt, style=style, validate=validate)
18
+
19
+ def _get_format(self, level: int, select=False):
20
+ bold_red = f"\33[31;1;3;4{';7' if select else ''}m"
21
+ red = f"\33[31;1{';7' if select else ''}m"
22
+ green = f"\33[32;1{';7' if select else ''}m"
23
+ yellow = f"\33[33;1{';7' if select else ''}m"
24
+ blue = f"\33[34;1{';7' if select else ''}m"
25
+ reset = "\33[0m"
26
+
27
+ if level <= logging.NOTSET:
28
+ fmt = self.format_str
29
+ elif level <= logging.DEBUG:
30
+ fmt = blue + self.format_str + reset
31
+ elif level <= logging.INFO:
32
+ fmt = green + self.format_str + reset
33
+ elif level <= logging.WARNING:
34
+ fmt = yellow + self.format_str + reset
35
+ elif level <= logging.ERROR:
36
+ fmt = red + self.format_str + reset
37
+ else:
38
+ fmt = bold_red + self.format_str + reset
39
+
40
+ return fmt
41
+
42
+ def format(self, record):
43
+ log_fmt = self._get_format(level=record.levelno)
44
+ formatter = logging.Formatter(log_fmt, datefmt=self.date_fmt, style=self.style)
45
+ return formatter.format(record)
46
+
47
+
48
+ def get_logger(**kwargs) -> logging.Logger:
49
+ level = kwargs.get('level', LOG_LEVEL)
50
+ stream_io = kwargs.get('stream_io', sys.stdout)
51
+ formatter = kwargs.get('formatter', ColoredFormatter())
52
+ global LOGGER
53
+
54
+ if LOGGER is not None:
55
+ return LOGGER
56
+
57
+ LOGGER = logging.getLogger('PyAlgoEngine')
58
+ LOGGER.setLevel(level)
59
+ logging.Formatter.converter = time.gmtime
60
+
61
+ if stream_io:
62
+ have_handler = False
63
+ for handler in LOGGER.handlers:
64
+ # noinspection PyUnresolvedReferences
65
+ if type(handler) == logging.StreamHandler and handler.stream == stream_io:
66
+ have_handler = True
67
+ break
68
+
69
+ if not have_handler:
70
+ logger_ch = logging.StreamHandler(stream=stream_io)
71
+ logger_ch.setLevel(level=level)
72
+ logger_ch.setFormatter(fmt=formatter)
73
+ LOGGER.addHandler(logger_ch)
74
+
75
+ return LOGGER
76
+
77
+
78
+ _ = get_logger()