diff --git a/CLAUDE.md b/CLAUDE.md index 9e2e5b5..7203c71 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -63,7 +63,10 @@ Prima ondata di ricerca onesta su BTC/ETH certificati (5 track, harness condivis - **Universo Hyperliquid: ESPANDERLO NON aiuta XS01** (provato): 52-asset / top-liquidità dinamico / trend-multi-asset → tutti peggiori (small-cap/memecoin diluiscono il momentum relativo; il trend multi-asset è ridondante con TP01, corr 0.74). I margini su XS sono nella STRUTTURA DEL SEGNALE - (blend + gate), non nel numero di asset. I 52 parquet certificati restano per ricerca futura. + (blend + gate), non nel numero di asset. I **51** parquet certificati restano per ricerca futura. + ⚠️ Il test "52-asset = negativo" era in parte inquinato dal backfill sintetico (AXS 83%, ALGO/SAND + 37% di barre vol=0) poi rimosso — vedi correzione estrazione 2026-06-20 sotto; resta comunque vero + che il long-tail diluisce XS01, ma il numero netto post-fix è 51. - **Lead OPZIONI VRP (income short-vol) — quantificato, NON deploy** — `scripts/research/options_vrp_*.py`. Vendita put settimanali che incassa il volatility risk premium (IV>RV), scorrelato al trend (~0.07). Premio prezzato BS su DVOL reale (`fetch_dvol.py`) + calibrato su quote REALI cerbero-bite mainnet @@ -169,6 +172,13 @@ df = load_data("BTC", "1h") # OK. load_data("SOL", ...) -> FileNotFoundError ( nativa solo **~2.5 anni** (2024-2026; pre-2024 = backfill, vol 0). Abilita le strategie CROSS-SECTIONAL (impossibili a 2 asset). NB: Cerbero col token TESTNET = farlocco; col token **mainnet** (`.env.mainnet`) = reale, ma SEMPRE da certificare (cross-venue + liquidità). + ⚠️ **CORREZIONE estrazione (2026-06-20):** il backfill NON è solo pre-2024 — cerbero MCP padda con + barre SINTETICHE (volume 0, prezzi copiati da Binance → matchano cross-venue e non sono flat) ogni + asset listato su HL **dopo** lo START. Il `flat`+cross-venue da soli non lo vedono: il rivelatore è + il **VOLUME**. `fetch_hyperliquid.py` ora (1) taglia il run iniziale a volume 0, (2) scarta chi resta + < 365g reali (es. **AXS 83% sintetico → fuori**), (3) gata i gap vol=0 interni. Universo certificato + = **51** (era 52). I **19 major di XS01 hanno 0 backfill → invariati** (strategia live non toccata). + Verificato direttamente su cerbero MCP. Diario `2026-06-20-cerbero-backfill-fix.md`. ## Metodologia obbligatoria per ogni nuova strategia diff --git a/docs/diary/2026-06-20-cerbero-backfill-fix.md b/docs/diary/2026-06-20-cerbero-backfill-fix.md new file mode 100644 index 0000000..97e5bb1 --- /dev/null +++ b/docs/diary/2026-06-20-cerbero-backfill-fix.md @@ -0,0 +1,86 @@ +# 2026-06-20 — Correzione estrazione cerbero MCP: il backfill sintetico (vol=0) ingannava la certificazione + +## Contesto + +Richiesta: "analizza cerbero MCP correggendo l'estrazione dati storici secondo le analisi fatte". +Le analisi del progetto avevano già fissato un principio — *"storia nativa Hyperliquid solo dal 2024, +pre-2024 = backfill, volume 0"* — e `fetch_hyperliquid.py` lo gestiva con un floor `START=2024-01-01`. +**Il floor non basta.** + +## Il difetto + +`fetch_hl` chiedeva a cerbero MCP `get_historical` dal 2024-01-01 e certificava ogni asset con tre +gate: **flat-bar** (O==H==L==C), **cross-venue** (mediana |close − Binance| < 60 bps), **recency**. +Nessuno guardava il **volume**. Risultato: gli asset listati su HL *dopo* lo START passavano come +PULITO pur essendo in gran parte **backfill sintetico**. + +Ispezione del volume sui parquet (leading run di barre a volume 0): + +| asset | barre | leading vol=0 | primo trade reale | % sintetico | +|---|---|---|---|---| +| **AXS** | 902 | **748** | 2026-01-18 | **82.9%** | +| ALGO | 902 | 338 | 2024-12-04 | 37.5% | +| SAND | 902 | 338 | 2024-12-04 | 37.5% | +| AR | 902 | 58 | 2024-02-28 | 6.4% | +| ETC | 902 | 11 | 2024-01-12 | 1.2% | +| BTC/ETH + 19 major | 902 | 0 | 2024-01-01 | 0% | + +AXS era **certificato PULITO** (flat 0%, cross-venue 9.5 bps) pur avendo solo ~5 mesi di trading reale. + +## Verifica diretta su cerbero MCP (token mainnet) + +Interrogato l'endpoint `cerbero-mcp.tielogic.xyz/mcp/tools/get_historical` (bot-tag +`pythagoras-mainnet`): + +- **BTC**: 902 barre, leading vol=0 = 0, volume reale dal 2024-01-01 (V=699, 2437, 5306…). Nativo. ✓ +- **AXS**: 902 barre, **748 leading vol=0**, primo vol>0 = 2026-01-18. Le barre a volume 0 hanno + prezzi (O/H/L/C) che **coincidono con Binance**: + + | data | cerbero close | binance close | Δ | + |---|---|---|---| + | 2024-01-01 | 9.262 | 9.26 | 2.2 bps | + | 2024-01-02 | 8.949 | 8.94 | 10.1 bps | + | 2024-01-03 | 7.937 | 7.95 | 16.4 bps | + +**Diagnosi provata:** cerbero MCP riempie il periodo pre-quotazione con barre **sintetiche — volume 0, +prezzi copiati da un venue di riferimento (Binance)**. Per questo i vecchi gate venivano ingannati: +- cross-venue passa → i prezzi *sono* Binance (Δ 1–16 bps); +- flat passa → le barre non sono flat (hanno movimento di prezzo); +- ma **volume 0** → su HL quelle candele **non erano negoziabili**. È esattamente il caso v2.0.0 + (edge su un book che non c'era). + +## Correzione (`scripts/analysis/fetch_hyperliquid.py`) + +1. **Il VOLUME è il rivelatore del backfill** → `trim_backfill()` taglia il run iniziale di barre a + volume 0; si tiene solo la **serie nativa**. +2. **Gate storia nativa** `MIN_NATIVE_DAYS=365`: dopo il taglio serve ≥ 1 anno di vita reale → + scarta chi è troppo corto (AXS, 154 barre reali → fuori). +3. **Gate vol=0 interno** `INTERIOR_VOL0_MAX=5%`: gap di liquidità oltre il taglio iniziale. +4. **cross-venue/flat ricalcolati SOLO sulle barre reali** (non più sui sintetici). +5. **I parquet degli asset scartati vengono rimossi** (disco == set certificato; niente file + contaminati a riposo). + +## Risultato + +- Universo certificato: **52 → 51** (AXS scartato). +- ALGO/SAND (−338 barre), AR (−58), ETC (−11) ripuliti dal backfill → ora start reale corretto. +- **I 19 major di XS01 hanno 0 backfill → invariati**: la strategia live (`XS_UNIVERSE` esplicito) NON + è toccata. Verificato: portafoglio 3-way (TP01+XS01+VRP01) gira identico, FULL Sh 1.68 / HOLD 1.67. +- Re-fetch end-to-end su cerbero reale: 51 PULITO, sweep su tutti i file → 0 backfill residuo. + +## Nota su una conclusione precedente + +Il diario `2026-06-19-xsec-universe-expansion.md` concludeva "cross-section dei 52 = negativo". Quella +finestra includeva i sintetici (AXS 83%, ALGO/SAND 37% di barre vol=0 con ritorni non eseguibili): la +magnitudine del risultato era **in parte un artefatto**. La conclusione qualitativa (il long-tail +diluisce XS01; i 19 major sono il sweet spot) resta valida, ma il numero netto è 51 e il test andrebbe +ri-girato sui dati puliti se si volesse riusare quell'universo. + +## Lezione + +`flat` + cross-venue **non bastano** a certificare un feed che fa backfill copiando un altro venue: il +backfill è plausibile sui prezzi proprio perché è copiato. Il **volume** (=liquidità reale) è il gate +che mancava. Coerente con la regola di prim'ordine v2.0.0: certificare il dato — anche il *volume*, +non solo il prezzo — prima della strategia. + +File: `scripts/analysis/fetch_hyperliquid.py`. Universo: `data/raw/hl_*_1d.parquet` (51, serie native). diff --git a/scripts/analysis/fetch_hyperliquid.py b/scripts/analysis/fetch_hyperliquid.py index 3f1bd5f..879dcde 100644 --- a/scripts/analysis/fetch_hyperliquid.py +++ b/scripts/analysis/fetch_hyperliquid.py @@ -2,11 +2,22 @@ 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 (liquidita'). Scrivo SOLO i puliti -in data/raw/hl__1d.parquet (namespace dedicato, NON mischiato col Deribit BTC/ETH). +asset come BTC/ETH: cross-venue vs Binance (realismo) + flat-bar + VOLUME (liquidita'). Scrivo SOLO +i puliti in data/raw/hl__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 @@ -17,7 +28,9 @@ 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.utcnow().strftime("%Y-%m-%d") # dinamico (refresh giornaliero) +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. @@ -62,17 +75,33 @@ def binance_daily(sym_b, start_ms, end_ms): 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("="*92); print(" FETCH + CERTIFY Hyperliquid 1d (Cerbero mainnet) — cross-venue vs Binance + liquidita'"); print("="*92) - print(f" {'sym':<6}{'barre':>7}{'start':>12}{'flat%':>7}{'med_bps':>9}{'>1%':>7}{'verdetto':>12}") + 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: - df=fetch_hl(s,H) - if df.empty: print(f" {s:<6} vuoto"); continue + 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 - # cross-venue vs Binance USDT (daily close) + 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() @@ -83,13 +112,20 @@ def main(): # 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") - clean = (not np.isnan(med)) and med<60 and g1<3 and flat<5 and recent - v = "PULITO" if clean else "scarta" - print(f" {s:<6}{len(df):>7}{str(ts.iloc[0].date()):>12}{flat:>6.1f}%{med:>9.1f}{g1:>6.1f}%{v:>12}") + # 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 vol06}{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(RAW/f"hl_{s.lower()}_1d.parquet", index=False); certified.append(s) + 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__1d.parquet (namespace dedicato). Universo per cross-sectional.") + print(" Scritti in data/raw/hl__1d.parquet (namespace dedicato, SERIE NATIVA senza backfill).") if __name__=="__main__":