"""EQ-MR — "scalping azioni IB quando sottoquotate" + CHECK DATI DALLA RETE. Analisi onesta.

L'utente chiede: su IB, comprare azioni quando "sottoquotate" (oversold/sotto fair-value), con
verifica incrociata dei dati dalla rete. Due pezzi, entrambi in-metodo (la lezione fondante del
progetto e' "non fidarti di un feed solo" — il disastro v2.0.0):

1) CHECK DATI DALLA RETE — confronta i nostri dati certificati IB (data/raw/eq_*_1d.parquet,
   ADJUSTED_LAST) con una SORGENTE INDIPENDENTE di rete (Yahoo Finance chart API, tokenless).
   Confronto sui RENDIMENTI giornalieri (invarianti all'aggiustamento) + ultimo close. Verdetto:
   feed CONCORDE (bps piccoli) o DIVERGENTE. E' il template del pre-trade price-check live.

2) MEAN-REVERSION "SOTTOQUOTATA" sul daily — lo *scalping* intraday NON e' backtestabile (non
   abbiamo dati intraday, solo eq_*_1d) ne' eseguibile (vedi sotto), quindi si testa la versione
   onesta: swing mean-reversion su ETF indice (RSI2 oversold + filtro trend MA200, exit a MA5 =
   Connors). Causale (segnale <= close[i], entry a close[i]), netto fee, hold-out OOS, per-anno.

ESEGUIBILITA' (muro, da dire subito):
  - PDT RULE: il day-trading di azioni US sotto $25.000 e' limitato a 3 day-trade/5gg -> lo scalping
    e' regolatoriamente BLOCCATO al capitale del progetto. (l'analogo equity del muro STAT-MODE a $600).
  - Niente dati INTRADAY -> scalping non backtestabile.
  - IB Gateway in questo ambiente e' instabile (timeout ordini diagnosticato) -> niente HFT affidabile.

    uv run python scripts/research/eq_meanrev_ib.py
"""
from __future__ import annotations
import sys
from pathlib import Path

ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(ROOT / "scripts" / "research"))

import numpy as np
import pandas as pd
import requests

from eqlib import load_eq  # type: ignore

SQ = np.sqrt(252)
H = {"User-Agent": "Mozilla/5.0"}


# ----------------------------- 1) CHECK DATI DALLA RETE -----------------------------
def yahoo_daily(sym: str, rng: str = "1y") -> pd.DataFrame:
    u = f"https://query1.finance.yahoo.com/v8/finance/chart/{sym}?range={rng}&interval=1d"
    j = requests.get(u, headers=H, timeout=30).json()["chart"]["result"][0]
    ts = pd.to_datetime(j["timestamp"], unit="s", utc=True).normalize()
    q = j["indicators"]["quote"][0]
    adj = j["indicators"].get("adjclose", [{}])[0].get("adjclose", q["close"])
    return pd.DataFrame({"close": q["close"], "adjclose": adj}, index=ts).dropna()


def cross_check(syms):
    print("=" * 90)
    print("  1) CHECK DATI DALLA RETE — IB certificato (eq_*, ADJUSTED) vs Yahoo adjclose (rete indip.)")
    print("=" * 90)
    print(f"  {'sym':5} {'n com':>6} {'ret maxΔ':>9} {'ret medΔ':>9} {'last IB':>10} {'last YHOO':>10} {'Δbps':>7}  esito")
    for s in syms:
        try:
            ib = load_eq(s)["close"].astype(float)
            ib.index = ib.index.normalize()
            yh = yahoo_daily(s)["adjclose"]                   # adjusted vs adjusted (apples-to-apples)
            J = pd.concat({"ib": ib, "yh": yh}, axis=1, join="inner").dropna().tail(180)
            if len(J) < 20:
                print(f"  {s:5}  overlap insufficiente"); continue
            R = pd.concat({"a": J["ib"].pct_change(), "b": J["yh"].pct_change()},
                          axis=1, join="inner").dropna()
            d = (R["a"] - R["b"]).abs()
            last_bps = abs(J["ib"].iloc[-1] / J["yh"].iloc[-1] - 1) * 1e4
            ok = d.max() < 0.001 and last_bps < 20            # ret entro 10bps, last entro 20bps
            print(f"  {s:5} {len(J):>6} {d.max()*1e4:>8.1f}b {d.median()*1e4:>8.1f}b "
                  f"{J['ib'].iloc[-1]:>10.2f} {J['yh'].iloc[-1]:>10.2f} {last_bps:>6.1f}b  "
                  f"{'CONCORDE' if ok else 'DIVERGENTE -> INDAGARE'}")
        except Exception as e:
            print(f"  {s:5}  ERRORE: {repr(e)[:80]}")
    print("  -> confronto sui RENDIMENTI adjusted-vs-adjusted: devono combaciare a pochi bps. Una")
    print("     divergenza non spiegata = feed sospetto, NON tradare prima di averla capita (v2.0.0).")


# ----------------------------- 2) MEAN-REVERSION "SOTTOQUOTATA" -----------------------------
def rsi(close: np.ndarray, n: int) -> np.ndarray:
    d = np.diff(close, prepend=close[0])
    up = pd.Series(np.where(d > 0, d, 0.0)).ewm(alpha=1 / n, adjust=False).mean().values
    dn = pd.Series(np.where(d < 0, -d, 0.0)).ewm(alpha=1 / n, adjust=False).mean().values
    rs = np.divide(up, dn, out=np.full_like(up, np.inf), where=dn > 0)
    return 100 - 100 / (1 + rs)


def mr_target(close: np.ndarray, entry=10.0, exit_ma=5, trend=200) -> np.ndarray:
    """Posizione 0/1 DECISA a close[i] (Connors RSI2): entra se sottoquotata (RSI2<entry) in uptrend
    (close>MA{trend}); esci quando close>MA{exit_ma}. Causale: usa solo dati <= close[i]."""
    r2 = rsi(close, 2)
    ma_t = pd.Series(close).rolling(trend).mean().values
    ma_x = pd.Series(close).rolling(exit_ma).mean().values
    pos = np.zeros(len(close)); inpos = False
    for i in range(len(close)):
        if not np.isfinite(ma_t[i]):
            continue
        if not inpos and close[i] > ma_t[i] and r2[i] < entry:
            inpos = True
        elif inpos and close[i] > ma_x[i]:
            inpos = False
        pos[i] = 1.0 if inpos else 0.0
    return pos


def backtest(sym: str, fee_bps=3.0, **kw) -> pd.Series:
    df = load_eq(sym); close = df["close"].astype(float).values
    idx = df.index
    tgt = mr_target(close, **kw)
    r = np.zeros(len(close)); r[1:] = close[1:] / close[:-1] - 1.0
    held = np.zeros(len(tgt)); held[1:] = tgt[:-1]            # decisa a close[i-1], tenuta in i
    turn = np.abs(np.diff(held, prepend=0.0))
    net = held * r - (fee_bps / 1e4) * turn
    return pd.Series(net, index=idx)


def metrics(daily: pd.Series, lo=None):
    if lo is not None:
        daily = daily[daily.index >= lo]
    rr = daily.values
    sh = float(np.mean(rr) / np.std(rr) * SQ) if np.std(rr) > 0 else 0.0
    eq = np.cumprod(1 + rr); pk = np.maximum.accumulate(eq)
    dd = float(np.max((pk - eq) / pk)) if len(eq) else 0.0
    yrs = (daily.index[-1] - daily.index[0]).days / 365.25 if len(daily) > 1 else 1.0
    cagr = eq[-1] ** (1 / yrs) - 1 if yrs > 0 and len(eq) and eq[-1] > 0 else -1.0
    expo = float((daily != 0).mean())
    return dict(sharpe=sh, dd=dd, cagr=cagr, expo=expo)


def buyhold(sym, lo=None):
    df = load_eq(sym); c = df["close"].astype(float)
    r = c.pct_change().fillna(0.0)
    return metrics(r, lo=lo)


def run_meanrev(syms, holdout="2015-01-01"):
    HO = pd.Timestamp(holdout, tz="UTC")
    print("\n" + "=" * 90)
    print(f"  2) MEAN-REVERSION 'SOTTOQUOTATA' (RSI2<10 + filtro MA200, exit MA5). Hold-out {holdout}.")
    print("=" * 90)
    print(f"  {'sym':5} | {'FULL Sh':>7} {'DD':>6} {'CAGR':>6} {'expo':>5} | "
          f"{'HOLD Sh':>7} {'HOLD CAGR':>9} | {'B&H Sh':>6} {'B&H HOLD':>8}")
    for s in syms:
        net = backtest(s)
        f = metrics(net); h = metrics(net, lo=HO)
        bh = buyhold(s); bhh = buyhold(s, lo=HO)
        print(f"  {s:5} | {f['sharpe']:>+7.2f} {f['dd']*100:>5.0f}% {f['cagr']*100:>+5.0f}% "
              f"{f['expo']*100:>4.0f}% | {h['sharpe']:>+7.2f} {h['cagr']*100:>+8.0f}% | "
              f"{bh['sharpe']:>+6.2f} {bhh['sharpe']:>+7.2f}")
    print("  expo = % giorni investito. B&H = buy&hold sullo stesso ETF (il benchmark onesto).")

    print("\n  Sweep fee (SPY) — quanto regge ai costi reali:")
    for fb in (0.0, 3.0, 5.0, 10.0):
        f = metrics(backtest("SPY", fee_bps=fb)); h = metrics(backtest("SPY", fee_bps=fb), lo=HO)
        print(f"    fee {fb:>4.1f}bps RT: FULL Sh {f['sharpe']:+.2f}  HOLD Sh {h['sharpe']:+.2f}")


def main():
    cross_check(["SPY", "QQQ", "IWM", "GLD", "TLT", "HYG"])
    run_meanrev(["SPY", "QQQ", "IWM", "DIA", "EEM"])
    print("\n" + "=" * 90)
    print("  3) ESEGUIBILITA' (il muro)")
    print("=" * 90)
    print("  - PDT RULE: day-trading azioni US < $25k = max 3 day-trade/5gg -> SCALPING BLOCCATO al")
    print("    capitale del progetto. E' l'analogo equity del muro STAT-MODE a $600 sul crypto.")
    print("  - Dati INTRADAY assenti (solo eq_*_1d) -> lo scalping non e' backtestabile, solo lo swing MR.")
    print("  - IB Gateway instabile in questo ambiente (timeout ordini) -> niente HFT affidabile.")


if __name__ == "__main__":
    main()
