#!/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)