修复项: - export_backtest_detail.py: 统一回测导出脚本的数据源调用逻辑 - test_trading_calendar.py: 交易日历功能测试 - verify_fix_result.py: 修复结果验证 - verify_mode_b.py: 模式 B 验证 策略修复: - momentum.py: 动量因子计算优化 - strategy.py: StrategyBase 数据获取修复(fetch_indices 返回 dict)
143 lines
4.4 KiB
Python
143 lines
4.4 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
验证修复后的回测结果是否与文档一致
|
|
|
|
文档预期结果 (Mode A - 指数信号+指数收益):
|
|
CAGR: 11.80%, 最大回撤: -29.49%, 夏普: 0.818, Calmar: 0.400
|
|
|
|
文档预期结果 (Mode B - 指数信号+ETF收益):
|
|
CAGR: 28.07%, 最大回撤: -13.34%, 夏普: 1.685, Calmar: 2.104
|
|
"""
|
|
|
|
import sys
|
|
from pathlib import Path
|
|
project_root = Path(__file__).parent.parent
|
|
sys.path.insert(0, str(project_root))
|
|
|
|
from dotenv import load_dotenv
|
|
load_dotenv()
|
|
|
|
import pandas as pd
|
|
import numpy as np
|
|
import yaml
|
|
from datetime import datetime
|
|
from strategies.rotation.strategy import RotationStrategy
|
|
|
|
|
|
def calculate_metrics(nav: pd.Series) -> dict:
|
|
"""计算绩效指标"""
|
|
start_date = nav.index[0]
|
|
end_date = nav.index[-1]
|
|
days = (end_date - start_date).days
|
|
years = days / 365
|
|
|
|
total_return = nav.iloc[-1] - 1
|
|
cagr = (nav.iloc[-1] / nav.iloc[0]) ** (1/years) - 1
|
|
|
|
daily_ret = nav.pct_change().dropna()
|
|
sharpe = daily_ret.mean() / daily_ret.std() * np.sqrt(252) if daily_ret.std() > 0 else 0
|
|
|
|
peak = nav.cummax()
|
|
drawdown = (nav - peak) / peak
|
|
max_dd = drawdown.min()
|
|
|
|
calmar = cagr / abs(max_dd) if max_dd != 0 else 0
|
|
win_rate = (daily_ret > 0).sum() / len(daily_ret)
|
|
|
|
return {
|
|
'start_date': start_date.strftime('%Y-%m-%d'),
|
|
'end_date': end_date.strftime('%Y-%m-%d'),
|
|
'years': years,
|
|
'days': len(nav),
|
|
'total_return': total_return,
|
|
'cagr': cagr,
|
|
'max_dd': max_dd,
|
|
'sharpe': sharpe,
|
|
'calmar': calmar,
|
|
'win_rate': win_rate
|
|
}
|
|
|
|
|
|
def main():
|
|
# 加载配置
|
|
config_path = project_root / 'strategies/rotation/config.yaml'
|
|
with open(config_path, 'r') as f:
|
|
config = yaml.safe_load(f)
|
|
|
|
# 设置回测区间(文档中的测试区间)
|
|
config['start_date'] = '2020-01-02'
|
|
config['end_date'] = '2026-05-19'
|
|
|
|
print('='*70)
|
|
print('修复后回测结果验证')
|
|
print('='*70)
|
|
print(f'回测区间: {config["start_date"]} ~ {config["end_date"]}')
|
|
|
|
# 初始化策略
|
|
strategy = RotationStrategy(config)
|
|
|
|
# 获取数据并执行回测
|
|
print('\n获取数据...')
|
|
data = strategy.get_data(use_flask_api=False)
|
|
|
|
print('\n执行回测...')
|
|
result = strategy.run_backtest(data=data)
|
|
|
|
if result.get('result') is None:
|
|
print('❌ 回测未生成结果')
|
|
return
|
|
|
|
# 计算指标
|
|
nav = result['result']['策略净值']
|
|
metrics = calculate_metrics(nav)
|
|
|
|
# 输出结果
|
|
print('\n' + '='*70)
|
|
print('修复后回测结果')
|
|
print('='*70)
|
|
print(f"回测区间: {metrics['start_date']} ~ {metrics['end_date']}")
|
|
print(f"回测年数: {metrics['years']:.2f} 年")
|
|
print(f"交易天数: {metrics['days']} 天")
|
|
print('-'*70)
|
|
print(f"CAGR: {metrics['cagr']:.2%}")
|
|
print(f"最大回撤: {metrics['max_dd']:.2%}")
|
|
print(f"夏普比率: {metrics['sharpe']:.3f}")
|
|
print(f"Calmar比率: {metrics['calmar']:.3f}")
|
|
print(f"日胜率: {metrics['win_rate']:.2%}")
|
|
print(f"累计收益: {metrics['total_return']:.2%}")
|
|
print(f"调仓次数: {len(result.get('rebalance_events', []))} 次")
|
|
print('='*70)
|
|
|
|
# 文档预期结果对比
|
|
print('\n' + '='*70)
|
|
print('文档预期结果对比')
|
|
print('='*70)
|
|
print("\nMode A (指数信号 → 指数收益):")
|
|
print(" 预期: CAGR 11.80%, MaxDD -29.49%, Sharpe 0.818, Calmar 0.400")
|
|
|
|
print("\nMode B (指数信号 → ETF收益):")
|
|
print(" 预期: CAGR 28.07%, MaxDD -13.34%, Sharpe 1.685, Calmar 2.104")
|
|
|
|
# 判断当前模式
|
|
print('\n' + '-'*70)
|
|
cagr_diff_a = abs(metrics['cagr'] - 0.1180)
|
|
cagr_diff_b = abs(metrics['cagr'] - 0.2807)
|
|
|
|
if cagr_diff_a < 0.03:
|
|
print(f"✓ 当前结果接近 Mode A (CAGR差异: {cagr_diff_a:.2%})")
|
|
print(" 说明: 当前回测使用指数收盘价计算收益")
|
|
elif cagr_diff_b < 0.03:
|
|
print(f"✓ 当前结果接近 Mode B (CAGR差异: {cagr_diff_b:.2%})")
|
|
print(" 说明: 当前回测使用ETF价格计算收益")
|
|
else:
|
|
print(f"⚠ 当前结果与文档预期有差异")
|
|
print(f" Mode A CAGR差异: {cagr_diff_a:.2%}")
|
|
print(f" Mode B CAGR差异: {cagr_diff_b:.2%}")
|
|
|
|
print('='*70)
|
|
|
|
return metrics
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main() |