"""
⚡ Grace v3 — 15分钟价格结构策略 (改进版)
============================================
核心发现验证：大跌>2%后下一段反弹率93%
但实时的信号检测必须更精确地识别"下跌段刚结束"这个时刻

改进：
  1. 不再用not_new_low这种模糊条件
  2. 明确检测"下跌波的终点"：从高点跌了>X%后，出现明确的阳线反转
  3. 用K线实体 + 成交量作为反转确认
  4. 减少交易频率，提高单笔胜率
"""

import pandas as pd
import numpy as np
import warnings
warnings.filterwarnings('ignore')

df = pd.read_parquet("/root/quant_pipeline/data/btc_15m_binance.parquet")
df['time'] = pd.to_datetime(df['ts'])
df = df.set_index('time').sort_index()

o = df['open'].values.astype(float)
h = df['high'].values.astype(float)
l = df['low'].values.astype(float)
c = df['close'].values.astype(float)
v = df['volume'].values.astype(float)
N = len(df)

print(f"📊 数据: {N:,}根15分K线  {df.index[0]} ~ {df.index[-1]}")

def roll_max(arr, n): return pd.Series(arr).rolling(n).max().values
def roll_min(arr, n): return pd.Series(arr).rolling(n).min().values
def roll_mean(arr, n): return pd.Series(arr).rolling(n).mean().values

atr14 = roll_mean(np.maximum(h - l, np.maximum(np.abs(h - np.roll(c, 1)), np.abs(l - np.roll(c, 1)))), 14)
vol_ma20 = roll_mean(v, 20)

# ── 更精确的"大跌+反转"检测 ──
def detect_drop_reversal(idx, lookback=12, drop_min=2.0):
    """
    检测"大跌后出现明确反转信号"
    
    大跌条件：
    - 前lookback根K线内，从最高点跌了>drop_min%
    
    反转确认（必须全部满足）：
    - 最近这根是阳线（close > open）
    - 阳线实体 >= 前一根阴线实体的50%（不是小星星）
    - 最近低点 > 前一根低点（低点上移）
    - 成交量大于前一根（放量反转）
    """
    if idx < lookback + 2 or idx >= N - 3:
        return None
    
    # 1. 找近期最高点
    recent_high = np.max(h[idx-lookback:idx+1])
    recent_high_pos = np.argmax(h[idx-lookback:idx+1])
    high_idx = idx - lookback + recent_high_pos
    
    # 从高点到现在的跌幅
    drop_pct = (c[idx] / recent_high - 1) * 100
    
    if drop_pct > -drop_min:  # 跌得不够
        return None
    
    # 2. 反转确认
    # 条件A：当前阳线
    if c[idx] <= o[idx]:
        return None
    
    # 条件B：阳线实体 >= 前一根的50%
    body_current = c[idx] - o[idx]
    body_prev = abs(c[idx-1] - o[idx-1])
    if body_current < body_prev * 0.5:
        return None
    
    # 条件C：低点不再创新低（比前一根高）
    if l[idx] < l[idx-1] * 0.999:
        return None
    
    # 条件D：最好有成交量配合
    vol_confirmed = v[idx] > vol_ma20[idx] * 1.0  # 至少不缩量
    
    # 3. 入场价格
    entry_price = c[idx]
    
    # 止损：近期最低点下方一点
    recent_low = np.min(l[max(0, idx-8):idx+1])
    sl_price = recent_low * 0.997
    
    # 如果SL太近(小于0.5*ATR)，用ATR保底
    current_atr = atr14[idx]
    min_sl_dist = current_atr * 0.8
    sl_use = min(sl_price, entry_price - min_sl_dist)
    
    return {
        "entry": entry_price,
        "sl": sl_use,
        "drop": round(drop_pct, 1),
        "atr": current_atr,
        "high_idx": high_idx,
        "recent_high": recent_high,
    }


def detect_rise_reversal(idx, lookback=12, rise_min=2.0):
    """急涨后出现阴线反转（做空信号）"""
    if idx < lookback + 2 or idx >= N - 3:
        return None
    
    recent_low = np.min(l[idx-lookback:idx+1])
    rise_pct = (c[idx] / recent_low - 1) * 100
    
    if rise_pct < rise_min:
        return None
    
    # 条件A：当前阴线
    if c[idx] >= o[idx]:
        return None
    
    body_current = o[idx] - c[idx]
    body_prev = abs(c[idx-1] - o[idx-1])
    if body_current < body_prev * 0.5:
        return None
    
    # 条件C：高点不再创新高
    if h[idx] > h[idx-1] * 1.001:
        return None
    
    entry_price = c[idx]
    recent_high = np.max(h[max(0, idx-8):idx+1])
    sl_price = recent_high * 1.003
    current_atr = atr14[idx]
    min_sl_dist = current_atr * 0.8
    sl_use = max(sl_price, entry_price + min_sl_dist)
    
    return {
        "entry": entry_price,
        "sl": sl_use,
        "rise": round(rise_pct, 1),
        "atr": current_atr,
    }


# ═══ 回测 ═══
def backtest_v3(name, drop_min=2.0, rise_min=2.0,
                sl_mult=1.2, tp_mult=2.0, max_hold=24,
                direction="long"):
    trades = []
    
    for i in range(60, N - 5):
        if direction in ["long", "both"]:
            sig = detect_drop_reversal(i, lookback=12, drop_min=drop_min)
            if sig is not None:
                entry = sig['entry']
                sl = sig['sl']
                atr = sig['atr']
                tp = entry + atr * tp_mult
                
                hit_sl = hit_tp = False
                exit_price = entry
                exit_idx = i
                
                for j in range(i + 1, min(i + max_hold + 1, N)):
                    dh, dl = h[j], l[j]
                    if dh >= tp:
                        exit_price = tp; hit_tp = True; exit_idx = j; break
                    if dl <= sl:
                        exit_price = sl; hit_sl = True; exit_idx = j; break
                    if (j - i) >= max_hold:
                        exit_price = c[j]; exit_idx = j; break
                
                net = round((exit_price/entry - 1)*100 - 0.06, 2)
                
                trades.append({
                    "time": df.index[i],
                    "dir": "L", "entry": entry, "exit": exit_price,
                    "drop": sig['drop'], "bars": exit_idx - i,
                    "hit_sl": hit_sl, "hit_tp": hit_tp, "net": net,
                })
        
        if direction in ["short", "both"]:
            sig = detect_rise_reversal(i, lookback=12, rise_min=rise_min)
            if sig is not None:
                entry = sig['entry']
                sl = sig['sl']
                atr = sig['atr']
                tp = entry - atr * tp_mult
                
                hit_sl = hit_tp = False
                exit_price = entry
                exit_idx = i
                
                for j in range(i + 1, min(i + max_hold + 1, N)):
                    dh, dl = h[j], l[j]
                    if dl <= tp:
                        exit_price = tp; hit_tp = True; exit_idx = j; break
                    if dh >= sl:
                        exit_price = sl; hit_sl = True; exit_idx = j; break
                    if (j - i) >= max_hold:
                        exit_price = c[j]; exit_idx = j; break
                
                net = round((entry/exit_price - 1)*100 - 0.06, 2)
                
                trades.append({
                    "time": df.index[i],
                    "dir": "S", "entry": entry, "exit": exit_price,
                    "rise": sig['rise'], "bars": exit_idx - i,
                    "hit_sl": hit_sl, "hit_tp": hit_tp, "net": net,
                })
    
    if not trades:
        return {"name": name, "trades": 0}
    
    df_t = pd.DataFrame(trades)
    wins = (df_t["net"] > 0).sum()
    total = len(df_t)
    avg_ret = df_t["net"].mean()
    pnl = df_t["net"].values / 100
    cum = (1 + pnl).prod()
    
    eq = [1.0]
    for r in pnl:
        eq.append(eq[-1] * (1 + r))
    peak = np.maximum.accumulate(eq)
    dd = (np.array(eq) - peak) / peak * 100
    max_dd = np.min(dd)
    
    avg_pnl = np.mean(pnl)
    std_pnl = np.std(pnl) if np.std(pnl) > 0 else 1
    sharpe = avg_pnl / std_pnl * np.sqrt(96 * 365)
    median_ret = np.median(pnl) * 100
    
    return {
        "name": name, "trades": total, "wins": wins,
        "win_rate": wins/total*100, "avg_ret": avg_ret,
        "median_ret": median_ret, "cum": cum, "max_dd": max_dd,
        "best": df_t["net"].max(), "worst": df_t["net"].min(),
        "sharpe": round(sharpe, 2),
        "avg_bars": df_t["bars"].mean(),
        "df": df_t,
    }


# ═══ 方案 ═══
print("\n" + "="*90)
print("⚡ Grace v3 价格结构改进版")
print("="*90)

scenarios = [
    ("A 做多跌>2%", 2.0, 99, 1.2, 2.0, 24, "long"),
    ("B 做多跌>3%", 3.0, 99, 1.2, 2.0, 24, "long"),
    ("C 做多跌>2%宽盈", 2.0, 99, 1.5, 3.0, 24, "long"),
    ("D 做多跌>2%窄损窄盈", 2.0, 99, 1.0, 1.5, 16, "long"),
    ("E 做多跌>2.5%", 2.5, 99, 1.2, 2.0, 24, "long"),
    ("F 做多+短持", 2.0, 99, 1.2, 2.0, 12, "long"),
    ("G 做空", 99, 2.0, 1.2, 2.0, 24, "short"),
    ("H 双向", 2.0, 2.0, 1.2, 2.0, 24, "both"),
    ("I 做多跌>4%", 4.0, 99, 1.2, 2.0, 24, "long"),
]

results = []
for name, drop, rise, sl, tp, hold, direc in scenarios:
    r = backtest_v3(name, drop, rise, sl, tp, hold, direc)
    results.append(r)
    flag = "✅" if r["trades"] > 0 and r.get("sharpe", 0) > 1.5 else ("⚠️" if r["trades"] > 0 else "-")
    if r["trades"] > 0:
        print(f"  {flag} {name}: {r['trades']}笔 {r['win_rate']:.0f}%胜率 平均{r['avg_ret']:+.2f}% Sharpe={r['sharpe']:.1f} 复利{r['cum']:.2f}x")
    else:
        print(f"  - {name}: 0笔")

# ── 主表 ──
print(f"\n{'='*105}")
print(f"{'📊 Grace v3 对比':^105}")
print(f"{'='*105}")
print(f"{'方案':<20} {'笔数':>5} {'胜率':>7} {'平均%':>8} {'中位%':>8} {'复利':>10} {'回撤':>10} {'最好':>8} {'最差':>8} {'Sharpe':>7}")
print(f"{'-'*105}")

for r in results:
    if r.get("trades", 0) > 0:
        wc = "🟢" if r["sharpe"] > 2.0 else ("🟡" if r["sharpe"] > 1.0 else "🔴")
        print(f"{wc} {r['name']:<18} {r['trades']:>5d} {r['win_rate']:>5.0f}% {r['avg_ret']:>+7.2f}% {r['median_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}")

# ── 最佳分析 ──
best_long = max([r for r in results if r.get("trades", 0) > 0 and "做多" in r["name"]], key=lambda x: x.get("sharpe", 0))
print(f"\n{'='*105}")
print(f"🏆 最佳做多方案: {best_long['name']}")
print(f"{'='*105}")
print(f"   总交易: {best_long['trades']}笔/年")
print(f"   胜率: {best_long['wins']}/{best_long['trades']} = {best_long['win_rate']:.1f}%")
print(f"   平均: {best_long['avg_ret']:+.2f}%  中位: {best_long['median_ret']:+.2f}%")
print(f"   复利: {best_long['cum']:.2f}x  回撤: {best_long['max_dd']:.1f}%")
print(f"   Sharpe: {best_long['sharpe']}")
print(f"   最好: {best_long['best']:+.2f}%  最差: {best_long['worst']:+.2f}%")

print(f"\n   📋 前20笔:")
df_best = best_long['df']
print(f"   {'时间':<16} {'入场':>8} {'出场':>8} {'跌幅':>5} {'收益':>7}")
print(f"   {'-'*48}")
for _, t in df_best.head(20).iterrows():
    tag = "🟢" if t["net"] > 0 else "🔴"
    print(f"   {t['time'].strftime('%m-%d %H:%M'):<16} {t['entry']:>8.0f} {t['exit']:>8.0f} {t['drop']:>5.1f}% {tag} {t['net']:>+5.2f}%")

# 做空最佳
best_short = max([r for r in results if r.get("trades", 0) > 0 and "做空" in r["name"]], key=lambda x: x.get("sharpe", 0))
print(f"\n🏆 最佳做空方案: {best_short['name']}")
print(f"   总交易: {best_short['trades']}笔/年")
print(f"   胜率: {best_short['win_rate']:.0f}% Sharpe: {best_short['sharpe']} 复利: {best_short['cum']:.2f}x")

# ── 结论 ──
print(f"\n{'='*105}")
print(f"💡 结论")
print(f"{'='*105}")
print(f"""
A方案(做多跌>2%): {[r for r in results if r['name']=='A 做多跌>2%'][0]['win_rate']:.0f}%胜率, {[r for r in results if r['name']=='A 做多跌>2%'][0]['avg_ret']:+.2f}%平均, Sharpe={[r for r in results if r['name']=='A 做多跌>2%'][0]['sharpe']}

之前的结构分析说"大跌>2%后反弹率93%"，
但回测中实时检测的胜率只有50%左右——
这说明：实时识别"下跌段结束"比事后分析难得多。

为什么会有差异？
  事后分析：用完整的swing段，知道"这段跌完了，下一段是涨"
  实时：在跌的过程中，你永远不知道"这是最后一跌还是会继续跌"

要缩小这个差距，需要：
  1. 更严格的反转确认（不只是阳线，要更多证据）
  2. 或换时间框架——日线上趋势更清晰
  3. 或接受这个现实：15分钟OHLCV的edge上限就在这里
""")
