cyqnt-trd 0.1.2__py3-none-any.whl → 0.1.7__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.
- cyqnt_trd/CHANGELOG_0.1.7.md +111 -0
- cyqnt_trd/__init__.py +1 -1
- cyqnt_trd/backtesting/factor_test.py +3 -2
- cyqnt_trd/get_data/__init__.py +16 -1
- cyqnt_trd/get_data/get_futures_data.py +3 -3
- cyqnt_trd/get_data/get_kline_with_factor.py +808 -0
- cyqnt_trd/get_data/get_web3_trending_data.py +389 -0
- cyqnt_trd/online_trading/__init__.py +1 -0
- cyqnt_trd/online_trading/realtime_price_tracker.py +142 -2
- cyqnt_trd/trading_signal/example_usage.py +123 -7
- cyqnt_trd/trading_signal/factor/__init__.py +23 -0
- cyqnt_trd/trading_signal/factor/adx_factor.py +116 -0
- cyqnt_trd/trading_signal/factor/ao_factor.py +66 -0
- cyqnt_trd/trading_signal/factor/bbp_factor.py +68 -0
- cyqnt_trd/trading_signal/factor/cci_factor.py +65 -0
- cyqnt_trd/trading_signal/factor/ema_factor.py +102 -0
- cyqnt_trd/trading_signal/factor/macd_factor.py +97 -0
- cyqnt_trd/trading_signal/factor/momentum_factor.py +44 -0
- cyqnt_trd/trading_signal/factor/stochastic_factor.py +76 -0
- cyqnt_trd/trading_signal/factor/stochastic_tsi_factor.py +129 -0
- cyqnt_trd/trading_signal/factor/uo_factor.py +92 -0
- cyqnt_trd/trading_signal/factor/williams_r_factor.py +60 -0
- cyqnt_trd/trading_signal/factor_details.json +107 -0
- cyqnt_trd/trading_signal/selected_alpha/alpha1.py +4 -2
- cyqnt_trd/trading_signal/selected_alpha/alpha15.py +4 -2
- cyqnt_trd/trading_signal/selected_alpha/generate_alphas.py +1 -0
- {cyqnt_trd-0.1.2.dist-info → cyqnt_trd-0.1.7.dist-info}/METADATA +16 -12
- {cyqnt_trd-0.1.2.dist-info → cyqnt_trd-0.1.7.dist-info}/RECORD +34 -23
- {cyqnt_trd-0.1.2.dist-info → cyqnt_trd-0.1.7.dist-info}/WHEEL +1 -1
- test/real_time_trade.py +467 -10
- test/test_now_factor.py +1082 -0
- test/track_k_line_continue.py +372 -0
- cyqnt_trd/test_script/get_symbols_by_volume.py +0 -227
- cyqnt_trd/test_script/test_alpha.py +0 -261
- cyqnt_trd/test_script/test_kline_data.py +0 -479
- test/test_example_usage.py +0 -381
- test/test_get_data.py +0 -310
- test/test_realtime_price_tracker.py +0 -546
- {cyqnt_trd-0.1.2.dist-info → cyqnt_trd-0.1.7.dist-info}/licenses/LICENSE +0 -0
- {cyqnt_trd-0.1.2.dist-info → cyqnt_trd-0.1.7.dist-info}/top_level.txt +0 -0
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
import sys
|
|
15
15
|
import os
|
|
16
|
+
import numpy as np
|
|
16
17
|
|
|
17
18
|
# 尝试直接导入(当作为 package 安装时)
|
|
18
19
|
try:
|
|
@@ -256,7 +257,7 @@ def example_5_alpha1_factor(data_path):
|
|
|
256
257
|
|
|
257
258
|
factor_results = framework.test_factor(
|
|
258
259
|
factor_func=alpha1_wrapper,
|
|
259
|
-
forward_periods=
|
|
260
|
+
forward_periods=24, # 未来7个周期
|
|
260
261
|
min_periods=30, # 至少需要30个周期(5+20+一些缓冲)
|
|
261
262
|
factor_name="Alpha#1因子"
|
|
262
263
|
)
|
|
@@ -269,6 +270,121 @@ def example_5_alpha1_factor(data_path):
|
|
|
269
270
|
)
|
|
270
271
|
|
|
271
272
|
|
|
273
|
+
def example_5_normalized_alpha1_factor(data_path):
|
|
274
|
+
"""
|
|
275
|
+
示例5(归一化版本): 使用归一化Alpha#1因子进行因子测试
|
|
276
|
+
"""
|
|
277
|
+
print("\n" + "=" * 60)
|
|
278
|
+
print("示例5(归一化版本): 使用归一化Alpha#1因子进行因子测试")
|
|
279
|
+
print("=" * 60)
|
|
280
|
+
print("\n因子说明:")
|
|
281
|
+
print(" - 公式: rank(Ts_ArgMax(SignedPower(((returns<0)?stddev(returns,20):close),2.),5))-0.5)")
|
|
282
|
+
print(" - 策略逻辑:对过去5天按照收盘价最高或下行波动率最高进行排名")
|
|
283
|
+
print(" - 下行波动率最高的一天离计算时间越近,越可以投资")
|
|
284
|
+
print(" - 收盘价最高离计算时间越近,越可以投资")
|
|
285
|
+
print(" - 标签:mean-reversion+momentum")
|
|
286
|
+
print("\n归一化说明:")
|
|
287
|
+
print(" - 计算当前周期和之前30个周期的因子值,进行归一化")
|
|
288
|
+
print(" - 使用Min-Max归一化将因子值映射到[-1, 1]区间")
|
|
289
|
+
print(" - 返回归一化后的当前因子值用于测试")
|
|
290
|
+
print()
|
|
291
|
+
|
|
292
|
+
framework = BacktestFramework(data_path=data_path)
|
|
293
|
+
|
|
294
|
+
# 归一化参数
|
|
295
|
+
lookback_periods = 30
|
|
296
|
+
min_required = 30 # alpha1_factor需要的最小周期数(保守估计)
|
|
297
|
+
|
|
298
|
+
# 使用归一化Alpha#1因子进行测试
|
|
299
|
+
def normalized_alpha1_wrapper(data_slice):
|
|
300
|
+
"""
|
|
301
|
+
归一化Alpha#1 因子包装函数
|
|
302
|
+
|
|
303
|
+
计算当前周期和之前lookback_periods个周期的因子值,进行归一化后返回当前归一化因子值
|
|
304
|
+
"""
|
|
305
|
+
# 需要至少min_required+lookback_periods+1行数据
|
|
306
|
+
# min_required用于计算因子,lookback_periods用于回看,+1用于当前周期
|
|
307
|
+
available_periods = len(data_slice) - min_required - 1
|
|
308
|
+
if available_periods < 2:
|
|
309
|
+
return 0.0 # 至少需要2个周期才能计算归一化
|
|
310
|
+
|
|
311
|
+
# 自适应调整回看周期,但不能小于2
|
|
312
|
+
actual_lookback = min(lookback_periods, max(2, available_periods))
|
|
313
|
+
|
|
314
|
+
try:
|
|
315
|
+
# 计算因子值:当前周期和之前actual_lookback个周期
|
|
316
|
+
factor_values = []
|
|
317
|
+
for i in range(actual_lookback + 1):
|
|
318
|
+
end_idx = len(data_slice) - i
|
|
319
|
+
start_idx = max(0, end_idx - min_required - 1)
|
|
320
|
+
if end_idx <= start_idx:
|
|
321
|
+
factor_values.append(0.0)
|
|
322
|
+
continue
|
|
323
|
+
|
|
324
|
+
period_slice = data_slice.iloc[start_idx:end_idx]
|
|
325
|
+
try:
|
|
326
|
+
factor_value = alpha1_factor(
|
|
327
|
+
period_slice,
|
|
328
|
+
lookback_days=5,
|
|
329
|
+
stddev_period=20,
|
|
330
|
+
power=2.0
|
|
331
|
+
)
|
|
332
|
+
if factor_value is not None:
|
|
333
|
+
factor_values.append(factor_value)
|
|
334
|
+
else:
|
|
335
|
+
factor_values.append(0.0)
|
|
336
|
+
except Exception:
|
|
337
|
+
factor_values.append(0.0)
|
|
338
|
+
|
|
339
|
+
# 如果收集到的因子值不足,返回0
|
|
340
|
+
if len(factor_values) < 2:
|
|
341
|
+
return 0.0
|
|
342
|
+
|
|
343
|
+
# 将因子值转换为numpy数组进行归一化
|
|
344
|
+
factor_array = np.array(factor_values)
|
|
345
|
+
|
|
346
|
+
# Min-Max归一化:将值映射到[-1, 1]区间
|
|
347
|
+
# 如果所有值都相同,则归一化后都为0
|
|
348
|
+
factor_min = factor_array.min()
|
|
349
|
+
factor_max = factor_array.max()
|
|
350
|
+
|
|
351
|
+
if factor_max == factor_min:
|
|
352
|
+
# 所有值相同,归一化后都为0
|
|
353
|
+
normalized_factors = np.zeros_like(factor_array)
|
|
354
|
+
else:
|
|
355
|
+
# Min-Max归一化到[-1, 1]区间
|
|
356
|
+
normalized_factors = 2 * (factor_array - factor_min) / (factor_max - factor_min) - 1
|
|
357
|
+
|
|
358
|
+
# 返回当前归一化因子值(第一个,即当前周期)
|
|
359
|
+
return float(normalized_factors[0])
|
|
360
|
+
|
|
361
|
+
except Exception:
|
|
362
|
+
return 0.0
|
|
363
|
+
|
|
364
|
+
# 测试因子
|
|
365
|
+
print("开始测试归一化 Alpha#1 因子...")
|
|
366
|
+
print(f" 回看天数: 5")
|
|
367
|
+
print(f" 标准差周期: 20")
|
|
368
|
+
print(f" 幂次: 2.0")
|
|
369
|
+
print(f" 归一化回看周期数: {lookback_periods}")
|
|
370
|
+
print(f" 向前看周期: 24")
|
|
371
|
+
print()
|
|
372
|
+
|
|
373
|
+
factor_results = framework.test_factor(
|
|
374
|
+
factor_func=normalized_alpha1_wrapper,
|
|
375
|
+
forward_periods=24, # 未来24个周期
|
|
376
|
+
min_periods=65, # 至少需要65个周期(30+30+5缓冲,确保有足够数据计算因子和归一化)
|
|
377
|
+
factor_name="归一化Alpha#1因子"
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
# 打印结果并保存
|
|
381
|
+
save_dir = '/Users/user/Desktop/repo/cyqnt_trd/result'
|
|
382
|
+
framework.print_factor_results(
|
|
383
|
+
factor_results,
|
|
384
|
+
save_dir=save_dir
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
|
|
272
388
|
def example_6_alpha1_signal(data_path):
|
|
273
389
|
"""
|
|
274
390
|
示例6: 使用基于Alpha#1因子的信号策略进行回测
|
|
@@ -378,7 +494,7 @@ def example_7_normalized_alpha15_signal(data_path):
|
|
|
378
494
|
backtest_results = framework.backtest_strategy(
|
|
379
495
|
signal_func=normalized_alpha15_signal_wrapper,
|
|
380
496
|
min_periods=65, # 至少需要65个周期(30+30+5缓冲,确保有足够数据计算因子和归一化)
|
|
381
|
-
position_size=0.
|
|
497
|
+
position_size=0.8, # 每次使用20%的资金
|
|
382
498
|
initial_capital=10000.0,
|
|
383
499
|
commission_rate=0.00001, # 0.001%手续费
|
|
384
500
|
take_profit=0.5, # 止盈10%
|
|
@@ -411,14 +527,14 @@ def main():
|
|
|
411
527
|
主函数:运行所有示例
|
|
412
528
|
"""
|
|
413
529
|
# 取消注释想要运行的示例
|
|
414
|
-
data_path = "/Users/user/Desktop/repo/
|
|
415
|
-
# example_1_use_factor()
|
|
530
|
+
data_path = "/Users/user/Desktop/repo/cyqnt_trd/tmp/data/ETHUSDT_futures/ETHUSDT_30m_1775_20251130_000000_20260105_235959_20260108_230726.json"
|
|
531
|
+
# example_1_use_factor(data_path)
|
|
416
532
|
# example_2_use_signal(data_path)
|
|
417
|
-
# example_4_multi_factor()
|
|
533
|
+
# example_4_multi_factor(data_path)
|
|
418
534
|
# example_3_factor_in_signal(data_path)
|
|
419
|
-
|
|
535
|
+
example_5_normalized_alpha1_factor(data_path) # Alpha#1因子测试
|
|
420
536
|
# example_6_alpha1_signal(data_path) # 基于Alpha#1因子的策略回测
|
|
421
|
-
example_7_normalized_alpha15_signal(data_path) # 基于归一化Alpha#15因子的策略回测
|
|
537
|
+
# example_7_normalized_alpha15_signal(data_path) # 基于归一化Alpha#15因子的策略回测
|
|
422
538
|
|
|
423
539
|
|
|
424
540
|
# print("\n提示:")
|
|
@@ -9,10 +9,33 @@
|
|
|
9
9
|
# 导入所有因子
|
|
10
10
|
from .ma_factor import ma_factor, ma_cross_factor
|
|
11
11
|
from .rsi_factor import rsi_factor
|
|
12
|
+
from .stochastic_factor import stochastic_k_factor
|
|
13
|
+
from .cci_factor import cci_factor
|
|
14
|
+
from .adx_factor import adx_factor
|
|
15
|
+
from .ao_factor import ao_factor
|
|
16
|
+
from .momentum_factor import momentum_factor
|
|
17
|
+
from .macd_factor import macd_level_factor
|
|
18
|
+
from .stochastic_tsi_factor import stochastic_tsi_fast_factor
|
|
19
|
+
from .williams_r_factor import williams_r_factor
|
|
20
|
+
from .bbp_factor import bbp_factor
|
|
21
|
+
from .uo_factor import uo_factor
|
|
22
|
+
from .ema_factor import ema_factor, ema_cross_factor
|
|
12
23
|
|
|
13
24
|
__all__ = [
|
|
14
25
|
'ma_factor',
|
|
15
26
|
'ma_cross_factor',
|
|
16
27
|
'rsi_factor',
|
|
28
|
+
'stochastic_k_factor',
|
|
29
|
+
'cci_factor',
|
|
30
|
+
'adx_factor',
|
|
31
|
+
'ao_factor',
|
|
32
|
+
'momentum_factor',
|
|
33
|
+
'macd_level_factor',
|
|
34
|
+
'stochastic_tsi_fast_factor',
|
|
35
|
+
'williams_r_factor',
|
|
36
|
+
'bbp_factor',
|
|
37
|
+
'uo_factor',
|
|
38
|
+
'ema_factor',
|
|
39
|
+
'ema_cross_factor',
|
|
17
40
|
]
|
|
18
41
|
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ADX(平均趋向指数)因子
|
|
3
|
+
|
|
4
|
+
基于ADX指标的交易因子
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def adx_factor(
|
|
12
|
+
data_slice: 'pd.DataFrame',
|
|
13
|
+
period: int = 14,
|
|
14
|
+
adx_threshold: float = 25.0
|
|
15
|
+
) -> float:
|
|
16
|
+
"""
|
|
17
|
+
ADX因子
|
|
18
|
+
|
|
19
|
+
当ADX高于阈值且+DI > -DI时看多,当ADX高于阈值且+DI < -DI时看空
|
|
20
|
+
|
|
21
|
+
Args:
|
|
22
|
+
data_slice: 数据切片,必须包含至少 period*2+1 行数据
|
|
23
|
+
最后一行是当前数据点,前面 period*2 行是历史数据
|
|
24
|
+
period: ADX计算周期(默认14)
|
|
25
|
+
adx_threshold: ADX阈值,低于此值表示趋势较弱(默认25)
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
因子值:1.0(看多)、-1.0(看空)或 0(数据不足或中性)
|
|
29
|
+
"""
|
|
30
|
+
if len(data_slice) < period * 2 + 1:
|
|
31
|
+
return 0.0
|
|
32
|
+
|
|
33
|
+
# 计算True Range (TR)
|
|
34
|
+
tr_list = []
|
|
35
|
+
di_plus_list = []
|
|
36
|
+
di_minus_list = []
|
|
37
|
+
|
|
38
|
+
# 需要period+1行数据来计算DI
|
|
39
|
+
for i in range(period):
|
|
40
|
+
if len(data_slice) < period + 1 + i:
|
|
41
|
+
return 0.0
|
|
42
|
+
|
|
43
|
+
idx = -(period + 1 + i)
|
|
44
|
+
if idx == -1:
|
|
45
|
+
current_high = data_slice.iloc[-1]['high_price']
|
|
46
|
+
current_low = data_slice.iloc[-1]['low_price']
|
|
47
|
+
prev_close = data_slice.iloc[-2]['close_price']
|
|
48
|
+
else:
|
|
49
|
+
current_high = data_slice.iloc[idx]['high_price']
|
|
50
|
+
current_low = data_slice.iloc[idx]['low_price']
|
|
51
|
+
prev_close = data_slice.iloc[idx-1]['close_price']
|
|
52
|
+
|
|
53
|
+
# True Range
|
|
54
|
+
tr1 = current_high - current_low
|
|
55
|
+
tr2 = abs(current_high - prev_close)
|
|
56
|
+
tr3 = abs(current_low - prev_close)
|
|
57
|
+
tr = max(tr1, tr2, tr3)
|
|
58
|
+
tr_list.append(tr)
|
|
59
|
+
|
|
60
|
+
# Directional Movement
|
|
61
|
+
if idx == -1:
|
|
62
|
+
prev_high = data_slice.iloc[-2]['high_price']
|
|
63
|
+
prev_low = data_slice.iloc[-2]['low_price']
|
|
64
|
+
else:
|
|
65
|
+
prev_high = data_slice.iloc[idx-1]['high_price']
|
|
66
|
+
prev_low = data_slice.iloc[idx-1]['low_price']
|
|
67
|
+
|
|
68
|
+
plus_dm = current_high - prev_high if current_high > prev_high else 0.0
|
|
69
|
+
minus_dm = prev_low - current_low if prev_low > current_low else 0.0
|
|
70
|
+
|
|
71
|
+
# 如果plus_dm和minus_dm都大于0,取较大者,另一个设为0
|
|
72
|
+
if plus_dm > minus_dm:
|
|
73
|
+
minus_dm = 0.0
|
|
74
|
+
elif minus_dm > plus_dm:
|
|
75
|
+
plus_dm = 0.0
|
|
76
|
+
else:
|
|
77
|
+
plus_dm = 0.0
|
|
78
|
+
minus_dm = 0.0
|
|
79
|
+
|
|
80
|
+
# 计算DI
|
|
81
|
+
if tr == 0:
|
|
82
|
+
di_plus = 0.0
|
|
83
|
+
di_minus = 0.0
|
|
84
|
+
else:
|
|
85
|
+
di_plus = 100.0 * plus_dm / tr
|
|
86
|
+
di_minus = 100.0 * minus_dm / tr
|
|
87
|
+
|
|
88
|
+
di_plus_list.append(di_plus)
|
|
89
|
+
di_minus_list.append(di_minus)
|
|
90
|
+
|
|
91
|
+
# 计算平滑后的TR、+DI和-DI(使用Wilder平滑)
|
|
92
|
+
atr = np.mean(tr_list) # 简化:使用简单平均,实际应该用Wilder平滑
|
|
93
|
+
di_plus_smooth = np.mean(di_plus_list)
|
|
94
|
+
di_minus_smooth = np.mean(di_minus_list)
|
|
95
|
+
|
|
96
|
+
# 计算DX
|
|
97
|
+
di_sum = di_plus_smooth + di_minus_smooth
|
|
98
|
+
if di_sum == 0:
|
|
99
|
+
dx = 0.0
|
|
100
|
+
else:
|
|
101
|
+
dx = 100.0 * abs(di_plus_smooth - di_minus_smooth) / di_sum
|
|
102
|
+
|
|
103
|
+
# ADX是DX的移动平均(这里简化处理,实际应该用平滑的DX)
|
|
104
|
+
adx = dx
|
|
105
|
+
|
|
106
|
+
# 根据ADX和DI关系返回因子
|
|
107
|
+
if adx < adx_threshold:
|
|
108
|
+
return 0.0 # 趋势不明显,中性
|
|
109
|
+
|
|
110
|
+
if di_plus_smooth > di_minus_smooth:
|
|
111
|
+
return 1.0 # 上升趋势,看多
|
|
112
|
+
elif di_minus_smooth > di_plus_smooth:
|
|
113
|
+
return -1.0 # 下降趋势,看空
|
|
114
|
+
else:
|
|
115
|
+
return 0.0 # 中性
|
|
116
|
+
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""
|
|
2
|
+
AO(动量震荡指标,Awesome Oscillator)因子
|
|
3
|
+
|
|
4
|
+
基于AO指标的交易因子
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def ao_factor(data_slice: 'pd.DataFrame') -> float:
|
|
12
|
+
"""
|
|
13
|
+
AO(动量震荡指标)因子
|
|
14
|
+
|
|
15
|
+
当AO为正值且上升时看多,为负值且下降时看空
|
|
16
|
+
|
|
17
|
+
Args:
|
|
18
|
+
data_slice: 数据切片,必须包含至少 35 行数据(5+34)
|
|
19
|
+
最后一行是当前数据点,前面 34 行是历史数据
|
|
20
|
+
|
|
21
|
+
Returns:
|
|
22
|
+
因子值:1.0(看多)、-1.0(看空)或 0(数据不足或中性)
|
|
23
|
+
"""
|
|
24
|
+
# AO使用5期和34期的简单移动平均
|
|
25
|
+
short_period = 5
|
|
26
|
+
long_period = 34
|
|
27
|
+
|
|
28
|
+
if len(data_slice) < long_period + 1:
|
|
29
|
+
return 0.0
|
|
30
|
+
|
|
31
|
+
# 计算中位数价格 (Median Price = (High + Low) / 2)
|
|
32
|
+
median_prices = (data_slice['high_price'] + data_slice['low_price']) / 2.0
|
|
33
|
+
|
|
34
|
+
# 计算短期和长期移动平均(不包括最后一行)
|
|
35
|
+
short_ma = median_prices.iloc[-short_period-1:-1].mean()
|
|
36
|
+
long_ma = median_prices.iloc[-long_period-1:-1].mean()
|
|
37
|
+
|
|
38
|
+
# 当前AO值
|
|
39
|
+
ao_current = short_ma - long_ma
|
|
40
|
+
|
|
41
|
+
# 计算上一期的AO值(需要更多历史数据)
|
|
42
|
+
if len(data_slice) < long_period + 2:
|
|
43
|
+
# 如果数据不足,只根据当前AO值判断
|
|
44
|
+
if ao_current > 0:
|
|
45
|
+
return 1.0
|
|
46
|
+
elif ao_current < 0:
|
|
47
|
+
return -1.0
|
|
48
|
+
else:
|
|
49
|
+
return 0.0
|
|
50
|
+
|
|
51
|
+
short_ma_prev = median_prices.iloc[-short_period-2:-2].mean()
|
|
52
|
+
long_ma_prev = median_prices.iloc[-long_period-2:-2].mean()
|
|
53
|
+
ao_prev = short_ma_prev - long_ma_prev
|
|
54
|
+
|
|
55
|
+
# 判断趋势
|
|
56
|
+
if ao_current > 0 and ao_current > ao_prev:
|
|
57
|
+
return 1.0 # 正值且上升,看多
|
|
58
|
+
elif ao_current < 0 and ao_current < ao_prev:
|
|
59
|
+
return -1.0 # 负值且下降,看空
|
|
60
|
+
elif ao_current > 0:
|
|
61
|
+
return 0.5 # 正值但未上升,轻微看多
|
|
62
|
+
elif ao_current < 0:
|
|
63
|
+
return -0.5 # 负值但未下降,轻微看空
|
|
64
|
+
else:
|
|
65
|
+
return 0.0 # 中性
|
|
66
|
+
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BBP(牛熊力量,Bull Bear Power)因子
|
|
3
|
+
|
|
4
|
+
基于BBP指标的交易因子
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _ema(data: np.ndarray, period: int) -> float:
|
|
12
|
+
"""计算指数移动平均"""
|
|
13
|
+
if len(data) < period:
|
|
14
|
+
return np.mean(data)
|
|
15
|
+
|
|
16
|
+
alpha = 2.0 / (period + 1.0)
|
|
17
|
+
ema = data[-period]
|
|
18
|
+
|
|
19
|
+
for i in range(-period + 1, 0):
|
|
20
|
+
ema = alpha * data[i] + (1 - alpha) * ema
|
|
21
|
+
|
|
22
|
+
return ema
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def bbp_factor(data_slice: 'pd.DataFrame', period: int = 13) -> float:
|
|
26
|
+
"""
|
|
27
|
+
牛熊力量(BBP)因子
|
|
28
|
+
|
|
29
|
+
当BBP为正值时看多,为负值时看空
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
data_slice: 数据切片,必须包含至少 period+1 行数据
|
|
33
|
+
最后一行是当前数据点,前面 period 行是历史数据
|
|
34
|
+
period: EMA计算周期(默认13)
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
因子值:1.0(看多)、-1.0(看空)或 0(数据不足)
|
|
38
|
+
"""
|
|
39
|
+
if len(data_slice) < period + 1:
|
|
40
|
+
return 0.0
|
|
41
|
+
|
|
42
|
+
# 获取收盘价
|
|
43
|
+
close_prices = data_slice['close_price'].values
|
|
44
|
+
|
|
45
|
+
# 计算EMA
|
|
46
|
+
ema_value = _ema(close_prices, period)
|
|
47
|
+
|
|
48
|
+
# 当前最高价和最低价
|
|
49
|
+
current_high = data_slice.iloc[-1]['high_price']
|
|
50
|
+
current_low = data_slice.iloc[-1]['low_price']
|
|
51
|
+
|
|
52
|
+
# 计算牛熊力量
|
|
53
|
+
# Bull Power = High - EMA
|
|
54
|
+
# Bear Power = Low - EMA
|
|
55
|
+
bull_power = current_high - ema_value
|
|
56
|
+
bear_power = current_low - ema_value
|
|
57
|
+
|
|
58
|
+
# BBP = Bull Power + Bear Power
|
|
59
|
+
bbp = bull_power + bear_power
|
|
60
|
+
|
|
61
|
+
# 根据BBP值返回因子
|
|
62
|
+
if bbp > 0:
|
|
63
|
+
return 1.0 # 正值,看多
|
|
64
|
+
elif bbp < 0:
|
|
65
|
+
return -1.0 # 负值,看空
|
|
66
|
+
else:
|
|
67
|
+
return 0.0 # 中性
|
|
68
|
+
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"""
|
|
2
|
+
CCI(商品通道指数)因子
|
|
3
|
+
|
|
4
|
+
基于CCI指标的交易因子
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def cci_factor(
|
|
12
|
+
data_slice: 'pd.DataFrame',
|
|
13
|
+
period: int = 20,
|
|
14
|
+
oversold: float = -100.0,
|
|
15
|
+
overbought: float = 100.0
|
|
16
|
+
) -> float:
|
|
17
|
+
"""
|
|
18
|
+
CCI因子
|
|
19
|
+
|
|
20
|
+
当CCI低于oversold时看多,高于overbought时看空
|
|
21
|
+
|
|
22
|
+
Args:
|
|
23
|
+
data_slice: 数据切片,必须包含至少 period+1 行数据
|
|
24
|
+
最后一行是当前数据点,前面 period 行是历史数据
|
|
25
|
+
period: CCI计算周期(默认20)
|
|
26
|
+
oversold: 超卖阈值(默认-100)
|
|
27
|
+
overbought: 超买阈值(默认100)
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
因子值:1.0(看多)、-1.0(看空)或 0(数据不足或中性)
|
|
31
|
+
"""
|
|
32
|
+
if len(data_slice) < period + 1:
|
|
33
|
+
return 0.0
|
|
34
|
+
|
|
35
|
+
# 获取最近period+1行数据
|
|
36
|
+
high_prices = data_slice.iloc[-period-1:]['high_price'].values
|
|
37
|
+
low_prices = data_slice.iloc[-period-1:]['low_price'].values
|
|
38
|
+
close_prices = data_slice.iloc[-period-1:]['close_price'].values
|
|
39
|
+
|
|
40
|
+
# 计算典型价格 (TP = (High + Low + Close) / 3)
|
|
41
|
+
tp = (high_prices + low_prices + close_prices) / 3.0
|
|
42
|
+
|
|
43
|
+
# 当前典型价格
|
|
44
|
+
current_tp = tp[-1]
|
|
45
|
+
|
|
46
|
+
# 计算过去period期的典型价格移动平均
|
|
47
|
+
sma_tp = np.mean(tp[:-1])
|
|
48
|
+
|
|
49
|
+
# 计算平均偏差
|
|
50
|
+
mean_deviation = np.mean(np.abs(tp[:-1] - sma_tp))
|
|
51
|
+
|
|
52
|
+
# 计算CCI
|
|
53
|
+
if mean_deviation == 0:
|
|
54
|
+
cci = 0.0 # 中性值
|
|
55
|
+
else:
|
|
56
|
+
cci = (current_tp - sma_tp) / (0.015 * mean_deviation)
|
|
57
|
+
|
|
58
|
+
# 根据CCI值返回因子
|
|
59
|
+
if cci < oversold:
|
|
60
|
+
return 1.0 # 超卖,看多
|
|
61
|
+
elif cci > overbought:
|
|
62
|
+
return -1.0 # 超买,看空
|
|
63
|
+
else:
|
|
64
|
+
return 0.0 # 中性
|
|
65
|
+
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""
|
|
2
|
+
EMA(指数移动平均线)因子
|
|
3
|
+
|
|
4
|
+
基于EMA指标的交易因子
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _ema(data: np.ndarray, period: int) -> float:
|
|
12
|
+
"""计算指数移动平均"""
|
|
13
|
+
if len(data) < period:
|
|
14
|
+
return np.mean(data)
|
|
15
|
+
|
|
16
|
+
alpha = 2.0 / (period + 1.0)
|
|
17
|
+
ema = data[-period]
|
|
18
|
+
|
|
19
|
+
for i in range(-period + 1, 0):
|
|
20
|
+
ema = alpha * data[i] + (1 - alpha) * ema
|
|
21
|
+
|
|
22
|
+
return ema
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def ema_factor(data_slice: 'pd.DataFrame', period: int = 10) -> float:
|
|
26
|
+
"""
|
|
27
|
+
EMA因子
|
|
28
|
+
|
|
29
|
+
如果当前价格高于EMA,返回1(看多)
|
|
30
|
+
否则返回-1(看空)
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
data_slice: 数据切片,必须包含至少 period+1 行数据
|
|
34
|
+
最后一行是当前数据点,前面 period 行是历史数据
|
|
35
|
+
period: EMA周期(默认10,可选10/20/30/50/100/200)
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
因子值:1.0(看多)、-1.0(看空)或 0(数据不足)
|
|
39
|
+
"""
|
|
40
|
+
if len(data_slice) < period + 1:
|
|
41
|
+
return 0.0
|
|
42
|
+
|
|
43
|
+
# 获取收盘价
|
|
44
|
+
close_prices = data_slice['close_price'].values
|
|
45
|
+
|
|
46
|
+
# 计算EMA(不包括最后一行)
|
|
47
|
+
ema = _ema(close_prices[:-1], period)
|
|
48
|
+
|
|
49
|
+
# 当前价格
|
|
50
|
+
current_price = data_slice.iloc[-1]['close_price']
|
|
51
|
+
|
|
52
|
+
if current_price > ema:
|
|
53
|
+
return 1.0 # 看多
|
|
54
|
+
else:
|
|
55
|
+
return -1.0 # 看空
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def ema_cross_factor(
|
|
59
|
+
data_slice: 'pd.DataFrame',
|
|
60
|
+
short_period: int = 10,
|
|
61
|
+
long_period: int = 20
|
|
62
|
+
) -> float:
|
|
63
|
+
"""
|
|
64
|
+
EMA交叉因子
|
|
65
|
+
|
|
66
|
+
当短期EMA上穿长期EMA时看多,下穿时看空
|
|
67
|
+
|
|
68
|
+
Args:
|
|
69
|
+
data_slice: 数据切片,必须包含至少 long_period+2 行数据
|
|
70
|
+
最后一行是当前数据点,前面 long_period+1 行是历史数据
|
|
71
|
+
short_period: 短期EMA周期(默认10)
|
|
72
|
+
long_period: 长期EMA周期(默认20)
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
因子值:1.0(看多)、-1.0(看空)或 0(数据不足)
|
|
76
|
+
"""
|
|
77
|
+
if len(data_slice) < long_period + 2:
|
|
78
|
+
return 0.0
|
|
79
|
+
|
|
80
|
+
# 获取收盘价
|
|
81
|
+
close_prices = data_slice['close_price'].values
|
|
82
|
+
|
|
83
|
+
# 计算当前周期的EMA(不包括最后一行)
|
|
84
|
+
short_ema_current = _ema(close_prices[:-1], short_period)
|
|
85
|
+
long_ema_current = _ema(close_prices[:-1], long_period)
|
|
86
|
+
|
|
87
|
+
# 计算上一周期的EMA(不包括最后两行)
|
|
88
|
+
if len(data_slice) < long_period + 3:
|
|
89
|
+
return 0.0
|
|
90
|
+
|
|
91
|
+
short_ema_prev = _ema(close_prices[:-2], short_period)
|
|
92
|
+
long_ema_prev = _ema(close_prices[:-2], long_period)
|
|
93
|
+
|
|
94
|
+
# 上穿:之前短期EMA在长期EMA下方,现在在上方
|
|
95
|
+
if short_ema_prev <= long_ema_prev and short_ema_current > long_ema_current:
|
|
96
|
+
return 1.0 # 看多
|
|
97
|
+
# 下穿:之前短期EMA在长期EMA上方,现在在下方
|
|
98
|
+
elif short_ema_prev >= long_ema_prev and short_ema_current < long_ema_current:
|
|
99
|
+
return -1.0 # 看空
|
|
100
|
+
else:
|
|
101
|
+
return 0.0 # 中性
|
|
102
|
+
|