"""CC01 — CASH-AND-CARRY (basis trade) delta-neutral su Hyperliquid. Backtest onesto, STAT-MODE.

DIVERSO da FC01 (funding cross-sectional, gia' SCARTATO 2026-06-22). Qui NON si pickano
vincitori/perdenti cross-section: si HARVESTA il premio di funding AGGREGATO restando
delta-neutral sullo STESSO asset.

MECCANISMO. Long spot + short perp (stesso asset, stessa size):
  long spot  -> +price_ret
  short perp -> -price_ret + funding   (lo short INCASSA il funding quando f>0)
  netto      -> +funding   (il prezzo si cancella -> zero esposizione direzionale)
Il ritorno della gamba delta-neutral = il funding realizzato, meno fee. Entrambe le gambe sono
lineari nel prezzo => restano matchate in nozionale senza ribilanciare (delta ~neutro da solo);
i costi reali sono entry/exit + spread, modellati come drag annuo fisso.

VARIANTI:
  CC-static  -> sempre long-spot/short-perp (basis trade classico): r = funding. Perde se f<0.
  CC-gated   -> harvest solo quando il funding trailing CAUSALE > 0 (esci dai regimi a funding
                negativo invece di pagarli). r = funding se trail>0 else 0.
UNIVERSI: BTC/ETH (sottoinsieme rilevante per l'esecuzione) e i 19 major (basket pieno).

GIUDIZIO: standalone (Sharpe/DD/anni) + marginal_vs_tp01. CAVEAT ONESTI (pre-risultato):
  - NON eseguibile a $600: serve spot+perp per gamba (BTC/ETH = 4 posizioni; 19 = 38). Su Deribit
    lo storico funding e' bloccato e non operiamo spot HL -> STAT-MODE.
  - Il modello "r=funding" IGNORA il rischio di base (perp != spot), la liquidazione dello short in
    uno squeeze, e l'inversione brusca del funding. La vol modellata SOTTOSTIMA la coda.
  - Lo storico funding parte 2023-05 -> NON contiene il deleveraging 2022 (il regime peggiore per il
    carry). Edge potenzialmente sovrastimato.

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

import numpy as np
import pandas as pd

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

from src.portfolio.sleeves import XS_UNIVERSE, _HL_DIR
from altlib import marginal_vs_tp01  # type: ignore

SQ365 = np.sqrt(365.25)
ANNUAL_COST = 0.02   # drag annuo fisso: entry/exit + spread + borrow (generoso per un hold continuo)


def load_funding_panel(universe):
    """FUND, PREM [date x asset]: funding giornaliero (somma oraria) e premium/basis (ultimo del
    giorno) per gli asset con dati. PREM serve a iniettare il mark-to-market della BASE (perp!=spot)."""
    fund, prem = {}, {}
    for sym in universe:
        fp = _HL_DIR / f"hlfund_{sym.lower()}_1h.parquet"
        if not fp.exists():
            continue
        df = pd.read_parquet(fp)
        fund[sym] = df["funding"].resample("1D").sum()
        prem[sym] = df["premium"].resample("1D").last()
    FUND = pd.concat(fund, axis=1).sort_index()
    PREM = pd.concat(prem, axis=1).sort_index().reindex(FUND.index)
    if FUND.index.tz is None:
        FUND.index = FUND.index.tz_localize("UTC"); PREM.index = PREM.index.tz_localize("UTC")
    return FUND, PREM


def cc_returns(universe, mode="static", trail=14, cost=ANNUAL_COST, with_basis=False) -> pd.Series:
    """Ritorno giornaliero del basket cash-and-carry equal-weight, netto drag annuo.
    with_basis=True inietta il mark-to-market reale della base: r = funding - Δpremium (lo short
    perp marca contro l'allargamento del basis). E' il rischio che 'r=funding' nasconde."""
    FUND, PREM = load_funding_panel(universe)
    daily_cost = cost / 365.25
    leg_raw = FUND - PREM.diff() if with_basis else FUND          # per-asset daily mark
    if mode == "gated":
        sig = FUND.rolling(trail, min_periods=trail // 2).mean().shift(1)   # funding trailing causale
        active = (sig > 0).astype(float)
        n_active = active.sum(axis=1).replace(0, np.nan)
        gross = ((leg_raw * active).sum(axis=1) / n_active).fillna(0.0)
        drag = (active.sum(axis=1) > 0).astype(float) * daily_cost
    else:
        gross = leg_raw.mean(axis=1)
        drag = daily_cost
    return (gross - drag).dropna()


def metrics(daily: pd.Series) -> dict:
    r = daily.values
    sh = float(np.mean(r) / np.std(r) * SQ365) if np.std(r) > 0 else 0.0
    eq = np.cumprod(1.0 + np.clip(r, -0.99, None))
    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
    s = pd.Series(eq, index=daily.index)
    yearly = {}
    for y, g in s.groupby(s.index.year):
        if len(g) > 1:
            v = g.values; p = np.maximum.accumulate(v)
            yearly[int(y)] = (float(g.iloc[-1] / g.iloc[0] - 1), float(np.max((p - v) / p)))
    return dict(sharpe=sh, dd=dd, cagr=cagr, yearly=yearly, gross_ann=float(np.mean(r) * 365.25))


def main():
    print("=" * 92)
    print("  CC01 — CASH-AND-CARRY (basis trade) delta-neutral su Hyperliquid")
    print("  long spot + short perp -> r = funding (zero esposizione prezzo). Netto drag 2%/anno.")
    print("=" * 92)

    configs = [
        ("BTC/ETH  CC-static", ["BTC", "ETH"], "static"),
        ("BTC/ETH  CC-gated",  ["BTC", "ETH"], "gated"),
        ("19-major CC-static", XS_UNIVERSE, "static"),
        ("19-major CC-gated",  XS_UNIVERSE, "gated"),
    ]
    print("\n  [A] MODELLO INGENUO (r = funding) — IGNORA il rischio di base:")
    series = {}
    for label, uni, mode in configs:
        r = cc_returns(uni, mode=mode)
        series[label] = r
        m = metrics(r)
        ys = "  ".join(f"{y}:{p*100:+.0f}%" for y, (p, d) in sorted(m['yearly'].items()))
        print(f"\n  --- {label} ---  ({r.index[0].date()} -> {r.index[-1].date()}, {len(r)}g)")
        print(f"    Sharpe {m['sharpe']:+.2f}  CAGR {m['cagr']*100:+.1f}%  maxDD {m['dd']*100:.1f}%  "
              f"carry lordo {m['gross_ann']*100:+.1f}%/anno  |  per-anno: {ys}")

    print("\n  [B] CON MARK-TO-MARKET DELLA BASE (r = funding - Δpremium) — il rischio nascosto:")
    series_b = {}
    for label, uni, mode in configs:
        r = cc_returns(uni, mode=mode, with_basis=True)
        series_b[label] = r
        m = metrics(r)
        ys = "  ".join(f"{y}:{p*100:+.0f}%" for y, (p, d) in sorted(m['yearly'].items()))
        print(f"\n  --- {label} (basis) ---")
        print(f"    Sharpe {m['sharpe']:+.2f}  CAGR {m['cagr']*100:+.1f}%  maxDD {m['dd']*100:.1f}%  "
              f"|  per-anno: {ys}")

    print("\n" + "=" * 92)
    print("  REALITY CHECK — perche' uno Sharpe 11-13 e' un ARTEFATTO, non un edge")
    print("=" * 92)
    FUND, _ = load_funding_panel(["BTC", "ETH"])
    agg = FUND.mean(axis=1)
    neg = float((agg < 0).mean())
    by_year = agg.groupby(agg.index.year).apply(lambda x: float(x.sum()))
    print(f"  funding aggregato BTC/ETH: giorni a funding NEGATIVO {neg*100:.0f}% | "
          f"per-anno (somma): " + "  ".join(f"{y}:{v*100:+.1f}%" for y, v in by_year.items()))
    print("  -> il carry e' PROCYCLICO: +23% nel toro 2024, ~+1% nel 2026 (si comprime nel bear).")
    print("  -> lo storico funding parte 2023-05: ASSENTE il deleveraging 2022 (funding -, basis blow-out),")
    print("     il regime che farebbe il vero drawdown. + assenti: liquidazione short in squeeze, slippage")
    print("     su spot+perp a $600. Sharpe reale di un basis-trade ~1-3 con code brusche, NON 13.")

    print("\n" + "=" * 92)
    print("  SCORING MARGINALE vs TP01 (sul modello ONESTO con rischio di base [B])")
    print("  NB: ADDS/robust_oos qui ESPONGONO un punto cieco dello scorer — si fida della vol")
    print("      riportata e non ha un gate 'Sharpe implausibile -> rischio nascosto'.")
    print("=" * 92)
    for label, r in series_b.items():
        if r.std() == 0:
            print(f"\n[{label}] flat — skip"); continue
        m = marginal_vs_tp01(r)
        b = m.get("blends", {}).get("w25", {})
        print(f"\n[{label}]")
        print(f"  verdict={m.get('marginal_verdict')}  corr->TP01 full={m.get('corr_full')} "
              f"hold={m.get('corr_hold')}  is_hedge={m.get('is_hedge')}  "
              f"has_insample_edge={m.get('has_insample_edge')} (cand IS Sharpe {m.get('cand_insample_sharpe')})")
        print(f"  cand Sharpe full={m.get('cand_full_sharpe')} hold={m.get('cand_hold_sharpe')}  |  "
              f"blend25 full {b.get('full')} (upl {b.get('uplift_full')})  "
              f"hold {b.get('hold')} (upl {b.get('uplift_hold')})  DD {b.get('dd')}")
        print(f"  multicut persistente={m.get('multicut_persistent')}  robust_oos={m.get('robust_oos')}  "
              f"hedge-check up/down {m.get('uplift_tp01_up')}/{m.get('uplift_tp01_down')}")


if __name__ == "__main__":
    main()
