"""
Sniper 反身性+微观结构改进版
核心思路：
1. 反身性：熊市下跌会自我强化，直到成交量枯竭才说明卖压耗尽
2. 微观结构：流动性枯竭时止损要宽，否则必被扫
3. 改进：信号出现后等"成交量确认"——价格新低但量萎缩 = 反身性耗尽
"""

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)

C = weekly["c"].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
d_ema20 = pd.Series(d_close).ewm(span=20, adjust=False).mean().values
d_vol_ma20 = pd.Series(d_vol).rolling(20).mean().values  # 20日均量

# ── 反身性确认信号 ──
def reflexivity_exhausted(pos, lookback=20):
    """
    检查反身性是否耗尽：
    条件1：当前价格接近或低于lookback内最低价（仍然在低位）
    条件2：但成交量显著低于近期均值（卖压在枯竭）
    条件3：价格在放量下跌后出现缩量企稳
    """
    if pos < lookback:
        return False
    
    vol_window = d_vol[pos-lookback:pos+1]
    close_window = d_close[pos-lookback:pos+1]
    
    current_vol = d_vol[pos]
    vol_mean = np.mean(vol_window[:-1])  # 去掉当前
    vol_ratio = current_vol / vol_mean
    
    # 条件：成交量萎缩到均量的70%以下（卖压在枯竭）
    vol_exhaust = vol_ratio < 0.7
    
    # 条件：最近5天出现过放量下跌（恐慌抛售），然后缩量
    panic_day = False
    for i in range(max(0, pos-10), pos+1):
        if d_close[i] < d_close[i-1] and d_vol[i] > d_vol_ma20[i] * 1.5:
            panic_day = True
    
    # 条件：价格在低位但成交量持续缩小（accumulation信号）
    recent_vols = d_vol[max(0, pos-5):pos+1]
    vol_declining = len(recent_vols) > 3 and np.mean(recent_vols[-3:]) < np.mean(recent_vols[:3]) * 0.85
    
    return vol_exhaust or vol_declining

def selling_climax(pos, lookback=30):
    """
    检测"恐慌性抛售"（selling climax）：
    特征：放巨量+大阴线，之后缩量企稳
    这是 Wyckoff 理论中的经典底部信号
    """
    if pos < lookback:
        return False
    
    vol_window = d_vol[pos-lookback:pos+1]
    vol_mean = np.mean(vol_window[:-1])
    vol_std = np.std(vol_window[:-1])
    
    # 最近5天是否有放量下跌日
    for i in range(max(0, pos-5), pos+1):
        if i < 1: continue
        ret = d_close[i] / d_close[i-1] - 1
        if ret < -0.03 and d_vol[i] > vol_mean + 2 * vol_std:
            # 之后缩量企稳
            later_vols = d_vol[i:pos+1]
            if len(later_vols) > 1 and np.mean(later_vols[1:]) < vol_mean * 0.8:
                return True
    
    return False

# ═══ 回测引擎 ═══
def backtest_reflexivity(name, sl_pct, tp_pct, leverage, 
                         use_volume_exhaust=False,
                         use_climax=False,
                         use_ema=False,
                         wide_stop=False,
                         trailing=False,
                         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)
        
        # 确认过滤
        confirmed = True
        actual_pos = pos
        
        if use_volume_exhaust:
            conf = reflexivity_exhausted(pos)
            if not conf:
                # 往后找3天，看是否有确认
                for offset in range(1, 4):
                    ci = pos + offset
                    if ci >= len(d): break
                    if reflexivity_exhausted(ci):
                        conf = True
                        actual_pos = ci
                        break
            if not conf:
                continue
        
        if use_climax:
            if not selling_climax(actual_pos):
                continue
        
        if use_ema:
            if d_close[actual_pos] <= d_ema20[actual_pos]:
                continue
        
        entry_used = entry_price
        
        # 根据波动率动态调整止损
        if wide_stop:
            # 计算近期日线ATR
            recent_high = np.max(d_high[max(0, actual_pos-20):actual_pos+1])
            recent_low = np.min(d_low[max(0, actual_pos-20):actual_pos+1])
            atr_pct = (recent_high - recent_low) / entry_used
            # 动态止损 = 1.5x ATR，但至少8%
            dyn_sl = max(sl_pct, atr_pct * 1.5)
        else:
            dyn_sl = sl_pct
        
        sl_price = entry_used * (1 - dyn_sl)
        tp_price = entry_used * (1 + tp_pct)
        highest = entry_used
        
        hit_sl = hit_tp = False
        exit_price = entry_used
        exit_idx = actual_pos
        
        for j in range(actual_pos + 1, min(actual_pos + max_hold + 1, len(d))):
            bars = j - actual_pos
            dh, dl = d_high[j], d_low[j]
            
            if trailing:
                highest = max(highest, dh)
                trail_level = highest * (1 - dyn_sl * 0.7)
                if dl <= trail_level:
                    exit_price = trail_level; hit_sl = True; exit_idx = j; break
                if bars >= 60:
                    exit_price = d_close[j]; exit_idx = j; break
            else:
                if dh >= tp_price and dl <= sl_price:
                    exit_price = entry_used; hit_tp = True; exit_idx = j; break
                elif dh >= tp_price:
                    exit_price = tp_price; hit_tp = True; exit_idx = j; break
                elif dl <= sl_price:
                    exit_price = sl_price; hit_sl = True; exit_idx = j; break
                if bars >= max_hold:
                    exit_price = d_close[j]; exit_idx = j; break
        else:
            exit_price = d_close[min(actual_pos + max_hold, len(d)-1)]
            exit_idx = min(actual_pos + max_hold, len(d)-1)
        
        ret = (exit_price / entry_used - 1) * leverage
        fee = (entry_used + exit_price) / entry_used * 0.0005 * leverage
        net = round((ret - fee) * 100, 2)
        
        trades.append({
            "entry": pd.Timestamp(dates[i]).date(),
            "exit": d.index[exit_idx].date(),
            "entry_px": entry_used, "rsi": rsi[i],
            "bars": exit_idx - actual_pos,
            "hit_sl": hit_sl, "hit_tp": hit_tp,
            "net": net,
        })
    
    if not trades:
        return {"name": name, "trades": 0}
    
    df = pd.DataFrame(trades)
    wins = (df["net"] > 0).sum()
    total = len(df)
    avg = 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)
    sharpe = (avg / 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,
        "cum": cum, "max_dd": max_dd,
        "best": df["net"].max(), "worst": df["net"].min(),
        "sharpe": sharpe,
        "df": df,
    }

# ═══ 方案对比 ═══
scenarios = [
    # 基底
    ("A 原版bot", 0.03, 0.06, 10, False, False, False, False, False),
    ("B EMA20过滤", 0.03, 0.06, 10, True, False, False, False, False),
    
    # 反身性相关
    ("C 量枯竭过滤", 0.03, 0.06, 10, False, True, False, False, False),
    ("D 恐慌抛售过滤", 0.03, 0.06, 10, False, False, True, False, False),
    
    # 组合过滤
    ("E EMA20+量枯竭", 0.03, 0.06, 10, True, True, False, False, False),
    ("F 量枯竭+宽动态止损", 0.05, 0.06, 8, False, True, False, True, False),
    
    # 尾随止盈版
    ("G 量枯竭+动态损+尾随", 0.05, 0.06, 6, False, True, False, True, True),
    
    # 全叠满
    ("H 全叠(best guess)", 0.05, 0.06, 6, True, True, True, True, True),
]

results = []
print("运行回测...")
for name, sl, tp, lev, ema, vol, cli, wide, trail in scenarios:
    r = backtest_reflexivity(name, sl, tp, lev, vol, cli, ema, wide, trail)
    results.append(r)
    rr = "✅" if r["trades"] > 0 else "⏭️"
    print(f"  {rr} {name} ({r['trades']}笔)")

# ── 主表 ──
print(f"\n{'='*95}")
print(f"{'📊 SNIPER 反身性+微观结构改进':^95}")
print(f"{'='*95}")
print(f"{'方案':<26} {'笔数':>5} {'胜率':>7} {'平均':>8} {'复利':>10} {'最大回撤':>10} {'最好':>8} {'最差':>8} {'Sharpe':>7}")
print(f"{'-'*95}")

for r in results:
    if r["trades"] > 0:
        wc = "🟢" if r["win_rate"] > 40 else ("🟡" if r["win_rate"] > 25 else "🔴")
        print(f"{wc} {r['name']:<24} {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{'='*95}")
print(f"📋 最佳方案逐笔: C (量枯竭过滤, 10x, SL=3% TP=6%)")
print(f"{'='*95}")

for r in results:
    if r["name"] == "C 量枯竭过滤" and r["trades"] > 0:
        df = r["df"]
        print(f"{'入场日期':<14} {'出场日期':<14} {'入场价':>8} {'RSI':>5} {'持仓':>4} {'收益':>8}")
        print("-" * 60)
        for _, t in df.iterrows():
            tag = "⚠️" if t["hit_sl"] else "✅"
            print(f"{str(t['entry']):<14} {str(t['exit']):<14} {t['entry_px']:>8.0f} {t['rsi']:>5.1f} {t['bars']:>3d}d {tag} {t['net']:>+7.2f}%")

# ── 方案B和方案C对比 ──
print(f"\n{'='*95}")
print(f"📋 F方案逐笔 (量枯竭+动态止损, 8x, SL=5%)")
print(f"{'='*95}")

for r in results:
    if r["name"] == "F 量枯竭+宽动态止损" and r["trades"] > 0:
        df = r["df"]
        print(f"{'入场日期':<14} {'出场日期':<14} {'入场价':>8} {'RSI':>5} {'持仓':>4} {'收益':>8}")
        print("-" * 60)
        for _, t in df.iterrows():
            tag = "⚠️" if t["hit_sl"] else "✅"
            print(f"{str(t['entry']):<14} {str(t['exit']):<14} {t['entry_px']:>8.0f} {t['rsi']:>5.1f} {t['bars']:>3d}d {tag} {t['net']:>+7.2f}%")

# ── 核心结论 ──
print(f"\n{'='*95}")
print(f"💡 从理论到实践的结论")
print(f"{'='*95}")
print(f"""
从反身性理论看Sniper的困境：

1️⃣ 反身性本质：价格↓→恐慌↑→更多人卖→价格更↓
   周线RSI<35 + 低于20周线 → 这只是"已经跌了很多"
   但不代表"卖完了"
   
2️⃣ 成交量枯竭 = 反身性耗尽
   方案C（量枯竭过滤）的信号逻辑：
   - 价格在低位 + 成交量萎缩到均量70%以下
   - 说明卖压已经枯竭，剩下还在卖的人少了
   - 这时候才真正到了"谁还必须买？"的问题
   
3️⃣ 动态止损 = 匹配微观结构
   做市商在波动大时拉宽价差，散户止损也应该拉宽
   固定3%止损在日线波动>5%的环境下就是白送

4️⃣ 最佳方案建议：方案C 量枯竭过滤
   用最简单的指标（成交量）来识别反身性的转折点
   比EMA20更贴近市场本质
""")
