"""FETCH + CERTIFY universo Hyperliquid (Cerbero MCP MAINNET) — espansione cross-sectional.

Hyperliquid (via cerbero-mcp mainnet) offre ~230 perp liquidi, ma storia nativa REALE solo dal
2024 (pre-2024 = backfill, volume 0). Qui scarico un set liquido a 1d (2024+), e CERTIFICO ogni
asset come BTC/ETH: cross-venue vs Binance (realismo) + flat-bar + VOLUME (liquidita'). Scrivo SOLO
i puliti in data/raw/hl_<sym>_1d.parquet (namespace dedicato, NON mischiato col Deribit BTC/ETH).

Disciplina: Cerbero ci ha gia' bruciato (testnet) -> niente fiducia, solo certificazione.

CORREZIONE estrazione (2026-06-20, "analisi fatte"): il floor START=2024-01-01 NON basta. Cerbero
restituisce BACKFILL SINTETICO (volume==0, ma prezzi copiati da un venue di riferimento -> matchano
Binance e NON sono flat) per il periodo PRIMA che l'asset quotasse davvero su Hyperliquid. Cosi'
asset listati a meta'/fine 2024+ passavano cross-venue+flat ed erano certificati PULITO pur essendo
in gran parte sintetici (es. AXS 83% backfill: trading reale solo da 2026-01; ALGO/SAND 37%). E' lo
stesso errore v2.0.0 (edge su un book che non c'era). Fix: (1) il VOLUME e' il rivelatore di backfill
-> si TAGLIA il run iniziale di barre a volume 0 e si tiene solo la serie NATIVA; (2) gate su storia
nativa minima (>= MIN_NATIVE_DAYS reali) -> scarta chi e' troppo corto dopo il taglio; (3) gate su
volume-0 INTERNO (gap di liquidita') oltre il taglio iniziale; (4) cross-venue/flat ricalcolati SOLO
sulle barre reali; (5) i parquet degli asset scartati vengono RIMOSSI (disco == set certificato).

    uv run python scripts/analysis/fetch_hyperliquid.py
"""
from __future__ import annotations
import sys, time
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[2]
sys.path.insert(0, str(PROJECT_ROOT))
import numpy as np, pandas as pd, requests, ccxt

RAW = PROJECT_ROOT / "data" / "raw"
START = "2024-01-01"; END = pd.Timestamp.now("UTC").strftime("%Y-%m-%d")   # dinamico (refresh giornaliero)
MIN_NATIVE_DAYS = 365     # storia NATIVA reale minima (post-taglio backfill) per entrare nell'universo
INTERIOR_VOL0_MAX = 5.0   # % max di barre a volume 0 DOPO il taglio iniziale (gap di liquidita' interni)
# UNIVERSO ESTESO: alt liquidi noti su Hyperliquid (mappa Binance auto = SYM/USDT). Il gate di
# certificazione (cross-venue + liquidita' + flat) scarta i non-conformi. k-prefissi esclusi
# (scaling 1000x complica il cross-venue). MATIC morto escluso.
SYMS = ["BTC","ETH","SOL","BNB","XRP","DOGE","AVAX","LINK","LTC","ADA","ARB","OP","SUI","APT",
        "INJ","TIA","SEI","NEAR","AAVE","ATOM","DYDX","APE","CRV","LDO","STX","GMX","SNX","BCH",
        "COMP","MKR","WLD","UNI","TRX","FIL","RUNE","ENA","ORDI","JUP","WIF","PYTH","FET","AR",
        "ETC","ALGO","GALA","SAND","AXS","DOT","FXS","BLUR","JTO","PENDLE","ONDO","TAO"]
BINANCE = {s: f"{s}/USDT" for s in SYMS}


def _h():
    env={}
    for ln in open(PROJECT_ROOT/".env.mainnet"):
        ln=ln.strip()
        if ln and not ln.startswith("#") and "=" in ln: k,v=ln.split("=",1); env[k]=v.strip()
    return {"Authorization":f"Bearer {env['CERBERO_TOKEN']}","X-Bot-Tag":env.get('CERBERO_BOT_TAG','fetch'),"Content-Type":"application/json"}


def fetch_hl(sym, H, interval="1d"):
    r=requests.post("https://cerbero-mcp.tielogic.xyz/mcp/tools/get_historical",
        headers=H, json={"exchange":"hyperliquid","instrument":sym,"interval":interval,
                         "start_date":START,"end_date":END}, timeout=60)
    c=r.json().get("candles",[])
    if not c: return pd.DataFrame()
    df=pd.DataFrame(c)[["timestamp","open","high","low","close","volume"]]
    return df.drop_duplicates("timestamp").sort_values("timestamp").reset_index(drop=True)


def binance_daily(sym_b, start_ms, end_ms):
    ex=ccxt.binance({"enableRateLimit":True})
    out={}; since=start_ms
    while since<=end_ms:
        try: r=ex.fetch_ohlcv(sym_b,"1d",since=since,limit=500)
        except Exception: break
        r=[x for x in r if x[0]>=since]
        if not r: break
        for x in r:
            if start_ms<=x[0]<=end_ms and x[4]: out[int(x[0])]=float(x[4])
        nxt=int(r[-1][0])+86400000
        if nxt<=since: break
        since=nxt
    return pd.Series(out)


def trim_backfill(df):
    """Taglia il run INIZIALE di barre a volume 0 (= backfill sintetico pre-quotazione su HL).
    Ritorna (serie_nativa, n_barre_tagliate). Il volume e' il rivelatore: il backfill copia i
    prezzi da un venue di riferimento (non flat, matcha Binance) ma ha volume 0."""
    vol = df["volume"].to_numpy()
    lead = int(np.argmax(vol > 0)) if (vol > 0).any() else len(df)
    return df.iloc[lead:].reset_index(drop=True), lead


def main():
    H=_h()
    print("="*100); print("  FETCH + CERTIFY Hyperliquid 1d (Cerbero mainnet) — cross-venue + flat + VOLUME (no backfill)"); print("="*100)
    print(f"  {'sym':<6}{'reali':>6}{'bfill':>6}{'start_reale':>13}{'flat%':>7}{'vol0%':>7}{'med_bps':>9}{'>1%':>7}{'verdetto':>14}")
    certified=[]
    for s in SYMS:
        path = RAW/f"hl_{s.lower()}_1d.parquet"
        raw=fetch_hl(s,H)
        if raw.empty:
            print(f"  {s:<6}  vuoto"); path.unlink(missing_ok=True); continue
        # --- CORREZIONE: taglia il backfill sintetico (volume 0 iniziale), tieni la serie nativa ---
        df, n_bfill = trim_backfill(raw)
        if df.empty:
            print(f"  {s:<6}  tutto backfill (vol0) -> scarta"); path.unlink(missing_ok=True); continue
        ts=pd.to_datetime(df["timestamp"],unit="ms",utc=True)
        flat=((df.open==df.high)&(df.high==df.low)&(df.low==df.close)).mean()*100
        vol0=(df["volume"].to_numpy()==0).mean()*100   # gap di liquidita' INTERNI (post-taglio)
        # cross-venue vs Binance USDT (daily close) — SOLO sulle barre reali
        ref=binance_daily(BINANCE[s], int(df["timestamp"].iloc[0]), int(df["timestamp"].iloc[-1]))
        a=df.set_index("timestamp")["close"]
        m=pd.concat([a.rename("a"),ref.rename("b")],axis=1,join="inner").dropna()
        if len(m)>5:
            bps=(m["a"]-m["b"]).abs()/m["b"]*1e4
            med=bps.median(); g1=(bps>100).mean()*100
        else: med=g1=float("nan")
        # gate "delistato/migrato": l'ultima barra dev'essere recente (entro ~21g da END),
        # altrimenti l'asset tronca l'universo cross-sectional (es. MKR fermo a 2025-09, FXS 2026-01).
        recent = (pd.Timestamp(END, tz="UTC") - ts.iloc[-1]) <= pd.Timedelta("21D")
        # gate storia NATIVA: dopo il taglio dev'esserci abbastanza vita reale (es. AXS quotato 2026-01 -> scarta)
        native_days = (ts.iloc[-1] - ts.iloc[0]).days
        enough = native_days >= MIN_NATIVE_DAYS
        clean = (not np.isnan(med)) and med<60 and g1<3 and flat<5 and vol0<INTERIOR_VOL0_MAX and recent and enough
        if clean: v="PULITO"
        elif not enough: v=f"corto<{MIN_NATIVE_DAYS}g"
        else: v="scarta"
        print(f"  {s:<6}{len(df):>6}{n_bfill:>6}{str(ts.iloc[0].date()):>13}{flat:>6.1f}%{vol0:>6.1f}%{med:>9.1f}{g1:>6.1f}%{v:>14}")
        if clean:
            df.to_parquet(path, index=False); certified.append(s)
        else:
            path.unlink(missing_ok=True)   # disco == set certificato (niente parquet contaminati a riposo)
    print(f"\n  CERTIFICATI ({len(certified)}): {certified}")
    print("  Scritti in data/raw/hl_<sym>_1d.parquet (namespace dedicato, SERIE NATIVA senza backfill).")


if __name__=="__main__":
    main()
