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.
Files changed (40) hide show
  1. cyqnt_trd/CHANGELOG_0.1.7.md +111 -0
  2. cyqnt_trd/__init__.py +1 -1
  3. cyqnt_trd/backtesting/factor_test.py +3 -2
  4. cyqnt_trd/get_data/__init__.py +16 -1
  5. cyqnt_trd/get_data/get_futures_data.py +3 -3
  6. cyqnt_trd/get_data/get_kline_with_factor.py +808 -0
  7. cyqnt_trd/get_data/get_web3_trending_data.py +389 -0
  8. cyqnt_trd/online_trading/__init__.py +1 -0
  9. cyqnt_trd/online_trading/realtime_price_tracker.py +142 -2
  10. cyqnt_trd/trading_signal/example_usage.py +123 -7
  11. cyqnt_trd/trading_signal/factor/__init__.py +23 -0
  12. cyqnt_trd/trading_signal/factor/adx_factor.py +116 -0
  13. cyqnt_trd/trading_signal/factor/ao_factor.py +66 -0
  14. cyqnt_trd/trading_signal/factor/bbp_factor.py +68 -0
  15. cyqnt_trd/trading_signal/factor/cci_factor.py +65 -0
  16. cyqnt_trd/trading_signal/factor/ema_factor.py +102 -0
  17. cyqnt_trd/trading_signal/factor/macd_factor.py +97 -0
  18. cyqnt_trd/trading_signal/factor/momentum_factor.py +44 -0
  19. cyqnt_trd/trading_signal/factor/stochastic_factor.py +76 -0
  20. cyqnt_trd/trading_signal/factor/stochastic_tsi_factor.py +129 -0
  21. cyqnt_trd/trading_signal/factor/uo_factor.py +92 -0
  22. cyqnt_trd/trading_signal/factor/williams_r_factor.py +60 -0
  23. cyqnt_trd/trading_signal/factor_details.json +107 -0
  24. cyqnt_trd/trading_signal/selected_alpha/alpha1.py +4 -2
  25. cyqnt_trd/trading_signal/selected_alpha/alpha15.py +4 -2
  26. cyqnt_trd/trading_signal/selected_alpha/generate_alphas.py +1 -0
  27. {cyqnt_trd-0.1.2.dist-info → cyqnt_trd-0.1.7.dist-info}/METADATA +16 -12
  28. {cyqnt_trd-0.1.2.dist-info → cyqnt_trd-0.1.7.dist-info}/RECORD +34 -23
  29. {cyqnt_trd-0.1.2.dist-info → cyqnt_trd-0.1.7.dist-info}/WHEEL +1 -1
  30. test/real_time_trade.py +467 -10
  31. test/test_now_factor.py +1082 -0
  32. test/track_k_line_continue.py +372 -0
  33. cyqnt_trd/test_script/get_symbols_by_volume.py +0 -227
  34. cyqnt_trd/test_script/test_alpha.py +0 -261
  35. cyqnt_trd/test_script/test_kline_data.py +0 -479
  36. test/test_example_usage.py +0 -381
  37. test/test_get_data.py +0 -310
  38. test/test_realtime_price_tracker.py +0 -546
  39. {cyqnt_trd-0.1.2.dist-info → cyqnt_trd-0.1.7.dist-info}/licenses/LICENSE +0 -0
  40. {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=7, # 未来7个周期
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.2, # 每次使用20%的资金
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/data_all/tmp/data/BTCUSDT_current/BTCUSDT_3m_2880_20251215_000000_20251221_000000_20251222_173038.json"
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
- # example_5_alpha1_factor() # Alpha#1因子测试
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
+