"""
Backtest strategia del RETTANGOLO (mean reversion daily).
Scarica daily klines da Bybit, applica la strategia, riporta risultati.
"""
import csv
import json
import time
import urllib.request
import urllib.parse
from datetime import datetime, timezone, timedelta

ASSETS = ["SOLUSDT", "BTCUSDT", "ETHUSDT"]
DAYS_BACK = 180
CATEGORY = "linear"
OUT_DIR = r"G:\AI TRADING ENGINE\live_deploy\bt_rettangolo"
PREMIUM_PCT = 0.20  # top 20% del range = premium zone
DISCOUNT_PCT = 0.20  # bottom 20% = discount zone
SL_BUFFER_PCT = 0.005  # 0.5% buffer oltre il range

def fetch_klines(symbol, interval="D", days=180):
    """Scarica candele daily da Bybit V5. Ritorna lista di dict OHLCV."""
    end_ms = int(time.time() * 1000)
    start_ms = end_ms - days * 24 * 3600 * 1000
    all_rows = []
    cursor = None
    while True:
        params = {"category": CATEGORY, "symbol": symbol, "interval": interval, "start": start_ms, "end": end_ms, "limit": 200}
        if cursor:
            params["cursor"] = cursor
        qs = urllib.parse.urlencode(params)
        url = f"https://api.bybit.com/v5/market/kline?{qs}"
        with urllib.request.urlopen(url, timeout=15) as r:
            data = json.loads(r.read().decode("utf-8"))
        result = data.get("result", {})
        rows = result.get("list", [])
        if not rows:
            break
        all_rows.extend(rows)
        cursor = result.get("nextPageCursor")
        if not cursor:
            break
        time.sleep(0.2)
    # ordina per timestamp crescente
    all_rows.sort(key=lambda x: int(x[0]))
    # formato Bybit: [ts, open, high, low, close, volume, turnover]
    klines = []
    for r in all_rows:
        klines.append({
            "ts": int(r[0]),
            "open": float(r[1]),
            "high": float(r[2]),
            "low": float(r[3]),
            "close": float(r[4]),
            "volume": float(r[5])
        })
    return klines

def is_bearish(k):
    return k["close"] < k["open"]

def is_bullish(k):
    return k["close"] > k["open"]

def has_hammer_bullish(k, body_ratio=0.3):
    """Hammer rialzista: lower shadow >= 2x body, body piccolo."""
    body = abs(k["close"] - k["open"])
    rng = k["high"] - k["low"]
    if rng == 0:
        return False
    lower_shadow = min(k["open"], k["close"]) - k["low"]
    return (lower_shadow >= 2 * body) and (body / rng <= body_ratio) and is_bullish(k)

def has_hammer_bearish(k, body_ratio=0.3):
    """Hammer ribassista (shooting star): upper shadow >= 2x body."""
    body = abs(k["close"] - k["open"])
    rng = k["high"] - k["low"]
    if rng == 0:
        return False
    upper_shadow = k["high"] - max(k["open"], k["close"])
    return (upper_shadow >= 2 * body) and (body / rng <= body_ratio) and is_bearish(k)

def is_doji(k, body_ratio=0.1):
    body = abs(k["close"] - k["open"])
    rng = k["high"] - k["low"]
    if rng == 0:
        return False
    return body / rng <= body_ratio

def backtest_symbol(symbol, klines):
    """Applica la strategia del rettangolo. Ritorna lista di trade."""
    trades = []
    i = 1  # parte da i=1 per avere il range del giorno precedente
    while i < len(klines) - 1:
        prev = klines[i - 1]
        cur = klines[i]
        rng = prev["high"] - prev["low"]
        if rng <= 0:
            i += 1
            continue
        # Tolleranza tocco confine: 1% del range
        touch_tolerance = rng * 0.01
        # SEGNALI - logica stretta
        signal = None
        # LONG: il LOW della candela corrente tocca il bottom del range (entro tolleranza)
        # + pattern di inversione rialzista (Doji o Hammer rialzista)
        if cur["low"] <= prev["low"] + touch_tolerance:
            if is_doji(cur) or has_hammer_bullish(cur):
                signal = "LONG"
        # SHORT: l'HIGH della candela corrente tocca il top del range (entro tolleranza)
        # + pattern di inversione ribassista (Doji o Hammer ribassista)
        elif cur["high"] >= prev["high"] - touch_tolerance:
            if is_doji(cur) or has_hammer_bearish(cur):
                signal = "SHORT"
        if signal:
            entry_bar = klines[i + 1]
            entry = entry_bar["open"]
            if signal == "LONG":
                sl = prev["low"] * (1 - SL_BUFFER_PCT)
                tp = prev["high"]
            else:
                sl = prev["high"] * (1 + SL_BUFFER_PCT)
                tp = prev["low"]
            # simula trade bar-by-bar
            outcome = None
            exit_price = None
            exit_bar_idx = None
            for j in range(i + 1, len(klines)):
                bar = klines[j]
                if signal == "LONG":
                    if bar["low"] <= sl:
                        outcome = "SL"
                        exit_price = sl
                        exit_bar_idx = j
                        break
                    if bar["high"] >= tp:
                        outcome = "TP"
                        exit_price = tp
                        exit_bar_idx = j
                        break
                else:
                    if bar["high"] >= sl:
                        outcome = "SL"
                        exit_price = sl
                        exit_bar_idx = j
                        break
                    if bar["low"] <= tp:
                        outcome = "TP"
                        exit_price = tp
                        exit_bar_idx = j
                        break
            if outcome is None:
                outcome = "OPEN"
                exit_price = klines[-1]["close"]
                exit_bar_idx = len(klines) - 1
            if signal == "LONG":
                pnl_pct = (exit_price - entry) / entry * 100
            else:
                pnl_pct = (entry - exit_price) / entry * 100
            trades.append({
                "signal": signal,
                "entry_date": datetime.fromtimestamp(entry_bar["ts"]/1000, tz=timezone.utc).strftime("%Y-%m-%d"),
                "entry": entry,
                "sl": sl,
                "tp": tp,
                "outcome": outcome,
                "exit": exit_price,
                "exit_date": datetime.fromtimestamp(klines[exit_bar_idx]["ts"]/1000, tz=timezone.utc).strftime("%Y-%m-%d"),
                "pnl_pct": round(pnl_pct, 2),
                "bars_held": exit_bar_idx - (i + 1) + 1
            })
        i += 1
    return trades

def summarize(symbol, trades):
    if not trades:
        return f"{symbol}: nessun trade"
    n = len(trades)
    wins = sum(1 for t in trades if t["outcome"] == "TP")
    losses = sum(1 for t in trades if t["outcome"] == "SL")
    opens = sum(1 for t in trades if t["outcome"] == "OPEN")
    wr = wins / n * 100
    avg_win = sum(t["pnl_pct"] for t in trades if t["outcome"] == "TP") / max(wins, 1)
    avg_loss = sum(t["pnl_pct"] for t in trades if t["outcome"] == "SL") / max(losses, 1)
    total_pnl = sum(t["pnl_pct"] for t in trades)
    longs = sum(1 for t in trades if t["signal"] == "LONG")
    shorts = n - longs
    # max drawdown (cumulativo)
    cum = 0
    peak = 0
    max_dd = 0
    for t in trades:
        cum += t["pnl_pct"]
        peak = max(peak, cum)
        dd = peak - cum
        max_dd = max(max_dd, dd)
    return {
        "symbol": symbol,
        "n_trades": n,
        "wins": wins,
        "losses": losses,
        "open_at_end": opens,
        "win_rate_pct": round(wr, 1),
        "avg_win_pct": round(avg_win, 2),
        "avg_loss_pct": round(avg_loss, 2),
        "total_pnl_pct": round(total_pnl, 2),
        "max_dd_pct": round(max_dd, 2),
        "longs": longs,
        "shorts": shorts,
    }

# === MAIN ===
import os
os.makedirs(OUT_DIR, exist_ok=True)
print(f"Scarico daily da Bybit per {len(ASSETS)} asset, ultimi {DAYS_BACK} giorni...")
print()

all_results = []
for sym in ASSETS:
    print(f"[{sym}] scarico...")
    klines = fetch_klines(sym, "D", DAYS_BACK)
    if len(klines) < 30:
        print(f"  pochi dati ({len(klines)}), skip")
        continue
    # salva csv
    csv_path = os.path.join(OUT_DIR, f"{sym}_D.csv")
    with open(csv_path, "w", newline="", encoding="utf-8") as f:
        w = csv.writer(f)
        w.writerow(["ts", "date", "open", "high", "low", "close", "volume"])
        for k in klines:
            date = datetime.fromtimestamp(k["ts"]/1000, tz=timezone.utc).strftime("%Y-%m-%d")
            w.writerow([k["ts"], date, k["open"], k["high"], k["low"], k["close"], k["volume"]])
    print(f"  {len(klines)} candele salvate in {csv_path}")
    # backtest
    trades = backtest_symbol(sym, klines)
    # salva trades
    trades_path = os.path.join(OUT_DIR, f"{sym}_trades.csv")
    if trades:
        with open(trades_path, "w", newline="", encoding="utf-8") as f:
            w = csv.DictWriter(f, fieldnames=list(trades[0].keys()))
            w.writeheader()
            w.writerows(trades)
    summary = summarize(sym, trades)
    all_results.append(summary)
    if isinstance(summary, dict):
        print(f"  trades: {summary['n_trades']} | WR: {summary['win_rate_pct']}% | avg W: {summary['avg_win_pct']}% | avg L: {summary['avg_loss_pct']}% | tot PnL: {summary['total_pnl_pct']}% | max DD: {summary['max_dd_pct']}%")
    print()

# === REPORT FINALE ===
print("=" * 80)
print(f"REPORT FINALE - Strategia Rettangolo daily, ultimi {DAYS_BACK} giorni")
print("=" * 80)
print(f"{'Asset':<12} {'N':>5} {'W':>4} {'L':>4} {'Open':>4} {'WR%':>6} {'avgW%':>7} {'avgL%':>7} {'totPnL%':>9} {'maxDD%':>7} {'L/S':>6}")
print("-" * 80)
tot_trades = 0
tot_wins = 0
tot_losses = 0
tot_pnl = 0
worst_dd = 0
for r in all_results:
    if not isinstance(r, dict):
        continue
    print(f"{r['symbol']:<12} {r['n_trades']:>5} {r['wins']:>4} {r['losses']:>4} {r['open_at_end']:>4} {r['win_rate_pct']:>6} {r['avg_win_pct']:>7} {r['avg_loss_pct']:>7} {r['total_pnl_pct']:>9} {r['max_dd_pct']:>7} {r['longs']}/{r['shorts']:>4}")
    tot_trades += r['n_trades']
    tot_wins += r['wins']
    tot_losses += r['losses']
    tot_pnl += r['total_pnl_pct']
    worst_dd = max(worst_dd, r['max_dd_pct'])
print("-" * 80)
if tot_trades > 0:
    wr = tot_wins / tot_trades * 100
    print(f"{'TOTALE':<12} {tot_trades:>5} {tot_wins:>4} {tot_losses:>4} {'':>4} {wr:>6.1f} {'':>7} {'':>7} {tot_pnl:>9.2f} {worst_dd:>7}")
print()
print(f"Report salvato in: {OUT_DIR}")
