cyqnt-trd 0.1.2__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 (147) hide show
  1. cyqnt_trd/__init__.py +26 -0
  2. cyqnt_trd/backtesting/README.md +264 -0
  3. cyqnt_trd/backtesting/__init__.py +12 -0
  4. cyqnt_trd/backtesting/factor_test.py +332 -0
  5. cyqnt_trd/backtesting/framework.py +311 -0
  6. cyqnt_trd/backtesting/strategy_backtest.py +545 -0
  7. cyqnt_trd/diagnose_api.py +28 -0
  8. cyqnt_trd/get_data/__init__.py +15 -0
  9. cyqnt_trd/get_data/get_futures_data.py +472 -0
  10. cyqnt_trd/get_data/get_trending_data.py +771 -0
  11. cyqnt_trd/online_trading/__init__.py +13 -0
  12. cyqnt_trd/online_trading/realtime_price_tracker.py +1001 -0
  13. cyqnt_trd/test.py +119 -0
  14. cyqnt_trd/test_script/README.md +411 -0
  15. cyqnt_trd/test_script/get_network_info.py +192 -0
  16. cyqnt_trd/test_script/get_symbols_by_volume.py +227 -0
  17. cyqnt_trd/test_script/realtime_price_tracker.py +839 -0
  18. cyqnt_trd/test_script/test_alpha.py +261 -0
  19. cyqnt_trd/test_script/test_kline_data.py +479 -0
  20. cyqnt_trd/test_script/test_order.py +1360 -0
  21. cyqnt_trd/trading_signal/README.md +276 -0
  22. cyqnt_trd/trading_signal/__init__.py +17 -0
  23. cyqnt_trd/trading_signal/example_test_alpha.py +430 -0
  24. cyqnt_trd/trading_signal/example_usage.py +431 -0
  25. cyqnt_trd/trading_signal/factor/__init__.py +18 -0
  26. cyqnt_trd/trading_signal/factor/ma_factor.py +75 -0
  27. cyqnt_trd/trading_signal/factor/rsi_factor.py +56 -0
  28. cyqnt_trd/trading_signal/selected_alpha/__init__.py +158 -0
  29. cyqnt_trd/trading_signal/selected_alpha/alpha1.py +87 -0
  30. cyqnt_trd/trading_signal/selected_alpha/alpha10.py +90 -0
  31. cyqnt_trd/trading_signal/selected_alpha/alpha100.py +74 -0
  32. cyqnt_trd/trading_signal/selected_alpha/alpha101.py +86 -0
  33. cyqnt_trd/trading_signal/selected_alpha/alpha11.py +86 -0
  34. cyqnt_trd/trading_signal/selected_alpha/alpha12.py +86 -0
  35. cyqnt_trd/trading_signal/selected_alpha/alpha13.py +86 -0
  36. cyqnt_trd/trading_signal/selected_alpha/alpha14.py +87 -0
  37. cyqnt_trd/trading_signal/selected_alpha/alpha15.py +87 -0
  38. cyqnt_trd/trading_signal/selected_alpha/alpha16.py +86 -0
  39. cyqnt_trd/trading_signal/selected_alpha/alpha17.py +88 -0
  40. cyqnt_trd/trading_signal/selected_alpha/alpha18.py +88 -0
  41. cyqnt_trd/trading_signal/selected_alpha/alpha19.py +87 -0
  42. cyqnt_trd/trading_signal/selected_alpha/alpha2.py +86 -0
  43. cyqnt_trd/trading_signal/selected_alpha/alpha20.py +88 -0
  44. cyqnt_trd/trading_signal/selected_alpha/alpha21.py +89 -0
  45. cyqnt_trd/trading_signal/selected_alpha/alpha22.py +87 -0
  46. cyqnt_trd/trading_signal/selected_alpha/alpha23.py +88 -0
  47. cyqnt_trd/trading_signal/selected_alpha/alpha24.py +88 -0
  48. cyqnt_trd/trading_signal/selected_alpha/alpha25.py +86 -0
  49. cyqnt_trd/trading_signal/selected_alpha/alpha26.py +87 -0
  50. cyqnt_trd/trading_signal/selected_alpha/alpha27.py +88 -0
  51. cyqnt_trd/trading_signal/selected_alpha/alpha28.py +88 -0
  52. cyqnt_trd/trading_signal/selected_alpha/alpha29.py +87 -0
  53. cyqnt_trd/trading_signal/selected_alpha/alpha3.py +86 -0
  54. cyqnt_trd/trading_signal/selected_alpha/alpha30.py +87 -0
  55. cyqnt_trd/trading_signal/selected_alpha/alpha31.py +90 -0
  56. cyqnt_trd/trading_signal/selected_alpha/alpha32.py +86 -0
  57. cyqnt_trd/trading_signal/selected_alpha/alpha33.py +86 -0
  58. cyqnt_trd/trading_signal/selected_alpha/alpha34.py +87 -0
  59. cyqnt_trd/trading_signal/selected_alpha/alpha35.py +88 -0
  60. cyqnt_trd/trading_signal/selected_alpha/alpha36.py +86 -0
  61. cyqnt_trd/trading_signal/selected_alpha/alpha37.py +86 -0
  62. cyqnt_trd/trading_signal/selected_alpha/alpha38.py +87 -0
  63. cyqnt_trd/trading_signal/selected_alpha/alpha39.py +87 -0
  64. cyqnt_trd/trading_signal/selected_alpha/alpha4.py +86 -0
  65. cyqnt_trd/trading_signal/selected_alpha/alpha40.py +86 -0
  66. cyqnt_trd/trading_signal/selected_alpha/alpha41.py +86 -0
  67. cyqnt_trd/trading_signal/selected_alpha/alpha42.py +86 -0
  68. cyqnt_trd/trading_signal/selected_alpha/alpha43.py +86 -0
  69. cyqnt_trd/trading_signal/selected_alpha/alpha44.py +87 -0
  70. cyqnt_trd/trading_signal/selected_alpha/alpha45.py +88 -0
  71. cyqnt_trd/trading_signal/selected_alpha/alpha46.py +89 -0
  72. cyqnt_trd/trading_signal/selected_alpha/alpha47.py +86 -0
  73. cyqnt_trd/trading_signal/selected_alpha/alpha48.py +74 -0
  74. cyqnt_trd/trading_signal/selected_alpha/alpha49.py +88 -0
  75. cyqnt_trd/trading_signal/selected_alpha/alpha5.py +86 -0
  76. cyqnt_trd/trading_signal/selected_alpha/alpha50.py +86 -0
  77. cyqnt_trd/trading_signal/selected_alpha/alpha51.py +88 -0
  78. cyqnt_trd/trading_signal/selected_alpha/alpha52.py +87 -0
  79. cyqnt_trd/trading_signal/selected_alpha/alpha53.py +86 -0
  80. cyqnt_trd/trading_signal/selected_alpha/alpha54.py +86 -0
  81. cyqnt_trd/trading_signal/selected_alpha/alpha55.py +88 -0
  82. cyqnt_trd/trading_signal/selected_alpha/alpha56.py +86 -0
  83. cyqnt_trd/trading_signal/selected_alpha/alpha57.py +86 -0
  84. cyqnt_trd/trading_signal/selected_alpha/alpha58.py +74 -0
  85. cyqnt_trd/trading_signal/selected_alpha/alpha59.py +74 -0
  86. cyqnt_trd/trading_signal/selected_alpha/alpha6.py +86 -0
  87. cyqnt_trd/trading_signal/selected_alpha/alpha60.py +89 -0
  88. cyqnt_trd/trading_signal/selected_alpha/alpha61.py +88 -0
  89. cyqnt_trd/trading_signal/selected_alpha/alpha62.py +86 -0
  90. cyqnt_trd/trading_signal/selected_alpha/alpha63.py +74 -0
  91. cyqnt_trd/trading_signal/selected_alpha/alpha64.py +86 -0
  92. cyqnt_trd/trading_signal/selected_alpha/alpha65.py +86 -0
  93. cyqnt_trd/trading_signal/selected_alpha/alpha66.py +86 -0
  94. cyqnt_trd/trading_signal/selected_alpha/alpha67.py +74 -0
  95. cyqnt_trd/trading_signal/selected_alpha/alpha68.py +86 -0
  96. cyqnt_trd/trading_signal/selected_alpha/alpha69.py +74 -0
  97. cyqnt_trd/trading_signal/selected_alpha/alpha7.py +88 -0
  98. cyqnt_trd/trading_signal/selected_alpha/alpha70.py +74 -0
  99. cyqnt_trd/trading_signal/selected_alpha/alpha71.py +92 -0
  100. cyqnt_trd/trading_signal/selected_alpha/alpha72.py +86 -0
  101. cyqnt_trd/trading_signal/selected_alpha/alpha73.py +91 -0
  102. cyqnt_trd/trading_signal/selected_alpha/alpha74.py +86 -0
  103. cyqnt_trd/trading_signal/selected_alpha/alpha75.py +86 -0
  104. cyqnt_trd/trading_signal/selected_alpha/alpha76.py +74 -0
  105. cyqnt_trd/trading_signal/selected_alpha/alpha77.py +92 -0
  106. cyqnt_trd/trading_signal/selected_alpha/alpha78.py +86 -0
  107. cyqnt_trd/trading_signal/selected_alpha/alpha79.py +74 -0
  108. cyqnt_trd/trading_signal/selected_alpha/alpha8.py +87 -0
  109. cyqnt_trd/trading_signal/selected_alpha/alpha80.py +74 -0
  110. cyqnt_trd/trading_signal/selected_alpha/alpha81.py +86 -0
  111. cyqnt_trd/trading_signal/selected_alpha/alpha82.py +74 -0
  112. cyqnt_trd/trading_signal/selected_alpha/alpha83.py +86 -0
  113. cyqnt_trd/trading_signal/selected_alpha/alpha84.py +86 -0
  114. cyqnt_trd/trading_signal/selected_alpha/alpha85.py +86 -0
  115. cyqnt_trd/trading_signal/selected_alpha/alpha86.py +86 -0
  116. cyqnt_trd/trading_signal/selected_alpha/alpha87.py +74 -0
  117. cyqnt_trd/trading_signal/selected_alpha/alpha88.py +92 -0
  118. cyqnt_trd/trading_signal/selected_alpha/alpha89.py +74 -0
  119. cyqnt_trd/trading_signal/selected_alpha/alpha9.py +90 -0
  120. cyqnt_trd/trading_signal/selected_alpha/alpha90.py +74 -0
  121. cyqnt_trd/trading_signal/selected_alpha/alpha91.py +74 -0
  122. cyqnt_trd/trading_signal/selected_alpha/alpha92.py +92 -0
  123. cyqnt_trd/trading_signal/selected_alpha/alpha93.py +74 -0
  124. cyqnt_trd/trading_signal/selected_alpha/alpha94.py +86 -0
  125. cyqnt_trd/trading_signal/selected_alpha/alpha95.py +86 -0
  126. cyqnt_trd/trading_signal/selected_alpha/alpha96.py +92 -0
  127. cyqnt_trd/trading_signal/selected_alpha/alpha97.py +74 -0
  128. cyqnt_trd/trading_signal/selected_alpha/alpha98.py +87 -0
  129. cyqnt_trd/trading_signal/selected_alpha/alpha99.py +86 -0
  130. cyqnt_trd/trading_signal/selected_alpha/alpha_utils.py +342 -0
  131. cyqnt_trd/trading_signal/selected_alpha/create_all_alphas.py +279 -0
  132. cyqnt_trd/trading_signal/selected_alpha/generate_alphas.py +133 -0
  133. cyqnt_trd/trading_signal/selected_alpha/test_alpha.py +261 -0
  134. cyqnt_trd/trading_signal/signal/__init__.py +20 -0
  135. cyqnt_trd/trading_signal/signal/factor_based_signal.py +387 -0
  136. cyqnt_trd/trading_signal/signal/ma_signal.py +163 -0
  137. cyqnt_trd/utils/__init__.py +3 -0
  138. cyqnt_trd/utils/set_user.py +33 -0
  139. cyqnt_trd-0.1.2.dist-info/METADATA +148 -0
  140. cyqnt_trd-0.1.2.dist-info/RECORD +147 -0
  141. cyqnt_trd-0.1.2.dist-info/WHEEL +5 -0
  142. cyqnt_trd-0.1.2.dist-info/licenses/LICENSE +21 -0
  143. cyqnt_trd-0.1.2.dist-info/top_level.txt +2 -0
  144. test/real_time_trade.py +746 -0
  145. test/test_example_usage.py +381 -0
  146. test/test_get_data.py +310 -0
  147. test/test_realtime_price_tracker.py +546 -0
cyqnt_trd/__init__.py ADDED
@@ -0,0 +1,26 @@
1
+ """
2
+ Cyqnt Trading Package
3
+
4
+ 一个用于加密货币交易的工具包,包含数据获取、交易信号生成和回测功能。
5
+
6
+ 主要模块:
7
+ - get_data: 数据获取模块,支持从 Binance 获取期货和现货数据
8
+ - trading_signal: 交易信号模块,包含因子计算和信号策略
9
+ - backtesting: 回测框架,支持因子测试和策略回测
10
+ """
11
+
12
+ __version__ = "0.1.0"
13
+
14
+ # 导入主要模块
15
+ from . import get_data
16
+ from . import trading_signal
17
+ from . import backtesting
18
+
19
+ __all__ = [
20
+ 'get_data',
21
+ 'trading_signal',
22
+ 'backtesting',
23
+ 'utils',
24
+ '__version__',
25
+ ]
26
+
@@ -0,0 +1,264 @@
1
+ # 回测框架使用说明
2
+
3
+ 本回测框架提供了两个主要功能:
4
+ 1. **单因子胜率测试** - 测试某个因子在预测未来价格方向上的胜率
5
+ 2. **策略回测** - 根据买卖信号进行回测,计算收益率和收益曲线
6
+
7
+ ## 功能概述
8
+
9
+ ### 1. 单因子胜率测试 (FactorTester)
10
+
11
+ 用于测试某个因子在预测未来一段时间内价格方向(多/空)的胜率。
12
+
13
+ **主要功能:**
14
+ - 计算因子值(正数=看多,负数=看空,0=中性)
15
+ - 统计看多/看空信号的胜率
16
+ - 计算平均收益率
17
+ - 输出详细的测试结果
18
+
19
+ ### 2. 策略回测 (StrategyBacktester)
20
+
21
+ 根据买卖信号进行回测,计算收益率和收益曲线。
22
+
23
+ **主要功能:**
24
+ - 根据买卖信号模拟交易
25
+ - 计算总收益率、胜率、最大回撤、夏普比率等指标
26
+ - 绘制资金曲线、价格曲线和回撤曲线
27
+ - 记录所有交易详情
28
+
29
+ ## 快速开始
30
+
31
+ ### 基本使用
32
+
33
+ ```python
34
+ import pandas as pd
35
+ import json
36
+ from cyqnt_trd.backtesting import BacktestFramework
37
+
38
+ # 加载数据
39
+ data_path = 'path/to/your/data.json'
40
+ framework = BacktestFramework(data_path=data_path)
41
+
42
+ # 定义因子函数
43
+ def my_factor(data: pd.DataFrame, index: int) -> float:
44
+ """因子函数:返回因子值(正数=看多,负数=看空)"""
45
+ current_price = data.iloc[index]['close_price']
46
+ # 你的因子计算逻辑
47
+ # ...
48
+ return 1.0 # 或 -1.0, 0.0
49
+
50
+ # 测试因子
51
+ results = framework.test_factor(
52
+ factor_func=my_factor,
53
+ forward_periods=7, # 未来7个周期
54
+ min_periods=0,
55
+ factor_name="我的因子"
56
+ )
57
+
58
+ # 打印结果
59
+ framework.print_factor_results(results)
60
+ ```
61
+
62
+ ### 策略回测
63
+
64
+ ```python
65
+ # 定义信号函数
66
+ def my_signal(data: pd.DataFrame, index: int) -> str:
67
+ """信号函数:返回 'buy', 'sell', 'hold' 或 None"""
68
+ # 你的信号生成逻辑
69
+ # ...
70
+ return 'buy' # 或 'sell', 'hold'
71
+
72
+ # 回测策略
73
+ results = framework.backtest_strategy(
74
+ signal_func=my_signal,
75
+ min_periods=0,
76
+ position_size=0.5, # 每次使用50%的资金
77
+ initial_capital=10000.0,
78
+ commission_rate=0.001 # 0.1%手续费
79
+ )
80
+
81
+ # 打印结果
82
+ framework.print_backtest_results(results)
83
+
84
+ # 绘制结果
85
+ framework.plot_backtest_results(results)
86
+ ```
87
+
88
+ ## 数据格式要求
89
+
90
+ 数据可以是以下两种格式之一:
91
+
92
+ ### 1. JSON文件格式
93
+
94
+ ```json
95
+ {
96
+ "symbol": "BTCUSDT",
97
+ "interval": "1m",
98
+ "data": [
99
+ {
100
+ "open_time": 1234567890000,
101
+ "open_time_str": "2023-01-01 00:00:00",
102
+ "open_price": 100.0,
103
+ "high_price": 105.0,
104
+ "low_price": 95.0,
105
+ "close_price": 102.0,
106
+ "volume": 1000.0,
107
+ ...
108
+ },
109
+ ...
110
+ ]
111
+ }
112
+ ```
113
+
114
+ ### 2. DataFrame格式
115
+
116
+ DataFrame必须包含以下列:
117
+ - `datetime` 或 `open_time_str` 或 `open_time`: 时间
118
+ - `close_price`: 收盘价
119
+ - 其他因子计算所需的列(如 `open_price`, `high_price`, `low_price`, `volume` 等)
120
+
121
+ ## 详细示例
122
+
123
+ ### 示例1: 移动平均线因子测试
124
+
125
+ ```python
126
+ def ma_factor(data: pd.DataFrame, index: int) -> float:
127
+ """MA5因子:价格高于MA5看多,低于MA5看空"""
128
+ if index < 5:
129
+ return 0
130
+
131
+ current_price = data.iloc[index]['close_price']
132
+ ma5 = data.iloc[index-5:index]['close_price'].mean()
133
+
134
+ if current_price > ma5:
135
+ return 1.0 # 看多
136
+ else:
137
+ return -1.0 # 看空
138
+
139
+ # 测试因子
140
+ framework = BacktestFramework(data_path='data.json')
141
+ results = framework.test_factor(
142
+ factor_func=ma_factor,
143
+ forward_periods=7,
144
+ min_periods=5,
145
+ factor_name="MA5因子"
146
+ )
147
+ framework.print_factor_results(results)
148
+ ```
149
+
150
+ ### 示例2: 移动平均线交叉策略
151
+
152
+ ```python
153
+ def ma_cross_signal(data: pd.DataFrame, index: int) -> str:
154
+ """MA交叉策略:价格上穿MA5买入,下穿MA5卖出"""
155
+ if index < 5:
156
+ return 'hold'
157
+
158
+ current_price = data.iloc[index]['close_price']
159
+ ma5 = data.iloc[index-5:index]['close_price'].mean()
160
+ prev_price = data.iloc[index-1]['close_price']
161
+ prev_ma5 = data.iloc[index-6:index-1]['close_price'].mean()
162
+
163
+ # 上穿
164
+ if prev_price <= prev_ma5 and current_price > ma5:
165
+ return 'buy'
166
+ # 下穿
167
+ elif prev_price >= prev_ma5 and current_price < ma5:
168
+ return 'sell'
169
+ else:
170
+ return 'hold'
171
+
172
+ # 回测策略
173
+ results = framework.backtest_strategy(
174
+ signal_func=ma_cross_signal,
175
+ min_periods=5,
176
+ position_size=0.5,
177
+ initial_capital=10000.0,
178
+ commission_rate=0.001
179
+ )
180
+ framework.plot_backtest_results(results)
181
+ ```
182
+
183
+ ## API参考
184
+
185
+ ### BacktestFramework
186
+
187
+ 主回测框架类,提供统一的接口。
188
+
189
+ #### 方法
190
+
191
+ - `test_factor(factor_func, forward_periods=7, min_periods=0, factor_name="factor")` - 测试因子胜率
192
+ - `backtest_strategy(signal_func, min_periods=0, position_size=1.0, initial_capital=10000.0, commission_rate=0.001)` - 回测策略
193
+ - `plot_backtest_results(results, figsize=(14, 10))` - 绘制回测结果
194
+ - `print_factor_results(results)` - 打印因子测试结果
195
+ - `print_backtest_results(results)` - 打印回测结果
196
+
197
+ ### FactorTester
198
+
199
+ 单因子胜率测试器。
200
+
201
+ #### 方法
202
+
203
+ - `test_factor(factor_func, forward_periods=7, min_periods=0, factor_name="factor")` - 测试因子
204
+ - `print_results(results)` - 打印结果
205
+ - `save_results(results, filepath)` - 保存结果到JSON文件
206
+
207
+ ### StrategyBacktester
208
+
209
+ 策略回测器。
210
+
211
+ #### 方法
212
+
213
+ - `backtest(signal_func, min_periods=0, position_size=1.0)` - 执行回测
214
+ - `plot_results(results, figsize=(14, 10))` - 绘制结果
215
+ - `print_results(results)` - 打印结果
216
+ - `save_results(results, filepath)` - 保存结果到JSON文件
217
+
218
+ ## 注意事项
219
+
220
+ 1. **因子函数** (`factor_func`):
221
+ - 接受 `(data: pd.DataFrame, index: int)` 作为参数
222
+ - 返回 `float`: 正数表示看多,负数表示看空,0表示中性
223
+
224
+ 2. **信号函数** (`signal_func`):
225
+ - 接受 `(data: pd.DataFrame, index: int)` 作为参数
226
+ - 返回 `str`: `'buy'`(买入)、`'sell'`(卖出)、`'hold'`(持有)或 `None`
227
+
228
+ 3. **数据要求**:
229
+ - 数据必须按时间排序
230
+ - 必须包含 `close_price` 列
231
+ - 必须包含时间列(`datetime`, `open_time_str` 或 `open_time`)
232
+
233
+ 4. **性能考虑**:
234
+ - 对于大量数据,建议使用适当的数据切片
235
+ - 因子和信号函数应该尽可能高效
236
+
237
+ ## 输出结果说明
238
+
239
+ ### 因子测试结果
240
+
241
+ - `total_samples`: 总样本数
242
+ - `long_signals`: 看多信号数量
243
+ - `short_signals`: 看空信号数量
244
+ - `long_win_rate`: 看多信号胜率
245
+ - `short_win_rate`: 看空信号胜率
246
+ - `overall_win_rate`: 总体胜率
247
+ - `long_avg_return`: 看多信号平均收益率
248
+ - `short_avg_return`: 看空信号平均收益率
249
+ - `details`: 详细结果列表
250
+
251
+ ### 策略回测结果
252
+
253
+ - `initial_capital`: 初始资金
254
+ - `final_capital`: 最终资金
255
+ - `total_return`: 总收益率
256
+ - `total_trades`: 总交易次数
257
+ - `win_trades`: 盈利交易次数
258
+ - `loss_trades`: 亏损交易次数
259
+ - `win_rate`: 胜率
260
+ - `max_drawdown`: 最大回撤
261
+ - `sharpe_ratio`: 夏普比率
262
+ - `equity_curve`: 资金曲线(DataFrame)
263
+ - `trades`: 交易记录列表
264
+
@@ -0,0 +1,12 @@
1
+ """
2
+ 回测框架模块
3
+
4
+ 提供单因子胜率测试和策略回测功能
5
+ """
6
+
7
+ from .factor_test import FactorTester
8
+ from .strategy_backtest import StrategyBacktester
9
+ from .framework import BacktestFramework
10
+
11
+ __all__ = ['FactorTester', 'StrategyBacktester', 'BacktestFramework']
12
+
@@ -0,0 +1,332 @@
1
+ """
2
+ 单因子胜率测试模块
3
+
4
+ 用于测试某个因子在预测未来价格方向上的胜率
5
+ """
6
+
7
+ import pandas as pd
8
+ import numpy as np
9
+ from typing import Callable, Optional, Dict, List, Tuple
10
+ from datetime import datetime, timedelta
11
+ import json
12
+ import os
13
+
14
+
15
+ class FactorTester:
16
+ """
17
+ 单因子胜率测试器
18
+
19
+ 用于测试某个因子在预测未来一段时间内价格方向(多/空)的胜率
20
+ """
21
+
22
+ def __init__(self, data: pd.DataFrame):
23
+ """
24
+ 初始化因子测试器
25
+
26
+ Args:
27
+ data: 包含K线数据的DataFrame,必须包含以下列:
28
+ - datetime 或 open_time_str: 时间
29
+ - close_price: 收盘价
30
+ 以及其他因子计算所需的列
31
+ """
32
+ self.data = data.copy()
33
+
34
+ # 确保有datetime列
35
+ if 'datetime' not in self.data.columns:
36
+ if 'open_time_str' in self.data.columns:
37
+ self.data['datetime'] = pd.to_datetime(self.data['open_time_str'])
38
+ elif 'open_time' in self.data.columns:
39
+ self.data['datetime'] = pd.to_datetime(self.data['open_time'], unit='ms')
40
+ else:
41
+ raise ValueError("数据必须包含 'datetime', 'open_time_str' 或 'open_time' 列")
42
+
43
+ # 确保有close_price列
44
+ if 'close_price' not in self.data.columns:
45
+ raise ValueError("数据必须包含 'close_price' 列")
46
+
47
+ # 按时间排序
48
+ self.data = self.data.sort_values('datetime').reset_index(drop=True)
49
+
50
+ def test_factor(
51
+ self,
52
+ factor_func: Callable[[pd.DataFrame], float],
53
+ forward_periods: int = 7,
54
+ min_periods: int = 0,
55
+ factor_name: str = "factor"
56
+ ) -> Dict:
57
+ """
58
+ 测试单因子的胜率
59
+
60
+ Args:
61
+ factor_func: 因子计算函数,接受数据切片作为参数,返回因子值
62
+ - data_slice: 数据切片(包含历史数据和当前数据点)
63
+ - 返回: 因子值(正数表示看多,负数表示看空,0表示中性)
64
+ forward_periods: 向前看的周期数(例如:7表示未来7个周期)
65
+ min_periods: 最小需要的周期数(用于计算因子时)
66
+ factor_name: 因子名称,用于结果标识
67
+
68
+ Returns:
69
+ 包含测试结果的字典:
70
+ - factor_name: 因子名称
71
+ - total_samples: 总样本数
72
+ - long_signals: 看多信号数量
73
+ - short_signals: 看空信号数量
74
+ - neutral_signals: 中性信号数量
75
+ - long_win_rate: 看多信号胜率
76
+ - short_win_rate: 看空信号胜率
77
+ - overall_win_rate: 总体胜率
78
+ - long_avg_return: 看多信号平均收益率
79
+ - short_avg_return: 看空信号平均收益率
80
+ - details: 详细结果列表
81
+ """
82
+ results = {
83
+ 'factor_name': factor_name,
84
+ 'total_samples': 0,
85
+ 'long_signals': 0,
86
+ 'short_signals': 0,
87
+ 'neutral_signals': 0,
88
+ 'long_win_rate': 0.0,
89
+ 'short_win_rate': 0.0,
90
+ 'overall_win_rate': 0.0,
91
+ 'long_avg_return': 0.0,
92
+ 'short_avg_return': 0.0,
93
+ 'details': []
94
+ }
95
+
96
+ long_wins = 0
97
+ short_wins = 0
98
+ long_returns = []
99
+ short_returns = []
100
+
101
+ # 遍历每个时间点(确保有足够的数据向前看)
102
+ for i in range(min_periods, len(self.data) - forward_periods):
103
+ try:
104
+ # 计算因子值:传递数据切片而不是整个data和index
105
+ # 需要确定因子函数需要多少历史数据
106
+ # 为了兼容性,传递足够的数据切片(假设最多需要50个周期)
107
+ max_period = 50
108
+ start_idx = max(0, i - max_period)
109
+ data_slice = self.data.iloc[start_idx:i+1].copy()
110
+ factor_value = factor_func(data_slice)
111
+
112
+ if factor_value is None or np.isnan(factor_value):
113
+ continue
114
+
115
+ # 获取当前价格
116
+ current_price = self.data.iloc[i]['close_price']
117
+
118
+ # 获取未来 forward_periods 个周期的价格数据
119
+ future_start_idx = i + 1
120
+ future_end_idx = min(i + forward_periods + 1, len(self.data))
121
+
122
+ if future_end_idx <= future_start_idx:
123
+ continue
124
+
125
+ future_prices = self.data.iloc[future_start_idx:future_end_idx]['close_price'].values
126
+ future_prices_max = self.data.iloc[future_start_idx:future_end_idx]['high_price'].values
127
+ future_prices_min = self.data.iloc[future_start_idx:future_end_idx]['low_price'].values
128
+
129
+ # 获取最后一个周期的价格用于计算收益率
130
+ future_idx = i + forward_periods
131
+ if future_idx >= len(self.data):
132
+ future_price = self.data.iloc[-1]['close_price']
133
+ else:
134
+ future_price = self.data.iloc[future_idx]['close_price']
135
+
136
+ # 计算未来收益率(基于最后一个周期)
137
+ future_return = (future_price - current_price) / current_price
138
+
139
+ # 判断信号类型和是否获胜
140
+ signal_type = None
141
+ is_win = False
142
+
143
+ if factor_value > 0:
144
+ # 看多信号:只要未来 n 个周期内存在任一价格高于当前价格,即认为获胜
145
+ signal_type = 'long'
146
+ results['long_signals'] += 1
147
+ is_win = bool(np.any(future_prices > current_price))
148
+ if is_win:
149
+ long_wins += 1
150
+ long_returns.append(future_return)
151
+
152
+ elif factor_value < 0:
153
+ # 看空信号:只要未来 n 个周期内存在任一价格低于当前价格,即认为获胜
154
+ signal_type = 'short'
155
+ results['short_signals'] += 1
156
+ is_win = bool(np.any(future_prices < current_price))
157
+ if is_win:
158
+ short_wins += 1
159
+ short_returns.append(future_return)
160
+
161
+ else:
162
+ # 中性信号
163
+ signal_type = 'neutral'
164
+ results['neutral_signals'] += 1
165
+
166
+ # 记录详细信息
167
+ results['details'].append({
168
+ 'datetime': self.data.iloc[i]['datetime'],
169
+ 'index': i,
170
+ 'factor_value': float(factor_value),
171
+ 'signal_type': signal_type,
172
+ 'current_price': float(current_price),
173
+ 'future_price': float(future_price),
174
+ 'future_return': float(future_return),
175
+ 'is_win': is_win
176
+ })
177
+
178
+ results['total_samples'] += 1
179
+
180
+ except Exception as e:
181
+ # 如果因子计算出错,跳过该点
182
+ continue
183
+
184
+ # 计算胜率
185
+ if results['long_signals'] > 0:
186
+ results['long_win_rate'] = long_wins / results['long_signals']
187
+ results['long_avg_return'] = np.mean(long_returns) if long_returns else 0.0
188
+
189
+ if results['short_signals'] > 0:
190
+ results['short_win_rate'] = short_wins / results['short_signals']
191
+ results['short_avg_return'] = np.mean(short_returns) if short_returns else 0.0
192
+
193
+ total_wins = long_wins + short_wins
194
+ total_signals = results['long_signals'] + results['short_signals']
195
+ if total_signals > 0:
196
+ results['overall_win_rate'] = total_wins / total_signals
197
+
198
+ return results
199
+
200
+ def print_results(
201
+ self,
202
+ results: Dict,
203
+ save_dir: Optional[str] = None,
204
+ factor_name: Optional[str] = None,
205
+ data_name: Optional[str] = None,
206
+ save_json: Optional[str] = None,
207
+ source_start_str: Optional[str] = None,
208
+ source_end_str: Optional[str] = None
209
+ ):
210
+ """
211
+ 打印测试结果
212
+
213
+ Args:
214
+ results: test_factor返回的结果字典
215
+ save_dir: 保存目录(如果提供factor_name和data_name,将在此目录下保存文件)
216
+ factor_name: 因子名称(用于自动生成文件名,如果为None,从results中获取)
217
+ data_name: 数据名称(用于自动生成文件名)
218
+ save_json: JSON结果保存路径(如果提供,将保存JSON,优先级高于自动生成)
219
+ source_start_str: 源数据的开始日期字符串(格式:YYYYMMDD),如果提供则优先使用
220
+ source_end_str: 源数据的结束日期字符串(格式:YYYYMMDD),如果提供则优先使用
221
+ """
222
+ print(f"\n{'='*60}")
223
+ print(f"因子测试结果: {results['factor_name']}")
224
+ print(f"{'='*60}")
225
+ print(f"总样本数: {results['total_samples']}")
226
+ print(f"看多信号数: {results['long_signals']}")
227
+ print(f"看空信号数: {results['short_signals']}")
228
+ print(f"中性信号数: {results['neutral_signals']}")
229
+ print(f"\n看多信号胜率: {results['long_win_rate']:.2%}")
230
+ print(f"看多信号次日平均收益率: {results['long_avg_return']:.4%}")
231
+ print(f"\n看空信号胜率: {results['short_win_rate']:.2%}")
232
+ print(f"看空信号次日平均收益率: {results['short_avg_return']:.4%}")
233
+ print(f"\n总体胜率: {results['overall_win_rate']:.2%}")
234
+ print(f"{'='*60}\n")
235
+
236
+ # 自动保存JSON结果
237
+ if save_dir and (factor_name or results.get('factor_name')) and data_name:
238
+ # 获取因子名称
239
+ if factor_name is None:
240
+ factor_name = results.get('factor_name', 'factor')
241
+
242
+ # 清理名称,移除特殊字符,用于文件名
243
+ clean_factor_name = factor_name.replace(' ', '_').replace('/', '_').replace('\\', '_')
244
+ clean_data_name = data_name.replace(' ', '_').replace('/', '_').replace('\\', '_')
245
+
246
+ # 优先使用源数据的时间范围,如果没有则从数据中提取测试时间段
247
+ start_str = source_start_str or ""
248
+ end_str = source_end_str or ""
249
+
250
+ if not start_str or not end_str:
251
+ # 从数据中提取测试时间段
252
+ if not self.data.empty and 'datetime' in self.data.columns:
253
+ try:
254
+ start_time = self.data['datetime'].min()
255
+ end_time = self.data['datetime'].max()
256
+
257
+ # 转换为datetime对象(如果还不是)
258
+ if not isinstance(start_time, (pd.Timestamp, datetime)):
259
+ start_time = pd.to_datetime(start_time)
260
+ end_time = pd.to_datetime(end_time)
261
+
262
+ # 格式化时间为 YYYYMMDD
263
+ start_str = start_time.strftime('%Y%m%d')
264
+ end_str = end_time.strftime('%Y%m%d')
265
+ except Exception:
266
+ # 如果时间提取失败,使用空字符串
267
+ pass
268
+
269
+ # 确定保存目录结构
270
+ if start_str and end_str:
271
+ # 子文件夹格式:{数据名称}_{开始日期}_{结束日期}
272
+ subfolder_name = f"{clean_data_name}_{start_str}_{end_str}"
273
+ final_save_dir = os.path.join(save_dir, subfolder_name)
274
+ else:
275
+ # 如果没有时间段,只使用数据名称
276
+ final_save_dir = os.path.join(save_dir, clean_data_name)
277
+
278
+ # 确保目录存在
279
+ os.makedirs(final_save_dir, exist_ok=True)
280
+
281
+ # 文件名只包含因子名称
282
+ base_filename = f"{clean_factor_name}_factor"
283
+ base_path = os.path.join(final_save_dir, base_filename)
284
+
285
+ # 如果未明确指定路径,使用自动生成的路径
286
+ if save_json is None:
287
+ save_json = f"{base_path}.json"
288
+
289
+ # 保存JSON结果
290
+ if save_json:
291
+ self.save_results(results, save_json)
292
+
293
+ def save_results(self, results: Dict, filepath: str):
294
+ """
295
+ 保存测试结果到JSON文件
296
+
297
+ Args:
298
+ results: test_factor返回的结果字典
299
+ filepath: 保存路径
300
+ """
301
+ # 转换datetime为字符串以便JSON序列化
302
+ output = results.copy()
303
+ for detail in output['details']:
304
+ if isinstance(detail['datetime'], (pd.Timestamp, datetime)):
305
+ detail['datetime'] = detail['datetime'].isoformat()
306
+ # 转换numpy类型为Python原生类型
307
+ if 'is_win' in detail:
308
+ detail['is_win'] = bool(detail['is_win'])
309
+ if 'factor_value' in detail:
310
+ detail['factor_value'] = float(detail['factor_value'])
311
+ if 'current_price' in detail:
312
+ detail['current_price'] = float(detail['current_price'])
313
+ if 'future_price' in detail:
314
+ detail['future_price'] = float(detail['future_price'])
315
+ if 'future_return' in detail:
316
+ detail['future_return'] = float(detail['future_return'])
317
+
318
+ # 转换统计指标中的numpy类型
319
+ for key in ['long_win_rate', 'short_win_rate', 'overall_win_rate',
320
+ 'long_avg_return', 'short_avg_return']:
321
+ if key in output:
322
+ value = output[key]
323
+ if isinstance(value, (np.integer, np.floating)):
324
+ output[key] = float(value)
325
+ elif isinstance(value, np.bool_):
326
+ output[key] = bool(value)
327
+
328
+ with open(filepath, 'w', encoding='utf-8') as f:
329
+ json.dump(output, f, indent=2, ensure_ascii=False)
330
+
331
+ print(f"结果已保存到: {filepath}")
332
+