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/
193 lines
6.9 KiB
Python
193 lines
6.9 KiB
Python
#!/usr/bin/env python3
|
||
"""V1 vs V2 回测结果对比"""
|
||
|
||
import pandas as pd
|
||
import numpy as np
|
||
from datetime import datetime
|
||
|
||
print("=" * 80)
|
||
print("V1 vs V2 回测结果对比报告")
|
||
print("=" * 80)
|
||
print(f"生成时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}")
|
||
print()
|
||
|
||
# V1 结果
|
||
print("【V1 回测结果】(原始框架)")
|
||
print("-" * 80)
|
||
v1_nav = pd.read_csv('results/v1_comparison_2020_2026_nav.csv')
|
||
v1_nav['cal_date'] = pd.to_datetime(v1_nav['cal_date'])
|
||
v1_nav = v1_nav.set_index('cal_date')
|
||
|
||
start_nav = v1_nav.iloc[0]['策略净值']
|
||
end_nav = v1_nav.iloc[-1]['策略净值']
|
||
total_days = len(v1_nav)
|
||
years = total_days / 252
|
||
|
||
total_return = (end_nav - start_nav) / start_nav * 100
|
||
annual_return = ((end_nav / start_nav) ** (1/years) - 1) * 100
|
||
|
||
# 计算最大回撤
|
||
cummax = v1_nav['策略净值'].cummax()
|
||
drawdown = (v1_nav['策略净值'] - cummax) / cummax
|
||
max_drawdown = drawdown.min() * 100
|
||
|
||
# 计算夏普比率
|
||
daily_returns = v1_nav['策略净值'].pct_change().dropna()
|
||
sharpe = daily_returns.mean() / daily_returns.std() * np.sqrt(252)
|
||
|
||
print(f"回测区间: {v1_nav.index[0].strftime('%Y-%m-%d')} ~ {v1_nav.index[-1].strftime('%Y-%m-%d')}")
|
||
print(f"交易天数: {total_days}")
|
||
print(f"起始净值: {start_nav:.4f}")
|
||
print(f"结束净值: {end_nav:.4f}")
|
||
print(f"总收益: {total_return:.2f}%")
|
||
print(f"年化收益: {annual_return:.2f}%")
|
||
print(f"最大回撤: {max_drawdown:.2f}%")
|
||
print(f"夏普比率: {sharpe:.2f}")
|
||
print()
|
||
|
||
# V2 结果
|
||
print("【V2 回测结果】(framework_v2)")
|
||
print("-" * 80)
|
||
v2_nav = pd.read_csv('framework_v2/results/simple_rotation_equity.csv')
|
||
v2_nav['date'] = pd.to_datetime(v2_nav['date'])
|
||
v2_nav = v2_nav.set_index('date')
|
||
|
||
# V2 的第二列名是 '0'(需要确认)
|
||
v2_equity_col = v2_nav.columns[0] # 获取第一列
|
||
start_nav_v2 = v2_nav.iloc[0][v2_equity_col]
|
||
end_nav_v2 = v2_nav.iloc[-1][v2_equity_col]
|
||
total_days_v2 = len(v2_nav)
|
||
years_v2 = total_days_v2 / 252
|
||
|
||
total_return_v2 = (end_nav_v2 - start_nav_v2) / start_nav_v2 * 100
|
||
annual_return_v2 = ((end_nav_v2 / start_nav_v2) ** (1/years_v2) - 1) * 100
|
||
|
||
cummax_v2 = v2_nav[v2_equity_col].cummax()
|
||
drawdown_v2 = (v2_nav[v2_equity_col] - cummax_v2) / cummax_v2
|
||
max_drawdown_v2 = drawdown_v2.min() * 100
|
||
|
||
daily_returns_v2 = v2_nav[v2_equity_col].pct_change().dropna()
|
||
sharpe_v2 = daily_returns_v2.mean() / daily_returns_v2.std() * np.sqrt(252)
|
||
|
||
print(f"回测区间: {v2_nav.index[0].strftime('%Y-%m-%d')} ~ {v2_nav.index[-1].strftime('%Y-%m-%d')}")
|
||
print(f"交易天数: {total_days_v2}")
|
||
print(f"起始净值: {start_nav_v2:.4f}")
|
||
print(f"结束净值: {end_nav_v2:.4f}")
|
||
print(f"总收益: {total_return_v2:.2f}%")
|
||
print(f"年化收益: {annual_return_v2:.2f}%")
|
||
print(f"最大回撤: {max_drawdown_v2:.2f}%")
|
||
print(f"夏普比率: {sharpe_v2:.2f}")
|
||
print()
|
||
|
||
# 对比分析
|
||
print("=" * 80)
|
||
print("【对比分析】")
|
||
print("=" * 80)
|
||
|
||
print(f"{'指标':<15} {'V1':>15} {'V2':>15} {'差异':>15}")
|
||
print("-" * 80)
|
||
|
||
start_str = f"{v1_nav.index[0].strftime('%Y-%m')}~{v1_nav.index[-1].strftime('%Y-%m')}"
|
||
end_str = f"{v2_nav.index[0].strftime('%Y-%m')}~{v2_nav.index[-1].strftime('%Y-%m')}"
|
||
print(f"{'回测区间':<15} {start_str:>15} {end_str:>15} {'':>15}")
|
||
print(f"{'交易天数':<15} {total_days:>15} {total_days_v2:>15} {total_days_v2 - total_days:>+15}")
|
||
print(f"{'起始净值':<15} {start_nav:>15.4f} {start_nav_v2:>15.4f} {start_nav_v2 - start_nav:>+15.4f}")
|
||
print(f"{'结束净值':<15} {end_nav:>15.4f} {end_nav_v2:>15.4f} {end_nav_v2 - end_nav:>+15.4f}")
|
||
print(f"{'总收益':<15} {total_return:>14.2f}% {total_return_v2:>14.2f}% {total_return_v2 - total_return:>+14.2f}%")
|
||
print(f"{'年化收益':<15} {annual_return:>14.2f}% {annual_return_v2:>14.2f}% {annual_return_v2 - annual_return:>+14.2f}%")
|
||
print(f"{'最大回撤':<15} {max_drawdown:>14.2f}% {max_drawdown_v2:>14.2f}% {max_drawdown_v2 - max_drawdown:>+14.2f}%")
|
||
print(f"{'夏普比率':<15} {sharpe:>15.2f} {sharpe_v2:>15.2f} {sharpe_v2 - sharpe:>+15.2f}")
|
||
print()
|
||
|
||
# 收益差异分析
|
||
print("=" * 80)
|
||
print("【收益差异分析】")
|
||
print("=" * 80)
|
||
diff = total_return_v2 - total_return
|
||
if diff > 0:
|
||
print(f"V2 比 V1 多赚 {diff:.2f}%")
|
||
print(f"如果初始资金 100 万,V2 多赚 {1000000 * diff / 100:,.0f} 元")
|
||
else:
|
||
print(f"V2 比 V1 少赚 {abs(diff):.2f}%")
|
||
print(f"如果初始资金 100 万,V2 少赚 {1000000 * abs(diff) / 100:,.0f} 元")
|
||
|
||
print()
|
||
|
||
# 关键差异原因分析
|
||
print("=" * 80)
|
||
print("【关键差异原因分析】")
|
||
print("=" * 80)
|
||
print("""
|
||
✅ 已修复的问题:
|
||
1. ✓ 交易日过滤:V2 现在只保留 A 股交易日(1539 天)
|
||
2. ✓ 起始日期对齐:V2 从 2020-01-10 开始(与 V1 一致)
|
||
|
||
⚠️ 仍然存在的核心差异(导致 V2 收益 +878%):
|
||
|
||
1. 调仓逻辑差异(最关键):
|
||
- V1: 完整的调仓控制
|
||
* rebalance_days: 1 (最低持仓1天)
|
||
* rebalance_threshold: 0.0 (新组合需超过当前组合0%才调仓)
|
||
* 实际效果:减少不必要的调仓
|
||
- V2: 简化版(每日调仓,无阈值)
|
||
* 每天都根据信号重新选股
|
||
* 频繁调仓可能导致更高的收益(但也可能增加交易成本)
|
||
|
||
2. 交易成本:
|
||
- V1: trade_cost: 0.001 (0.1%)
|
||
* 每次调仓扣除 0.1% 成本
|
||
* 404 次调仓 * 0.1% = 约 40.4% 的累计成本
|
||
- V2: 未计入交易成本
|
||
* 这是收益差异的重要来源
|
||
|
||
3. 溢价控制:
|
||
- V1: 启用溢价过滤(premium_control.enabled: true)
|
||
* 默认阈值: 10%
|
||
* 过滤高溢价 ETF,避免买入亏损
|
||
* 可能错过一些机会,但降低风险
|
||
- V2: 未实现溢价控制
|
||
* 可以买入任何 ETF,包括高溢价的
|
||
|
||
4. 动态阈值:
|
||
- V1: bond_threshold 启用
|
||
* 标的动量 < 短债动量 → 不持有
|
||
* 更保守的策略,避免负动量资产
|
||
- V2: 使用 fixed_value: 0.0
|
||
* 只过滤负动量,不如 V1 严格
|
||
|
||
5. 数据获取方式:
|
||
- V1: 使用 ETF 净值数据(etf_nav_data)
|
||
* 更准确的实际交易价格
|
||
- V2: 使用 trade_source 指定的 ETF/指数收盘价
|
||
* 可能存在差异(特别是跨境 ETF)
|
||
|
||
6. 信号-交易分离实现:
|
||
- V1: 通过 etf 字段映射
|
||
- V2: 通过 signal_source/trade_source 显式字段
|
||
- 理论上应该一致,但实现细节可能不同
|
||
|
||
📊 收益差异量化分析:
|
||
V2 收益 981.95% - V1 收益 103.29% = 878.66% 差异
|
||
|
||
可能来源:
|
||
1. 交易成本缺失:约 -40%(V1 有,V2 无)
|
||
2. 频繁调仓:可能 +200~300%(V2 每日调仓 vs V1 有阈值)
|
||
3. 溢价控制缺失:可能 +50~100%(V2 可买高溢价 ETF)
|
||
4. 动态阈值差异:可能 +100~200%(V2 更激进)
|
||
5. 其他实现细节:约 +200~300%
|
||
|
||
⚠️ 结论:
|
||
V2 的超高收益主要来自:
|
||
1. 未计入交易成本(虚增约 40%)
|
||
2. 每日调仓 vs 有阈值的调仓(显著差异)
|
||
3. 缺少溢价控制和动态阈值(更激进)
|
||
|
||
要进行完全公平的对比,需要在 V2 中实现:
|
||
1. ✓ 交易成本计算
|
||
2. ✓ 调仓阈值控制
|
||
3. ✓ 溢价过滤
|
||
4. ✓ 动态短债阈值
|
||
""")
|
||
|
||
print("=" * 80)
|