"""
Task 4: 资金管理问题诊断

分析维度：
4.1 止损机制模拟 - 组合级止损 vs 单资产止损
4.2 波动率适配 - 基于组合波动率的动态仓位
4.3 现金管理评估 - 全仓 vs 债券填充 vs 空仓
"""

import sys
from pathlib import Path
from collections import defaultdict
from typing import Dict, List

import numpy as np
import pandas as pd

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

from rotation.experiments.common import (
    load_nav, load_signals, load_detail_days, load_detail_meta,
    print_section, compute_drawdown, compute_sharpe,
    compute_annual_return,
)


def simulate_portfolio_stoploss(nav_df: pd.DataFrame, stop_threshold: float) -> dict:
    """模拟组合级止损：当组合从峰值回撤超过 stop_threshold 时，
    假设转为持有债券（短债收益近似 0.02/252 每天）。
    
    简化模型：止损触发后持有债券 10 天，然后恢复正常交易。
    """
    nav = nav_df['nav'].values.copy()
    returns = nav_df['daily_return'].values.copy()
    bond_daily_ret = 0.02 / 252  # 短债近似日收益

    simulated_nav = 1.0
    sim_returns = []
    peak = 1.0
    in_stoploss = False
    stoploss_days_remaining = 0
    stoploss_triggers = 0

    for i in range(len(nav)):
        if in_stoploss:
            # 止损期间持有债券
            ret = bond_daily_ret
            stoploss_days_remaining -= 1
            if stoploss_days_remaining <= 0:
                in_stoploss = False
        else:
            ret = returns[i]
            simulated_nav *= (1 + ret)
            peak = max(peak, simulated_nav)
            dd = (simulated_nav - peak) / peak
            if dd < -stop_threshold:
                in_stoploss = True
                stoploss_days_remaining = 10  # 止损后持有债券 10 天
                stoploss_triggers += 1
                ret = bond_daily_ret  # 当天就转债券

        simulated_nav *= (1 + ret) if not in_stoploss or stoploss_days_remaining < 10 else 1
        # 修正：简化处理
        if in_stoploss:
            sim_returns.append(bond_daily_ret)
        else:
            sim_returns.append(returns[i])

    # 重新计算 NAV
    sim_nav = pd.Series(sim_returns).add(1).cumprod()
    total_ret = sim_nav.iloc[-1] - 1
    n = len(sim_nav)
    annual_ret = (1 + total_ret) ** (252 / n) - 1
    max_dd = compute_drawdown(sim_nav).min()
    sharpe = compute_sharpe(pd.Series(sim_returns))

    return {
        'stop_threshold': stop_threshold,
        'total_return': total_ret,
        'annual_return': annual_ret,
        'max_drawdown': max_dd,
        'sharpe': sharpe,
        'trigger_count': stoploss_triggers,
    }


def simulate_asset_stoploss(days: List[dict], stop_pct: float) -> dict:
    """模拟单资产止损：当持仓资产从入场价回撤超过 stop_pct 时，强制卖出。
    
    简化模型：卖出后该仓位转为债券 5 天。
    """
    trade_cost = 0.001
    bond_daily_ret = 0.02 / 252

    # 追踪每个持仓的入场价格
    entry_prices = {}  # code -> entry_cum_return_etf at entry
    nav = 1.0
    stoploss_events = 0
    returns = []

    for day in days:
        daily_return = day.get('daily_return', 0)
        holdings = day.get('holdings', [])

        # 检查是否有资产触发止损
        triggered = []
        for code, asset in day.get('assets', {}).items():
            if asset.get('is_held') and asset.get('cum_return_etf') is not None:
                cum_ret = asset['cum_return_etf']
                if cum_ret < -stop_pct:
                    triggered.append(code)

        if triggered:
            stoploss_events += len(triggered)
            # 简化：被止损的仓位按债券收益计算，其余按原收益
            n_held = len(holdings) if holdings else 1
            weight = 1.0 / n_held
            adjusted_return = daily_return + weight * (bond_daily_ret - daily_return * weight)
            nav *= (1 + adjusted_return)
            returns.append(adjusted_return)
        else:
            nav *= (1 + daily_return)
            returns.append(daily_return)

    sim_nav = pd.Series(returns).add(1).cumprod()
    total_ret = sim_nav.iloc[-1] - 1
    n = len(returns)
    annual_ret = (1 + total_ret) ** (252 / n) - 1 if n > 0 else 0
    max_dd = compute_drawdown(sim_nav).min()
    sharpe = compute_sharpe(pd.Series(returns))

    return {
        'stop_pct': stop_pct,
        'total_return': total_ret,
        'annual_return': annual_ret,
        'max_drawdown': max_dd,
        'sharpe': sharpe,
        'stoploss_events': stoploss_events,
    }


def analyze_stoploss(nav: pd.DataFrame, days: List[dict]):
    """4.1 止损机制模拟"""
    print_section("4.1 组合级止损模拟")

    # 原始策略作为基准
    orig_nav = nav['nav']
    orig_total = orig_nav.iloc[-1] / orig_nav.iloc[0] - 1
    orig_dd = compute_drawdown(orig_nav).min()
    orig_sharpe = compute_sharpe(nav['daily_return'])
    print(f"  原始策略: 累计={orig_total:+.2%}, 最大回撤={orig_dd:.2%}, 夏普={orig_sharpe:.2f}")

    for threshold in [0.05, 0.08, 0.10, 0.12]:
        r = simulate_portfolio_stoploss(nav, threshold)
        print(f"  组合止损线={threshold:.0%}: 累计={r['total_return']:+.2%}, "
              f"回撤={r['max_drawdown']:.2%}, 夏普={r['sharpe']:.2f}, "
              f"触发{r['trigger_count']}次")

    print_section("4.1a 单资产止损模拟")
    for stop_pct in [0.05, 0.08, 0.10, 0.15]:
        r = simulate_asset_stoploss(days, stop_pct)
        print(f"  单资产止损线={stop_pct:.0%}: 累计={r['total_return']:+.2%}, "
              f"回撤={r['max_drawdown']:.2%}, 夏普={r['sharpe']:.2f}, "
              f"触发{r['stoploss_events']}次")

    return {}


def analyze_volatility_sizing(days: List[dict], nav: pd.DataFrame):
    """4.2 波动率适配分析"""
    print_section("4.2 波动率适配分析")

    # 计算滚动 20 日波动率
    nav_df = nav.copy()
    nav_df['rolling_vol'] = nav_df['daily_return'].rolling(20).std() * np.sqrt(252)

    # 按波动率分桶统计收益
    print("  组合波动率分桶统计:")
    valid = nav_df.dropna(subset=['rolling_vol'])
    buckets = [(0, 0.10), (0.10, 0.15), (0.15, 0.20), (0.20, 0.30), (0.30, 1.0)]
    for lo, hi in buckets:
        mask = (valid['rolling_vol'] >= lo) & (valid['rolling_vol'] < hi)
        subset = valid[mask]
        if len(subset) == 0:
            continue
        avg_ret = subset['daily_return'].mean() * 252
        avg_vol = subset['rolling_vol'].mean()
        win_rate = (subset['daily_return'] > 0).mean()
        print(f"    波动率 [{lo:.0%}, {hi:.0%}): {len(subset)} 天, "
              f"年化收益={avg_ret:+.2%}, 胜率={win_rate:.1%}")

    # 模拟：高波动期减仓
    print(f"\n  模拟: 波动率 > 20% 时仓位减至 2/3:")
    sim_returns = []
    for _, row in valid.iterrows():
        ret = row['daily_return']
        vol = row['rolling_vol']
        if vol > 0.20:
            ret = ret * 2 / 3  # 减仓至 2/3
        sim_returns.append(ret)

    sim_nav = pd.Series(sim_returns).add(1).cumprod()
    total_ret = sim_nav.iloc[-1] - 1
    n = len(sim_returns)
    annual_ret = (1 + total_ret) ** (252 / n) - 1
    max_dd = compute_drawdown(sim_nav).min()
    sharpe = compute_sharpe(pd.Series(sim_returns))

    orig_total = nav_df['nav'].iloc[-1] / nav_df['nav'].iloc[0] - 1
    print(f"    原始: 累计={orig_total:+.2%}")
    print(f"    波动率适配: 累计={total_ret:+.2%}, 回撤={max_dd:.2%}, 夏普={sharpe:.2f}")

    # 高波动期出现频率
    high_vol_days = (valid['rolling_vol'] > 0.20).sum()
    print(f"\n  高波动期(>20%): {high_vol_days}/{len(valid)} 天 ({high_vol_days/len(valid)*100:.1f}%)")

    return {}


def analyze_cash_management(days: List[dict]):
    """4.3 现金管理评估"""
    print_section("4.3 现金管理评估")

    # 统计所有资产动量都低于阈值的天数（全部防御）
    all_below = 0
    partial_below = 0
    total_days = 0

    for day in days:
        total_days += 1
        assets = day.get('assets', {})
        holdings = day.get('holdings', [])

        non_bond_assets = {c: a for c, a in assets.items()
                          if c != '931862.CSI' and a.get('momentum') is not None}
        if not non_bond_assets:
            continue

        below_count = sum(1 for a in non_bond_assets.values()
                         if not a.get('above_threshold', False))

        if below_count == len(non_bond_assets):
            all_below += 1
        elif below_count > 0:
            partial_below += 1

    print(f"  全部非债券资产动量低于阈值: {all_below} 天 ({all_below/total_days*100:.1f}%)")
    print(f"  部分非债券资产动量低于阈值: {partial_below} 天 ({partial_below/total_days*100:.1f}%)")
    print(f"  所有资产动量都高于阈值: {total_days - all_below - partial_below} 天")

    # 全部低于阈值时的后续收益
    print(f"\n  全部低于阈值后的 T+N 收益:")
    all_below_dates = []
    for day in days:
        assets = day.get('assets', {})
        non_bond = {c: a for c, a in assets.items()
                    if c != '931862.CSI' and a.get('momentum') is not None}
        if non_bond and all(not a.get('above_threshold', False) for a in non_bond.values()):
            all_below_dates.append(day['date'])

    # 计算全部低于阈值后 5/10/20 天的策略收益
    nav_df = pd.DataFrame({'date': [d['date'] for d in days],
                           'daily_return': [d['daily_return'] for d in days]})
    nav_df['date'] = pd.to_datetime(nav_df['date'])

    for forward in [5, 10, 20]:
        rets_after = []
        for d in all_below_dates:
            d_ts = pd.Timestamp(d)
            mask = (nav_df['date'] > d_ts)
            future = nav_df[mask].head(forward)
            if len(future) == forward:
                cum_ret = (1 + future['daily_return']).prod() - 1
                rets_after.append(cum_ret)
        if rets_after:
            avg = np.mean(rets_after)
            pos_rate = sum(1 for r in rets_after if r > 0) / len(rets_after)
            print(f"    T+{forward}: 均值={avg:+.4%}, 正收益占比={pos_rate:.1%}, 样本={len(rets_after)}")

    # 持仓数量分布
    print(f"\n  持仓数量分布:")
    holding_counts = defaultdict(int)
    for day in days:
        n = len(day.get('holdings', []))
        holding_counts[n] += 1
    for n in sorted(holding_counts.keys()):
        print(f"    持有 {n} 只资产: {holding_counts[n]} 天 ({holding_counts[n]/total_days*100:.1f}%)")

    return {}


def main():
    print_section("Task 4: 资金管理问题诊断")

    nav = load_nav()
    days = load_detail_days()
    meta = load_detail_meta()

    print(f"  数据期间: {meta['start_date']} ~ {meta['end_date']}")

    results = {}

    # 4.1 止损
    results['stoploss'] = analyze_stoploss(nav, days)

    # 4.2 波动率适配
    results['vol_sizing'] = analyze_volatility_sizing(days, nav)

    # 4.3 现金管理
    results['cash'] = analyze_cash_management(days)

    print_section("Task 4 总结")
    print("  1. 止损机制可减少极端回撤，但频繁止损可能拖累收益")
    print("  2. 高波动期减仓有助于控制回撤，但需要平衡收益损失")
    print("  3. 全部资产低于阈值时强制防御，后续短期收益偏弱")

    return results


if __name__ == '__main__':
    main()
