"""FETCH + CERTIFY funding rate Hyperliquid (API pubblica, tokenless) — per ricerca CARRY cross-sectional.

CONTESTO (2026-06-22, onda "nuova ricerca mirata"). Le due grandi ondate (sweep 104-ipotesi +
ortho relative-value) hanno esaurito gli angoli DIREZIONALI e RELATIVE-VALUE sul *prezzo* BTC/ETH.
L'unico meccanismo con una fonte di ritorno DIVERSA non ancora testato su dati certi e' il CARRY da
funding (incassare il cashflow perp, delta-neutral). Scan di fattibilita':
  * funding price-clock sul feed Deribit certificato -> gia' testato (agent_03 intraday) = FAIL.
  * funding carry su Deribit (dove eseguiamo) -> ccxt fetch_funding_rate_history = 0 righe (bloccato).
  * funding carry su Hyperliquid -> API pubblica /info {"type":"fundingHistory"} = DISPONIBILE,
    cadenza ORARIA, tokenless, serie native dal 2023-05-12. HL e' gia' l'universo certificato di XS01.

DISCIPLINA (lezione v2.0.0): nessuna fiducia nel dato finche' non e' certificato. Qui certifichiamo:
  (1) cadenza ~1h coerente, (2) gap interni, (3) copertura (giorni nativi reali per coin),
  (4) plausibilita' magnitudine (|funding orario| tipico < ~0.06%/h = cap HL; outlier flaggati),
  (5) il funding e' un CASHFLOW, non un prezzo -> niente cross-venue OHLC; il sanity check e'
      che il funding medio sia ~positivo e piccolo (premio long-pays-short tipico dei perp crypto).

Universo = i 19 major di XS01 (quelli che la strategia live userebbe). Output:
  data/raw/hlfund_<sym>_1h.parquet  (namespace dedicato 'hlfund', NON tocca hl_<sym>_1d di XS01).
"""
import sys, time, datetime as dt
from pathlib import Path
import numpy as np, pandas as pd, requests

ROOT = Path(__file__).resolve().parents[2]
RAW = ROOT / "data" / "raw"
RAW.mkdir(parents=True, exist_ok=True)

# 19 major di XS01 (CLAUDE.md)
UNIVERSE = ["BTC","ETH","SOL","BNB","XRP","DOGE","AVAX","LINK","LTC","ADA",
            "ARB","OP","SUI","APT","INJ","TIA","SEI","NEAR","AAVE"]

HL_INFO = "https://api.hyperliquid.xyz/info"
START = int(dt.datetime(2023, 1, 1, tzinfo=dt.timezone.utc).timestamp() * 1000)
HOUR_MS = 3600 * 1000


def _post(payload, max_retry=6):
    """POST con backoff esponenziale su 429/5xx (l'API pubblica HL throttla)."""
    delay = 1.0
    for attempt in range(max_retry):
        r = requests.post(HL_INFO, json=payload, timeout=30)
        if r.status_code == 429 or r.status_code >= 500:
            time.sleep(delay)
            delay = min(delay * 2, 20)  # 1,2,4,8,16,20
            continue
        r.raise_for_status()
        return r.json()
    r.raise_for_status()
    return r.json()


def fetch_funding(coin: str) -> pd.DataFrame:
    """Pagina fundingHistory (max 500/req, orario) avanzando startTime fino a oggi."""
    rows, start = [], START
    seen = set()
    while True:
        d = _post({"type": "fundingHistory", "coin": coin, "startTime": start})
        if not d:
            break
        new = [x for x in d if x["time"] not in seen]
        for x in new:
            seen.add(x["time"])
            rows.append((x["time"], float(x["fundingRate"]), float(x.get("premium", "nan"))))
        last = d[-1]["time"]
        if len(d) < 500:  # ultima pagina
            break
        nxt = last + 1
        if nxt <= start:  # niente progresso -> stop
            break
        start = nxt
        time.sleep(0.35)  # gentile con l'API pubblica
    if not rows:
        return pd.DataFrame(columns=["ts", "funding", "premium"]).set_index("ts")
    df = pd.DataFrame(rows, columns=["ts", "funding", "premium"]).drop_duplicates("ts").sort_values("ts")
    df["ts"] = pd.to_datetime(df["ts"], unit="ms", utc=True)
    return df.set_index("ts")


def certify(coin: str, df: pd.DataFrame) -> dict:
    if df.empty:
        return {"coin": coin, "n": 0, "status": "VUOTO"}
    idx = df.index
    span_days = (idx[-1] - idx[0]).total_seconds() / 86400
    # cadenza: differenze in ore
    deltas_h = np.diff(idx.view("int64")) / 1e9 / 3600
    median_dt = float(np.median(deltas_h)) if len(deltas_h) else float("nan")
    gaps = int((deltas_h > 1.5).sum())  # buchi > 1.5h
    expected = int(round(span_days * 24)) + 1
    coverage = len(df) / expected if expected else float("nan")
    f = df["funding"].values
    # statistiche funding (orario)
    ann = float(np.nanmean(f)) * 24 * 365  # funding annualizzato (carry teorico per chi paga)
    cap_hits = int((np.abs(f) > 0.0006).sum())  # cap HL ~0.06%/h (4%/8h clamp); fuori = sospetto
    status = "OK"
    if coverage < 0.97 or gaps > 50:
        status = "GAP"
    if span_days < 365:
        status = "corto<365g"
    return {"coin": coin, "n": len(df), "primo": idx[0].date(), "ultimo": idx[-1].date(),
            "giorni": round(span_days), "cad_h": round(median_dt, 3), "gap>1.5h": gaps,
            "cover%": round(coverage * 100, 1), "fund_med_bps": round(float(np.nanmedian(f)) * 1e4, 4),
            "fund_ann%": round(ann * 100, 1), "cap_hit": cap_hits, "status": status}


def main():
    print("=" * 100)
    print("  FETCH + CERTIFY funding Hyperliquid (orario, tokenless) — 19 major XS01 -> data/raw/hlfund_*")
    print("=" * 100)
    rep = []
    for sym in UNIVERSE:
        try:
            df = fetch_funding(sym)
        except Exception as e:
            print(f"  {sym:5} ERR {repr(e)[:80]}")
            rep.append({"coin": sym, "n": 0, "status": "ERR"})
            continue
        c = certify(sym, df)
        rep.append(c)
        if c.get("n", 0) > 0:
            out = RAW / f"hlfund_{sym.lower()}_1h.parquet"
            df.to_parquet(out)
        print(f"  {sym:5} n={c.get('n',0):>6} {str(c.get('primo','')):>10}->{str(c.get('ultimo','')):>10} "
              f"cad={c.get('cad_h','?')}h gap={c.get('gap>1.5h','?')} cov={c.get('cover%','?')}% "
              f"med={c.get('fund_med_bps','?')}bps ann={c.get('fund_ann%','?')}% cap_hit={c.get('cap_hit','?')} "
              f"[{c['status']}]")
    ok = [r["coin"] for r in rep if r.get("status") in ("OK",)]
    short = [r["coin"] for r in rep if r.get("status") == "corto<365g"]
    print("-" * 100)
    print(f"  CERTIFICATI OK ({len(ok)}): {ok}")
    if short:
        print(f"  CORTI <365g ({len(short)}): {short}")
    print(f"  Scritti in data/raw/hlfund_<sym>_1h.parquet (funding ORARIO, serie nativa).")


if __name__ == "__main__":
    main()
