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.
- PyAlgoEngine-0.7.4.dist-info/LICENSE +21 -0
- PyAlgoEngine-0.7.4.dist-info/METADATA +27 -0
- PyAlgoEngine-0.7.4.dist-info/RECORD +43 -0
- PyAlgoEngine-0.7.4.dist-info/WHEEL +5 -0
- PyAlgoEngine-0.7.4.dist-info/top_level.txt +1 -0
- algo_engine/__init__.py +41 -0
- algo_engine/apps/__init__.py +17 -0
- algo_engine/apps/backtest/__init__.py +20 -0
- algo_engine/apps/backtest/doc_server.py +331 -0
- algo_engine/apps/backtest/tester.py +254 -0
- algo_engine/apps/backtest/web_app.py +127 -0
- algo_engine/apps/bokeh_server.py +205 -0
- algo_engine/apps/demo/__init__.py +0 -0
- algo_engine/apps/demo/test.py +39 -0
- algo_engine/backtest/__init__.py +19 -0
- algo_engine/backtest/__main__.py +51 -0
- algo_engine/backtest/metrics.py +179 -0
- algo_engine/backtest/replay.py +261 -0
- algo_engine/backtest/sim_match.py +295 -0
- algo_engine/base/__init__.py +40 -0
- algo_engine/base/console_utils.py +1070 -0
- algo_engine/base/finance_decimal.py +258 -0
- algo_engine/base/market_buffer.py +571 -0
- algo_engine/base/market_utils.py +3092 -0
- algo_engine/base/market_utils_nt.py +188 -0
- algo_engine/base/market_utils_posix.py +3004 -0
- algo_engine/base/technical_analysis.py +406 -0
- algo_engine/base/telemetrics.py +78 -0
- algo_engine/base/trade_utils.py +709 -0
- algo_engine/engine/__init__.py +28 -0
- algo_engine/engine/algo_engine.py +901 -0
- algo_engine/engine/event_engine.py +53 -0
- algo_engine/engine/market_engine.py +370 -0
- algo_engine/engine/trade_engine.py +2037 -0
- algo_engine/monitor/__init__.py +15 -0
- algo_engine/monitor/advanced_data_interface.py +239 -0
- algo_engine/profile/__init__.py +121 -0
- algo_engine/profile/cn.py +175 -0
- algo_engine/strategy/__init__.py +44 -0
- algo_engine/strategy/strategy_engine.py +440 -0
- algo_engine/utils/__init__.py +3 -0
- algo_engine/utils/commit_regularizer.py +49 -0
- 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()
|