"""
🔥 烛龙 Sniper 完整回测
策略：RSI>20 + RSI<35 + 前8周跌超10% + 低于20周线 → 周线底狙击
持仓管理：10x杠杆, SL=3%, TP=6%（OCO）
逐日模拟止盈止损命中
"""

import pandas as pd
import numpy as np

# ── 数据加载 ──
d = pd.read_parquet("/root/quant_pipeline/data/btc_daily.parquet")
print(f"日线数据: {len(d)}根  {d.index[0].date()} ~ {d.index[-1].date()}")

# ── 构建周线（跟 bot 一致）──
daily = d.copy()
daily["week_label"] = daily.index.isocalendar().week.astype(str) + "_" + daily.index.year.astype(str)
weekly = daily.groupby("week_label").agg({
    "o": "first", "h": "max", "l": "min", "c": "last", "v": "sum"
}).reset_index()

# 按时间排序
# 从 week_label 提取年份和周数排序
weekly["year"] = weekly["week_label"].str.split("_").str[1].astype(int)
weekly["week_num"] = weekly["week_label"].str.split("_").str[0].astype(int)
weekly = weekly.sort_values(["year", "week_num"]).reset_index(drop=True)

# 构建日期索引（用每周的最后一个交易日）
# 从日线里找出每周最后一天
last_days = daily.groupby("week_label").apply(lambda x: x.index[-1])
weekly["date"] = weekly["week_label"].map(last_days)

O = weekly["o"].values.astype(float)
H = weekly["h"].values.astype(float)
L = weekly["l"].values.astype(float)
C = weekly["c"].values.astype(float)
V = weekly["v"].values.astype(float)
dates = weekly["date"].values
n = len(weekly)

print(f"周线: {n}根  {dates[0]} ~ {dates[-1]}")

# ── 指标计算 ──
# RSI(14) - 周线
delta = pd.Series(C).diff()
gain = delta.clip(lower=0).rolling(14).mean()
loss = (-delta.clip(upper=0)).rolling(14).mean()
rsi = (100 - 100 / (1 + gain / (loss + 1e-9))).values

# 20周均线
sma20 = pd.Series(C).rolling(20).mean().values

# 前8周跌幅
ret_8w = C / np.roll(C, 8) - 1

# ── 信号定义（跟 sniper_bot.py 完全一致）──
bottom = (rsi > 20) & (rsi < 35) & (ret_8w < -0.1) & (C < sma20)
signal_indices = np.where(bottom)[0]

print(f"\n{'='*65}")
print(f"📊 信号统计")
print(f"{'='*65}")
print(f"底部信号总次数: {len(signal_indices)}")

# 列出所有信号
print(f"\n📋 全部信号明细:")
for i in signal_indices:
    print(f"  {dates[i]}: 价格=${C[i]:.0f}  RSI={rsi[i]:.1f}  MA20=${sma20[i]:.0f}  8周跌幅={ret_8w[i]*100:.1f}%")

# ═══ 逐笔交易模拟 ═══
# 入场后逐日模拟：先碰 SL 还是先碰 TP

LEVERAGE = 10
SL_PCT = 0.03
TP_PCT = 0.06

trades = []
daily_idx = range(len(d))

for sig_i in signal_indices:
    entry_week_date = dates[sig_i]
    entry_price = C[sig_i]
    
    # 找入场日期后的日线数据
    # 找到 <= entry_week_date 的最后一个日线索引
    pos = np.searchsorted(daily.index.values, np.datetime64(entry_week_date), side='right') - 1
    entry_daily_idx = max(0, pos)
    
    sl_price = entry_price * (1 - SL_PCT)  # -3%
    tp_price = entry_price * (1 + TP_PCT)  # +6%
    
    # 逐根日线模拟（最多60个交易日 ≈ 12周）
    hit_sl = False
    hit_tp = False
    exit_date = None
    exit_price = None
    bars_held = 0
    highest = entry_price
    lowest = entry_price
    
    for j in range(entry_daily_idx + 1, min(entry_daily_idx + 61, len(daily))):
        bars_held += 1
        day_high = daily.iloc[j]["h"]
        day_low = daily.iloc[j]["l"]
        day_close = daily.iloc[j]["c"]
        
        highest = max(highest, day_high)
        lowest = min(lowest, day_low)
        
        # 日内先碰 SL 还是先碰 TP？
        # 用日线高低点判断：如果日内最高 >= TP 且 最低 <= SL，看哪个先碰
        # 简化：如果一天内两个都碰了，算平出（0%）
        if day_high >= tp_price and day_low <= sl_price:
            hit_tp = True  # 同时触碰，按打平处理
            exit_date = daily.index[j]
            exit_price = entry_price  # 打平
            break
        elif day_high >= tp_price:
            hit_tp = True
            exit_date = daily.index[j]
            exit_price = tp_price
            break
        elif day_low <= sl_price:
            hit_sl = True
            exit_date = daily.index[j]
            exit_price = sl_price
            break
    
    if not hit_sl and not hit_tp:
        # 60个交易日都没碰到，以最后收盘价退（概率极低）
        exit_date = daily.index[min(entry_daily_idx + 60, len(daily)-1)]
        exit_price = daily.iloc[min(entry_daily_idx + 60, len(daily)-1)]["c"]
        bars_held = 60
    
    # 计算收益（含10x杠杆）
    ret_pct = (exit_price / entry_price - 1) * LEVERAGE
    
    # 手续费（两次：开+平，各0.05%）
    fee = (entry_price * 0.0005 + exit_price * 0.0005) / entry_price * LEVERAGE
    
    net_ret = ret_pct - fee
    
    trades.append({
        "entry_date": str(pd.Timestamp(entry_week_date).date()),
        "exit_date": str(pd.Timestamp(exit_date).date()),
        "entry_price": entry_price,
        "exit_price": exit_price,
        "sl_price": sl_price,
        "tp_price": tp_price,
        "highest": highest,
        "lowest": lowest,
        "rsi": rsi[sig_i],
        "ma20": sma20[sig_i],
        "ret_8w_pct": ret_8w[sig_i] * 100,
        "bars_held": bars_held,
        "hit_sl": hit_sl,
        "hit_tp": hit_tp,
        "gross_ret_pct": round(ret_pct * 100, 2),
        "fee_pct": round(fee * 100, 2),
        "net_ret_pct": round(net_ret * 100, 2),
    })

df_trades = pd.DataFrame(trades)

print(f"\n{'='*65}")
print(f"🔥 回测结果 | {LEVERAGE}x杠杆 SL={SL_PCT*100:.0f}% TP={TP_PCT*100:.0f}%")
print(f"{'='*65}")
print(f"总交易次数: {len(df_trades)}")
if len(df_trades) > 0:
    wins = (df_trades["net_ret_pct"] > 0).sum()
    losses = (df_trades["net_ret_pct"] <= 0).sum()
    win_rate = wins / len(df_trades) * 100
    avg_ret = df_trades["net_ret_pct"].mean()
    total_ret = df_trades["net_ret_pct"].sum()
    best = df_trades["net_ret_pct"].max()
    worst = df_trades["net_ret_pct"].min()
    
    # 复利计算
    cum = 1.0
    equity_curve = [1.0]
    for r in df_trades["net_ret_pct"]:
        cum *= (1 + r / 100)
        equity_curve.append(cum)
    
    # 最大回撤
    peak = np.maximum.accumulate(equity_curve)
    drawdown = (equity_curve - peak) / peak * 100
    max_dd = drawdown.min()
    
    print(f"胜  率: {wins}/{wins + losses} = {win_rate:.1f}%")
    print(f"平均收益: {avg_ret:+.2f}%")
    print(f"累计收益: {total_ret:+.2f}%（简单加总）")
    print(f"复利净值: {cum:.3f}x （{((cum-1)*100):.0f}%）")
    print(f"最好单笔: {best:+.2f}%")
    print(f"最差单笔: {worst:+.2f}%")
    print(f"最大回撤: {max_dd:.1f}%")
    
    print(f"\n📈 逐笔明细:")
    print(f"{'入场日期':<14} {'出场日期':<14} {'入场价':>8} {'出场价':>8} {'SL':>8} {'TP':>8} {'持仓日':>6} {'净收益':>8}")
    print("-" * 72)
    for _, t in df_trades.iterrows():
        sl_tag = "⚠️" if t["hit_sl"] else " "
        tp_tag = "✅" if t["hit_tp"] else " "
        tag = f"{tp_tag}{sl_tag}"
        print(f"{t['entry_date']:<14} {t['exit_date']:<14} {t['entry_price']:>8.0f} {t['exit_price']:>8.0f} {t['sl_price']:>8.0f} {t['tp_price']:>8.0f} {t['bars_held']:>4d}d {tag} {t['net_ret_pct']:>+7.2f}%")
    
    print(f"\n📊 胜率按时间段:")
    # 按年份分组
    df_trades["year"] = df_trades["entry_date"].str[:4]
    for yr, grp in df_trades.groupby("year"):
        yr_wins = (grp["net_ret_pct"] > 0).sum()
        yr_total = len(grp)
        yr_avg = grp["net_ret_pct"].mean()
        print(f"  {yr}: {yr_wins}/{yr_total}笔 {yr_wins/yr_total*100:.0f}% 平均{yr_avg:+.2f}%")
    
    print(f"\n💡 信号触发后多久出结果:")
    print(f"  平均持仓: {df_trades['bars_held'].mean():.0f}天")
    print(f"  最短: {df_trades['bars_held'].min()}天  最长: {df_trades['bars_held'].max()}天")
    print(f"  SL被扫: {(df_trades['hit_sl']).sum()}次 ({((df_trades['hit_sl']).sum()/len(df_trades)*100):.0f}%)")
    print(f"  TP被扫: {(df_trades['hit_tp']).sum()}次 ({((df_trades['hit_tp']).sum()/len(df_trades)*100):.0f}%)")
    print(f"  超时退出: {(~(df_trades['hit_sl'] | df_trades['hit_tp'])).sum()}次")

else:
    print("无交易信号")

# ── 额外分析：如果调整 SL 和 TP ──
print(f"\n{'='*65}")
print(f"🔄 参数敏感性分析")
print(f"{'='*65}")

for sl_test in [0.02, 0.03, 0.04, 0.05]:
    for tp_test in [0.04, 0.06, 0.08, 0.10]:
        test_trades = []
        for sig_i in signal_indices:
            entry_price = C[sig_i]
            entry_daily_idx = max(0, np.searchsorted(daily.index.values, np.datetime64(dates[sig_i]), side='right') - 1)
            
            sl_p = entry_price * (1 - sl_test)
            tp_p = entry_price * (1 + tp_test)
            
            hit_sl = hit_tp = False
            exit_price = entry_price
            
            for j in range(entry_daily_idx + 1, min(entry_daily_idx + 61, len(daily))):
                day_high = daily.iloc[j]["h"]
                day_low = daily.iloc[j]["l"]
                
                if day_high >= tp_p and day_low <= sl_p:
                    exit_price = entry_price
                    hit_tp = True
                    break
                elif day_high >= tp_p:
                    exit_price = tp_p
                    hit_tp = True
                    break
                elif day_low <= sl_p:
                    exit_price = sl_p
                    hit_sl = True
                    break
            
            if not hit_sl and not hit_tp:
                exit_price = daily.iloc[min(entry_daily_idx + 60, len(daily)-1)]["c"]
            
            ret_pct = (exit_price / entry_price - 1) * 10  # 10x
            fee = (entry_price * 0.0005 + exit_price * 0.0005) / entry_price * 10
            net_ret = ret_pct - fee
            test_trades.append(net_ret * 100)
        
        if test_trades:
            wins = sum(1 for r in test_trades if r > 0)
            wr = wins / len(test_trades) * 100
            avg = np.mean(test_trades)
            total = sum(test_trades)
            print(f"  SL={sl_test*100:.0f}% TP={tp_test*100:.0f}% | {len(test_trades)}笔 胜率{wr:.0f}% 平均{avg:+.1f}% 合计{total:+.0f}%")

print(f"\n{'='*65}")
print(f"📋 原始信号条件 vs RSI<30（旧方案）对比")
print(f"{'='*65}")

# 旧版 RSI<30
old_bottom = (rsi < 30) & (ret_8w < -0.1) & (C < sma20)
old_indices = np.where(old_bottom)[0]
# 当前版 RSI>20 & <35
new_indices = signal_indices

# 对比
old_set = set(old_indices)
new_set = set(new_indices)
print(f"  旧版(RSI<30): {len(old_indices)}次信号")
print(f"  新版(RSI>20&<35): {len(new_indices)}次信号")
print(f"  新增信号: {len(new_set - old_set)}次")
print(f"  过滤掉的(极低RSI): {len(old_set - new_set)}次")
