forked from AtomNotShy/factor_mining
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtest_long_etf_momentum.py
More file actions
166 lines (140 loc) · 6.42 KB
/
test_long_etf_momentum.py
File metadata and controls
166 lines (140 loc) · 6.42 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
#!/usr/bin/env python3
"""
测试ETF动量策略长期回测,重现资金计算问题
"""
import asyncio
import pandas as pd
from datetime import date, datetime
import json
from src.strategies.user_strategies.etf_momentum_joinquant import ETFMomentumJoinQuantStrategy
from src.evaluation.backtesting.unified_engine import UnifiedBacktestEngine
from src.evaluation.backtesting.config import UnifiedConfig, TradeConfig, TimeConfig, FeatureFlag
async def test_long_backtest():
"""运行长期回测测试"""
print("=" * 80)
print("ETF动量策略长期回测测试 (重现资金计算问题)")
print("=" * 80)
# 创建策略
strategy = ETFMomentumJoinQuantStrategy(
strategy_id="long_test_etf_momentum",
etf_pool=["QQQ", "SPY", "IWM", "TLT", "GLD"],
lookback_days=20,
r2_threshold=0.5,
)
# 创建配置 - 使用与用户报告相同的配置
config = UnifiedConfig(
trade=TradeConfig(
initial_capital=100000,
commission_rate=0.001,
slippage_rate=0.0005,
max_position_size=0.2,
max_positions=1,
stake_amount=20000,
),
time=TimeConfig(
signal_timeframe="1d",
execution_timeframe="1d",
warmup_days=260,
clock_mode="daily",
),
features=FeatureFlag.VECTORIZED | FeatureFlag.FREQTRADE_PROTOCOL,
)
# 创建回测引擎
engine = UnifiedBacktestEngine(config=config)
# 运行回测(1年测试)
print("开始长期回测 (2024-01-01 到 2024-12-31)")
try:
result = await engine.run(
strategies=[strategy],
universe=["QQQ", "SPY", "IWM", "TLT", "GLD"],
start=date(2024, 1, 1),
end=date(2024, 12, 31), # 1年测试
auto_download=False,
)
# 分析结果
print("\n" + "=" * 80)
print("回测结果分析")
print("=" * 80)
print(f"初始资金: ${result.initial_capital:,.2f}")
print(f"最终权益: ${result.final_equity:,.2f}")
print(f"总回报: {result.total_return_pct:.2f}%")
print(f"交易次数: {result.total_trades}")
print(f"胜率: {result.win_rate:.2%}")
print(f"夏普比率: {result.sharpe_ratio:.2f}")
# 检查资金计算
calculated_return = ((result.final_equity / result.initial_capital - 1) * 100)
print(f"\n资金计算检查:")
print(f" 基于final_equity计算的总回报: {calculated_return:.2f}%")
print(f" result.total_return_pct: {result.total_return_pct:.2f}%")
diff = abs(calculated_return - result.total_return_pct)
if diff > 0.01:
print(f" ⚠️ 资金计算不一致! 差异: {diff:.2f}%")
# 分析交易频率
print(f"\n交易频率分析:")
print(f" 信号总数: {len(result.signals)}")
print(f" 订单总数: {len(result.orders)}")
print(f" 成交总数: {len(result.fills)}")
# 分析持仓变化
if result.portfolio_daily:
print(f"\n净值曲线分析:")
print(f" 净值记录数: {len(result.portfolio_daily)}")
# 检查净值变化
first_day = result.portfolio_daily[0]
last_day = result.portfolio_daily[-1]
print(f" 第一天净值: ${first_day.get('equity', 0):,.2f}")
print(f" 最后一天净值: ${last_day.get('equity', 0):,.2f}")
print(f" 第一天现金: ${first_day.get('cash', 0):,.2f}")
print(f" 最后一天现金: ${last_day.get('cash', 0):,.2f}")
# 检查现金和权益的关系
if last_day.get('cash', 0) < 0:
print(f" ⚠️ 最后一天现金为负: ${last_day.get('cash', 0):,.2f}")
# 计算持仓价值
positions_value = last_day.get('equity', 0) - last_day.get('cash', 0)
print(f" 最后一天持仓价值: ${positions_value:,.2f}")
# 分析交易详情
if result.fills:
print(f"\n交易详情分析:")
# 按交易对分组
trades_by_symbol = {}
for fill in result.fills:
symbol = fill.get('symbol', 'unknown')
if symbol not in trades_by_symbol:
trades_by_symbol[symbol] = []
trades_by_symbol[symbol].append(fill)
print(f" 交易对分布:")
for symbol, trades in trades_by_symbol.items():
print(f" {symbol}: {len(trades)} 次交易")
# 检查交易价格
print(f"\n 交易价格检查:")
for i, fill in enumerate(result.fills[:5]): # 只显示前5个
print(f" 交易 #{i+1}: {fill.get('symbol', 'N/A')} "
f"{fill.get('side', 'N/A')} "
f"{fill.get('qty', 0):.2f} @ ${fill.get('price', 0):.2f}")
# 保存详细结果
with open("long_test_result.json", "w") as f:
result_data = {
"initial_capital": result.initial_capital,
"final_equity": result.final_equity,
"total_return_pct": result.total_return_pct,
"calculated_return": calculated_return,
"total_trades": result.total_trades,
"win_rate": result.win_rate,
"sharpe_ratio": result.sharpe_ratio,
"signals_count": len(result.signals),
"orders_count": len(result.orders),
"fills_count": len(result.fills),
"portfolio_daily_summary": {
"first_day": result.portfolio_daily[0] if result.portfolio_daily else None,
"last_day": result.portfolio_daily[-1] if result.portfolio_daily else None,
"count": len(result.portfolio_daily),
},
"trades_by_symbol": {symbol: len(trades) for symbol, trades in trades_by_symbol.items()} if result.fills else {},
}
json.dump(result_data, f, indent=2, default=str)
print(f"\n详细结果已保存到: long_test_result.json")
except Exception as e:
print(f"回测失败: {e}")
import traceback
traceback.print_exc()
if __name__ == "__main__":
asyncio.run(test_long_backtest())