"""
Sniper 改进方案对比回测
数据：2017-2024（7.5年）
对比7个方案，包含多时间框架确认、分批进场、尾随止盈
"""

import pandas as pd
import numpy as np

# ── 数据 ──
d = pd.read_parquet("/root/quant_pipeline/data/btc_daily.parquet")

# 构建周线
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()
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)

# 周线指标
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
sma20 = pd.Series(C).rolling(20).mean().values
ret_8w = C / np.roll(C, 8) - 1

# 周线底部信号
weekly_signal = (rsi > 20) & (rsi < 35) & (ret_8w < -0.1) & (C < sma20)
sig_idx = np.where(weekly_signal)[0]

# ── 日线额外指标 ──
d_close = d["c"].values
d_high = d["h"].values
d_low = d["l"].values
d_vol = d["v"].values

# 日线 EMA20
def ema(arr, span):
    s = pd.Series(arr)
    return s.ewm(span=span, adjust=False).mean().values

d_ema20 = ema(d_close, 20)
d_ema50 = ema(d_close, 50)

# 日线 RSI(14)
d_delta = pd.Series(d_close).diff()
d_gain = d_delta.clip(lower=0).rolling(14).mean()
d_loss = (-d_delta.clip(upper=0)).rolling(14).mean()
d_rsi = (100 - 100/(1+d_gain/(d_loss+1e-9))).values

# 日线成交量20日均线
d_vol_ma20 = pd.Series(d_vol).rolling(20).mean().values

# 日线 RSI 底背离检测（价格新低但RSI没新低）
def detect_daily_divergence(price, rsi_arr, lookback=30):
    """检测日线RSI底背离：最近5根内价格新低但RSI没新低"""
    n = len(price)
    div = np.zeros(n, dtype=bool)
    for i in range(lookback, n):
        window_p = price[i-lookback:i+1]
        window_r = rsi_arr[i-lookback:i+1]
        # 当前价格 <= 窗口最低价（创新低）
        if price[i] <= window_p.min() + 1e-6:
            # 检查RSI是否在更高位置
            rsi_at_prev_low = window_r[np.argmin(window_p)]
            if rsi_arr[i] > rsi_at_prev_low + 1:
                div[i] = True
    return div

d_divergence = detect_daily_divergence(d_close, d_rsi)


# ═══ 回测引擎 ═══
def backtest_sniper(
    name,
    sl_pct=0.03, tp_pct=0.06, leverage=10,
    # 过滤条件
    require_daily_above_ema20=False,   # 日线收在20EMA之上才进
    require_daily_divergence=False,     # 日线RSI底背离才进
    require_daily_volume=False,         # 日线放量才进（成交>均量）
    # 分批进场
    scale_in=False,                      # 是否分批
    first_pct=0.3,                       # 第一批比例
    scale_confirm_days=3,               # 确认天数
    # 尾随止盈
    trailing_stop=False,                 # 是否用尾随替代固定TP
    trailing_atr_mult=2.0,               # ATR倍数
    max_hold=120,                        # 最大持仓天数
):
    trades = []
    
    for i in sig_idx:
        entry_price = C[i]
        pos = max(0, np.searchsorted(d.index.values, np.datetime64(dates[i]), side='right') - 1)
        
        # ── 日线过滤检查 ──
        # 看看入场后1-3天内的日线情况
        if require_daily_above_ema20 or require_daily_divergence or require_daily_volume:
            # 检查信号当天到之后3天的日线
            check_ok = False
            for offset in range(0, 4):  # 检查当天和之后3天
                check_idx = pos + offset
                if check_idx >= len(d):
                    break
                
                cond = True
                if require_daily_above_ema20:
                    cond = cond and (d_close[check_idx] > d_ema20[check_idx])
                if require_daily_divergence:
                    cond = cond and d_divergence[check_idx]
                if require_daily_volume:
                    cond = cond and (d_vol[check_idx] > d_vol_ma20[check_idx] * 1.2)
                
                if cond:
                    check_ok = True
                    pos = check_idx  # 实际入场日线位置
                    break
            
            if not check_ok:
                continue  # 跳过这个信号
        
        # ── 分批进场 ──
        if scale_in:
            # 第一批
            pos1 = pos
            # 第二批：等待确认
            pos2 = min(pos1 + scale_confirm_days, len(d)-1)
            # 合并两批的均价
            entry_price_avg = entry_price * first_pct + d_close[pos2] * (1 - first_pct)
            # 实际入场价用加权平均后的价格
            entry_price_used = d_close[pos2]  # 保守起见用第二批的价格
            # 简化：分批 = 平均成本
            entry_price_used = entry_price * first_pct + d_close[pos2] * (1 - first_pct)
            pos = pos2  # 从第二批的位置开始追踪
        else:
            entry_price_used = entry_price
        
        sl_price = entry_price_used * (1 - sl_pct) if sl_pct > 0 else 0
        tp_price = entry_price_used * (1 + tp_pct) if tp_pct > 0 and not trailing_stop else float('inf')
        
        # 尾随止盈：记录最高价
        highest = entry_price_used
        
        hit_sl = hit_tp = False
        exit_price = entry_price_used
        exit_idx = pos
        
        for j in range(pos + 1, min(pos + max_hold + 1, len(d))):
            bars = j - pos
            day_high = d_high[j]
            day_low = d_low[j]
            day_close = d_close[j]
            
            if trailing_stop:
                # 更新最高价
                highest = max(highest, day_high)
                # 尾随止损 = 最高价回撤 trailing_atr_mult * ATR
                # 简单版本：从最高回撤固定百分比
                trail_pct = sl_pct * 1.5  # 尾随止损比固定止损宽一点
                trail_level = highest * (1 - trail_pct)
                
                if day_low <= trail_level:
                    exit_price = trail_level
                    hit_sl = True
                    exit_idx = j
                    break
                elif day_low <= sl_price:
                    # 初始止损也生效
                    exit_price = sl_price
                    hit_sl = True
                    exit_idx = j
                    break
                elif bars >= 60:  # 最长持60天≈12周
                    exit_price = day_close
                    exit_idx = j
                    break
            else:
                # 标准SL/TP逻辑
                if tp_pct > 0 and sl_pct > 0 and day_high >= tp_price and day_low <= sl_price:
                    exit_price = entry_price_used
                    hit_tp = True
                    exit_idx = j
                    break
                elif tp_pct > 0 and day_high >= tp_price:
                    exit_price = tp_price
                    hit_tp = True
                    exit_idx = j
                    break
                elif sl_pct > 0 and day_low <= sl_price:
                    exit_price = sl_price
                    hit_sl = True
                    exit_idx = j
                    break
                
                if bars >= 120:
                    exit_price = day_close
                    exit_idx = j
                    break
        else:
            exit_price = d_close[min(pos + 120, len(d)-1)]
            exit_idx = min(pos + 120, len(d)-1)
        
        ret_pct = (exit_price / entry_price_used - 1) * leverage
        fee = (entry_price_used + exit_price) / entry_price_used * 0.0005 * leverage
        net_ret = ret_pct - fee
        
        trades.append({
            "entry": pd.Timestamp(dates[i]).date(),
            "exit": d.index[exit_idx].date(),
            "entry_px": entry_price_used,
            "rsi": rsi[i],
            "bars": bars,
            "hit_sl": hit_sl,
            "hit_tp": hit_tp,
            "net": round(net_ret * 100, 2),
        })
    
    if not trades:
        return {"name": name, "trades": 0}
    
    df = pd.DataFrame(trades)
    wins = (df["net"] > 0).sum()
    total = len(df)
    avg_ret = df["net"].mean()
    cum = (1 + df["net"] / 100).prod()
    
    eq = [(1 + df.iloc[:k+1]["net"] / 100).prod() for k in range(len(df))]
    peak = np.maximum.accumulate(eq)
    dd = [(eq[k] - peak[k]) / peak[k] * 100 for k in range(len(df))]
    max_dd = min(dd) if dd else 0
    
    sharpe = (avg_ret / df["net"].std() * np.sqrt(total)) if df["net"].std() > 0 else 0
    
    return {
        "name": name,
        "trades": total,
        "wins": wins,
        "win_rate": wins / total * 100,
        "avg_ret": avg_ret,
        "cum": cum,
        "max_dd": max_dd,
        "best": df["net"].max(),
        "worst": df["net"].min(),
        "sharpe": sharpe,
        "sl_hit": df["hit_sl"].sum(),
        "tp_hit": df["hit_tp"].sum(),
    }


# ═══ 方案定义 ═══
scenarios = [
    # 名称, sl, tp, lev, daily_ema, daily_div, daily_vol, scale_in, trail
    ("A 当前bot", 0.03, 0.06, 10, False, False, False, False, False),
    ("B 宽止损", 0.10, 0.20, 4, False, False, False, False, False),
    ("C 日EMA20过滤", 0.03, 0.06, 10, True, False, False, False, False),
    ("D 日底背离过滤", 0.03, 0.06, 10, False, True, False, False, False),
    ("E 放量过滤", 0.03, 0.06, 10, False, False, True, False, False),
    ("F 日EMA20+尾随止盈", 0.03, 0.06, 10, True, False, False, False, True),
    ("G 日底背离+宽止损", 0.10, 0.20, 4, False, True, False, False, False),
    ("H 分批进场", 0.03, 0.06, 10, False, False, False, True, False),
    ("I EMA20+底背离+尾随", 0.05, 0.06, 8, True, True, False, False, True),
    ("J 全叠满", 0.05, 0.06, 6, True, True, True, False, True),
]

results = []
for name, sl, tp, lev, ema_f, div_f, vol_f, scale, trail in scenarios:
    r = backtest_sniper(
        name=name, sl_pct=sl, tp_pct=tp, leverage=lev,
        require_daily_above_ema20=ema_f,
        require_daily_divergence=div_f,
        require_daily_volume=vol_f,
        scale_in=scale,
        trailing_stop=trail,
    )
    results.append(r)
    print(f"  ✅ {name}")

# ── 打印主表 ──
print(f"\n{'='*90}")
print(f"{'📊 SNIPER 改进方案对比':^90}")
print(f"{'='*90}")
print(f"{'方案':<24} {'笔数':>5} {'胜率':>7} {'平均':>8} {'复利净值':>10} {'最大回撤':>10} {'最好':>8} {'最差':>8} {'Sharpe':>7}")
print(f"{'-'*90}")

for r in results:
    if r["trades"] > 0:
        wr_color = "🟢" if r["win_rate"] > 40 else ("🟡" if r["win_rate"] > 25 else "🔴")
        print(f"{wr_color} {r['name']:<22} {r['trades']:>5d} {r['win_rate']:>5.0f}% {r['avg_ret']:>+7.2f}% {r['cum']:>8.2f}x {r['max_dd']:>+8.1f}% {r['best']:>+7.2f}% {r['worst']:>+7.2f}% {r['sharpe']:>6.1f}")

# ── 推荐方案深度分析 ──
print(f"\n{'='*90}")
print(f"💡 推荐的方案深度分析：I (EMA20+底背离+尾随止盈)")
print(f"{'='*90}")

r_rec = [r for r in results if r["name"] == "I EMA20+底背离+尾随"][0]
print(f"""
🔍 方案 I 详情:
   {r_rec['trades']}笔交易, 胜率{r_rec['win_rate']:.0f}%
   平均收益: {r_rec['avg_ret']:+.2f}%
   复利净值: {r_rec['cum']:.2f}x
   最大回撤: {r_rec['max_dd']:.1f}%
   Sharpe: {r_rec['sharpe']:.1f}
   SL被扫: {r_rec['sl_hit']}次 / TP被扫: {r_rec['tp_hit']}次
""")

print(f"📋 逐笔交易:")
r_trades = backtest_sniper("I", sl_pct=0.05, tp_pct=0.06, leverage=8,
    require_daily_above_ema20=True, require_daily_divergence=True, require_daily_volume=False,
    scale_in=False, trailing_stop=True)

# Re-run to get trade details
def get_trades_detailed():
    trades = []
    for i in sig_idx:
        entry_price = C[i]
        pos = max(0, np.searchsorted(d.index.values, np.datetime64(dates[i]), side='right') - 1)
        
        # 日线过滤
        check_ok = False
        actual_pos = pos
        for offset in range(0, 4):
            check_idx = pos + offset
            if check_idx >= len(d): break
            cond = True
            if d_close[check_idx] > d_ema20[check_idx] and d_divergence[check_idx]:
                cond = True
            else:
                cond = False
            if cond:
                check_ok = True
                actual_pos = check_idx
                break
        
        if not check_ok:
            continue
        
        entry_price_used = entry_price
        sl_price = entry_price_used * 0.95  # 5% SL
        highest = entry_price_used
        
        hit_sl = hit_tp = False
        exit_price = entry_price_used
        exit_idx = actual_pos
        trail_pct = 0.075  # 7.5% trailing
        
        for j in range(actual_pos + 1, min(actual_pos + 121, len(d))):
            bars = j - actual_pos
            day_high = d_high[j]
            day_low = d_low[j]
            day_close = d_close[j]
            
            highest = max(highest, day_high)
            trail_level = highest * (1 - trail_pct)
            
            if day_low <= trail_level:
                exit_price = trail_level
                hit_sl = True
                exit_idx = j
                break
            elif day_low <= sl_price:
                exit_price = sl_price
                hit_sl = True
                exit_idx = j
                break
            elif bars >= 60:
                exit_price = day_close
                exit_idx = j
                break
        else:
            exit_price = d_close[min(actual_pos + 60, len(d)-1)]
            exit_idx = min(actual_pos + 60, len(d)-1)
        
        ret_pct = (exit_price / entry_price_used - 1) * 8
        fee = (entry_price_used + exit_price) / entry_price_used * 0.0005 * 8
        net_ret = ret_pct - fee
        
        trades.append({
            "entry": pd.Timestamp(dates[i]).date(),
            "exit": d.index[exit_idx].date(),
            "entry_px": entry_price_used,
            "exit_px": exit_price,
            "rsi": rsi[i],
            "bars": exit_idx - actual_pos,
            "hit_sl": hit_sl,
            "net": round(net_ret * 100, 2),
        })
    
    return trades

detailed = get_trades_detailed()
if detailed:
    df_d = pd.DataFrame(detailed)
    print(f"{'入场日期':<14} {'出场日期':<14} {'入场价':>8} {'出场价':>8} {'RSI':>5} {'持仓':>4} {'净收益':>8}")
    print("-" * 62)
    for _, t in df_d.iterrows():
        tag = "⚠️" if t["hit_sl"] else "✅"
        print(f"{str(t['entry']):<14} {str(t['exit']):<14} {t['entry_px']:>8.0f} {t['exit_px']:>8.0f} {t['rsi']:>5.1f} {t['bars']:>3d}d {tag} {t['net']:>+7.2f}%")
    
    print(f"\n📊 按月统计:")
    df_d["month"] = df_d["entry"].astype(str).str[:7]
    for m, grp in df_d.groupby("month"):
        mw = (grp["net"] > 0).sum()
        print(f"  {m}: {len(grp)}笔 {mw}/{len(grp)}胜 平均{grp['net'].mean():+.1f}%")
