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
test/test_now_factor.py
ADDED
|
@@ -0,0 +1,1082 @@
|
|
|
1
|
+
"""
|
|
2
|
+
计算当前币对合约各个factor的值、看多/看空信号和胜率
|
|
3
|
+
|
|
4
|
+
使用方法:
|
|
5
|
+
python test_now_factor.py --symbol BTCUSDT --interval 30m --lookback 500
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import os
|
|
9
|
+
import sys
|
|
10
|
+
import argparse
|
|
11
|
+
import warnings
|
|
12
|
+
import tempfile
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
from datetime import datetime, timedelta
|
|
15
|
+
from typing import Optional, Dict, Any, Callable
|
|
16
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
17
|
+
import pandas as pd
|
|
18
|
+
import numpy as np
|
|
19
|
+
|
|
20
|
+
# 抑制 pandas FutureWarning 关于 fillna 的警告
|
|
21
|
+
warnings.filterwarnings('ignore', category=FutureWarning, message='.*Downcasting object dtype arrays on .fillna.*')
|
|
22
|
+
|
|
23
|
+
# 添加项目根目录到路径
|
|
24
|
+
project_root = Path(__file__).parent.parent
|
|
25
|
+
if str(project_root) not in sys.path:
|
|
26
|
+
sys.path.insert(0, str(project_root))
|
|
27
|
+
|
|
28
|
+
# 导入 cyqnt_trd 包
|
|
29
|
+
try:
|
|
30
|
+
from cyqnt_trd.get_data.get_futures_data import get_and_save_futures_klines
|
|
31
|
+
from cyqnt_trd.trading_signal.factor.ma_factor import ma_factor
|
|
32
|
+
from cyqnt_trd.trading_signal.factor.rsi_factor import rsi_factor
|
|
33
|
+
from cyqnt_trd.trading_signal.factor.stochastic_factor import stochastic_k_factor
|
|
34
|
+
from cyqnt_trd.trading_signal.factor.cci_factor import cci_factor
|
|
35
|
+
from cyqnt_trd.trading_signal.factor.adx_factor import adx_factor
|
|
36
|
+
from cyqnt_trd.trading_signal.factor.ao_factor import ao_factor
|
|
37
|
+
from cyqnt_trd.trading_signal.factor.momentum_factor import momentum_factor
|
|
38
|
+
from cyqnt_trd.trading_signal.factor.macd_factor import macd_level_factor
|
|
39
|
+
from cyqnt_trd.trading_signal.factor.stochastic_tsi_factor import stochastic_tsi_fast_factor
|
|
40
|
+
from cyqnt_trd.trading_signal.factor.williams_r_factor import williams_r_factor
|
|
41
|
+
from cyqnt_trd.trading_signal.factor.bbp_factor import bbp_factor
|
|
42
|
+
from cyqnt_trd.trading_signal.factor.uo_factor import uo_factor
|
|
43
|
+
from cyqnt_trd.trading_signal.factor.ema_factor import ema_factor, ema_cross_factor
|
|
44
|
+
from cyqnt_trd.trading_signal.selected_alpha import (
|
|
45
|
+
alpha1_factor, alpha3_factor, alpha7_factor, alpha9_factor,
|
|
46
|
+
alpha11_factor, alpha15_factor, alpha17_factor, alpha21_factor,
|
|
47
|
+
alpha23_factor, alpha25_factor, alpha29_factor, alpha33_factor,
|
|
48
|
+
alpha34_factor, ALPHA_FACTORS
|
|
49
|
+
)
|
|
50
|
+
from cyqnt_trd.backtesting.factor_test import FactorTester
|
|
51
|
+
from cyqnt_trd.utils import set_user
|
|
52
|
+
except ImportError as e:
|
|
53
|
+
print(f"导入错误: {e}")
|
|
54
|
+
print("\n提示:请确保已安装 cyqnt_trd package: pip install -e /path/to/crypto_trading")
|
|
55
|
+
sys.exit(1)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def klines_to_dataframe(klines_data: list) -> pd.DataFrame:
|
|
59
|
+
"""
|
|
60
|
+
将K线数据列表转换为DataFrame
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
klines_data: K线数据列表,每个元素是一个包含K线信息的列表或字典
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
DataFrame,包含以下列:
|
|
67
|
+
- open_time: 开盘时间(毫秒时间戳)
|
|
68
|
+
- open_time_str: 开盘时间字符串
|
|
69
|
+
- open_price: 开盘价
|
|
70
|
+
- high_price: 最高价
|
|
71
|
+
- low_price: 最低价
|
|
72
|
+
- close_price: 收盘价
|
|
73
|
+
- volume: 成交量
|
|
74
|
+
- close_time: 收盘时间(毫秒时间戳)
|
|
75
|
+
- quote_volume: 成交额
|
|
76
|
+
- trades: 成交笔数
|
|
77
|
+
- taker_buy_base_volume: 主动买入成交量
|
|
78
|
+
- taker_buy_quote_volume: 主动买入成交额
|
|
79
|
+
"""
|
|
80
|
+
if not klines_data:
|
|
81
|
+
return pd.DataFrame()
|
|
82
|
+
|
|
83
|
+
# 转换为DataFrame
|
|
84
|
+
df = pd.DataFrame(klines_data)
|
|
85
|
+
|
|
86
|
+
# 标准化列名(根据Binance API返回的格式)
|
|
87
|
+
if len(df.columns) >= 12:
|
|
88
|
+
df.columns = [
|
|
89
|
+
'open_time', 'open_price', 'high_price', 'low_price', 'close_price',
|
|
90
|
+
'volume', 'close_time', 'quote_volume', 'trades',
|
|
91
|
+
'taker_buy_base_volume', 'taker_buy_quote_volume', 'ignore'
|
|
92
|
+
]
|
|
93
|
+
elif len(df.columns) >= 11:
|
|
94
|
+
df.columns = [
|
|
95
|
+
'open_time', 'open_price', 'high_price', 'low_price', 'close_price',
|
|
96
|
+
'volume', 'close_time', 'quote_volume', 'trades',
|
|
97
|
+
'taker_buy_base_volume', 'taker_buy_quote_volume'
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
# 转换数据类型
|
|
101
|
+
numeric_columns = ['open_price', 'high_price', 'low_price', 'close_price',
|
|
102
|
+
'volume', 'quote_volume', 'taker_buy_base_volume', 'taker_buy_quote_volume']
|
|
103
|
+
for col in numeric_columns:
|
|
104
|
+
if col in df.columns:
|
|
105
|
+
df[col] = pd.to_numeric(df[col], errors='coerce')
|
|
106
|
+
|
|
107
|
+
# 转换时间
|
|
108
|
+
if 'open_time' in df.columns:
|
|
109
|
+
df['open_time'] = pd.to_numeric(df['open_time'], errors='coerce')
|
|
110
|
+
df['open_time_str'] = df['open_time'].apply(
|
|
111
|
+
lambda x: datetime.fromtimestamp(x / 1000).strftime('%Y-%m-%d %H:%M:%S') if pd.notna(x) else ''
|
|
112
|
+
)
|
|
113
|
+
|
|
114
|
+
# 按时间排序
|
|
115
|
+
if 'open_time' in df.columns:
|
|
116
|
+
df = df.sort_values('open_time').reset_index(drop=True)
|
|
117
|
+
|
|
118
|
+
return df
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
def calculate_normalized_alpha_factor(
|
|
122
|
+
data_slice: pd.DataFrame,
|
|
123
|
+
factor_func: Callable,
|
|
124
|
+
factor_name: str,
|
|
125
|
+
min_required: int = 30,
|
|
126
|
+
lookback_periods: int = 30,
|
|
127
|
+
**factor_kwargs
|
|
128
|
+
) -> Optional[Dict[str, Any]]:
|
|
129
|
+
"""
|
|
130
|
+
计算归一化Alpha因子的通用函数
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
data_slice: 数据切片
|
|
134
|
+
factor_func: 因子计算函数
|
|
135
|
+
factor_name: 因子名称(用于日志)
|
|
136
|
+
min_required: 因子计算所需的最小周期数
|
|
137
|
+
lookback_periods: 归一化回看周期数
|
|
138
|
+
**factor_kwargs: 传递给因子函数的额外参数
|
|
139
|
+
|
|
140
|
+
Returns:
|
|
141
|
+
包含因子值和看多/看空结果的字典,如果计算失败则返回None
|
|
142
|
+
"""
|
|
143
|
+
try:
|
|
144
|
+
if len(data_slice) < min_required + 2:
|
|
145
|
+
return None
|
|
146
|
+
|
|
147
|
+
available_periods = len(data_slice) - min_required - 1
|
|
148
|
+
if available_periods < 2:
|
|
149
|
+
return None
|
|
150
|
+
|
|
151
|
+
actual_lookback = min(lookback_periods, max(2, available_periods))
|
|
152
|
+
|
|
153
|
+
# 计算因子值:当前周期和之前actual_lookback个周期(使用多线程并行计算)
|
|
154
|
+
def compute_factor_value(i):
|
|
155
|
+
"""计算单个时间点的因子值"""
|
|
156
|
+
end_idx = len(data_slice) - i
|
|
157
|
+
start_idx = max(0, end_idx - min_required - 1)
|
|
158
|
+
if end_idx <= start_idx:
|
|
159
|
+
return 0.0
|
|
160
|
+
|
|
161
|
+
period_slice = data_slice.iloc[start_idx:end_idx]
|
|
162
|
+
try:
|
|
163
|
+
# 调用因子函数,传入额外参数
|
|
164
|
+
factor_value = factor_func(period_slice, **factor_kwargs)
|
|
165
|
+
if factor_value is not None:
|
|
166
|
+
return factor_value
|
|
167
|
+
else:
|
|
168
|
+
return 0.0
|
|
169
|
+
except Exception:
|
|
170
|
+
return 0.0
|
|
171
|
+
|
|
172
|
+
# 使用多线程并行计算因子值
|
|
173
|
+
indices = list(range(actual_lookback + 1))
|
|
174
|
+
factor_values = []
|
|
175
|
+
with ThreadPoolExecutor() as executor:
|
|
176
|
+
# 返回顺序保证和原for循环一致
|
|
177
|
+
factor_values = list(executor.map(compute_factor_value, indices))
|
|
178
|
+
|
|
179
|
+
if len(factor_values) < 2:
|
|
180
|
+
return None
|
|
181
|
+
|
|
182
|
+
# 归一化
|
|
183
|
+
factor_array = np.array(factor_values)
|
|
184
|
+
factor_min = factor_array.min()
|
|
185
|
+
factor_max = factor_array.max()
|
|
186
|
+
|
|
187
|
+
if factor_max == factor_min:
|
|
188
|
+
normalized_factors = np.zeros_like(factor_array)
|
|
189
|
+
else:
|
|
190
|
+
# Min-Max归一化到[-1, 1]区间
|
|
191
|
+
normalized_factors = 2 * (factor_array - factor_min) / (factor_max - factor_min) - 1
|
|
192
|
+
|
|
193
|
+
current_normalized = float(normalized_factors[0])
|
|
194
|
+
prev_normalized = float(normalized_factors[1]) if len(normalized_factors) > 1 else 0.0
|
|
195
|
+
|
|
196
|
+
# 判断信号:从负转正看多,从正转负看空
|
|
197
|
+
if prev_normalized <= 0 and current_normalized > 0:
|
|
198
|
+
signal = '看多'
|
|
199
|
+
elif prev_normalized >= 0 and current_normalized < 0:
|
|
200
|
+
signal = '看空'
|
|
201
|
+
else:
|
|
202
|
+
signal = '中性'
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
'value': current_normalized,
|
|
206
|
+
'signal': signal,
|
|
207
|
+
'raw_value': float(factor_values[0]) if len(factor_values) > 0 else 0.0,
|
|
208
|
+
'prev_normalized': prev_normalized
|
|
209
|
+
}
|
|
210
|
+
except Exception as e:
|
|
211
|
+
print(f"计算归一化{factor_name}因子时出错: {e}")
|
|
212
|
+
return None
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def calculate_factor_win_rate(
|
|
216
|
+
data_df: pd.DataFrame,
|
|
217
|
+
factor_func: Callable,
|
|
218
|
+
forward_periods: int = 24,
|
|
219
|
+
min_periods: int = 30,
|
|
220
|
+
factor_name: str = "factor"
|
|
221
|
+
) -> Optional[Dict[str, float]]:
|
|
222
|
+
"""
|
|
223
|
+
计算因子基于历史数据的胜率(使用FactorTester.test_factor)
|
|
224
|
+
|
|
225
|
+
Args:
|
|
226
|
+
data_df: 历史数据DataFrame
|
|
227
|
+
factor_func: 因子计算函数,接受数据切片作为参数,返回因子值
|
|
228
|
+
forward_periods: 向前看的周期数(默认24,即未来24个周期)
|
|
229
|
+
min_periods: 最小需要的周期数
|
|
230
|
+
factor_name: 因子名称
|
|
231
|
+
|
|
232
|
+
Returns:
|
|
233
|
+
包含胜率信息的字典,如果计算失败则返回None
|
|
234
|
+
"""
|
|
235
|
+
try:
|
|
236
|
+
if len(data_df) < min_periods + forward_periods + 1:
|
|
237
|
+
return None
|
|
238
|
+
|
|
239
|
+
# 创建FactorTester实例
|
|
240
|
+
factor_tester = FactorTester(data_df)
|
|
241
|
+
|
|
242
|
+
# 调用test_factor计算胜率
|
|
243
|
+
test_results = factor_tester.test_factor(
|
|
244
|
+
factor_func=factor_func,
|
|
245
|
+
forward_periods=forward_periods,
|
|
246
|
+
min_periods=min_periods,
|
|
247
|
+
factor_name=factor_name
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
# 提取需要的胜率信息
|
|
251
|
+
result = {
|
|
252
|
+
'long_win_rate': test_results.get('long_win_rate', 0.0),
|
|
253
|
+
'short_win_rate': test_results.get('short_win_rate', 0.0),
|
|
254
|
+
'overall_win_rate': test_results.get('overall_win_rate', 0.0),
|
|
255
|
+
'long_avg_return': test_results.get('long_avg_return', 0.0),
|
|
256
|
+
'short_avg_return': test_results.get('short_avg_return', 0.0),
|
|
257
|
+
'long_signals': test_results.get('long_signals', 0),
|
|
258
|
+
'short_signals': test_results.get('short_signals', 0),
|
|
259
|
+
'total_samples': test_results.get('total_samples', 0)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
return result
|
|
263
|
+
|
|
264
|
+
except Exception as e:
|
|
265
|
+
print(f"计算因子胜率时出错: {e}")
|
|
266
|
+
return None
|
|
267
|
+
|
|
268
|
+
|
|
269
|
+
def calculate_all_factors(data_df: pd.DataFrame, forward_periods=24) -> Dict[str, Any]:
|
|
270
|
+
"""
|
|
271
|
+
计算所有因子的因子值、看多/看空信号和胜率
|
|
272
|
+
|
|
273
|
+
Args:
|
|
274
|
+
data_df: 历史数据DataFrame
|
|
275
|
+
|
|
276
|
+
Returns:
|
|
277
|
+
包含所有因子结果的字典
|
|
278
|
+
"""
|
|
279
|
+
result = {}
|
|
280
|
+
|
|
281
|
+
if len(data_df) < 10:
|
|
282
|
+
print("数据量不足,无法计算因子")
|
|
283
|
+
return result
|
|
284
|
+
|
|
285
|
+
# 使用足够的数据切片(对于alpha因子,需要更多数据)
|
|
286
|
+
min_slice_size = 65
|
|
287
|
+
if len(data_df) >= min_slice_size:
|
|
288
|
+
data_slice = data_df.iloc[-min_slice_size:].copy()
|
|
289
|
+
elif len(data_df) >= 30:
|
|
290
|
+
data_slice = data_df.iloc[-30:].copy()
|
|
291
|
+
else:
|
|
292
|
+
data_slice = data_df.copy()
|
|
293
|
+
|
|
294
|
+
try:
|
|
295
|
+
# 定义所有需要计算的因子任务
|
|
296
|
+
def calculate_ma_factor():
|
|
297
|
+
"""计算MA因子"""
|
|
298
|
+
if len(data_slice) < 6:
|
|
299
|
+
return None
|
|
300
|
+
try:
|
|
301
|
+
ma_factor_value = ma_factor(data_slice, period=5)
|
|
302
|
+
ma_win_rate = calculate_factor_win_rate(
|
|
303
|
+
data_df=data_df,
|
|
304
|
+
factor_func=lambda d: ma_factor(d, period=5),
|
|
305
|
+
forward_periods=forward_periods,
|
|
306
|
+
min_periods=6,
|
|
307
|
+
factor_name="MA5因子"
|
|
308
|
+
)
|
|
309
|
+
return ('ma_factor_5', {
|
|
310
|
+
'value': ma_factor_value,
|
|
311
|
+
'signal': '看多' if ma_factor_value > 0 else '看空' if ma_factor_value < 0 else '中性',
|
|
312
|
+
'raw_value': ma_factor_value,
|
|
313
|
+
'win_rate': ma_win_rate
|
|
314
|
+
})
|
|
315
|
+
except Exception as e:
|
|
316
|
+
print(f"计算MA因子时出错: {e}")
|
|
317
|
+
return None
|
|
318
|
+
|
|
319
|
+
def calculate_rsi_factor():
|
|
320
|
+
"""计算RSI因子"""
|
|
321
|
+
if len(data_slice) < 16:
|
|
322
|
+
return None
|
|
323
|
+
try:
|
|
324
|
+
rsi_factor_value = rsi_factor(data_slice, period=14)
|
|
325
|
+
rsi_win_rate = calculate_factor_win_rate(
|
|
326
|
+
data_df=data_df,
|
|
327
|
+
factor_func=lambda d: rsi_factor(d, period=14),
|
|
328
|
+
forward_periods=forward_periods,
|
|
329
|
+
min_periods=16,
|
|
330
|
+
factor_name="RSI14因子"
|
|
331
|
+
)
|
|
332
|
+
return ('rsi_factor_14', {
|
|
333
|
+
'value': rsi_factor_value,
|
|
334
|
+
'signal': '看多' if rsi_factor_value > 0 else '看空' if rsi_factor_value < 0 else '中性',
|
|
335
|
+
'raw_value': rsi_factor_value,
|
|
336
|
+
'win_rate': rsi_win_rate
|
|
337
|
+
})
|
|
338
|
+
except Exception as e:
|
|
339
|
+
print(f"计算RSI因子时出错: {e}")
|
|
340
|
+
return None
|
|
341
|
+
|
|
342
|
+
def calculate_stochastic_k_factor():
|
|
343
|
+
"""计算Stochastic %K因子"""
|
|
344
|
+
if len(data_slice) < 18:
|
|
345
|
+
return None
|
|
346
|
+
try:
|
|
347
|
+
stoch_value = stochastic_k_factor(data_slice, period=14, k_smooth=3, d_smooth=3)
|
|
348
|
+
stoch_win_rate = calculate_factor_win_rate(
|
|
349
|
+
data_df=data_df,
|
|
350
|
+
factor_func=lambda d: stochastic_k_factor(d, period=14, k_smooth=3, d_smooth=3),
|
|
351
|
+
forward_periods=forward_periods,
|
|
352
|
+
min_periods=18,
|
|
353
|
+
factor_name="Stochastic %K(14,3,3)因子"
|
|
354
|
+
)
|
|
355
|
+
return ('stochastic_k_factor_14_3_3', {
|
|
356
|
+
'value': stoch_value,
|
|
357
|
+
'signal': '看多' if stoch_value > 0 else '看空' if stoch_value < 0 else '中性',
|
|
358
|
+
'raw_value': stoch_value,
|
|
359
|
+
'win_rate': stoch_win_rate
|
|
360
|
+
})
|
|
361
|
+
except Exception as e:
|
|
362
|
+
print(f"计算Stochastic %K因子时出错: {e}")
|
|
363
|
+
return None
|
|
364
|
+
|
|
365
|
+
def calculate_cci_factor():
|
|
366
|
+
"""计算CCI因子"""
|
|
367
|
+
if len(data_slice) < 21:
|
|
368
|
+
return None
|
|
369
|
+
try:
|
|
370
|
+
cci_value = cci_factor(data_slice, period=20)
|
|
371
|
+
cci_win_rate = calculate_factor_win_rate(
|
|
372
|
+
data_df=data_df,
|
|
373
|
+
factor_func=lambda d: cci_factor(d, period=20),
|
|
374
|
+
forward_periods=forward_periods,
|
|
375
|
+
min_periods=21,
|
|
376
|
+
factor_name="CCI(20)因子"
|
|
377
|
+
)
|
|
378
|
+
return ('cci_factor_20', {
|
|
379
|
+
'value': cci_value,
|
|
380
|
+
'signal': '看多' if cci_value > 0 else '看空' if cci_value < 0 else '中性',
|
|
381
|
+
'raw_value': cci_value,
|
|
382
|
+
'win_rate': cci_win_rate
|
|
383
|
+
})
|
|
384
|
+
except Exception as e:
|
|
385
|
+
print(f"计算CCI因子时出错: {e}")
|
|
386
|
+
return None
|
|
387
|
+
|
|
388
|
+
def calculate_adx_factor():
|
|
389
|
+
"""计算ADX因子"""
|
|
390
|
+
if len(data_slice) < 30:
|
|
391
|
+
return None
|
|
392
|
+
try:
|
|
393
|
+
adx_value = adx_factor(data_slice, period=14)
|
|
394
|
+
adx_win_rate = calculate_factor_win_rate(
|
|
395
|
+
data_df=data_df,
|
|
396
|
+
factor_func=lambda d: adx_factor(d, period=14),
|
|
397
|
+
forward_periods=forward_periods,
|
|
398
|
+
min_periods=30,
|
|
399
|
+
factor_name="ADX(14)因子"
|
|
400
|
+
)
|
|
401
|
+
return ('adx_factor_14', {
|
|
402
|
+
'value': adx_value,
|
|
403
|
+
'signal': '看多' if adx_value > 0 else '看空' if adx_value < 0 else '中性',
|
|
404
|
+
'raw_value': adx_value,
|
|
405
|
+
'win_rate': adx_win_rate
|
|
406
|
+
})
|
|
407
|
+
except Exception as e:
|
|
408
|
+
print(f"计算ADX因子时出错: {e}")
|
|
409
|
+
return None
|
|
410
|
+
|
|
411
|
+
def calculate_ao_factor():
|
|
412
|
+
"""计算AO因子"""
|
|
413
|
+
if len(data_slice) < 36:
|
|
414
|
+
return None
|
|
415
|
+
try:
|
|
416
|
+
ao_value = ao_factor(data_slice)
|
|
417
|
+
ao_win_rate = calculate_factor_win_rate(
|
|
418
|
+
data_df=data_df,
|
|
419
|
+
factor_func=ao_factor,
|
|
420
|
+
forward_periods=forward_periods,
|
|
421
|
+
min_periods=36,
|
|
422
|
+
factor_name="AO因子"
|
|
423
|
+
)
|
|
424
|
+
return ('ao_factor', {
|
|
425
|
+
'value': ao_value,
|
|
426
|
+
'signal': '看多' if ao_value > 0 else '看空' if ao_value < 0 else '中性',
|
|
427
|
+
'raw_value': ao_value,
|
|
428
|
+
'win_rate': ao_win_rate
|
|
429
|
+
})
|
|
430
|
+
except Exception as e:
|
|
431
|
+
print(f"计算AO因子时出错: {e}")
|
|
432
|
+
return None
|
|
433
|
+
|
|
434
|
+
def calculate_momentum_factor():
|
|
435
|
+
"""计算动量因子"""
|
|
436
|
+
if len(data_slice) < 11:
|
|
437
|
+
return None
|
|
438
|
+
try:
|
|
439
|
+
momentum_value = momentum_factor(data_slice, period=10)
|
|
440
|
+
momentum_win_rate = calculate_factor_win_rate(
|
|
441
|
+
data_df=data_df,
|
|
442
|
+
factor_func=lambda d: momentum_factor(d, period=10),
|
|
443
|
+
forward_periods=forward_periods,
|
|
444
|
+
min_periods=11,
|
|
445
|
+
factor_name="Momentum(10)因子"
|
|
446
|
+
)
|
|
447
|
+
return ('momentum_factor_10', {
|
|
448
|
+
'value': momentum_value,
|
|
449
|
+
'signal': '看多' if momentum_value > 0 else '看空' if momentum_value < 0 else '中性',
|
|
450
|
+
'raw_value': momentum_value,
|
|
451
|
+
'win_rate': momentum_win_rate
|
|
452
|
+
})
|
|
453
|
+
except Exception as e:
|
|
454
|
+
print(f"计算Momentum因子时出错: {e}")
|
|
455
|
+
return None
|
|
456
|
+
|
|
457
|
+
def calculate_macd_factor():
|
|
458
|
+
"""计算MACD因子"""
|
|
459
|
+
if len(data_slice) < 48:
|
|
460
|
+
return None
|
|
461
|
+
try:
|
|
462
|
+
macd_value = macd_level_factor(data_slice, fast_period=12, slow_period=26)
|
|
463
|
+
macd_win_rate = calculate_factor_win_rate(
|
|
464
|
+
data_df=data_df,
|
|
465
|
+
factor_func=lambda d: macd_level_factor(d, fast_period=12, slow_period=26),
|
|
466
|
+
forward_periods=forward_periods,
|
|
467
|
+
min_periods=48,
|
|
468
|
+
factor_name="MACD(12,26)因子"
|
|
469
|
+
)
|
|
470
|
+
return ('macd_factor_12_26', {
|
|
471
|
+
'value': macd_value,
|
|
472
|
+
'signal': '看多' if macd_value > 0 else '看空' if macd_value < 0 else '中性',
|
|
473
|
+
'raw_value': macd_value,
|
|
474
|
+
'win_rate': macd_win_rate
|
|
475
|
+
})
|
|
476
|
+
except Exception as e:
|
|
477
|
+
print(f"计算MACD因子时出错: {e}")
|
|
478
|
+
return None
|
|
479
|
+
|
|
480
|
+
def calculate_stochastic_tsi_factor():
|
|
481
|
+
"""计算Stochastic TSI因子"""
|
|
482
|
+
if len(data_slice) < 35:
|
|
483
|
+
return None
|
|
484
|
+
try:
|
|
485
|
+
stoch_tsi_value = stochastic_tsi_fast_factor(data_slice, r_period=3, s_period=3, tsi_period1=14, tsi_period2=14)
|
|
486
|
+
stoch_tsi_win_rate = calculate_factor_win_rate(
|
|
487
|
+
data_df=data_df,
|
|
488
|
+
factor_func=lambda d: stochastic_tsi_fast_factor(d, r_period=3, s_period=3, tsi_period1=14, tsi_period2=14),
|
|
489
|
+
forward_periods=24,
|
|
490
|
+
min_periods=35,
|
|
491
|
+
factor_name="Stochastic TSI Fast(3,3,14,14)因子"
|
|
492
|
+
)
|
|
493
|
+
return ('stochastic_tsi_factor_3_3_14_14', {
|
|
494
|
+
'value': stoch_tsi_value,
|
|
495
|
+
'signal': '看多' if stoch_tsi_value > 0 else '看空' if stoch_tsi_value < 0 else '中性',
|
|
496
|
+
'raw_value': stoch_tsi_value,
|
|
497
|
+
'win_rate': stoch_tsi_win_rate
|
|
498
|
+
})
|
|
499
|
+
except Exception as e:
|
|
500
|
+
print(f"计算Stochastic TSI因子时出错: {e}")
|
|
501
|
+
return None
|
|
502
|
+
|
|
503
|
+
def calculate_williams_r_factor():
|
|
504
|
+
"""计算Williams %R因子"""
|
|
505
|
+
if len(data_slice) < 15:
|
|
506
|
+
return None
|
|
507
|
+
try:
|
|
508
|
+
williams_r_value = williams_r_factor(data_slice, period=14)
|
|
509
|
+
williams_r_win_rate = calculate_factor_win_rate(
|
|
510
|
+
data_df=data_df,
|
|
511
|
+
factor_func=lambda d: williams_r_factor(d, period=14),
|
|
512
|
+
forward_periods=forward_periods,
|
|
513
|
+
min_periods=15,
|
|
514
|
+
factor_name="Williams %R(14)因子"
|
|
515
|
+
)
|
|
516
|
+
return ('williams_r_factor_14', {
|
|
517
|
+
'value': williams_r_value,
|
|
518
|
+
'signal': '看多' if williams_r_value > 0 else '看空' if williams_r_value < 0 else '中性',
|
|
519
|
+
'raw_value': williams_r_value,
|
|
520
|
+
'win_rate': williams_r_win_rate
|
|
521
|
+
})
|
|
522
|
+
except Exception as e:
|
|
523
|
+
print(f"计算Williams %R因子时出错: {e}")
|
|
524
|
+
return None
|
|
525
|
+
|
|
526
|
+
def calculate_bbp_factor():
|
|
527
|
+
"""计算BBP因子"""
|
|
528
|
+
if len(data_slice) < 14:
|
|
529
|
+
return None
|
|
530
|
+
try:
|
|
531
|
+
bbp_value = bbp_factor(data_slice, period=13)
|
|
532
|
+
bbp_win_rate = calculate_factor_win_rate(
|
|
533
|
+
data_df=data_df,
|
|
534
|
+
factor_func=lambda d: bbp_factor(d, period=13),
|
|
535
|
+
forward_periods=24,
|
|
536
|
+
min_periods=14,
|
|
537
|
+
factor_name="BBP因子"
|
|
538
|
+
)
|
|
539
|
+
return ('bbp_factor', {
|
|
540
|
+
'value': bbp_value,
|
|
541
|
+
'signal': '看多' if bbp_value > 0 else '看空' if bbp_value < 0 else '中性',
|
|
542
|
+
'raw_value': bbp_value,
|
|
543
|
+
'win_rate': bbp_win_rate
|
|
544
|
+
})
|
|
545
|
+
except Exception as e:
|
|
546
|
+
print(f"计算BBP因子时出错: {e}")
|
|
547
|
+
return None
|
|
548
|
+
|
|
549
|
+
def calculate_uo_factor():
|
|
550
|
+
"""计算UO因子"""
|
|
551
|
+
if len(data_slice) < 29:
|
|
552
|
+
return None
|
|
553
|
+
try:
|
|
554
|
+
uo_value = uo_factor(data_slice, period1=7, period2=14, period3=28)
|
|
555
|
+
uo_win_rate = calculate_factor_win_rate(
|
|
556
|
+
data_df=data_df,
|
|
557
|
+
factor_func=lambda d: uo_factor(d, period1=7, period2=14, period3=28),
|
|
558
|
+
forward_periods=forward_periods,
|
|
559
|
+
min_periods=29,
|
|
560
|
+
factor_name="UO(7,14,28)因子"
|
|
561
|
+
)
|
|
562
|
+
return ('uo_factor_7_14_28', {
|
|
563
|
+
'value': uo_value,
|
|
564
|
+
'signal': '看多' if uo_value > 0 else '看空' if uo_value < 0 else '中性',
|
|
565
|
+
'raw_value': uo_value,
|
|
566
|
+
'win_rate': uo_win_rate
|
|
567
|
+
})
|
|
568
|
+
except Exception as e:
|
|
569
|
+
print(f"计算UO因子时出错: {e}")
|
|
570
|
+
return None
|
|
571
|
+
|
|
572
|
+
def calculate_ema_factors():
|
|
573
|
+
"""计算EMA因子(多个周期)"""
|
|
574
|
+
results = {}
|
|
575
|
+
ema_periods = [10, 20, 30, 50, 100, 200]
|
|
576
|
+
|
|
577
|
+
for period in ema_periods:
|
|
578
|
+
if len(data_slice) < period + 1:
|
|
579
|
+
continue
|
|
580
|
+
try:
|
|
581
|
+
ema_value = ema_factor(data_slice, period=period)
|
|
582
|
+
ema_win_rate = calculate_factor_win_rate(
|
|
583
|
+
data_df=data_df,
|
|
584
|
+
factor_func=lambda d, p=period: ema_factor(d, period=p),
|
|
585
|
+
forward_periods=24,
|
|
586
|
+
min_periods=period + 1,
|
|
587
|
+
factor_name=f"EMA({period})因子"
|
|
588
|
+
)
|
|
589
|
+
results[f'ema_factor_{period}'] = {
|
|
590
|
+
'value': ema_value,
|
|
591
|
+
'signal': '看多' if ema_value > 0 else '看空' if ema_value < 0 else '中性',
|
|
592
|
+
'raw_value': ema_value,
|
|
593
|
+
'win_rate': ema_win_rate
|
|
594
|
+
}
|
|
595
|
+
except Exception as e:
|
|
596
|
+
print(f"计算EMA({period})因子时出错: {e}")
|
|
597
|
+
|
|
598
|
+
return results if results else None
|
|
599
|
+
|
|
600
|
+
def calculate_normalized_alpha(factor_key, factor_func, min_req, alpha_num, **kwargs):
|
|
601
|
+
"""计算归一化Alpha因子的通用函数"""
|
|
602
|
+
try:
|
|
603
|
+
normalized_result = calculate_normalized_alpha_factor(
|
|
604
|
+
data_slice=data_slice,
|
|
605
|
+
factor_func=factor_func,
|
|
606
|
+
factor_name=f"Alpha#{alpha_num}",
|
|
607
|
+
min_required=min_req,
|
|
608
|
+
lookback_periods=30,
|
|
609
|
+
**kwargs
|
|
610
|
+
)
|
|
611
|
+
if normalized_result:
|
|
612
|
+
def normalized_wrapper(d, func=factor_func, req=min_req, num=alpha_num, kw=kwargs):
|
|
613
|
+
norm_res = calculate_normalized_alpha_factor(
|
|
614
|
+
data_slice=d,
|
|
615
|
+
factor_func=func,
|
|
616
|
+
factor_name=f"Alpha#{num}",
|
|
617
|
+
min_required=req,
|
|
618
|
+
lookback_periods=30,
|
|
619
|
+
**kw
|
|
620
|
+
)
|
|
621
|
+
if norm_res:
|
|
622
|
+
return norm_res['value']
|
|
623
|
+
return 0.0
|
|
624
|
+
|
|
625
|
+
win_rate = calculate_factor_win_rate(
|
|
626
|
+
data_df=data_df,
|
|
627
|
+
factor_func=normalized_wrapper,
|
|
628
|
+
forward_periods=forward_periods,
|
|
629
|
+
min_periods=65,
|
|
630
|
+
factor_name=f"归一化Alpha#{alpha_num}因子"
|
|
631
|
+
)
|
|
632
|
+
normalized_result['win_rate'] = win_rate
|
|
633
|
+
return (f'normalized_{factor_key}', normalized_result)
|
|
634
|
+
except Exception as e:
|
|
635
|
+
print(f"计算{factor_key}因子时出错: {e}")
|
|
636
|
+
return None
|
|
637
|
+
|
|
638
|
+
# 准备所有因子计算任务
|
|
639
|
+
tasks = []
|
|
640
|
+
|
|
641
|
+
# MA因子和RSI因子
|
|
642
|
+
if len(data_slice) >= 6:
|
|
643
|
+
tasks.append(calculate_ma_factor)
|
|
644
|
+
if len(data_slice) >= 16:
|
|
645
|
+
tasks.append(calculate_rsi_factor)
|
|
646
|
+
|
|
647
|
+
# 新增技术指标因子
|
|
648
|
+
if len(data_slice) >= 18:
|
|
649
|
+
tasks.append(calculate_stochastic_k_factor)
|
|
650
|
+
if len(data_slice) >= 21:
|
|
651
|
+
tasks.append(calculate_cci_factor)
|
|
652
|
+
if len(data_slice) >= 30:
|
|
653
|
+
tasks.append(calculate_adx_factor)
|
|
654
|
+
if len(data_slice) >= 36:
|
|
655
|
+
tasks.append(calculate_ao_factor)
|
|
656
|
+
if len(data_slice) >= 11:
|
|
657
|
+
tasks.append(calculate_momentum_factor)
|
|
658
|
+
if len(data_slice) >= 48:
|
|
659
|
+
tasks.append(calculate_macd_factor)
|
|
660
|
+
if len(data_slice) >= 35:
|
|
661
|
+
tasks.append(calculate_stochastic_tsi_factor)
|
|
662
|
+
if len(data_slice) >= 15:
|
|
663
|
+
tasks.append(calculate_williams_r_factor)
|
|
664
|
+
if len(data_slice) >= 14:
|
|
665
|
+
tasks.append(calculate_bbp_factor)
|
|
666
|
+
if len(data_slice) >= 29:
|
|
667
|
+
tasks.append(calculate_uo_factor)
|
|
668
|
+
|
|
669
|
+
# EMA因子(多个周期)
|
|
670
|
+
def calculate_ema_wrapper():
|
|
671
|
+
ema_results = calculate_ema_factors()
|
|
672
|
+
if ema_results:
|
|
673
|
+
return list(ema_results.items())
|
|
674
|
+
return None
|
|
675
|
+
|
|
676
|
+
if len(data_slice) >= 11:
|
|
677
|
+
tasks.append(calculate_ema_wrapper)
|
|
678
|
+
|
|
679
|
+
# 归一化Alpha因子(选择一些常用的)
|
|
680
|
+
alpha_factors_to_add = [
|
|
681
|
+
('alpha1', alpha1_factor, 30, '1', {'lookback_days': 5, 'stddev_period': 20, 'power': 2.0}),
|
|
682
|
+
('alpha3', alpha3_factor, 30, '3', {}),
|
|
683
|
+
('alpha7', alpha7_factor, 30, '7', {}),
|
|
684
|
+
('alpha9', alpha9_factor, 30, '9', {}),
|
|
685
|
+
('alpha11', alpha11_factor, 30, '11', {}),
|
|
686
|
+
('alpha15', alpha15_factor, 30, '15', {}),
|
|
687
|
+
('alpha17', alpha17_factor, 30, '17', {}),
|
|
688
|
+
('alpha21', alpha21_factor, 30, '21', {}),
|
|
689
|
+
('alpha23', alpha23_factor, 30, '23', {}),
|
|
690
|
+
('alpha25', alpha25_factor, 30, '25', {}),
|
|
691
|
+
('alpha29', alpha29_factor, 30, '29', {}),
|
|
692
|
+
('alpha33', alpha33_factor, 30, '33', {}),
|
|
693
|
+
('alpha34', alpha34_factor, 30, '34', {}),
|
|
694
|
+
]
|
|
695
|
+
|
|
696
|
+
for factor_key, factor_func, min_req, alpha_num, kwargs in alpha_factors_to_add:
|
|
697
|
+
# 使用默认参数捕获循环变量,避免闭包问题
|
|
698
|
+
tasks.append(lambda k=factor_key, f=factor_func, r=min_req, n=alpha_num, kw=kwargs:
|
|
699
|
+
calculate_normalized_alpha(k, f, r, n, **kw))
|
|
700
|
+
|
|
701
|
+
# 使用多线程并行计算所有因子
|
|
702
|
+
with ThreadPoolExecutor() as executor:
|
|
703
|
+
futures = [executor.submit(task) for task in tasks]
|
|
704
|
+
for future in futures:
|
|
705
|
+
try:
|
|
706
|
+
task_result = future.result()
|
|
707
|
+
if task_result is not None:
|
|
708
|
+
# 处理EMA因子返回的多个结果
|
|
709
|
+
if isinstance(task_result, list):
|
|
710
|
+
for key, value in task_result:
|
|
711
|
+
result[key] = value
|
|
712
|
+
else:
|
|
713
|
+
key, value = task_result
|
|
714
|
+
result[key] = value
|
|
715
|
+
except Exception as e:
|
|
716
|
+
print(f"计算因子任务时出错: {e}")
|
|
717
|
+
|
|
718
|
+
except Exception as e:
|
|
719
|
+
print(f"计算因子值时出错: {e}")
|
|
720
|
+
import traceback
|
|
721
|
+
traceback.print_exc()
|
|
722
|
+
|
|
723
|
+
return result
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
def print_factor_results(factor_results: Dict[str, Any]):
|
|
727
|
+
"""
|
|
728
|
+
打印因子结果
|
|
729
|
+
|
|
730
|
+
Args:
|
|
731
|
+
factor_results: 因子结果字典
|
|
732
|
+
"""
|
|
733
|
+
print("\n" + "="*100)
|
|
734
|
+
print("📊 因子分析结果")
|
|
735
|
+
print("="*100)
|
|
736
|
+
|
|
737
|
+
if not factor_results:
|
|
738
|
+
print("未计算出任何因子结果")
|
|
739
|
+
return
|
|
740
|
+
|
|
741
|
+
# 按因子类型分组显示
|
|
742
|
+
print("\n【基础因子】")
|
|
743
|
+
print("-"*100)
|
|
744
|
+
|
|
745
|
+
# MA因子
|
|
746
|
+
if 'ma_factor_5' in factor_results:
|
|
747
|
+
ma_info = factor_results['ma_factor_5']
|
|
748
|
+
signal_emoji = "🟢" if ma_info.get('signal') == '看多' else "🔴" if ma_info.get('signal') == '看空' else "⚪"
|
|
749
|
+
signal_text = ma_info.get('signal', '中性')
|
|
750
|
+
print(f"MA5因子:")
|
|
751
|
+
print(f" 因子值: {ma_info.get('raw_value', 0):+.6f}")
|
|
752
|
+
print(f" 信号: {signal_emoji} {signal_text}")
|
|
753
|
+
if ma_info.get('win_rate'):
|
|
754
|
+
wr = ma_info['win_rate']
|
|
755
|
+
if wr and isinstance(wr, dict):
|
|
756
|
+
print(f" 看多胜率: {wr.get('long_win_rate', 0):.2%} (样本={wr.get('long_signals', 0)})")
|
|
757
|
+
print(f" 看空胜率: {wr.get('short_win_rate', 0):.2%} (样本={wr.get('short_signals', 0)})")
|
|
758
|
+
print(f" 总体胜率: {wr.get('overall_win_rate', 0):.2%} (总样本={wr.get('total_samples', 0)})")
|
|
759
|
+
print()
|
|
760
|
+
|
|
761
|
+
# RSI因子
|
|
762
|
+
if 'rsi_factor_14' in factor_results:
|
|
763
|
+
rsi_info = factor_results['rsi_factor_14']
|
|
764
|
+
signal_emoji = "🟢" if rsi_info.get('signal') == '看多' else "🔴" if rsi_info.get('signal') == '看空' else "⚪"
|
|
765
|
+
signal_text = rsi_info.get('signal', '中性')
|
|
766
|
+
print(f"RSI14因子:")
|
|
767
|
+
print(f" 因子值: {rsi_info.get('raw_value', 0):+.6f}")
|
|
768
|
+
print(f" 信号: {signal_emoji} {signal_text}")
|
|
769
|
+
if rsi_info.get('win_rate'):
|
|
770
|
+
wr = rsi_info['win_rate']
|
|
771
|
+
if wr and isinstance(wr, dict):
|
|
772
|
+
print(f" 看多胜率: {wr.get('long_win_rate', 0):.2%} (样本={wr.get('long_signals', 0)})")
|
|
773
|
+
print(f" 看空胜率: {wr.get('short_win_rate', 0):.2%} (样本={wr.get('short_signals', 0)})")
|
|
774
|
+
print(f" 总体胜率: {wr.get('overall_win_rate', 0):.2%} (总样本={wr.get('total_samples', 0)})")
|
|
775
|
+
print()
|
|
776
|
+
|
|
777
|
+
# Stochastic %K因子
|
|
778
|
+
if 'stochastic_k_factor_14_3_3' in factor_results:
|
|
779
|
+
stoch_info = factor_results['stochastic_k_factor_14_3_3']
|
|
780
|
+
signal_emoji = "🟢" if stoch_info.get('signal') == '看多' else "🔴" if stoch_info.get('signal') == '看空' else "⚪"
|
|
781
|
+
signal_text = stoch_info.get('signal', '中性')
|
|
782
|
+
print(f"Stochastic %K(14,3,3)因子:")
|
|
783
|
+
print(f" 因子值: {stoch_info.get('raw_value', 0):+.6f}")
|
|
784
|
+
print(f" 信号: {signal_emoji} {signal_text}")
|
|
785
|
+
if stoch_info.get('win_rate'):
|
|
786
|
+
wr = stoch_info['win_rate']
|
|
787
|
+
if wr and isinstance(wr, dict):
|
|
788
|
+
print(f" 看多胜率: {wr.get('long_win_rate', 0):.2%} (样本={wr.get('long_signals', 0)})")
|
|
789
|
+
print(f" 看空胜率: {wr.get('short_win_rate', 0):.2%} (样本={wr.get('short_signals', 0)})")
|
|
790
|
+
print(f" 总体胜率: {wr.get('overall_win_rate', 0):.2%} (总样本={wr.get('total_samples', 0)})")
|
|
791
|
+
print()
|
|
792
|
+
|
|
793
|
+
# CCI因子
|
|
794
|
+
if 'cci_factor_20' in factor_results:
|
|
795
|
+
cci_info = factor_results['cci_factor_20']
|
|
796
|
+
signal_emoji = "🟢" if cci_info.get('signal') == '看多' else "🔴" if cci_info.get('signal') == '看空' else "⚪"
|
|
797
|
+
signal_text = cci_info.get('signal', '中性')
|
|
798
|
+
print(f"CCI(20)因子:")
|
|
799
|
+
print(f" 因子值: {cci_info.get('raw_value', 0):+.6f}")
|
|
800
|
+
print(f" 信号: {signal_emoji} {signal_text}")
|
|
801
|
+
if cci_info.get('win_rate'):
|
|
802
|
+
wr = cci_info['win_rate']
|
|
803
|
+
if wr and isinstance(wr, dict):
|
|
804
|
+
print(f" 看多胜率: {wr.get('long_win_rate', 0):.2%} (样本={wr.get('long_signals', 0)})")
|
|
805
|
+
print(f" 看空胜率: {wr.get('short_win_rate', 0):.2%} (样本={wr.get('short_signals', 0)})")
|
|
806
|
+
print(f" 总体胜率: {wr.get('overall_win_rate', 0):.2%} (总样本={wr.get('total_samples', 0)})")
|
|
807
|
+
print()
|
|
808
|
+
|
|
809
|
+
# ADX因子
|
|
810
|
+
if 'adx_factor_14' in factor_results:
|
|
811
|
+
adx_info = factor_results['adx_factor_14']
|
|
812
|
+
signal_emoji = "🟢" if adx_info.get('signal') == '看多' else "🔴" if adx_info.get('signal') == '看空' else "⚪"
|
|
813
|
+
signal_text = adx_info.get('signal', '中性')
|
|
814
|
+
print(f"ADX(14)因子:")
|
|
815
|
+
print(f" 因子值: {adx_info.get('raw_value', 0):+.6f}")
|
|
816
|
+
print(f" 信号: {signal_emoji} {signal_text}")
|
|
817
|
+
if adx_info.get('win_rate'):
|
|
818
|
+
wr = adx_info['win_rate']
|
|
819
|
+
if wr and isinstance(wr, dict):
|
|
820
|
+
print(f" 看多胜率: {wr.get('long_win_rate', 0):.2%} (样本={wr.get('long_signals', 0)})")
|
|
821
|
+
print(f" 看空胜率: {wr.get('short_win_rate', 0):.2%} (样本={wr.get('short_signals', 0)})")
|
|
822
|
+
print(f" 总体胜率: {wr.get('overall_win_rate', 0):.2%} (总样本={wr.get('total_samples', 0)})")
|
|
823
|
+
print()
|
|
824
|
+
|
|
825
|
+
# AO因子
|
|
826
|
+
if 'ao_factor' in factor_results:
|
|
827
|
+
ao_info = factor_results['ao_factor']
|
|
828
|
+
signal_emoji = "🟢" if ao_info.get('signal') == '看多' else "🔴" if ao_info.get('signal') == '看空' else "⚪"
|
|
829
|
+
signal_text = ao_info.get('signal', '中性')
|
|
830
|
+
print(f"AO因子:")
|
|
831
|
+
print(f" 因子值: {ao_info.get('raw_value', 0):+.6f}")
|
|
832
|
+
print(f" 信号: {signal_emoji} {signal_text}")
|
|
833
|
+
if ao_info.get('win_rate'):
|
|
834
|
+
wr = ao_info['win_rate']
|
|
835
|
+
if wr and isinstance(wr, dict):
|
|
836
|
+
print(f" 看多胜率: {wr.get('long_win_rate', 0):.2%} (样本={wr.get('long_signals', 0)})")
|
|
837
|
+
print(f" 看空胜率: {wr.get('short_win_rate', 0):.2%} (样本={wr.get('short_signals', 0)})")
|
|
838
|
+
print(f" 总体胜率: {wr.get('overall_win_rate', 0):.2%} (总样本={wr.get('total_samples', 0)})")
|
|
839
|
+
print()
|
|
840
|
+
|
|
841
|
+
# Momentum因子
|
|
842
|
+
if 'momentum_factor_10' in factor_results:
|
|
843
|
+
momentum_info = factor_results['momentum_factor_10']
|
|
844
|
+
signal_emoji = "🟢" if momentum_info.get('signal') == '看多' else "🔴" if momentum_info.get('signal') == '看空' else "⚪"
|
|
845
|
+
signal_text = momentum_info.get('signal', '中性')
|
|
846
|
+
print(f"Momentum(10)因子:")
|
|
847
|
+
print(f" 因子值: {momentum_info.get('raw_value', 0):+.6f}")
|
|
848
|
+
print(f" 信号: {signal_emoji} {signal_text}")
|
|
849
|
+
if momentum_info.get('win_rate'):
|
|
850
|
+
wr = momentum_info['win_rate']
|
|
851
|
+
if wr and isinstance(wr, dict):
|
|
852
|
+
print(f" 看多胜率: {wr.get('long_win_rate', 0):.2%} (样本={wr.get('long_signals', 0)})")
|
|
853
|
+
print(f" 看空胜率: {wr.get('short_win_rate', 0):.2%} (样本={wr.get('short_signals', 0)})")
|
|
854
|
+
print(f" 总体胜率: {wr.get('overall_win_rate', 0):.2%} (总样本={wr.get('total_samples', 0)})")
|
|
855
|
+
print()
|
|
856
|
+
|
|
857
|
+
# MACD因子
|
|
858
|
+
if 'macd_factor_12_26' in factor_results:
|
|
859
|
+
macd_info = factor_results['macd_factor_12_26']
|
|
860
|
+
signal_emoji = "🟢" if macd_info.get('signal') == '看多' else "🔴" if macd_info.get('signal') == '看空' else "⚪"
|
|
861
|
+
signal_text = macd_info.get('signal', '中性')
|
|
862
|
+
print(f"MACD(12,26)因子:")
|
|
863
|
+
print(f" 因子值: {macd_info.get('raw_value', 0):+.6f}")
|
|
864
|
+
print(f" 信号: {signal_emoji} {signal_text}")
|
|
865
|
+
if macd_info.get('win_rate'):
|
|
866
|
+
wr = macd_info['win_rate']
|
|
867
|
+
if wr and isinstance(wr, dict):
|
|
868
|
+
print(f" 看多胜率: {wr.get('long_win_rate', 0):.2%} (样本={wr.get('long_signals', 0)})")
|
|
869
|
+
print(f" 看空胜率: {wr.get('short_win_rate', 0):.2%} (样本={wr.get('short_signals', 0)})")
|
|
870
|
+
print(f" 总体胜率: {wr.get('overall_win_rate', 0):.2%} (总样本={wr.get('total_samples', 0)})")
|
|
871
|
+
print()
|
|
872
|
+
|
|
873
|
+
# Stochastic TSI因子
|
|
874
|
+
if 'stochastic_tsi_factor_3_3_14_14' in factor_results:
|
|
875
|
+
stoch_tsi_info = factor_results['stochastic_tsi_factor_3_3_14_14']
|
|
876
|
+
signal_emoji = "🟢" if stoch_tsi_info.get('signal') == '看多' else "🔴" if stoch_tsi_info.get('signal') == '看空' else "⚪"
|
|
877
|
+
signal_text = stoch_tsi_info.get('signal', '中性')
|
|
878
|
+
print(f"Stochastic TSI Fast(3,3,14,14)因子:")
|
|
879
|
+
print(f" 因子值: {stoch_tsi_info.get('raw_value', 0):+.6f}")
|
|
880
|
+
print(f" 信号: {signal_emoji} {signal_text}")
|
|
881
|
+
if stoch_tsi_info.get('win_rate'):
|
|
882
|
+
wr = stoch_tsi_info['win_rate']
|
|
883
|
+
if wr and isinstance(wr, dict):
|
|
884
|
+
print(f" 看多胜率: {wr.get('long_win_rate', 0):.2%} (样本={wr.get('long_signals', 0)})")
|
|
885
|
+
print(f" 看空胜率: {wr.get('short_win_rate', 0):.2%} (样本={wr.get('short_signals', 0)})")
|
|
886
|
+
print(f" 总体胜率: {wr.get('overall_win_rate', 0):.2%} (总样本={wr.get('total_samples', 0)})")
|
|
887
|
+
print()
|
|
888
|
+
|
|
889
|
+
# Williams %R因子
|
|
890
|
+
if 'williams_r_factor_14' in factor_results:
|
|
891
|
+
williams_r_info = factor_results['williams_r_factor_14']
|
|
892
|
+
signal_emoji = "🟢" if williams_r_info.get('signal') == '看多' else "🔴" if williams_r_info.get('signal') == '看空' else "⚪"
|
|
893
|
+
signal_text = williams_r_info.get('signal', '中性')
|
|
894
|
+
print(f"Williams %R(14)因子:")
|
|
895
|
+
print(f" 因子值: {williams_r_info.get('raw_value', 0):+.6f}")
|
|
896
|
+
print(f" 信号: {signal_emoji} {signal_text}")
|
|
897
|
+
if williams_r_info.get('win_rate'):
|
|
898
|
+
wr = williams_r_info['win_rate']
|
|
899
|
+
if wr and isinstance(wr, dict):
|
|
900
|
+
print(f" 看多胜率: {wr.get('long_win_rate', 0):.2%} (样本={wr.get('long_signals', 0)})")
|
|
901
|
+
print(f" 看空胜率: {wr.get('short_win_rate', 0):.2%} (样本={wr.get('short_signals', 0)})")
|
|
902
|
+
print(f" 总体胜率: {wr.get('overall_win_rate', 0):.2%} (总样本={wr.get('total_samples', 0)})")
|
|
903
|
+
print()
|
|
904
|
+
|
|
905
|
+
# BBP因子
|
|
906
|
+
if 'bbp_factor' in factor_results:
|
|
907
|
+
bbp_info = factor_results['bbp_factor']
|
|
908
|
+
signal_emoji = "🟢" if bbp_info.get('signal') == '看多' else "🔴" if bbp_info.get('signal') == '看空' else "⚪"
|
|
909
|
+
signal_text = bbp_info.get('signal', '中性')
|
|
910
|
+
print(f"BBP因子:")
|
|
911
|
+
print(f" 因子值: {bbp_info.get('raw_value', 0):+.6f}")
|
|
912
|
+
print(f" 信号: {signal_emoji} {signal_text}")
|
|
913
|
+
if bbp_info.get('win_rate'):
|
|
914
|
+
wr = bbp_info['win_rate']
|
|
915
|
+
if wr and isinstance(wr, dict):
|
|
916
|
+
print(f" 看多胜率: {wr.get('long_win_rate', 0):.2%} (样本={wr.get('long_signals', 0)})")
|
|
917
|
+
print(f" 看空胜率: {wr.get('short_win_rate', 0):.2%} (样本={wr.get('short_signals', 0)})")
|
|
918
|
+
print(f" 总体胜率: {wr.get('overall_win_rate', 0):.2%} (总样本={wr.get('total_samples', 0)})")
|
|
919
|
+
print()
|
|
920
|
+
|
|
921
|
+
# UO因子
|
|
922
|
+
if 'uo_factor_7_14_28' in factor_results:
|
|
923
|
+
uo_info = factor_results['uo_factor_7_14_28']
|
|
924
|
+
signal_emoji = "🟢" if uo_info.get('signal') == '看多' else "🔴" if uo_info.get('signal') == '看空' else "⚪"
|
|
925
|
+
signal_text = uo_info.get('signal', '中性')
|
|
926
|
+
print(f"UO(7,14,28)因子:")
|
|
927
|
+
print(f" 因子值: {uo_info.get('raw_value', 0):+.6f}")
|
|
928
|
+
print(f" 信号: {signal_emoji} {signal_text}")
|
|
929
|
+
if uo_info.get('win_rate'):
|
|
930
|
+
wr = uo_info['win_rate']
|
|
931
|
+
if wr and isinstance(wr, dict):
|
|
932
|
+
print(f" 看多胜率: {wr.get('long_win_rate', 0):.2%} (样本={wr.get('long_signals', 0)})")
|
|
933
|
+
print(f" 看空胜率: {wr.get('short_win_rate', 0):.2%} (样本={wr.get('short_signals', 0)})")
|
|
934
|
+
print(f" 总体胜率: {wr.get('overall_win_rate', 0):.2%} (总样本={wr.get('total_samples', 0)})")
|
|
935
|
+
print()
|
|
936
|
+
|
|
937
|
+
# EMA因子(多个周期)
|
|
938
|
+
ema_factors = [k for k in factor_results.keys() if k.startswith('ema_factor_')]
|
|
939
|
+
if ema_factors:
|
|
940
|
+
print("\n【EMA因子】")
|
|
941
|
+
print("-"*100)
|
|
942
|
+
# 按周期排序
|
|
943
|
+
ema_factors.sort(key=lambda x: int(x.replace('ema_factor_', '')))
|
|
944
|
+
for factor_key in ema_factors:
|
|
945
|
+
ema_info = factor_results[factor_key]
|
|
946
|
+
period = factor_key.replace('ema_factor_', '')
|
|
947
|
+
signal_emoji = "🟢" if ema_info.get('signal') == '看多' else "🔴" if ema_info.get('signal') == '看空' else "⚪"
|
|
948
|
+
signal_text = ema_info.get('signal', '中性')
|
|
949
|
+
print(f"EMA({period})因子:")
|
|
950
|
+
print(f" 因子值: {ema_info.get('raw_value', 0):+.6f}")
|
|
951
|
+
print(f" 信号: {signal_emoji} {signal_text}")
|
|
952
|
+
if ema_info.get('win_rate'):
|
|
953
|
+
wr = ema_info['win_rate']
|
|
954
|
+
if wr and isinstance(wr, dict):
|
|
955
|
+
print(f" 看多胜率: {wr.get('long_win_rate', 0):.2%} (样本={wr.get('long_signals', 0)})")
|
|
956
|
+
print(f" 看空胜率: {wr.get('short_win_rate', 0):.2%} (样本={wr.get('short_signals', 0)})")
|
|
957
|
+
print(f" 总体胜率: {wr.get('overall_win_rate', 0):.2%} (总样本={wr.get('total_samples', 0)})")
|
|
958
|
+
print()
|
|
959
|
+
|
|
960
|
+
# 归一化Alpha因子
|
|
961
|
+
print("\n【归一化Alpha因子】")
|
|
962
|
+
print("-"*100)
|
|
963
|
+
|
|
964
|
+
normalized_alpha_factors = [k for k in factor_results.keys() if k.startswith('normalized_alpha')]
|
|
965
|
+
if normalized_alpha_factors:
|
|
966
|
+
# 按Alpha编号排序
|
|
967
|
+
normalized_alpha_factors.sort(key=lambda x: int(x.replace('normalized_alpha', '')))
|
|
968
|
+
|
|
969
|
+
for factor_key in normalized_alpha_factors:
|
|
970
|
+
alpha_info = factor_results[factor_key]
|
|
971
|
+
alpha_num = factor_key.replace('normalized_alpha', '')
|
|
972
|
+
signal_emoji = "🟢" if alpha_info.get('signal') == '看多' else "🔴" if alpha_info.get('signal') == '看空' else "⚪"
|
|
973
|
+
signal_text = alpha_info.get('signal', '中性')
|
|
974
|
+
|
|
975
|
+
print(f"归一化Alpha#{alpha_num}:")
|
|
976
|
+
print(f" 原始值: {alpha_info.get('raw_value', 0):+.6f}")
|
|
977
|
+
print(f" 归一化值: {alpha_info.get('value', 0):+.4f}")
|
|
978
|
+
print(f" 信号: {signal_emoji} {signal_text}")
|
|
979
|
+
if alpha_info.get('win_rate'):
|
|
980
|
+
wr = alpha_info['win_rate']
|
|
981
|
+
if wr and isinstance(wr, dict):
|
|
982
|
+
print(f" 看多胜率: {wr.get('long_win_rate', 0):.2%} (样本={wr.get('long_signals', 0)})")
|
|
983
|
+
print(f" 看空胜率: {wr.get('short_win_rate', 0):.2%} (样本={wr.get('short_signals', 0)})")
|
|
984
|
+
print(f" 总体胜率: {wr.get('overall_win_rate', 0):.2%} (总样本={wr.get('total_samples', 0)})")
|
|
985
|
+
print()
|
|
986
|
+
else:
|
|
987
|
+
print("未计算出归一化Alpha因子结果")
|
|
988
|
+
|
|
989
|
+
print("="*100)
|
|
990
|
+
|
|
991
|
+
|
|
992
|
+
def main():
|
|
993
|
+
"""
|
|
994
|
+
主函数
|
|
995
|
+
"""
|
|
996
|
+
parser = argparse.ArgumentParser(description='计算当前币对合约各个factor的值、看多/看空信号和胜率')
|
|
997
|
+
parser.add_argument('--symbol', type=str, default='ETHUSDT', help='交易对符号,例如 BTCUSDT, ETHUSDT')
|
|
998
|
+
parser.add_argument('--interval', type=str, default='5m', help='时间间隔,例如 1m, 5m, 30m, 1h, 1d')
|
|
999
|
+
parser.add_argument('--lookback', type=int, default=500, help='回看周期数(获取多少条历史数据)')
|
|
1000
|
+
parser.add_argument('--forward', type=int, default=1, help='向前看周期数(用于计算胜率)')
|
|
1001
|
+
|
|
1002
|
+
args = parser.parse_args()
|
|
1003
|
+
|
|
1004
|
+
# 设置用户(如果需要API认证)
|
|
1005
|
+
set_user()
|
|
1006
|
+
|
|
1007
|
+
print("="*100)
|
|
1008
|
+
print("📊 因子计算工具")
|
|
1009
|
+
print("="*100)
|
|
1010
|
+
print(f"交易对: {args.symbol}")
|
|
1011
|
+
print(f"时间间隔: {args.interval}")
|
|
1012
|
+
print(f"回看周期数: {args.lookback}")
|
|
1013
|
+
print(f"向前看周期数: {args.forward}")
|
|
1014
|
+
print("="*100)
|
|
1015
|
+
|
|
1016
|
+
# 计算开始时间和结束时间
|
|
1017
|
+
end_time = datetime.now()
|
|
1018
|
+
# 根据interval和lookback计算start_time
|
|
1019
|
+
interval_durations = {
|
|
1020
|
+
"1m": timedelta(minutes=1),
|
|
1021
|
+
"3m": timedelta(minutes=3),
|
|
1022
|
+
"5m": timedelta(minutes=5),
|
|
1023
|
+
"15m": timedelta(minutes=15),
|
|
1024
|
+
"30m": timedelta(minutes=30),
|
|
1025
|
+
"1h": timedelta(hours=1),
|
|
1026
|
+
"2h": timedelta(hours=2),
|
|
1027
|
+
"4h": timedelta(hours=4),
|
|
1028
|
+
"6h": timedelta(hours=6),
|
|
1029
|
+
"8h": timedelta(hours=8),
|
|
1030
|
+
"12h": timedelta(hours=12),
|
|
1031
|
+
"1d": timedelta(days=1),
|
|
1032
|
+
"3d": timedelta(days=3),
|
|
1033
|
+
"1w": timedelta(weeks=1),
|
|
1034
|
+
}
|
|
1035
|
+
interval_delta = interval_durations.get(args.interval, timedelta(hours=1))
|
|
1036
|
+
start_time = end_time - interval_delta * args.lookback
|
|
1037
|
+
|
|
1038
|
+
print(f"\n正在获取数据...")
|
|
1039
|
+
print(f" 开始时间: {start_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
1040
|
+
print(f" 结束时间: {end_time.strftime('%Y-%m-%d %H:%M:%S')}")
|
|
1041
|
+
|
|
1042
|
+
# 获取数据(使用临时目录,但不保存文件)
|
|
1043
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
|
1044
|
+
klines_data = get_and_save_futures_klines(
|
|
1045
|
+
symbol=args.symbol,
|
|
1046
|
+
interval=args.interval,
|
|
1047
|
+
start_time=start_time,
|
|
1048
|
+
end_time=end_time,
|
|
1049
|
+
output_dir=temp_dir,
|
|
1050
|
+
save_csv=False,
|
|
1051
|
+
save_json=False
|
|
1052
|
+
)
|
|
1053
|
+
|
|
1054
|
+
if not klines_data:
|
|
1055
|
+
print("❌ 获取数据失败")
|
|
1056
|
+
return
|
|
1057
|
+
|
|
1058
|
+
print(f"✅ 成功获取 {len(klines_data)} 条数据")
|
|
1059
|
+
|
|
1060
|
+
# 转换为DataFrame
|
|
1061
|
+
data_df = klines_to_dataframe(klines_data)
|
|
1062
|
+
|
|
1063
|
+
if len(data_df) == 0:
|
|
1064
|
+
print("❌ 数据为空")
|
|
1065
|
+
return
|
|
1066
|
+
|
|
1067
|
+
print(f"✅ 数据已转换为DataFrame,共 {len(data_df)} 行")
|
|
1068
|
+
print(f" 时间范围: {data_df.iloc[0]['open_time_str']} 至 {data_df.iloc[-1]['open_time_str']}")
|
|
1069
|
+
|
|
1070
|
+
# 计算所有因子
|
|
1071
|
+
print(f"\n正在计算因子...")
|
|
1072
|
+
factor_results = calculate_all_factors(data_df,forward_periods=3)
|
|
1073
|
+
|
|
1074
|
+
# 打印结果
|
|
1075
|
+
print_factor_results(factor_results)
|
|
1076
|
+
|
|
1077
|
+
print("\n✅ 计算完成!")
|
|
1078
|
+
|
|
1079
|
+
|
|
1080
|
+
if __name__ == "__main__":
|
|
1081
|
+
main()
|
|
1082
|
+
|