diff --git a/CLAUDE.md b/CLAUDE.md index 6e47bbb..2cd7f72 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -107,6 +107,21 @@ Prima ondata di ricerca onesta su BTC/ETH certificati (5 track, harness condivis rehedge lento = premio, veloce = fee → **nessuna frequenza vince.** Regola gemella del VRP: *niente long-vol scalp da modello in deploy*. Il VRP01 (lato short, gated) resta l'unico edge opzioni — funziona perché sta sul lato *giusto* dello stesso premio. Diario `2026-06-26-gamma-scalp-options.md`. +- **CASH-AND-CARRY (basis trade) "CC01" — premio REALE, Sharpe ARTEFATTO, NON deployabile (2026-06-26)** — + `scripts/research/cash_carry_hl.py`, test `tests/test_cash_carry.py`. Diverso da FC01 (funding + cross-sectional, già scartato): qui delta-neutral long-spot/short-perp sullo *stesso* asset → + ritorno ≈ **+funding** (zero esposizione prezzo). Il premio di funding è **reale** (~+8-14%/anno + aggregato, positivo ogni anno in-sample, ortogonale a TP01 corr ~0.05). MA lo **Sharpe modellato + 11-13 (DD 0.3%) è un ARTEFATTO**: il modello cattura solo il cashflow liscio del funding e i + rischi di coda sono **strutturalmente fuori dal dataset** — (1) storico funding dal 2023-05 → + **manca il 2022** (deleveraging, funding −, basis blow-out); (2) **procyclico** (carry +23% toro + 2024 → +1.7% bear 2026, si comprime quando servirebbe); (3) liquidazione short/slippage non + modellati. Il mark-to-market della base (`premium` col → r=funding−Δpremium) sgonfia lo Sharpe + solo 13→11 → il basis-from-data NON è il rischio vero. Sharpe reale di un basis-trade ~1-3 con + code brusche. **NON eseguibile a $600** (spot+perp = 4-38 gambe, funding HL non Deribit) → STAT-MODE. + **LEAD da rivedere a scala (~20k+ e venue con funding eseguibile), non uno sleeve.** Sottoprodotto: + CC01 passa OGNI gate del marginal scorer → **punto cieco** (manca un gate "Sharpe implausibile → + rischio nascosto"; prossima indurita raccomandata). Diario `2026-06-26-cash-carry-hl.md`. - **Soffitto strutturale BTC/ETH-direzionale ~1.3** superato SOLO espandendo a un meccanismo diverso: cross-sectional su universo Hyperliquid certificato (XS01) → portafoglio Sharpe ~1.55. - **Sweep "strategie alternative" (2026-06-20) — 104 ipotesi / 153 agenti / NIENTE di nuovo regge.** diff --git a/docs/diary/2026-06-26-cash-carry-hl.md b/docs/diary/2026-06-26-cash-carry-hl.md new file mode 100644 index 0000000..dc8f054 --- /dev/null +++ b/docs/diary/2026-06-26-cash-carry-hl.md @@ -0,0 +1,69 @@ +# 2026-06-26 — Cash-and-carry (basis trade) delta-neutral su HL (CC01): premio REALE, Sharpe ARTEFATTO, NON deployabile + +**Goal utente:** dopo lo scarto del gamma scalping, "provare" gli altri due angoli proposti — +funding-carry cross-sectional e basis/cash-and-carry. Il **funding cross-sectional (FC01) è già +SCARTATO** (diario 2026-06-22: DILUTES, sign-flip su un nome). L'angolo non testato è il +**cash-and-carry delta-neutral (CC01)**. Branch `research/gamma-scalp-options`, +script `scripts/research/cash_carry_hl.py`. + +## Meccanismo (diverso da FC01) + +FC01 pickava cross-section (short alto-funding / long basso-funding) → scommessa relativa, fragile. +CC01 **non picka**: harvesta il premio di funding AGGREGATO restando delta-neutral sullo *stesso* +asset. Long spot + short perp (stessa size): le gambe lineari nel prezzo si cancellano → ritorno +≈ **+funding** (lo short incassa il funding quando f>0), zero esposizione direzionale. Dati: funding +orario reale HL dal 2023-05 (`hlfund_*_1h.parquet`), gli stessi 19 major di XS01. + +## Risultato grezzo — e perché NON va creduto + +| config | Sharpe | CAGR | maxDD | per-anno | +|---|---|---|---|---| +| BTC/ETH CC-static | **+12.9** | +13% | 0.8% | 2023:+10 2024:+23 2025:+8 2026:+1% | +| BTC/ETH CC-gated | +13.9 | +14% | 0.3% | tutti positivi | +| 19-major CC-gated | +11.6 | +16% | 0.4% | tutti positivi | + +**Uno Sharpe di 13 con DD 0.3% è un ALLARME, non un edge.** È la firma di un modello che cattura +solo un cashflow liscio (il funding) e azzera la volatilità reale. Il marginal scorer dà **ADDS / +robust_oos / has_insample_edge=True / multicut_persistent** su OGNI config — ma questo **espone un +punto cieco dello scorer**: si fida della vol riportata dal candidato e non ha un gate "Sharpe +implausibile → rischio nascosto". Un Sharpe di 13 dovrebbe squalificare-e-indagare, non passare. + +## Il rischio è NASCOSTO, non assente + +- **Mark-to-market della base** (`premium` col → `r = funding − Δpremium`): sgonfia lo Sharpe solo + 13→11. La colonna premium di HL è già lisciata/clampata → a risoluzione daily il basis-vol è + minimo. **Quindi il basis-from-data NON è il rischio vero.** +- **I rischi che giustificherebbero uno Sharpe realistico (~1-3) sono ASSENTI dal dataset:** + 1. **2022 mancante.** Lo storico funding parte 2023-05 → niente deleveraging LUNA/3AC/FTX, il + regime di funding profondamente negativo + basis blow-out che farebbe il vero drawdown. + 2. **Procyclicità.** Funding aggregato BTC/ETH: +23% nel toro 2024 → **+1.7% nel bear 2026** + (e 19-major CC-static 2026 = **−1%**). Solo 10% di giorni a funding negativo *in-sample* — ma + in un vero bear quella frazione esplode. Il carry **non è all-weather**: è un risk-on premium. + 3. **Liquidazione dello short** in uno squeeze e **slippage** su spot+perp: non modellati. +- **Sharpe reale di un crypto basis-trade ≈ 1-3 con code brusche** (Mar-2020, Mag-2021, FTX), non 13. + +## Eseguibilità a $600 — muro indipendente + +Serve spot **+** perp per gamba: BTC/ETH = 4 posizioni, 19-major = 38. Su Deribit (dove operiamo) lo +storico funding è **bloccato** (404) e non tradiamo spot HL. **STAT-MODE**, come XS01/VRP01 ma peggio +(doppia gamba per asset). Non deployabile a questo capitale. + +## Verdetto + +**CC01 = premio di funding REALE (~+8-14%/anno aggregato, positivo ogni anno in-sample) e +genuinamente ortogonale a TP01 (corr ~0.05, NON direzionale).** È la "fonte di ritorno diversa" più +concreta trovata finora. MA: +- lo **Sharpe headline è fiction** (rischi di coda strutturalmente fuori dal dataset); +- è **procyclico** (si comprime/inverte nel bear — proprio quando servirebbe); +- **non eseguibile** a $600 / sul nostro venue. + +→ **LEAD da rivedere a scala** (capitale ~20k+ e un venue con funding eseguibile), **non uno sleeve**. +Nessun impatto sul book live (branch separato). + +## Sottoprodotto metodologico (raccomandazione) + +CC01 ha passato ogni gate del marginal scorer pur essendo un'illusione di rischio. **Prossima +indurita dello scorer:** un gate di *plausibilità dello Sharpe* — uno standalone Sharpe ≫ 4 deve +declassare a "SUSPECT/INVESTIGATE" invece di alimentare ADDS, perché segnala vol sottostimata +(mark-to-cashflow, dati che escludono il regime di stress, leva nascosta). Documentato qui, non +implementato in questa passata per non toccare lo scorer condiviso su `main`. diff --git a/scripts/research/cash_carry_hl.py b/scripts/research/cash_carry_hl.py new file mode 100644 index 0000000..84b436f --- /dev/null +++ b/scripts/research/cash_carry_hl.py @@ -0,0 +1,173 @@ +"""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() diff --git a/tests/test_cash_carry.py b/tests/test_cash_carry.py new file mode 100644 index 0000000..2aaf2d6 --- /dev/null +++ b/tests/test_cash_carry.py @@ -0,0 +1,36 @@ +"""Lock della conclusione 2026-06-26 (CC01 cash-and-carry): il premio di funding e' REALE e +positivo, ma PROCYCLICO (si comprime nel bear) — NON un edge all-weather. Lo Sharpe headline ~13 e' +un artefatto (rischi di coda assenti dal dataset) -> non testiamo lo Sharpe, testiamo il FATTO +economico e la procyclicita'. Diario docs/diary/2026-06-26-cash-carry-hl.md.""" +import sys +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(ROOT)) +sys.path.insert(0, str(ROOT / "scripts" / "research")) + +from cash_carry_hl import cc_returns, load_funding_panel, metrics # type: ignore + + +def test_funding_premium_is_real_and_positive(): + """Il carry esiste: funding aggregato BTC/ETH positivo, CC-static con CAGR > 0.""" + r = cc_returns(["BTC", "ETH"], mode="static") + m = metrics(r) + assert m["gross_ann"] > 0.0 # premio di funding netto positivo + assert m["cagr"] > 0.0 + + +def test_basis_markup_does_not_explain_high_sharpe(): + """Aggiungere il mark-to-market della base (Δpremium) NON sgonfia lo Sharpe in modo materiale: + prova che il rischio vero e' FUORI dal dataset (2022/liquidazione), non nel basis dei dati.""" + naive = metrics(cc_returns(["BTC", "ETH"], mode="static"))["sharpe"] + basis = metrics(cc_returns(["BTC", "ETH"], mode="static", with_basis=True))["sharpe"] + assert basis > 0.7 * naive # cala poco -> il basis-from-data non e' il rischio nascosto + + +def test_carry_is_procyclical_fades_in_bear(): + """Il carry e' procyclico: il funding aggregato del toro 2024 supera quello del bear 2026.""" + FUND, _ = load_funding_panel(["BTC", "ETH"]) + agg = FUND.mean(axis=1) + by_year = agg.groupby(agg.index.year).sum() + assert by_year.get(2024, 0.0) > by_year.get(2026, 0.0)