#!/usr/bin/env python3
"""
对比指数收益 vs ETF 收益的 KPI 指标

从 backtest_detail_v2.json 中提取：
1. 策略实际持仓
2. 各标的当日指数收益率
3. 各标的当日 ETF 收益率

分别计算两种收益模式下的 KPI：
- 指数收益模式：使用 index_return 计算策略净值
- ETF 收益模式：使用 etf_return_ctc 计算策略净值
"""

import json
import sys
from pathlib import Path
import numpy as np
import pandas as pd

project_root = Path(__file__).parent.parent.parent
sys.path.insert(0, str(project_root))


def load_detail_json(json_path: str) -> dict:
    """加载 detail JSON"""
    print(f"[1] 加载 JSON: {json_path}")
    with open(json_path, 'r', encoding='utf-8') as f:
        data = json.load(f)
    print(f"    天数: {len(data['days'])}")
    print(f"    标的: {len(data['meta']['codes'])}")
    return data


def calculate_returns_from_detail(data: dict, return_field: str, trade_cost: float = 0.001) -> tuple:
    """
    从 detail JSON 计算策略收益率（与 rotation.py 完全对齐）
    
    逻辑（与 rotation.py 第 354-362 行一致）：
    1. T+1 执行：今天的持仓信号，明天才产生收益
    2. 等权分配仓位
    3. 扣除交易成本（0.1%）
    
    Args:
        data: detail JSON 数据
        return_field: 收益率字段名 ('index_return' 或 'etf_return_ctc')
        trade_cost: 交易成本（默认 0.1%）
    
    Returns:
        (策略收益率序列, 调仓次数)
    """
    dates = []
    strategy_returns = []
    positions = []  # 记录每日仓位，用于计算调仓次数
    
    for i, day in enumerate(data['days']):
        date = day['date']
        holdings = day['holdings']
        
        dates.append(pd.Timestamp(date))
        
        if not holdings:
            # 空仓
            positions.append({})
            if i == 0:
                strategy_returns.append(0.0)
            else:
                # T+1：昨天空仓，今天收益为 0
                strategy_returns.append(0.0)
        else:
            # 记录仓位（等权）
            n_holdings = len(holdings)
            pos = {code: 1.0 / n_holdings for code in holdings}
            positions.append(pos)
            
            if i == 0:
                # 第一天，T+1 执行，收益为 0
                strategy_returns.append(0.0)
            else:
                # T+1 执行：用昨天的仓位 × 今天的收益率
                daily_return = 0.0
                for code, weight in positions[i-1].items():
                    if code in day['assets']:
                        asset = day['assets'][code]
                        ret = asset.get(return_field, 0.0)
                        if ret is None:
                            ret = 0.0
                        daily_return += weight * ret
                strategy_returns.append(daily_return)
    
    # 转换为 Series
    returns_series = pd.Series(strategy_returns, index=dates, name='strategy_returns')
    
    # 计算调仓次数（与 rotation.py 第 425 行一致）
    # 检测持仓变化
    rebalance_count = 0
    for i in range(1, len(positions)):
        if positions[i] != positions[i-1]:
            rebalance_count += 1
    
    # 扣除交易成本（与 rotation.py 第 429 行一致）
    if trade_cost > 0 and rebalance_count > 0:
        # 检测调仓日
        position_changes = []
        for i in range(1, len(positions)):
            position_changes.append(positions[i] != positions[i-1])
        position_changes.insert(0, False)  # 第一天
        
        # 在调仓日扣除成本
        for i, is_change in enumerate(position_changes):
            if is_change:
                returns_series.iloc[i] -= trade_cost
    
    return returns_series, rebalance_count


def calculate_kpi(strategy_returns: pd.Series, mode_name: str, rebalance_count: int) -> dict:
    """
    计算 KPI 指标（与 rotation.py 第 383-394 行完全一致）
    
    Args:
        strategy_returns: 策略收益率序列
        mode_name: 模式名称（用于打印）
        rebalance_count: 调仓次数
    
    Returns:
        KPI 字典
    """
    # 净值曲线（与 rotation.py 第 365 行一致）
    equity_curve = (1 + strategy_returns).cumprod()
    
    # 总收益（与 rotation.py 第 384 行一致）
    total_return = equity_curve.iloc[-1] / equity_curve.iloc[0] - 1
    
    # 年化收益（与 rotation.py 第 385-386 行一致，使用 252 天）
    n_days = len(strategy_returns)
    annual_return = (1 + total_return) ** (252 / n_days) - 1 if n_days > 0 else 0
    
    # 最大回撤（与 rotation.py 第 388-391 行一致）
    cumulative_max = equity_curve.cummax()
    drawdown = (equity_curve - cumulative_max) / cumulative_max
    max_drawdown = drawdown.min()
    
    # 夏普比率（与 rotation.py 第 394 行一致，使用 252 天）
    sharpe = (strategy_returns.mean() / strategy_returns.std() * np.sqrt(252) 
              if strategy_returns.std() > 0 else 0)
    
    kpi = {
        'mode': mode_name,
        'total_return': total_return,
        'annual_return': annual_return,
        'max_drawdown': max_drawdown,
        'sharpe_ratio': sharpe,
        'n_days': n_days,
        'rebalance_count': rebalance_count,
        'final_nav': equity_curve.iloc[-1],
    }
    
    return kpi


def print_kpi_comparison(index_kpi: dict, etf_kpi: dict):
    """打印 KPI 对比表"""
    print("\n" + "=" * 80)
    print("   KPI 指标对比：指数收益 vs ETF 收益")
    print("=" * 80)
    
    # 表头
    print(f"\n{'指标':<20} {'指数收益':>15} {'ETF 收益':>15} {'差异':>15}")
    print("-" * 80)
    
    # 数据行
    metrics = [
        ('总收益', 'total_return', '{:.2%}'),
        ('年化收益', 'annual_return', '{:.2%}'),
        ('最大回撤', 'max_drawdown', '{:.2%}'),
        ('夏普比率', 'sharpe_ratio', '{:.2f}'),
        ('最终净值', 'final_nav', '{:.4f}'),
        ('交易天数', 'n_days', '{:.0f}'),
        ('调仓次数', 'rebalance_count', '{:.0f}'),
    ]
    
    for label, key, fmt in metrics:
        idx_val = index_kpi[key]
        etf_val = etf_kpi[key]
        diff = etf_val - idx_val
        
        # 特殊处理百分比格式的差异
        if 'return' in key or 'drawdown' in key:
            diff_fmt = '{:+.2%}'.format(diff)
        else:
            diff_fmt = '{:+.2f}'.format(diff) if key != 'n_days' and key != 'rebalance_count' else '{:+.0f}'.format(diff)
        
        print(f"{label:<20} {fmt.format(idx_val):>15} {fmt.format(etf_val):>15} {diff_fmt:>15}")
    
    print("=" * 80)


def save_comparison_csv(index_returns: pd.Series, etf_returns: pd.Series, output_path: str):
    """保存对比数据到 CSV"""
    df = pd.DataFrame({
        'date': index_returns.index,
        'index_return': index_returns.values,
        'etf_return': etf_returns.values,
        'index_nav': (1 + index_returns).cumprod().values,
        'etf_nav': (1 + etf_returns).cumprod().values,
    })
    
    df['nav_diff'] = df['etf_nav'] - df['index_nav']
    df['return_diff'] = df['etf_return'] - df['index_return']
    
    output_path = Path(output_path)
    output_path.parent.mkdir(parents=True, exist_ok=True)
    df.to_csv(output_path, index=False)
    
    print(f"\n[3] 对比数据已保存: {output_path}")
    print(f"    行数: {len(df)}")


def main():
    print("=" * 80)
    print("   指数收益 vs ETF 收益 KPI 对比分析")
    print("=" * 80)
    
    # 1. 加载数据
    json_path = project_root / 'framework_v2' / 'results' / 'backtest_detail_v2.json'
    if not json_path.exists():
        print(f"错误: JSON 文件不存在: {json_path}")
        sys.exit(1)
    
    data = load_detail_json(str(json_path))
    
    # 2. 计算两种模式的收益率
    print("\n[2] 计算收益率（与 rotation.py 完全对齐）...")
    print("  - 指数收益模式 (index_return)")
    print("    T+1 执行 + 等权仓位 + 交易成本 0.1%")
    index_returns, index_rebalance = calculate_returns_from_detail(data, 'index_return', trade_cost=0.001)
    print(f"    调仓次数: {index_rebalance}")
    
    print("  - ETF 收益模式 (etf_return_ctc)")
    print("    T+1 执行 + 等权仓位 + 交易成本 0.1%")
    etf_returns, etf_rebalance = calculate_returns_from_detail(data, 'etf_return_ctc', trade_cost=0.001)
    print(f"    调仓次数: {etf_rebalance}")
    
    # 3. 计算 KPI
    print("\n计算 KPI 指标（使用 252 天/年，与 rotation.py 一致）...")
    index_kpi = calculate_kpi(index_returns, '指数收益', index_rebalance)
    etf_kpi = calculate_kpi(etf_returns, 'ETF 收益', etf_rebalance)
    
    # 4. 打印对比
    print_kpi_comparison(index_kpi, etf_kpi)
    
    # 5. 保存 CSV
    csv_path = project_root / 'framework_v2' / 'results' / 'kpi_comparison_index_vs_etf.csv'
    save_comparison_csv(index_returns, etf_returns, str(csv_path))
    
    # 6. 分析差异来源
    print("\n[4] 差异分析...")
    daily_diff = (etf_returns - index_returns).abs()
    large_diff_days = (daily_diff > 0.001).sum()  # 差异 > 0.1%
    print(f"    差异 > 0.1% 的天数: {large_diff_days} / {len(index_returns)}")
    print(f"    平均日差异: {daily_diff.mean():.6f} ({daily_diff.mean()*100:.4f}%)")
    print(f"    最大日差异: {daily_diff.max():.6f} ({daily_diff.max()*100:.4f}%)")
    
    print("\n" + "=" * 80)
    print("   分析完成！")
    print("=" * 80)


if __name__ == '__main__':
    main()
