"""Test ingresso intra-barra: rottura banda squeeze rilevata sul 5m vs close 15m.

Domanda: entrando sul 5m appena il prezzo rompe la banda di Bollinger dello
squeeze (bande dall'ultima barra 15m CHIUSA -> nessun look-ahead), si recupera
parte del movimento che l'ingresso al close della barra 15m si perde?

Confronto a parita' di EXIT (stesso wall-clock): l'unica differenza e' il prezzo
d'ingresso (5m anticipato vs close 15m ritardato). La differenza di rendimento e'
esattamente lo "scatto" del breakout catturato in piu'.
"""
from __future__ import annotations

import sys
from pathlib import Path

import numpy as np
import pandas as pd

PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))

from src.data.downloader import load_data
from src.live.signal_engine import keltner_ratio

OOS_START = "2023-11-20"
BB_W = 14
SQ_THR = 0.8
MIN_DUR = 5
LEV = 3.0
POS = 0.15
M15 = 15 * 60 * 1000
M5 = 5 * 60 * 1000


def build_15m_levels(df15: pd.DataFrame) -> pd.DataFrame:
    c = df15["close"].values
    h = df15["high"].values
    l = df15["low"].values
    n = len(c)
    kcr = keltner_ratio(c, h, l, BB_W)
    ma = np.full(n, np.nan)
    sd = np.full(n, np.nan)
    for t in range(BB_W, n):
        w = c[t - BB_W + 1 : t + 1]
        ma[t] = w.mean()
        sd[t] = w.std()
    upper = ma + 2 * sd
    lower = ma - 2 * sd

    # durata squeeze consecutiva e maturita'
    dur = np.zeros(n, dtype=int)
    run = 0
    for t in range(n):
        if not np.isnan(kcr[t]) and kcr[t] < SQ_THR:
            run += 1
        else:
            run = 0
        dur[t] = run
    mature = dur >= MIN_DUR

    return pd.DataFrame({
        "ts15": df15["timestamp"].values,
        "close_time15": df15["timestamp"].values + M15,
        "close15": c,
        "upper": upper,
        "lower": lower,
        "mature": mature,
    })


def run_asset(asset: str, hold_min: int, fee_rt: float) -> dict:
    df5 = load_data(asset, "5m").reset_index(drop=True)
    df15 = load_data(asset, "15m").reset_index(drop=True)
    lvl = build_15m_levels(df15)

    d5 = pd.DataFrame({
        "ts5": df5["timestamp"].values,
        "close_time5": df5["timestamp"].values + M5,
        "close5": df5["close"].values,
    })

    # banda armata: ultima barra 15m CHIUSA prima della chiusura del bar 5m
    armed = pd.merge_asof(
        d5.sort_values("close_time5"),
        lvl[["close_time15", "upper", "lower", "mature"]].sort_values("close_time15"),
        left_on="close_time5", right_on="close_time15", direction="backward",
    )
    # barra 15m CONTENENTE il bar 5m (per l'ingresso ritardato a close 15m)
    cont = pd.merge_asof(
        d5.sort_values("ts5"),
        lvl[["ts15", "close15", "close_time15"]].rename(
            columns={"close_time15": "cont_close_time"}).sort_values("ts15"),
        left_on="ts5", right_on="ts15", direction="backward",
    )

    m = armed.copy()
    m["cont_close"] = cont["close15"].values
    m["cont_close_time"] = cont["cont_close_time"].values

    oos_ms = int(pd.Timestamp(OOS_START, tz="UTC").timestamp() * 1000)
    close5 = m["close5"].values
    ct5 = m["close_time5"].values
    upper = m["upper"].values
    lower = m["lower"].values
    mature = m["mature"].values
    cont_close = m["cont_close"].values
    cont_ct = m["cont_close_time"].values
    n = len(m)

    cap_e = cap_l = 1000.0   # equity ingresso early(5m) e late(15m)
    peak_e = peak_l = 1000.0
    dd_e = dd_l = 0.0
    trades = win_e = win_l = 0
    thrust_sum = 0.0
    fee = fee_rt * LEV
    busy_until = -1

    for i in range(n):
        if ct5[i] < oos_ms or ct5[i] <= busy_until:
            continue
        if not mature[i] or np.isnan(upper[i]):
            continue
        if close5[i] > upper[i]:
            d = 1
        elif close5[i] < lower[i]:
            d = -1
        else:
            continue

        entry_e = close5[i]
        entry_l = cont_close[i]
        exit_time = cont_ct[i] + hold_min * 60 * 1000
        # primo close 5m al/oltre exit_time
        j = np.searchsorted(ct5, exit_time, side="left")
        if j >= n:
            break
        exit_p = close5[j]

        ret_e = ((exit_p - entry_e) / entry_e) * d * LEV - fee
        ret_l = ((exit_p - entry_l) / entry_l) * d * LEV - fee
        thrust_sum += (entry_l - entry_e) / entry_e * d * 100  # scatto % (no leva)

        cb_e, cb_l = cap_e, cap_l
        cap_e = max(cb_e + cb_e * POS * ret_e, 10.0)
        cap_l = max(cb_l + cb_l * POS * ret_l, 10.0)
        peak_e = max(peak_e, cap_e); dd_e = max(dd_e, (peak_e - cap_e) / peak_e)
        peak_l = max(peak_l, cap_l); dd_l = max(dd_l, (peak_l - cap_l) / peak_l)
        trades += 1
        win_e += ret_e > 0
        win_l += ret_l > 0
        busy_until = exit_time

    return {
        "trades": trades,
        "avg_thrust": thrust_sum / trades if trades else 0.0,
        "early_win": win_e / trades * 100 if trades else 0.0,
        "late_win": win_l / trades * 100 if trades else 0.0,
        "early_ret": (cap_e / 1000 - 1) * 100,
        "late_ret": (cap_l / 1000 - 1) * 100,
        "early_dd": dd_e * 100,
        "late_dd": dd_l * 100,
    }


def main():
    for fee_rt in (0.002, 0.001):
        print("=" * 104)
        print(f"  INGRESSO INTRA-BARRA 5m vs CLOSE 15m — OOS da {OOS_START} | leva={LEV:.0f}x "
              f"| fee={fee_rt*100:.2f}% RT")
        print("  EARLY = entra al close 5m che rompe la banda | LATE = entra al close della barra 15m | stesso exit")
        print("=" * 104)
        print(f"  {'Asset':>5s}{'Hold':>6s}{'Trd':>6s}{'Scatto%':>9s}"
              f"{'EARLY win%':>12s}{'EARLY ret%':>12s}{'LATE win%':>11s}{'LATE ret%':>11s}{'Δret%':>9s}")
        print("  " + "-" * 100)
        for asset in ["BTC", "ETH"]:
            for hold_min in (15, 30, 45):
                r = run_asset(asset, hold_min, fee_rt)
                print(f"  {asset:>5s}{hold_min:>5d}m{r['trades']:>6d}{r['avg_thrust']:>+9.3f}"
                      f"{r['early_win']:>12.1f}{r['early_ret']:>+12.1f}"
                      f"{r['late_win']:>11.1f}{r['late_ret']:>+11.1f}"
                      f"{r['early_ret']-r['late_ret']:>+9.1f}")
            print("  " + "-" * 100)
        print("  Scatto% = movimento medio (no leva) catturato tra rottura 5m e close 15m, nella direzione.")
        print("  Δret% = vantaggio dell'ingresso anticipato. Se ~0 o negativo, il 5m non aiuta.\n")


if __name__ == "__main__":
    main()
