Files
etf/archive/tests/verify_fix_result.py
aszerW c905230a40 refactor(archive): move unused modules to archive/
Archive legacy framework and utility modules that are no longer
referenced by the active core (datasource/ and rotation/):

- framework/ -> archive/framework/
- framework_v2/ -> archive/framework_v2/
- strategies/ -> archive/strategies/
- config/ -> archive/config/
- visualization/ -> archive/visualization/
- scripts/ -> archive/scripts/
- tests/ -> archive/tests/
- run_rotation.py, run_us_rotation.py -> archive/single_files/
- compare_*.py, test_api_dates.py -> archive/single_files/
2026-06-03 23:41:46 +08:00

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()