"""Strategia 13: Squeeze + ML ibrida.
Squeeze breakout come PRE-FILTRO (quando tradare),
ML come CONFERMA DIREZIONALE (quale direzione).

Pipeline:
1. Rileva squeeze release (Bollinger esce da Keltner)
2. Estrai features frattali/strutturali dalla finestra
3. ML predice direzione con confidenza
4. Trade SOLO se squeeze + ML concordano

Obiettivo: accuracy squeeze (>80%) + volume trade ML.
"""
from __future__ import annotations
import sys
sys.path.insert(0, ".")

import numpy as np
import pandas as pd
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.preprocessing import StandardScaler
from src.data.downloader import load_data
from src.fractal.patterns import encode_candles

FEE = 0.001
INITIAL = 1000


def keltner_ratio(close, high, low, window=20):
    n = len(close)
    result = np.full(n, np.nan)
    for i in range(window, n):
        wc = close[i-window:i]
        wh = high[i-window:i]
        wl = low[i-window:i]
        ma = np.mean(wc)
        bb_std = np.std(wc)
        tr = np.maximum(wh - wl, np.maximum(np.abs(wh - np.roll(wc, 1)), np.abs(wl - np.roll(wc, 1))))
        atr = np.mean(tr[1:])
        kc_r = (ma + 1.5*atr) - (ma - 1.5*atr)
        bb_r = (ma + 2*bb_std) - (ma - 2*bb_std)
        if kc_r > 0:
            result[i] = bb_r / kc_r
    return result


def detect_squeeze_releases(close, high, low, volume, bb_w, sq_thr, min_duration=5):
    kcr = keltner_ratio(close, high, low, bb_w)
    events = []
    in_sq = False
    sq_start = 0

    for i in range(bb_w + 1, len(close)):
        if np.isnan(kcr[i]):
            continue
        is_sq = kcr[i] < sq_thr
        if is_sq and not in_sq:
            in_sq = True
            sq_start = i
        elif not is_sq and in_sq:
            in_sq = False
            duration = i - sq_start
            if duration < min_duration:
                continue

            avg_vol = np.mean(volume[sq_start:i])
            events.append({
                "idx": i,
                "squeeze_start": sq_start,
                "duration": duration,
                "avg_vol_squeeze": avg_vol,
                "kcr_at_release": kcr[i],
            })
    return events


def build_features_at(df, i, squeeze_info):
    """Features per il punto di squeeze release."""
    if i < 100:
        return None

    o = df["open"].values
    h = df["high"].values
    l = df["low"].values
    c = df["close"].values
    v = df["volume"].values

    feats = []

    # Structural features multi-window
    for w in [12, 24, 48]:
        win_c = c[i-w:i]
        win_o = o[i-w:i]
        win_h = h[i-w:i]
        win_l = l[i-w:i]
        win_v = v[i-w:i]

        mn, mx = win_l.min(), max(win_h.max(), win_c.max())
        rng = mx - mn if mx - mn > 0 else 1e-10

        total = win_h - win_l
        total = np.where(total == 0, 1e-10, total)
        body = np.abs(win_c - win_o) / total
        direction = np.sign(win_c - win_o)

        log_c = np.log(np.where(win_c == 0, 1e-10, win_c))
        rets = np.diff(log_c)

        v_mean = np.mean(win_v)

        feats.extend([
            np.mean(rets) if len(rets) > 0 else 0,
            np.std(rets) if len(rets) > 0 else 0,
            np.sum(rets) if len(rets) > 0 else 0,
            float(pd.Series(rets).skew()) if len(rets) > 2 else 0,
            float(pd.Series(rets).kurtosis()) if len(rets) > 3 else 0,
            np.mean(body),
            np.std(body),
            np.mean(direction),
            np.mean(direction[-min(3, w):]),
            (win_c[-1] - mn) / rng,
            win_v[-1] / v_mean if v_mean > 0 else 1,
            np.corrcoef(rets[:-1], rets[1:])[0, 1] if len(rets) > 1 and np.std(rets) > 0 else 0,
        ])

    # Squeeze-specific features
    sq = squeeze_info
    feats.extend([
        sq["duration"],
        sq["duration"] / 24,  # durata in giorni
        sq["kcr_at_release"],
        v[i-1] / sq["avg_vol_squeeze"] if sq["avg_vol_squeeze"] > 0 else 1,
        np.mean(v[i:min(i+3, len(v))]) / sq["avg_vol_squeeze"] if sq["avg_vol_squeeze"] > 0 else 1,
    ])

    # Price position
    h48 = np.max(h[max(0, i-48):i])
    l48 = np.min(l[max(0, i-48):i])
    r48 = h48 - l48
    feats.append((c[i-1] - l48) / r48 if r48 > 0 else 0.5)

    # ATR normalized
    tr = np.maximum(h[i-14:i] - l[i-14:i],
                    np.maximum(np.abs(h[i-14:i] - np.roll(c[i-14:i], 1)),
                               np.abs(l[i-14:i] - np.roll(c[i-14:i], 1))))
    atr = np.mean(tr[1:])
    feats.append(atr / c[i-1] if c[i-1] > 0 else 0)

    # First bar momentum (la barra di breakout)
    first_ret = (c[i] - c[i-1]) / c[i-1] if c[i-1] > 0 else 0
    feats.append(first_ret)

    return np.nan_to_num(np.array(feats), nan=0, posinf=1e6, neginf=-1e6)


def run_hybrid(asset, tf, bb_w, sq_thr, brk_bars, leverage, pos_pct):
    print(f"\n{'='*65}")
    print(f"  {asset} {tf} — HYBRID Squeeze+ML (BBw={bb_w}, sq={sq_thr}, brk={brk_bars})")
    print(f"  Leverage: {leverage}x, Position: {pos_pct*100:.0f}%")
    print(f"{'='*65}")

    df = load_data(asset, tf)
    close = df["close"].values
    high = df["high"].values
    low = df["low"].values
    volume = df["volume"].values
    n = len(df)

    events = detect_squeeze_releases(close, high, low, volume, bb_w, sq_thr)
    print(f"  Squeeze releases totali: {len(events)}")

    # Build dataset: solo ai punti di squeeze
    X_all, y_all, ev_all = [], [], []
    for ev in events:
        i = ev["idx"]
        if i + brk_bars >= n or i < 100:
            continue
        feats = build_features_at(df, i, ev)
        if feats is None:
            continue
        actual_ret = (close[i + brk_bars - 1] - close[i - 1]) / close[i - 1]
        X_all.append(feats)
        y_all.append(1 if actual_ret > 0 else 0)
        ev_all.append(ev)

    if len(X_all) < 50:
        print("  Troppi pochi campioni.")
        return None

    X = np.array(X_all)
    y = np.array(y_all)

    print(f"  Samples: {len(X)}, Up: {np.mean(y)*100:.1f}%")

    # Walk-forward
    TRAIN_SIZE = max(int(len(X) * 0.5), 50)
    STEP_SIZE = max(int(len(X) * 0.1), 10)

    results = {}

    for ml_thr in [0.50, 0.55, 0.60, 0.65, 0.70]:
        capital = float(INITIAL)
        equity = [capital]
        trades_list = []
        correct = 0
        total = 0

        start = 0
        while start + TRAIN_SIZE + STEP_SIZE <= len(X):
            train_end = start + TRAIN_SIZE
            test_end = min(train_end + STEP_SIZE, len(X))

            X_tr = X[start:train_end]
            y_tr = y[start:train_end]
            X_te = X[train_end:test_end]
            y_te = y[train_end:test_end]

            if len(np.unique(y_tr)) < 2:
                start += STEP_SIZE
                continue

            scaler = StandardScaler()
            X_tr_s = scaler.fit_transform(X_tr)
            X_te_s = scaler.transform(X_te)

            model = GradientBoostingClassifier(
                n_estimators=150, max_depth=4, min_samples_leaf=10,
                learning_rate=0.05, subsample=0.8, random_state=42,
            )
            model.fit(X_tr_s, y_tr)

            up_idx = list(model.classes_).index(1) if 1 in model.classes_ else -1
            if up_idx < 0:
                start += STEP_SIZE
                continue

            for j in range(len(X_te)):
                proba = model.predict_proba(X_te_s[j:j+1])[0]
                p_up = proba[up_idx]

                ev = ev_all[train_end + j]
                i = ev["idx"]
                actual_ret = (close[i + brk_bars - 1] - close[i - 1]) / close[i - 1]

                # ML decide direction
                direction = None
                if p_up >= ml_thr:
                    direction = "long"
                elif p_up <= (1 - ml_thr):
                    direction = "short"

                if direction is None:
                    continue

                is_correct = (direction == "long" and actual_ret > 0) or (direction == "short" and actual_ret < 0)
                total += 1
                if is_correct:
                    correct += 1

                trade_ret = actual_ret if direction == "long" else -actual_ret
                net = trade_ret * leverage - FEE * 2 * leverage
                pnl = capital * pos_pct * net
                capital += pnl
                capital = max(capital, 0)
                equity.append(capital)

                trades_list.append({
                    "idx": i,
                    "direction": direction,
                    "p_up": p_up,
                    "actual_ret": actual_ret,
                    "correct": is_correct,
                    "pnl": pnl,
                })

            start += STEP_SIZE

        if total == 0:
            continue

        acc = correct / total * 100

        # Max drawdown
        peak = equity[0]
        max_dd = 0
        for v in equity:
            if v > peak:
                peak = v
            dd = (peak - v) / peak if peak > 0 else 0
            max_dd = max(max_dd, dd)

        # Annualized
        first_ev = ev_all[TRAIN_SIZE] if TRAIN_SIZE < len(ev_all) else ev_all[0]
        last_ev = ev_all[-1]
        test_candles = last_ev["idx"] - first_ev["idx"]
        if tf == "1h":
            test_days = test_candles / 24
        elif tf == "15m":
            test_days = test_candles / (24 * 4)
        else:
            test_days = test_candles / 24
        test_years = test_days / 365.25 if test_days > 0 else 1
        ann = ((capital / INITIAL) ** (1 / test_years) - 1) * 100 if test_years > 0 and capital > 0 else -100
        daily_pnl = (capital - INITIAL) / test_days if test_days > 0 else 0
        trades_yr = total / test_years if test_years > 0 else 0

        tag = ""
        if acc >= 80:
            tag = " ✅✅"
        elif acc >= 70:
            tag = " ✅"

        print(f"  ml_thr={ml_thr:.2f}: trades={total:4d} acc={acc:.1f}%{tag} ret={(capital-INITIAL)/INITIAL*100:+.1f}% ann={ann:+.1f}% dd={max_dd*100:.1f}% sharpe= trades/yr={trades_yr:.0f} €/day={daily_pnl:.2f}")

        results[ml_thr] = {
            "trades": total, "accuracy": acc, "capital": capital,
            "annualized": ann, "max_dd": max_dd * 100, "daily_pnl": daily_pnl,
            "trades_yr": trades_yr,
        }

    # Modalità "squeeze puro" come baseline
    capital_sq = float(INITIAL)
    correct_sq = 0
    total_sq = 0
    split = int(len(X) * 0.5)

    for k in range(split, len(X)):
        ev = ev_all[k]
        i = ev["idx"]
        if i + brk_bars >= n:
            continue
        first_ret = (close[i] - close[i-1]) / close[i-1]
        if abs(first_ret) < 0.001:
            continue
        direction = 1 if first_ret > 0 else -1
        actual_ret = (close[i + brk_bars - 1] - close[i - 1]) / close[i - 1]
        is_correct = (direction == 1 and actual_ret > 0) or (direction == -1 and actual_ret < 0)
        total_sq += 1
        if is_correct:
            correct_sq += 1
        trade_ret = actual_ret * direction
        net = trade_ret * leverage - FEE * 2 * leverage
        capital_sq += capital_sq * pos_pct * net
        capital_sq = max(capital_sq, 0)

    if total_sq > 0:
        acc_sq = correct_sq / total_sq * 100
        print(f"  BASELINE squeeze puro: trades={total_sq:4d} acc={acc_sq:.1f}% ret={(capital_sq-INITIAL)/INITIAL*100:+.1f}%")

    return results


# ===== MAIN: test su multiple configurazioni =====
print("=" * 70)
print("  STRATEGIA 13: SQUEEZE + ML IBRIDA")
print("=" * 70)

configs = [
    # (asset, tf, bb_w, sq_thr, brk_bars, leverage, pos_pct)
    ("ETH", "1h", 20, 0.8, 3, 3, 0.2),
    ("ETH", "1h", 30, 0.9, 3, 3, 0.2),
    ("ETH", "1h", 14, 0.8, 3, 3, 0.2),
    ("ETH", "1h", 20, 0.9, 3, 3, 0.2),
    ("BTC", "1h", 14, 0.8, 3, 3, 0.2),
    ("BTC", "1h", 20, 0.8, 3, 3, 0.2),
    ("BTC", "1h", 14, 0.9, 6, 3, 0.2),
    ("ETH", "15m", 14, 0.8, 3, 3, 0.15),
    ("ETH", "15m", 20, 0.9, 3, 3, 0.15),
    ("BTC", "15m", 14, 0.9, 3, 3, 0.15),
    # Aggressive
    ("ETH", "1h", 20, 0.8, 3, 5, 0.3),
    ("ETH", "1h", 30, 0.9, 3, 5, 0.3),
]

all_results = []
for cfg in configs:
    r = run_hybrid(*cfg)
    if r:
        for thr, data in r.items():
            all_results.append({
                "config": f"{cfg[0]} {cfg[1]} BBw={cfg[2]} sq={cfg[3]} brk={cfg[4]} lev={cfg[5]} pos={cfg[6]}",
                "ml_thr": thr,
                **data,
            })

# Sort by accuracy
print("\n\n" + "=" * 70)
print("  CLASSIFICA PER ACCURACY (top 20)")
print("=" * 70)
sorted_acc = sorted(all_results, key=lambda x: x["accuracy"], reverse=True)
for r in sorted_acc[:20]:
    tag = "✅✅" if r["accuracy"] >= 80 else "✅" if r["accuracy"] >= 70 else ""
    print(f"  {r['config']:55s} ml={r['ml_thr']:.2f} → acc={r['accuracy']:.1f}% {tag:4s} trades={r['trades']:4d} ret={(r['capital']-INITIAL)/INITIAL*100:+.1f}% ann={r['annualized']:+.1f}% dd={r['max_dd']:.1f}% €/day={r['daily_pnl']:.2f}")

print("\n" + "=" * 70)
print("  CLASSIFICA PER ROI ANNUO (top 20, min 20 trades)")
print("=" * 70)
sorted_roi = sorted([r for r in all_results if r["trades"] >= 20], key=lambda x: x["annualized"], reverse=True)
for r in sorted_roi[:20]:
    tag = "✅✅" if r["accuracy"] >= 80 else "✅" if r["accuracy"] >= 70 else ""
    print(f"  {r['config']:55s} ml={r['ml_thr']:.2f} → acc={r['accuracy']:.1f}% {tag:4s} ann={r['annualized']:+.1f}% trades={r['trades']:4d} dd={r['max_dd']:.1f}% €/day={r['daily_pnl']:.2f}")

print("\n" + "=" * 70)
print("  SWEET SPOT: acc>=75% AND ann>=20% AND trades>=15")
print("=" * 70)
sweet = [r for r in all_results if r["accuracy"] >= 75 and r["annualized"] >= 20 and r["trades"] >= 15]
sweet.sort(key=lambda x: x["accuracy"] * x["annualized"], reverse=True)
for r in sweet:
    print(f"  {r['config']:55s} ml={r['ml_thr']:.2f} → acc={r['accuracy']:.1f}% ann={r['annualized']:+.1f}% trades={r['trades']:4d} dd={r['max_dd']:.1f}% €/day={r['daily_pnl']:.2f}")
