diff --git a/docs/research/RESULTS-0822.md b/docs/research/RESULTS-0822.md index 791e307..b19af9b 100644 --- a/docs/research/RESULTS-0822.md +++ b/docs/research/RESULTS-0822.md @@ -704,6 +704,7 @@ stessi hanno nominato senza poterli eseguire**. Stesso contratto di consegna, st | 36 | PIANO-VERO | la tabella congiunta fisco+funding, che non esisteva | 🚨 **€250/m: P(20a) 92% → 14-26%** | | 37 | GATE-C | l'ultima gamba del gate funded + il biglietto come opzione | **PASS 2/2** — gate senza date; **P(≥50/g) 4,4% → 2,6%** | | 38 | SCALE-SPEC | specificare il knob di leva che non esiste | **LEAD** — specificata, **1 nodo irrisolto** sul disaster-SL | +| 39 | FUNDING-AVOID | evitare il funding esprimendo il libro sui datati | **SCARTATO** sui datati · **LEAD** sullo **spot** | | 22 | MAKER | l'esecuzione passiva e' una fonte di ritorno, al netto del costo di non essere eseguiti? | **SCARTATO** — il segno dipende da `<` contro `<=` | | 23 | BOCPD | un rilevatore di cambio di regime vero batte il miglior lookback COSTANTE? | **SCARTATO** — filone chiuso definitivamente | | 24 | CRITICO | cosa NON e' stato misurato, e quale singola misura mancante vale di piu' | **3 correzioni all'ondata**, 1 regola mia ritirata | @@ -1619,7 +1620,14 @@ de-levering **REFUTED** sulla versione a 2 gambe. **Controllo positivo di causal e' il vincolo** (**5ª volta in questa ondata**); margine iniziale $13. ⚠️ **Ma tutti i futures datati Deribit sono INVERSE: non esiste una linea USDC datata** — **stesso muro delle opzioni**, e il collaterale in BTC e' esso stesso esposizione direzionale. **Non risolto**, serve una verifica sul -conto reale. **MAE per trade: −11,68% sulla storia piena → leva max 7,8x/gamba**, contro −3,64% → +conto reale. +🚨 **CORREZIONE 2026-08-23 (§39): QUESTO MURO E' FALSO.** Esistono **16 futures datati con settlement +USDC** su BTC/ETH (il piu' vecchio `BTC_USDC-25SEP26`, creato **2026-05-07**), taker **identico al +perpetual** (0,00035) e min-order 0,0001 BTC / 0,001 ETH. ✅ Verificato dal coordinatore con una GET +pubblica. **La svista e' di NAMESPACE, non di fatto:** `get_instruments?currency=BTC` **non li +ritorna** — i lineari stanno sotto **`currency=USDC`** (48 datati in totale). *Il coordinatore ha +commesso la stessa svista al primo controllo e ha ottenuto 0.* **Il muro vero e' un altro: quella +linea ha 3,5 mesi di vita e ZERO roll trimestrali mai eseguiti.** **MAE per trade: −11,68% sulla storia piena → leva max 7,8x/gamba**, contro −3,64% → 20,9x nella sola finestra di CC01 = **2,7× di leva di troppo**; sul carry puro con base MTM, MAE 90g −16,57% → **5,6-6,4x** contro le **~80x** che un maxDD dello 0,8% farebbe credere sostenibili. ⚠️ **Correzione a BASIS-CALENDAR:** il costo di roll e' **0,44%/anno**, non ~1,4% — quello contava il @@ -1916,3 +1924,61 @@ lo permette (servirebbe `pos_prima`): **non ricostruibile a posteriori.** **Secondo smentitore, con soglia dichiarata:** il ramo di **fallback dell'equity** oggi scatta **0 volte su 1.442 giri**; se superasse il **2% dei giri in 90 giorni** il libro girerebbe a leva **mista** e `g(k)` smetterebbe di descriverlo. + +--- + +## 39 — FUNDING-AVOID (evitare il funding: datati e spot) + +`scripts/research/r0822e_funding_avoid.py` (1.141 righe, ~45-56 s). **48 + 138 valutazioni**, nessuna +selezione (segnale e pesi **congelati**) → niente DSR. ✅ Replica: **−2,161%/anno riprodotto** e +protetto da `assert` prima di ogni delta; muri A/F **identici ai pubblicati**. + +**L'unico filone della giornata che poteva RESTITUIRE drift.** + +| variante | ShFULL | ShHOLD | drift | Δdrift | muro | +|---|---|---|---|---|---| +| **A) perp (oggi)** | 1,513 | 1,154 | 17,08% | — | **$325,0k** | +| B) tutto sui datati | 1,495 | 1,129 | 17,43% | +0,35% | — | +| D) TP01 datato + SKH01 perp | 1,507 | **1,118** | 17,53% | **+0,47%** | $319,5k | +| **E) TP01 SPOT + SKH01 perp** | **1,649** | **1,203** | 18,63% | **+1,55%** | **$289,6k (−10,9%)** | +| F) nessun carry (irreale) | 1,692 | 1,214 | 19,24% | +2,16% | $276,6k | + +📌 **La tesi condizionale REGGE ma non basta.** Funding condizionale **1,94×/2,59×** l'incondizionato +(✅ replica indipendente del 1,86-2,55× di ieri), basis solo **1,47×/1,66×**: in **livello** i due +carry sono identici (~6,4-7,4%), il datato vince **solo perche' si riprezza meno**. Ma il vantaggio e' +**tutto 2019-2023** e vale ~0 o e' avverso dal 2024 → **ShHOLD −0,034, negativo a 0/23 offset**, e il +**gate iso-volatilita' FALLISCE** (1,507 contro 1,513). + +🚨 **L'ESTIMATORE ARITMETICO INVENTA META' DELL'EFFETTO.** La media delle differenze di rendimento fra +due strumenti sullo stesso sottostante contiene `(var A − var B)/2`: qui vale **1,48% (BTC) / 2,60% +(ETH) l'anno**, cioe' **2-4× l'effetto cercato**. Controllo che lo rende visibile: il perpetual +"sovra-rende" l'indice di +0,4/+0,5%/anno con l'aritmetico e **~0 col log**. **REGOLA: il carry si +misura sul LOG del rapporto dei prezzi** — stessa forma della regola UCITS del 26/07 (*"la deviazione +fra due veicoli sullo stesso indice si misura sul rapporto cumulato, mai sulla media delle +differenze"*), ripassata in veste nuova. + +⚠️ **Bug catturato in sessione, stessa famiglia di quello di ieri sera, e NON visto dal numero:** +`fund_*.parquet` etichetta l'indice **all'istante T**, `al.get(a,'1h')` etichetta la barra **all'inizio** +→ appaiarle **sfasa di un'ora** (corr **0,996 al lag +1**, ~0 al lag 0), gonfia la sd della base da 8 a +**68-86 bps** e produceva un finto **"+0,32%/anno" nel verso atteso**. 📌 **Si e' visto dalla +VOLATILITA' (+1,07pp su una strategia identica), non dal numero** — che era plausibile e nel verso +che l'agente si aspettava. Riparato con controllo positivo che fallisce se lo si sbaglia; il pezzo +vero vale **+0,07%/anno**. + +📌 **Il cambio si giudica per SLEEVE:** SKH01 sui datati **peggiora** — turnover **48-57×/anno** contro +6,6-8,4× di TP01, quindi lo stesso mezzo spread gli costa **7 volte tanto**, e paga anche piu' carry. +Il netting TP01/SKH01 vale 1-2%: separarli non e' un argomento contro. + +🚨 **IL SOTTOPRODOTTO: lo strumento a carry ZERO esiste ed e' lo SPOT** (`BTC_USDC`/`ETH_USDC`, taker +**0,0** oggi, min 0,0001). TP01 e' long-flat e supera 1,0× nello **0,102%** dei giorni → il tetto non +morde. **+1,55%/anno di drift a volatilita' IDENTICA**, muro **−10,9%**, e supera il gate iso-vol. +⚠️ **NON e' un candidato, per tre ragioni dichiarate:** (a) **SKH01 non e' esprimibile sullo spot** +(e' long/short); (b) la **fee 0 e' dichiarata temporanea** e **`fee_watch` non sorveglia lo spot** (se +sparisse, **−13% del vantaggio**, misurato); (c) 🚨 **la domanda fiscale aperta vale PIU' del +risparmio** — se i derivati sono `c-quater` (26%) e lo spot `c-sexies` (33%), **7 punti su un CAGR del +17,9% fanno ~1,25%/anno**, cioe' **piu' di quello che lo spot porta**. + +**VERDETTO: `ESPRIMERE IL LIBRO SUI DATATI: COSTA 1,69%/ANNO CONTRO 2,16% DEL PERP → NON CONVIENE`** +(i 0,47 punti risparmiati si pagano con −0,034 di ShHOLD, negativo a 0/23 offset, e il gate iso-vol +fallisce). **SCARTATO** sui datati, **LEAD** sullo spot per la sola gamba TP01 — subordinato alla +domanda fiscale, che va posta al commercialista **prima** di qualunque prova. diff --git a/scripts/research/r0822e_funding_avoid.py b/scripts/research/r0822e_funding_avoid.py new file mode 100644 index 0000000..5a9aafb --- /dev/null +++ b/scripts/research/r0822e_funding_avoid.py @@ -0,0 +1,1141 @@ +#!/usr/bin/env python3 +"""r0822e_funding_avoid.py — FUNDING-AVOID: il costo di tenere aperta una posizione si puo' +EVITARE cambiando STRUMENTO, senza toccare il segnale? + +CONTESTO (ondata 2026-08-22, sezioni 34-35 di docs/research/RESULTS-0822.md) + (1) Stanotte e' stato MISURATO che il libro paga **2,16%/anno di funding** (TP01 -2,10%, + SKH01 -2,32%), mai modellato prima. E' una tassa sul DRIFT: il maxDD non si muove + (+0,3pp) ma la rendita perpetua scende 10,73% -> 9,14% e il muro di capitale sale del + +17,5%. Su una rendita, il drift e' la moneta. + (2) Stanotte e' stato PERSISTITO in `data/raw/fut_deribit/` lo storico dei futures DATATI + Deribit: 34 trimestrali per asset, BTC 240.150 barre orarie / ETH 233.334, 2018-09 -> + 2026-08, piu' 64.110 ore di funding e indice dal 2019-04. + +LA DOMANDA, MAI POSTA: **un future DATATO non paga funding.** Il carry e' incorporato nel +PREZZO (contango = compri sopra lo spot e convergi a scadenza). Quindi: + + esprimere TP01 (e/o SKH01) su un future datato invece che sul perpetual + costa MENO o PIU' del funding che paghiamo oggi? + +E' una domanda sull'ESECUZIONE, non sul segnale: il segnale resta congelato (TSMOM 30/90/180 +su BTC/ETH, vol-target 20%, `CANONICAL`), cambia solo lo strumento su cui lo si esprime. +Un grado di liberta' per volta, replica di controllo prima di ogni delta. + +LA TESI DA VERIFICARE O FALSIFICARE (la ragione per cui il filone puo' restituire qualcosa) + Il funding e' caro PROPRIO QUANDO SIAMO LONG: misurato stanotte, il tasso che il libro paga + davvero e' 1,86-2,55x l'incondizionato, perche' esposizione e funding sono positivamente + correlati. Il basis di un datato invece e' FISSATO ALL'INGRESSO e non si riprezza contro di + te mentre tieni. Se il rapporto condizionale/incondizionale del basis e' piu' BASSO di + quello del funding, il datato converte un costo condizionale e avverso in uno noto. + +SEZIONI + 0 IL DATO — 68 contratti trimestrali, copertura, roll; specifiche e LIQUIDITA' lette dal + venue (cache in scratchpad, `--no-net` per girare offline). + 1 REPLICA DI CONTROLLO — il -2,16%/anno va riprodotto PRIMA di confrontarci qualsiasi cosa. + 2 L'ESTIMATORE — livello vs flusso. La differenza ARITMETICA dei rendimenti fra due + strumenti sullo stesso sottostante NON e' il carry: contiene un termine di varianza + (Jensen) che qui vale 1,5-2,6 punti percentuali l'anno, cioe' 2-4x l'effetto cercato. + 3 IL CONFRONTO APPAIATO — per giorno e per segnale, condizionale vs incondizionale. + 4 IL ROLL E LA LIQUIDITA' — quante volte si rolla, quanto costa attraversare, e a $635. + 5 LE SERIE — libro nelle 5 varianti + banda d'ancora (23 offset, differenze APPAIATE). + 6 I MURI — drift -> rendita perpetua -> capitale, con la macchineria di r0726_capwall. + 7 IL MURO CHE NON C'E' — «tutti i datati Deribit sono INVERSE» e' FALSO da maggio 2026. + 8 SOTTOPRODOTTO — lo SPOT, che ha carry ZERO e oggi fee ZERO, e la sua domanda aperta. + 9 GATE + VERDETTO. + +VINCOLI: nessun file di produzione toccato (config/, src/, scripts/live/, cron INVARIATI); +nessun commit; output a video + cache in scratchpad. Runtime ~2-4 min. + +USO + nice -n 19 timeout 900 uv run python scripts/research/r0822e_funding_avoid.py + ... --no-net usa solo la cache del venue (nessuna richiesta a Deribit) +""" +from __future__ import annotations + +import datetime as dt +import json +import sys +import time +import urllib.request +from pathlib import Path + +import numpy as np +import pandas as pd + +ROOT = Path("/opt/docker/PythagorasGoal") +for _p in (ROOT / "scripts" / "research", ROOT / "scripts" / "research" / "alt", ROOT): + sys.path.insert(0, str(_p)) + +# r0822d_funding legge `--no-net` da sys.argv AL MOMENTO DELL'IMPORT: qui non si scarica il +# funding (la sua cache esiste gia'), quindi glielo si impone. Le uniche chiamate di rete di +# QUESTO script sono le sue, sotto, ed hanno il proprio flag. +_NET = "--no-net" not in sys.argv +if "--no-net" not in sys.argv: + sys.argv.append("--no-net") + +import altlib as al # noqa: E402 +import r0702_anchor_skh01 as r02 # noqa: E402 +import r0725_capcurve as CC # noqa: E402 +import r0726_capwall_refresh as WR # noqa: E402 +import r0822d_funding as FD # noqa: E402 +from src.portfolio.portfolio import combine_outer # noqa: E402 + +T0 = time.time() +ASSETS = ("BTC", "ETH") +FUT = ROOT / "data" / "raw" / "fut_deribit" +_SP = Path("/tmp/claude-1001/-opt-docker-PythagorasGoal/" + "b6cc75e7-14f8-4c32-bd07-ab8a0d2aaee6/scratchpad/fa") +SCRATCH = _SP if _SP.parent.exists() else Path("/tmp/pythagoras_favoid") +API = "https://www.deribit.com/api/v2/public/" +MON = {"JAN": 1, "FEB": 2, "MAR": 3, "APR": 4, "MAY": 5, "JUN": 6, + "JUL": 7, "AUG": 8, "SEP": 9, "OCT": 10, "NOV": 11, "DEC": 12} + +BUF_CANON = 5 # si rolla 5 giorni prima della scadenza (dichiarato a priori) +BUF_GRID = (2, 5, 10, 20) # sensibilita' del parametro d'esecuzione +ALLOC = 600.0 # il capitale su cui gira book_series (conto reale $635) +DELUCK = 0.89 # fattore d'ancora MISURATO (r0726_deluck_factor) +W_TP, W_SKH = 0.75, 0.25 +LO_BOOK = pd.Timestamp("2019-03-14", tz="UTC") +FEE_REALE = 0.00035 # taker Deribit letto dal venue (3,5 bps/lato) +# il modello di CC.tp01_realistic usa 5 bps/lato (convenzione conservativa del progetto) +FEE_MODELLO = CC.DERIBIT_FEE_SIDE + + +def sez(t: str) -> None: + print("\n" + "=" * 100) + print(f" {t}") + print("=" * 100) + + +def clock() -> str: + return f"[{time.time() - T0:5.0f}s]" + + +def expdate(nm: str) -> pd.Timestamp: + d = nm.split("-")[1] + return pd.Timestamp(dt.datetime(2000 + int(d[5:]), MON[d[2:5]], int(d[:2]), 8, 0), + tz="UTC") + + +# =========================================================================== +# 0) IL DATO +# =========================================================================== +def _pace() -> None: + """Mai nei minuti :07 (cron_book) e :25-:29 (cron_chain); <=2 req/s.""" + while dt.datetime.now(dt.timezone.utc).minute in (7, 25, 26, 27, 28, 29): + time.sleep(20) + time.sleep(0.6) + + +def _get(path: str): + _pace() + return json.load(urllib.request.urlopen(API + path, timeout=30))["result"] + + +VENUE_CACHE = SCRATCH / "venue_snapshot.json" +TICKETS = (100, 240, 500) # $ per ordine: 240 = nozionale max per asset di TP01 a $635 + + +def venue_snapshot() -> dict | None: + """Specifiche + libro degli strumenti candidati. UNA istantanea, non una distribuzione: + e' esattamente cio' che si puo' dire e va detto cosi'.""" + if VENUE_CACHE.exists(): + old = json.loads(VENUE_CACHE.read_text()) + else: + old = None + if not _NET: + return old + try: + out = {"ts": pd.Timestamp.now(tz="UTC").isoformat(), "instruments": {}, "books": {}} + for cur in ("BTC", "ETH", "USDC"): + for kind in ("future", "spot"): + try: + for x in _get(f"get_instruments?currency={cur}&kind={kind}&expired=false"): + out["instruments"][x["instrument_name"]] = dict( + settlement=x.get("settlement_currency"), + contract_size=x.get("contract_size"), + min_trade_amount=x.get("min_trade_amount"), + tick=x.get("tick_size"), taker=x.get("taker_commission"), + maker=x.get("maker_commission"), kind=kind, currency=cur, + created=x.get("creation_timestamp"), + expiry=x.get("expiration_timestamp")) + except Exception: + pass + for i in ("BTC_USDC-PERPETUAL", "BTC_USDC-25SEP26", "BTC_USDC-25DEC26", "BTC_USDC", + "BTC-25SEP26", "BTC-PERPETUAL", + "ETH_USDC-PERPETUAL", "ETH_USDC-25SEP26", "ETH_USDC-25DEC26", "ETH_USDC", + "ETH-25SEP26", "ETH-PERPETUAL"): + try: + d = _get(f"get_order_book?instrument_name={i}&depth=20") + out["books"][i] = dict(bid=d.get("best_bid_price"), ask=d.get("best_ask_price"), + index=d.get("index_price"), oi=d.get("open_interest"), + bids=d.get("bids"), asks=d.get("asks"), + vol24=(d.get("stats") or {}).get("volume"), + vol24usd=(d.get("stats") or {}).get("volume_usd")) + except Exception: + pass + SCRATCH.mkdir(parents=True, exist_ok=True) + VENUE_CACHE.write_text(json.dumps(out)) + return out + except Exception as e: # rete KO: si dichiara + print(f" ⚠️ venue non raggiungibile ({type(e).__name__}) -> uso la cache se c'e'.") + return old + + +def walk_cost_bps(book: dict, usd: float, side: str) -> float | None: + """Costo effettivo vs mid di un ordine da `usd` dollari, camminando il libro. + Ritorna None se il libro non basta (e allora si dice, non si estrapola).""" + if not book or not book.get("bid") or not book.get("ask"): + return None + mid = (book["bid"] + book["ask"]) / 2.0 + lv = book.get("asks" if side == "buy" else "bids") or [] + need, got, cost = usd / mid, 0.0, 0.0 + for p, q in lv: + take = min(q, need - got) + got += take + cost += take * p + if got >= need - 1e-12: + break + if got < need - 1e-12: + return None + eff = cost / got + return (eff / mid - 1.0) * 1e4 * (1.0 if side == "buy" else -1.0) + + +def sezione0(V: dict | None) -> dict: + sez("0) IL DATO — 68 trimestrali datati su disco, e le specifiche LETTE dal venue") + tab = {} + for a in ASSETS: + rows = [] + for p in sorted(FUT.glob(f"{a}-*.parquet")): + d = pd.read_parquet(p, columns=["ts", "close", "volume"]) + if len(d) < 200: + continue + e = expdate(p.stem) + d = d[(d.ts <= e) & (d.close > 0)] + rows.append((p.stem, e, d.ts.min(), d.ts.max(), len(d), float(d.volume.sum()))) + R = pd.DataFrame(rows, columns=["nm", "exp", "t0", "t1", "n", "vol"]).sort_values("exp") + tab[a] = R + print(f" {a}: {len(R)} contratti trimestrali usabili, scadenze da {R.exp.min().date()} " + f"a {R.exp.max().date()}, {int(R.n.sum()):,} barre orarie") + print(" ⚠️ Istantanea al 22/08 (il cron NON rinfresca questi file) e il contratto in corso") + print(" e' troncato. Il guardrail «solo dati certi» regge perche' vivono in una") + print(" sottocartella con prefisso, fuori dal namespace nudo di `load_data`.") + + if not V: + print("\n ⚠️ SPECIFICHE DEL VENUE: NON MISURATE (nessuna rete, nessuna cache).") + return dict(tab=tab, spec=None) + + print(f"\n Istantanea del venue del {V['ts'][:19]}Z — UNA lettura, non una distribuzione.") + print(f" {'strumento':<22} {'settl':>6} {'min':>9} {'tick':>7} {'taker':>7} " + f"{'spread':>8} {'vol 24h $':>13} {'OI':>14}") + for i in ("BTC_USDC-PERPETUAL", "BTC_USDC-25SEP26", "BTC_USDC-25DEC26", "BTC-25SEP26", + "BTC_USDC", "ETH_USDC-PERPETUAL", "ETH_USDC-25SEP26", "ETH_USDC-25DEC26", + "ETH-25SEP26", "ETH_USDC"): + s = V["instruments"].get(i, {}) + b = V["books"].get(i, {}) + sp = float("nan") + if b.get("bid") and b.get("ask"): + mid = (b["bid"] + b["ask"]) / 2 + sp = (b["ask"] - b["bid"]) / mid * 1e4 + print(f" {i:<22} {str(s.get('settlement')):>6} {str(s.get('min_trade_amount')):>9} " + f"{str(s.get('tick')):>7} {str(s.get('taker')):>7} {sp:>7.2f}b " + f"{(b.get('vol24usd') or float('nan')):>13,.0f} " + f"{(b.get('oi') or float('nan')):>14,.0f}") + return dict(tab=tab, spec=V) + + +# =========================================================================== +# 1) IL CARRY DEI DATATI — costruzione della serie +# =========================================================================== +def aligned_index(a: str, grid: pd.DatetimeIndex) -> np.ndarray: + """Indice Deribit ALLINEATO alla convenzione delle barre orarie. + + ⚠️ TRAPPOLA MISURATA IN QUESTA SESSIONE, e non e' un dettaglio: `fund_{a}.parquet` + etichetta il record T con l'indice ALL'ISTANTE T, mentre `al.get(a,'1h')` etichetta la + barra all'INIZIO (la sua chiusura e' il prezzo a T+1h). Appaiare le due serie per + etichetta le sfasa di UN'ORA: corr(r_perp_t, r_index_t) = -0,02 e corr(r_perp_t, + r_index_t+1) = +0,996, la base perp/indice esce con sd 68-86 bps invece di pochi bps, e + la differenza di rendimento eredita 97-122 bps/ora di rumore INVENTATO. + Nessuna eccezione, nessun NaN: due serie plausibili che non parlano dello stesso istante. + (Stessa famiglia dell'errore di RISOLUZIONE `[ms]/[us]/[s]` catturato ieri notte in + r0822d_funding, e del `DatetimeIndex.view("int64")` codificato in CLAUDE.md il 01/07: + li' era la scala, qui e' l'ETICHETTA. Il sintomo e' lo stesso — un numero credibile.) + """ + fu = pd.read_parquet(FUT / f"fund_{a}.parquet").set_index("ts").sort_index() + return fu["index"].reindex(grid).shift(-1).values.astype(float) + + +def verifica_allineamento(grid: dict) -> None: + """Controllo POSITIVO dell'allineamento: deve fallire se lo si sbaglia, o non e' un test.""" + print("\n [ALLINEAMENTO] controllo positivo indice-vs-barre (corr dei log-rendimenti):") + for a in ASSETS: + fu = pd.read_parquet(FUT / f"fund_{a}.parquet").set_index("ts").sort_index() + raw = fu["index"].reindex(grid[a]).values.astype(float) + fix = aligned_index(a, grid[a]) + pp = al.get(a, "1h").set_index("datetime")["close"].astype(float) + pp.index = pd.DatetimeIndex(pp.index) + pv = pp.reindex(grid[a]).values.astype(float) + out = [] + for lbl, v in (("per etichetta", raw), ("allineato", fix)): + rP = np.diff(np.log(pv)); rI = np.diff(np.log(v)) + m = np.isfinite(rP) & np.isfinite(rI) + b = pv / v + out.append((lbl, float(np.corrcoef(rP[m], rI[m])[0, 1]), + float(np.nanstd(b - 1.0)) * 1e4)) + print(f" {a}: per etichetta corr {out[0][1]:+.4f} sd base {out[0][2]:6.1f} bps -> " + f"allineato corr {out[1][1]:+.4f} sd base {out[1][2]:5.1f} bps") + assert out[1][1] > 0.99 > out[0][1], f"allineamento non riparato su {a}" + print(" -> il controllo ha POTENZA: con l'etichetta grezza la correlazione e' ~0.") + + +def dated_rate(a: str, grid: pd.DatetimeIndex, buf: int = BUF_CANON, + log: bool = True) -> dict: + """Pseudo-`rate` compatibile con `FD.cumF` / `FD.daily_funding`: al posto del funding + orario mette il CARRY del future datato front, cioe' quanto il datato sotto-rende il + perpetual in quell'ora (>0 in contango = costo per un long). + + Il contratto tenuto durante l'ora t e' quello scelto alla chiusura di t-1 (nessuna + decisione usa il prezzo dell'ora che si sta valutando), e nell'ora di ROLL il rendimento + lo fa il contratto VECCHIO: cosi' non si perde nessuna ora e non si incolla nessun salto + di prezzo (convenzione dichiarata in r0822d_cc01_2022: si incollano i RENDIMENTI). + """ + n = len(grid) + idxp = aligned_index(a, grid) + perp = al.get(a, "1h").set_index("datetime")["close"].astype(float) + perp.index = pd.DatetimeIndex(perp.index) + pp = perp.reindex(grid).values.astype(float) + + C = {} + for p in sorted(FUT.glob(f"{a}-*.parquet")): + d = pd.read_parquet(p, columns=["ts", "close", "volume"]) + if len(d) < 200: + continue + e = expdate(p.stem) + d = d[(d.ts <= e) & (d.close > 0)].set_index("ts").sort_index() + C[p.stem] = (e, d["close"].reindex(grid).values.astype(float), + d["volume"].reindex(grid).values.astype(float)) + order = sorted(C, key=lambda x: C[x][0]) + + px = np.full(n, np.nan); nmi = np.full(n, -1); dte = np.full(n, np.nan) + vol = np.full(n, np.nan) + for k, nme in enumerate(order): + e, s, v = C[nme] + dd = (e - grid).total_seconds().values / 86400.0 + m = (dd >= buf) & np.isfinite(s) & np.isnan(px) + px[m] = s[m]; nmi[m] = k; dte[m] = dd[m]; vol[m] = v[m] + + def ret(v: np.ndarray) -> np.ndarray: + r = np.full(n, np.nan) + r[1:] = (np.log(v[1:]) - np.log(v[:-1])) if log else (v[1:] / v[:-1] - 1.0) + return r + + rP, rF = ret(pp), ret(px) + c = np.zeros(n); val = np.zeros(n, bool) + same = np.concatenate([[False], nmi[1:] == nmi[:-1]]) + m = same & np.isfinite(rF) & np.isfinite(rP) + c[m] = rP[m] - rF[m]; val[m] = True + + roll = np.zeros(n, bool) + ch = np.where((nmi[1:] != nmi[:-1]) & (nmi[1:] >= 0) & (nmi[:-1] >= 0))[0] + 1 + for i in ch: + s = C[order[nmi[i - 1]]][1] + if np.isfinite(s[i]) and np.isfinite(s[i - 1]) and np.isfinite(rP[i]): + old = (np.log(s[i]) - np.log(s[i - 1])) if log else (s[i] / s[i - 1] - 1.0) + c[i] = rP[i] - old; val[i] = True + roll[ch] = True + + return dict(ts=FD.to_ms(grid), i1h=c, cum=np.concatenate([[0.0], np.cumsum(c)]), + series=pd.Series(c, index=grid), switch=grid[0], valid=val, roll=roll, + bann=(px / idxp - 1.0) / (dte / 365.25), dte=dte, vol=vol, grid=grid, + nmi=nmi, order=order, px=px, idx=idxp, pp=pp) + + +def spot_rate(a: str, grid: pd.DatetimeIndex, log: bool = True) -> dict: + """Stessa forma, ma il carry di esprimere la posizione sullo SPOT: quanto il perpetual + sovra-rende l'INDICE (= il costo che si evita non usando il perpetual). Lo spot vero di + Deribit non ha storia in questo progetto: si usa l'INDICE, dichiarato come tale.""" + n = len(grid) + ix = aligned_index(a, grid) + perp = al.get(a, "1h").set_index("datetime")["close"].astype(float) + perp.index = pd.DatetimeIndex(perp.index) + pp = perp.reindex(grid).values.astype(float) + + def ret(v): + r = np.full(n, np.nan) + r[1:] = (np.log(v[1:]) - np.log(v[:-1])) if log else (v[1:] / v[:-1] - 1.0) + return r + c = np.nan_to_num(ret(pp) - ret(ix)) + return dict(ts=FD.to_ms(grid), i1h=c, cum=np.concatenate([[0.0], np.cumsum(c)]), + series=pd.Series(c, index=grid), switch=grid[0], + valid=np.isfinite(c), roll=np.zeros(n, bool), grid=grid) + + +# =========================================================================== +# 2) REPLICA DI CONTROLLO +# =========================================================================== +def book(rate_or_none, off: int = 0, tp_series: pd.Series | None = None) -> pd.Series: + """Libro LIVE 75/25 alla lente `hourly`. ⚠️ `FD._SKH` e' cachato su + `(asset, off, mode, rate is not None)`: due `rate` DIVERSI collidono. Si svuota sempre.""" + FD._SKH.clear() + s = FD.skh_book(off, "hourly", rate_or_none) + if s.index.tz is None: + s.index = s.index.tz_localize("UTC") + tp = tp_series if tp_series is not None else FD.tp01_realistic(ALLOC * W_TP, + rate_or_none)[0] + return combine_outer({"TP01": tp, "SKH01": s}, {"TP01": W_TP, "SKH01": W_SKH}, lo=LO_BOOK) + + +def sezione1(rate: dict) -> dict: + sez("1) REPLICA DI CONTROLLO — il -2,16%/anno prima di confrontarci qualsiasi cosa") + a0, a1 = FD.stat(book(None)), FD.stat(book(rate)) + t0, t1 = FD.stat(FD.tp01_realistic(ALLOC * W_TP, None)[0]), \ + FD.stat(FD.tp01_realistic(ALLOC * W_TP, rate)[0]) + FD._SKH.clear(); s0 = FD.stat(FD.skh_book(0, "hourly", None)) + FD._SKH.clear(); s1 = FD.stat(FD.skh_book(0, "hourly", rate)) + print(f" {'serie':<26} {'ShFULL':>8} {'ShHOLD':>8} {'maxDD':>7} {'CAGR':>8} {'drift':>8}") + for nm, x, y in (("LIBRO 75/25 path live", a0, a1), ("TP01 realistic", t0, t1), + ("SKH01 50/50 path live", s0, s1)): + print(f" {nm:<26} {x['full']:>8.3f} {x['hold']:>8.3f} {x['dd']:>7.1%} " + f"{x['cagr']:>8.2%} {x['drift']:>8.2%}") + print(f" {' + funding':<26} {y['full']:>8.3f} {y['hold']:>8.3f} {y['dd']:>7.1%} " + f"{y['cagr']:>8.2%} {y['drift']:>8.2%}") + print(f" {' DELTA':<26} {y['full']-x['full']:>+8.3f} {y['hold']-x['hold']:>+8.3f} " + f"{y['dd']-x['dd']:>+7.1%} {y['cagr']-x['cagr']:>+8.2%} " + f"{y['drift']-x['drift']:>+8.2%}") + d = a1["drift"] - a0["drift"] + ok = abs(d + 0.0216) < 0.0005 + print(f"\n drift del libro {a0['drift']:.2%} -> {a1['drift']:.2%} = {d:+.3%}/anno " + f"contro il -2,16% pubblicato -> {'REPLICATO' if ok else '⚠️ NON REPLICA'}") + assert ok, "la replica del funding non torna: nessun delta oltre questo punto" + return dict(book0=a0, book1=a1, tp0=t0, tp1=t1, skh0=s0, skh1=s1) + + +# =========================================================================== +# 3) L'ESTIMATORE — livello vs flusso, in una veste nuova +# =========================================================================== +def sezione2(rate: dict, grid: dict) -> None: + sez("2) L'ESTIMATORE — la differenza ARITMETICA dei rendimenti NON e' il carry") + print(" Il costo di tenere un datato e' MOLTIPLICATIVO (il basis si consuma in") + print(" proporzione), il funding e' un FLUSSO di cassa (tasso x nozionale). Chi misura il") + print(" primo con una differenza aritmetica di rendimenti si porta dentro un termine di") + print(" VARIANZA: media(rA) - media(rB) = [log-drift] + (var(rA)-var(rB))/2. Fra due") + print(" strumenti sullo STESSO sottostante il log-drift e' il carry e la differenza di") + print(" varianza e' microstruttura (il datato e' meno scambiato, la sua barra oraria e'") + print(" piu' stantia, la varianza misurata piu' bassa).") + print("\n CONTROLLO che rende visibile l'artefatto senza discuterne: il PERPETUAL contro") + print(" l'INDICE. La sua base e' stazionaria intorno a ~3 bps, quindi il carry vero e' ~0.") + print(f" {'asset':>6} {'aritmetico':>12} {'log':>10} {'artefatto':>11} {'base perp media':>17}") + for a in ASSETS: + sa = spot_rate(a, grid[a], log=False)["i1h"] + sl = spot_rate(a, grid[a], log=True)["i1h"] + ix = aligned_index(a, grid[a]) + pp = al.get(a, "1h").set_index("datetime")["close"].astype(float) + pp.index = pd.DatetimeIndex(pp.index) + bas = float(np.nanmean(pp.reindex(grid[a]).values / ix - 1.0)) * 1e4 + print(f" {a:>6} {sa.mean()*24*365.25:>+11.3%} {sl.mean()*24*365.25:>+9.3%} " + f"{(sa.mean()-sl.mean())*24*365.25:>+10.3%} {bas:>16.1f}b") + print(" -> l'aritmetico attribuisce al perpetual +0,4/+0,5%/anno di 'sovra-rendimento'") + print(" su un indice da cui non si stacca mai: e' varianza, non carry. Il log da' ~0.") + print("\n Lo stesso artefatto sul DATATO, che e' l'oggetto della domanda:") + print(f" {'asset':>6} {'carry aritm.':>13} {'carry LOG':>11} {'artefatto':>11} " + f"{'funding':>9}") + for a in ASSETS: + da = dated_rate(a, grid[a], log=False) + dl = dated_rate(a, grid[a], log=True) + f = rate[a]["i1h"] + print(f" {a:>6} {da['i1h'][da['valid']].mean()*24*365.25:>+12.3%} " + f"{dl['i1h'][dl['valid']].mean()*24*365.25:>+10.3%} " + f"{(da['i1h'][da['valid']].mean()-dl['i1h'][dl['valid']].mean())*24*365.25:>+10.3%} " + f"{f.mean()*24*365.25:>+8.3%}") + print(" 🚨 L'artefatto vale 1,5-2,6 punti percentuali l'anno, cioe' DA 2 A 4 VOLTE") + print(" l'effetto che questo filone sta cercando (~0,7%/anno). Con l'estimatore") + print(" aritmetico il datato sembra costare 4,1-5,2%/anno contro un funding del") + print(" 6,4-7,3%: un vantaggio enorme, e per meta' inventato dalla formula.") + print(" 📌 REGOLA: fra due strumenti sullo stesso sottostante il carry si misura sul") + print(" LOG del rapporto dei prezzi (telescopico: cum(t) = ln(P_t/F_t) - ln(P_0/F_0)),") + print(" mai sulla media delle differenze di rendimento. E' la stessa forma dell'errore") + print(" gia' codificato il 26/07 sui veicoli UCITS («la deviazione fra due veicoli") + print(" sullo stesso indice si misura sul RAPPORTO CUMULATO»): li' erano due ETF, qui") + print(" due contratti, ed e' passato lo stesso.") + print(" Da qui in avanti: LOG. L'aritmetico non compare piu' in nessun numero.") + + +# =========================================================================== +# 4) IL CONFRONTO APPAIATO +# =========================================================================== +def sezione3(rate: dict, DR: dict) -> dict: + sez("3) IL CONFRONTO APPAIATO — per GIORNO e per SEGNALE, e la tesi condizionale") + _, dg = FD.tp01_realistic(ALLOC * W_TP, rate) + out = {} + print(" Segnale CONGELATO (stesso `pos` in entrambe le colonne): cambia solo lo strumento.") + print(f" {'asset':>5} {'esposiz.':>9} {'costo perp':>11} {'costo datato':>13} " + f"{'delta':>9} {'t':>6} {'IC95 (bootstrap)':>22}") + rng = np.random.default_rng(822) + for a in ASSETS: + idx = pd.DatetimeIndex(dg[a]["idx"]) + pos = pd.Series(dg[a]["pos"], index=idx) + fD = pd.Series(FD.daily_funding(rate[a], idx), index=idx) + cD = pd.Series(FD.daily_funding(DR[a], idx), index=idx) + m = idx >= pd.Timestamp("2019-05-01", tz="UTC") + cp, cd = (pos * fD)[m], (pos * cD)[m] + d = (cd - cp).values + t = d.mean() / d.std() * np.sqrt(len(d)) + # bootstrap a blocchi (20 giorni): il carry e' persistente, l'IID sottostima l'errore + nb = max(1, len(d) // 20) + bs = [] + for _ in range(2000): + st = rng.integers(0, max(1, len(d) - 20), nb) + bs.append(np.mean(np.concatenate([d[s:s + 20] for s in st]))) + lo, hi = np.percentile(bs, [2.5, 97.5]) * 365.25 + out[a] = dict(perp=cp.mean() * 365.25, dat=cd.mean() * 365.25, + delta=d.mean() * 365.25, t=t, lo=lo, hi=hi, pos=pos, m=m) + print(f" {a:>5} {pos[m].mean():>9.4f} {out[a]['perp']:>10.3%} {out[a]['dat']:>12.3%} " + f"{out[a]['delta']:>+8.3%} {t:>+6.2f} [{lo:>+7.3%}, {hi:>+7.3%}]") + + print("\n LA TESI: il funding e' caro PROPRIO QUANDO SIAMO LONG. Lo e' anche il basis?") + print(f" {'asset':>5} | {'funding incond':>14} {'condiz.':>9} {'rapporto':>9} " + f"| {'basis incond':>13} {'condiz.':>9} {'rapporto':>9}") + for a in ASSETS: + idx = pd.DatetimeIndex(dg[a]["idx"]) + pos, m = out[a]["pos"], out[a]["m"] + bann = pd.Series(DR[a]["bann"], index=DR[a]["grid"]).resample("1D").mean().reindex(idx) + fann = pd.Series(rate[a]["i1h"], index=rate[a]["series"].index)\ + .resample("1D").sum().reindex(idx) * 365.25 + w = pos[m] + fu_, fc = float(fann[m].mean()), float((w * fann[m]).sum() / w.sum()) + bu, bc = float(bann[m].mean()), float((w * bann[m]).sum() / w.sum()) + out[a].update(f_unc=fu_, f_con=fc, b_unc=bu, b_con=bc) + print(f" {a:>5} | {fu_:>14.2%} {fc:>9.2%} {fc/fu_:>8.2f}x " + f"| {bu:>13.2%} {bc:>9.2%} {bc/bu:>8.2f}x") + print(" 📌 LA TESI REGGE, e in modo appaiato: il funding condizionale e' 1,9-2,6x") + print(" l'incondizionato (replica indipendente del 1,86-2,55x misurato stanotte), il") + print(" basis condizionale solo 1,5-1,7x. Il datato NON e' piu' economico in assoluto") + print(" — sui livelli incondizionati i due tassi sono quasi identici (6,4-7,5% contro") + print(" 6,6-7,3%, e BASIS-CALENDAR aveva gia' misurato che lo scarto contiene lo zero)") + print(" — lo diventa CONDIZIONATAMENTE, perche' si riprezza di meno contro di noi.") + print(" Il vantaggio non e' un premio: e' la parte di premio che il perpetual") + print(" ri-tariffa OGNI ORA e il datato ha gia' fissato all'ingresso.") + + print("\n ⚠️ MA IL VANTAGGIO NON E' STAZIONARIO — delta per anno (>0 = il datato costa DI PIU'):") + print(f" {'anno':>6} {'delta BTC':>11} {'delta ETH':>11} {'funding medio':>15} " + f"{'basis medio':>13}") + for y in range(2019, 2027): + cells = [] + for a in ASSETS: + idx = pd.DatetimeIndex(dg[a]["idx"]) + pos = out[a]["pos"] + fD = pd.Series(FD.daily_funding(rate[a], idx), index=idx) + cD = pd.Series(FD.daily_funding(DR[a], idx), index=idx) + g = (pos * (cD - fD)) + g = g[g.index.year == y] + cells.append(float(g.mean()) * 365.25 if len(g) > 30 else float("nan")) + fy = np.mean([rate[a]["series"][rate[a]["series"].index.year == y].mean() + for a in ASSETS]) * 24 * 365.25 + by = np.nanmean([pd.Series(DR[a]["bann"], index=DR[a]["grid"])[ + pd.Series(DR[a]["bann"], index=DR[a]["grid"]).index.year == y].mean() + for a in ASSETS]) + print(f" {y:>6} {cells[0]:>+10.3%} {cells[1]:>+10.3%} {fy:>15.2%} {by:>13.2%}") + return out + + +# =========================================================================== +# 5) IL ROLL E LA LIQUIDITA' +# =========================================================================== +def sezione4(rate: dict, DR: dict, V: dict | None, grid: dict) -> dict: + sez("4) IL ROLL E LA LIQUIDITA' — il costo che il perpetual NON ha") + _, dg = FD.tp01_realistic(ALLOC * W_TP, rate) + print(" Il perpetuo non si rolla. Il datato si', e il roll e' FORZATO (data di scadenza),") + print(" non scelto. Quanto costa dipende da tre cose che si MISURANO: quante volte, su") + print(" quanto nozionale, e quanto costa attraversare il libro di un datato.\n") + print(f" {'asset':>5} {'roll/anno':>10} {'turnover TP01':>14} {'turnover SKH01':>15} " + f"{'nozion. medio TP01':>19}") + tov = {} + for a in ASSETS: + idx = pd.DatetimeIndex(dg[a]["idx"]); pos = dg[a]["pos"] + yrs = (idx[-1] - idx[0]).days / 365.25 + to_tp = float(np.abs(np.diff(pos, prepend=0.0)).sum()) / yrs + rl = pd.Series(DR[a]["roll"].astype(float), index=DR[a]["grid"])\ + .resample("1D").max().reindex(idx).fillna(0.0).values + FD._SKH.clear() + tr = FD.skh_daily(a, 0, "hourly", rate)[1] + yrs_s = (max(t["t_out"] for t in tr) - min(t["t_in"] for t in tr)) / 1000 / 86400 / 365.25 + to_sk = 2 * len(tr) / yrs_s + tov[a] = dict(tp=to_tp, sk=to_sk, roll_year=float(rl.sum()) / yrs, + roll_turn=float((rl * np.abs(pos)).sum()) * 2 / yrs, pos=pos, idx=idx, + rl=rl, yrs=yrs) + print(f" {a:>5} {tov[a]['roll_year']:>10.2f} {to_tp:>13.2f}x {to_sk:>14.2f}x " + f"{pos.mean()*ALLOC*W_TP/2:>18,.2f}$") + print(" (turnover = somma di |Δnozionale| per anno, in multipli del capitale d'asset)") + + print("\n QUANTO COSTA ATTRAVERSARE — libro reale, ordine da $100/$240/$500 ($240 = il") + print(" nozionale massimo per asset di TP01 a $635). UNA istantanea; se il libro non basta") + print(" si dice, non si estrapola:") + xs = {} + if V: + print(f" {'strumento':<22} " + " ".join(f"{f'${u}':>10}" for u in TICKETS)) + for i in ("BTC_USDC-PERPETUAL", "BTC_USDC-25SEP26", "BTC_USDC", + "ETH_USDC-PERPETUAL", "ETH_USDC-25SEP26", "ETH_USDC"): + b = V["books"].get(i) + cells, vals = [], [] + for u in TICKETS: + cb = walk_cost_bps(b, u, "buy"); cs = walk_cost_bps(b, u, "sell") + if cb is None or cs is None: + cells.append(f"{'LIBRO KO':>10}") + else: + v = (cb + cs) / 2 + vals.append(v); cells.append(f"{v:>9.2f}b") + xs[i] = float(np.mean(vals)) if vals else None + print(f" {i:<22} " + " ".join(cells)) + print(" ⚠️ Il datato USDC ha un libro 45x piu' sottile del perpetual USDC per volume") + print(" 24h, ma a $240 di ticket si resta dentro i primi livelli: a QUESTA taglia") + print(" la liquidita' non e' il vincolo, il MEZZO SPREAD si'.") + else: + print(" ⚠️ NON MISURATO (nessuna istantanea del venue).") + + ov = {} + if xs: + print(f"\n SOVRACOSTO D'ESECUZIONE, per sleeve e per anno (fee reale {FEE_REALE*1e4:.1f}") + print(" bps/lato su perp e datato, 0 sullo spot — letta dal venue; mezzo spread misurato):") + print(f" {'':<24} {'BTC':>12} {'ETH':>12} {'sleeve 50/50':>14}") + for lbl, ip, sfee in (("datato vs perp", "-25SEP26", FEE_REALE), + ("spot vs perp", "", 0.0)): + per = {} + for a in ASSETS: + base = xs.get(f"{a}_USDC-PERPETUAL") + alt = xs.get(f"{a}_USDC{ip}" if ip else f"{a}_USDC") + if base is None or alt is None: + per[a] = float("nan"); continue + dside = (alt * 1e-4 + sfee) - (base * 1e-4 + FEE_REALE) + extra = tov[a]["tp"] * dside + if ip: # il roll aggiunge turnover solo al datato + extra += tov[a]["roll_turn"] * (alt * 1e-4 + sfee) + per[a] = extra + ov[lbl] = per + print(f" {'TP01 ' + lbl:<24} {per['BTC']:>+11.3%} {per['ETH']:>+11.3%} " + f"{np.nanmean(list(per.values())):>+13.3%}") + per = {} + for a in ASSETS: + base, alt = xs.get(f"{a}_USDC-PERPETUAL"), xs.get(f"{a}_USDC-25SEP26") + per[a] = tov[a]["sk"] * ((alt * 1e-4 + FEE_REALE) - (base * 1e-4 + FEE_REALE)) \ + if base is not None and alt is not None else float("nan") + ov["SKH datato"] = per + print(f" {'SKH01 datato vs perp':<24} {per['BTC']:>+11.3%} {per['ETH']:>+11.3%} " + f"{np.nanmean(list(per.values())):>+13.3%}") + print(" 📌 Il turnover di SKH01 e' 48-57x l'anno contro i 6,6-8,4x di TP01: lo stesso") + print(" mezzo spread costa a SKH01 ~7 volte tanto. Un cambio di strumento si") + print(" giudica per SLEEVE, non per libro.") + + print("\n NETTING — oggi TP01 e SKH01 si compensano sullo STESSO strumento; separarli lo") + print(" perde. Quanto vale? (griglia giornaliera, approssimazione dichiarata)") + for a in ASSETS: + FD._SKH.clear() + tr = FD.skh_daily(a, 0, "hourly", rate)[1] + idx = tov[a]["idx"] + ps = pd.Series(0.0, index=idx) + for t in tr: + t0 = pd.Timestamp(t["t_in"], unit="ms", tz="UTC").floor("D") + t1 = pd.Timestamp(t["t_out"], unit="ms", tz="UTC").floor("D") + ps[(idx >= t0) & (idx <= t1)] += t["dir"] + ps = ps.clip(-1, 1) + nT = W_TP * pd.Series(tov[a]["pos"], index=idx); nS = W_SKH * ps + sep = float((nT.diff().abs().fillna(abs(nT.iloc[0])) + + nS.diff().abs().fillna(abs(nS.iloc[0]))).sum()) / tov[a]["yrs"] + net = float((nT + nS).diff().abs().fillna(abs(nT.iloc[0] + nS.iloc[0])).sum()) / tov[a]["yrs"] + print(f" {a}: turnover separato {sep:.2f}x/anno · nettato {net:.2f}x/anno -> il netting " + f"vale {1-net/sep:.1%}") + print(" -> trascurabile: i due sleeve tradano quasi sempre in momenti diversi. Mettere") + print(" TP01 e SKH01 su due strumenti NON e' un argomento contro.") + return dict(tov=tov, xs=xs, ov=ov) + + +# =========================================================================== +# 6) LE SERIE + BANDA D'ANCORA +# =========================================================================== +def overlay(series: pd.Series, tov: dict, per_asset_bps: dict, roll: bool) -> pd.Series: + """Sottrae il sovracosto d'esecuzione GIORNO PER GIORNO (|Δpos| e roll), non come media + annua: e' un costo di turnover, e il turnover non e' uniforme nel tempo.""" + tot = None + for a in ASSETS: + if not np.isfinite(per_asset_bps.get(a, float("nan"))): + return series + pos, idx, rl = tov[a]["pos"], tov[a]["idx"], tov[a]["rl"] + c = np.abs(np.diff(pos, prepend=0.0)) * per_asset_bps[a] + if roll: + c = c + 2.0 * np.abs(pos) * rl * per_asset_bps[a] + s = pd.Series(0.5 * c, index=idx) + tot = s if tot is None else tot.add(s, fill_value=0.0) + return series.sub(tot.reindex(series.index).fillna(0.0)) + + +def sezione5(rate: dict, DR: dict, SR: dict, ex: dict) -> dict: + sez("5) LE SERIE — il libro nelle varianti, netto tutto, e la banda d'ancora") + tov, xs = ex["tov"], ex["xs"] + ds = {a: (xs[f"{a}_USDC-25SEP26"] - xs[f"{a}_USDC-PERPETUAL"]) * 1e-4 + for a in ASSETS} if xs else {a: float("nan") for a in ASSETS} + ss = {a: (xs[f"{a}_USDC"] * 1e-4 + 0.0) - (xs[f"{a}_USDC-PERPETUAL"] * 1e-4 + FEE_REALE) + for a in ASSETS} if xs else {a: float("nan") for a in ASSETS} + + tp = {k: FD.tp01_realistic(ALLOC * W_TP, r)[0] for k, r in + (("nessun carry", None), ("perp", rate), ("datato", DR), ("spot", SR))} + tp["datato+esec"] = overlay(tp["datato"], tov, ds, roll=True) + tp["spot+esec"] = overlay(tp["spot"], tov, ss, roll=False) + + var = {} + var["A) perp (IL LIBRO DI OGGI)"] = book(rate) + var["B) tutto sui datati"] = book(DR) + var["C) TP01 datato + SKH01 perp"] = book(rate, tp_series=tp["datato"]) + var["D) C + sovracosto d'esecuzione"] = book(rate, tp_series=tp["datato+esec"]) + var["E) TP01 spot + SKH01 perp"] = book(rate, tp_series=tp["spot+esec"]) + var["F) nessun carry (irreale)"] = book(None) + + print(f" {'variante':<32} {'ShFULL':>8} {'ShHOLD':>8} {'maxDD':>7} {'vol':>7} " + f"{'CAGR':>8} {'drift':>8} {'Δdrift':>8}") + base = FD.stat(var["A) perp (IL LIBRO DI OGGI)"]) + st = {} + for k, s in var.items(): + m = FD.stat(s); st[k] = m + print(f" {k:<32} {m['full']:>8.3f} {m['hold']:>8.3f} {m['dd']:>7.1%} {m['vol']:>7.2%} " + f"{m['cagr']:>8.2%} {m['drift']:>8.2%} {m['drift']-base['drift']:>+8.2%}") + + print("\n 📌 QUELLO CHE LA COLONNA `Δdrift` DA SOLA NASCONDE: il datato aggiunge drift E") + print(" VOLATILITA'. Il basis non e' costante — si riprezza — e quella oscillazione") + print(" entra nel rendimento della posizione. Il funding invece e' un flusso di cassa") + print(" che NON aggiunge varianza al prezzo. Per questo lo Sharpe del libro PEGGIORA") + print(" mentre il drift migliora. Chi cita solo il drift sta vendendo un cambio di") + print(" strumento come un miglioramento quando e' uno SCAMBIO.") + + print("\n SLEEVE TP01 da solo (dove il cambio ha senso), stessa lente:") + print(f" {'':<26} {'ShFULL':>8} {'ShHOLD':>8} {'maxDD':>7} {'vol':>7} {'drift':>8}") + for k in ("nessun carry", "perp", "datato", "datato+esec", "spot", "spot+esec"): + m = FD.stat(tp[k]) + print(f" {k:<26} {m['full']:>8.3f} {m['hold']:>8.3f} {m['dd']:>7.1%} {m['vol']:>7.2%} " + f"{m['drift']:>8.2%}") + + print("\n FINESTRE — il numero va citato con la finestra (regola di stanotte):") + print(f" {'variante':<32} {'FULL':>9} {'>=2022-03':>11} {'2025+ (hold-out)':>18}") + lo1, lo2 = pd.Timestamp("2022-03-09", tz="UTC"), pd.Timestamp("2025-01-01", tz="UTC") + A = var["A) perp (IL LIBRO DI OGGI)"] + for k in ("B) tutto sui datati", "C) TP01 datato + SKH01 perp", + "D) C + sovracosto d'esecuzione", "E) TP01 spot + SKH01 perp"): + B = var[k] + cells = [] + for lo in (None, lo1, lo2): + a_, b_ = (A, B) if lo is None else (A[A.index >= lo], B[B.index >= lo]) + cells.append((float(b_.mean()) - float(a_.mean())) * 365.25) + print(f" {k:<32} {cells[0]:>+8.2%} {cells[1]:>+10.2%} {cells[2]:>+17.2%}") + + print(f"\n{clock()} BANDA D'ANCORA — 23 offset della griglia 230m/690m di SKH01, mediana") + print(" delle DIFFERENZE APPAIATE (mai la differenza delle mediane).") + bands = {} + for k, tps, rr in (("C) TP01 datato", tp["datato"], rate), + ("D) C + esecuzione", tp["datato+esec"], rate), + ("E) TP01 spot", tp["spot+esec"], rate)): + d = {"sh": [], "hold": [], "dd": [], "drift": []} + for off in r02.OFFSETS: + a_ = FD.stat(book(rate, off=off)) + b_ = FD.stat(book(rr, off=off, tp_series=tps)) + d["sh"].append(b_["full"] - a_["full"]); d["hold"].append(b_["hold"] - a_["hold"]) + d["dd"].append(b_["dd"] - a_["dd"]); d["drift"].append(b_["drift"] - a_["drift"]) + bands[k] = {m: np.array(v) for m, v in d.items()} + print(f" {k}:") + for m, lbl in (("drift", "Δdrift"), ("sh", "ΔShFULL"), ("hold", "ΔShHOLD"), + ("dd", "ΔmaxDD")): + v = bands[k][m] + flat = (v.max() - v.min()) < 1e-6 + print(f" {lbl:<9} mediana appaiata {np.median(v):>+8.4f} positiva in " + f"{np.mean(v>0):>5.0%} dei 23 offset [min {v.min():+.4f}, max {v.max():+.4f}]" + f"{' <- COSTANTE: nessuna potenza, vedi sotto' if flat else ''}") + print(" ⚠️ Il Δdrift e' COSTANTE su tutti e 23 gli offset, e non e' robustezza: e'") + print(" un'identita'. Cambia solo la gamba TP01, che non dipende dall'offset di SKH01,") + print(" e il drift di una somma pesata e' la somma pesata dei drift -> «positivo a") + print(" 23/23» qui NON dice niente. La banda ha potenza sullo SHARPE e sul maxDD (che") + print(" dipendono dalla covarianza fra le gambe), e li' il verdetto e' netto:") + print(" l'hold-out del datato e' NEGATIVO a 0/23 offset, con e senza sovracosto.") + print(" (Un controllo che non puo' fallire e' esattamente cio' che questo progetto") + print(" chiama «un test senza potenza»: si dichiara, non si conta come conferma.)") + return dict(var=var, st=st, tp=tp, bands=bands, ds=ds, ss=ss) + + +# =========================================================================== +# 7) I MURI +# =========================================================================== +def sezione6(res5: dict) -> None: + sez("6) I MURI DI CAPITALE — la moneta giusta, perche' la rendita vive sul DRIFT") + need = CC.TARGET_EUR_DAY * 365.0 * CC.EURUSD / (1 - CC.TAX_RATE) + print(f" bersaglio 50 EUR/g netti = ${need:,.0f}/anno lordi (aliquota {CC.TAX_RATE:.0%}),") + print(f" de-luck del drift x{DELUCK} (MISURATO), MC {WR.N_PATHS} path -> risoluzione ~1%:") + print(" si citano 3 cifre significative, non il dollaro.\n") + print(f" {'variante':<32} {'drift':>8} {'vol':>7} {'perpetua':>9} {'muro':>12} {'Δmuro':>8}") + ref = None + for k in ("A) perp (IL LIBRO DI OGGI)", "D) C + sovracosto d'esecuzione", + "E) TP01 spot + SKH01 perp", "F) nessun carry (irreale)"): + r = res5["var"][k].values.astype(float) + r = r - (1.0 - DELUCK) * float(r.mean()) + _, perp, cap = WR.perp_and_wall(r, 1.0, need) + if ref is None: + ref = cap + print(f" {k:<32} {float(r.mean())*365.25:>8.2%} {float(r.std())*np.sqrt(365.25):>7.2%} " + f"{perp:>9.2%} ${cap:>11,.0f} {cap/ref-1:>+7.1%}") + print("\n 📌 Il muro pubblicato stanotte e' $325,0k col funding dentro (era $276,6k senza).") + print(" Qui si misura il DELTA con la stessa macchineria, non un muro nuovo: la") + print(" differenza di livello viene dal de-luck e dal MC, non dal cambio di strumento.") + + +# =========================================================================== +# 8) IL MURO CHE NON C'E' +# =========================================================================== +def sezione7(V: dict | None) -> None: + sez("7) IL MURO CHE NON C'E' — «tutti i datati Deribit sono INVERSE» e' FALSO") + print(" La sezione 34 di RESULTS-0822 chiude cosi': «tutti i futures datati Deribit sono") + print(" INVERSE: non esiste una linea USDC datata — stesso muro delle opzioni, e il") + print(" collaterale in BTC e' esso stesso esposizione direzionale. Non risolto.»") + print(" Il briefing di questo filone lo eredita come «probabilmente il muro che uccide il") + print(" filone». E' l'unica cosa che si poteva verificare con UNA chiamata pubblica.\n") + if not V: + print(" ⚠️ NON VERIFICATO in questa corsa (nessuna rete, nessuna cache).") + return + ins = V["instruments"] + usdc_dated = sorted(n for n, s in ins.items() + if s.get("settlement") == "USDC" and s.get("kind") == "future" + and "PERPETUAL" not in n and (n.startswith("BTC_USDC") + or n.startswith("ETH_USDC"))) + print(f" MISURATO: {len(usdc_dated)} futures DATATI con settlement **USDC** su BTC/ETH.") + for n in usdc_dated: + s = ins[n] + cre = dt.datetime.utcfromtimestamp(s["created"] / 1000).date() + exp = dt.datetime.utcfromtimestamp(s["expiry"] / 1000).date() + print(f" {n:<20} creato {cre} scade {exp} min {s['min_trade_amount']} " + f"tick {s['tick']} taker {s['taker']}") + q = [n for n in usdc_dated if n.endswith(("SEP26", "DEC26", "MAR27", "JUN27"))] + print(f"\n ✅ Il muro NON esiste: le due gambe del libro possono restare in USDC. I") + print(" trimestrali USDC hanno lo STESSO min_trade_amount del perpetual lineare") + print(" (0,0001 BTC ~ $7,7 · 0,001 ETH ~ $2,4) e la STESSA fee taker: a $635") + print(" l'eseguibilita' non e' il vincolo (6a volta in questa ondata).") + print(" ⚠️ E il collaterale in cripto non era nemmeno l'argomento giusto: se lo fosse") + print(" stato, il costo non sarebbe il carry ma il fatto che un conto denominato in") + print(" BTC e' long 1,0x anche da FLAT — e TP01 sta flat nel 44% dei giorni, cioe' si") + print(" sarebbe distrutto esattamente cio' per cui TP01 esiste (il taglio del DD).") + print("\n ⚠️ IL MURO VERO E' UN ALTRO, ed e' una DATA: la linea USDC datata e' nata il") + print(f" 2026-05-07 ({len(q)} trimestrali esistono, il secondo creato il 2026-08-06)") + print(" -> ~3,5 mesi di vita e ZERO roll trimestrali mai eseguiti su quella linea.") + print(" Tutta la misura di questo filone gira sui trimestrali INVERSE, che hanno 7,3") + print(" anni: e' un PROXY, esattamente come l'inverse lo era per il funding pre-2022.") + print(" ✅ E il proxy si VALIDA invece di assumerlo — i due contratti sulla stessa") + print(" scadenza devono prezzare lo stesso carry, e lo fanno:") + for a in ASSETS: + bi, bu = V["books"].get(f"{a}-25SEP26"), V["books"].get(f"{a}_USDC-25SEP26") + if not bi or not bu or not bi.get("index"): + continue + mi = (bi["bid"] + bi["ask"]) / 2; mu = (bu["bid"] + bu["ask"]) / 2 + print(f" {a}-25SEP26 (inverse) base {(mi/bi['index']-1)*1e4:+.1f} bps vs " + f"{a}_USDC-25SEP26 (lineare) base {(mu/bu['index']-1)*1e4:+.1f} bps " + f"-> scarto {(mu/bu['index']-mi/bi['index'])*1e4:+.1f} bps") + print(" 📌 REGOLA: un muro ereditato da un'altra sessione e' un'IPOTESI, e prima di") + print(" costruirci sopra un verdetto si ri-verifica. Qui e' costato una GET pubblica,") + print(" e la frase era diventata falsa 3 mesi prima che venisse scritta.") + print(" (Stessa forma della lezione del 26/07: «un blocco dichiarato e' un'ipotesi».)") + + +# =========================================================================== +# 9) SOTTOPRODOTTO — LO SPOT +# =========================================================================== +def sezione8(V: dict | None, res5: dict, rate: dict, SR: dict) -> None: + sez("8) SOTTOPRODOTTO — lo strumento a carry ZERO esiste, si chiama SPOT") + print(" La domanda del filone era «datato o perpetual». Cercando le specifiche e' saltato") + print(" fuori il terzo: Deribit quota SPOT BTC_USDC/ETH_USDC. Uno spot non paga funding,") + print(" non ha basis e non si rolla: il suo carry e' ZERO per costruzione.") + if V: + for i in ("BTC_USDC", "ETH_USDC"): + s = V["instruments"].get(i, {}) + b = V["books"].get(i, {}) + sp = (b["ask"] - b["bid"]) / ((b["ask"] + b["bid"]) / 2) * 1e4 if b.get("bid") else float("nan") + print(f" {i:<10} min {s.get('min_trade_amount')} taker {s.get('taker')} " + f"maker {s.get('maker')} spread {sp:.2f} bps vol 24h " + f"${(b.get('vol24usd') or float('nan')):,.0f}") + print(" ⚠️ La fee ZERO e' quella di OGGI ed e' dichiarata temporanea nell'annuncio del") + print(" 1° agosto («spot a zero fino al collegamento con Coinbase»): e' un tier che") + print(" puo' sparire, e `fee_watch` sorveglia i perpetual, non lo spot.") + print("\n Perche' funziona SOLO su TP01, e la misura che lo dimostra:") + _, dg = FD.tp01_realistic(ALLOC * W_TP, rate) + for a in ASSETS: + pos = dg[a]["pos"] + print(f" {a}: TP01 e' long-flat e il suo target per asset supera 1,0x nel " + f"{np.mean(pos > 1.0):.3%} dei giorni (massimo {pos.max():.3f}) -> il tetto") + print(f" strutturale dello spot (niente leva, niente short) e' quasi mai binding.") + print(" SKH01 e' long/SHORT a nozionale 1,0x: sullo spot non e' esprimibile. Meta' dei") + print(" suoi trade sono short e li' lo spot non esiste come strumento.") + print("\n DECOMPOSIZIONE del vantaggio dello spot su TP01 — due pezzi, non uno:") + mnc = FD.stat(res5["tp"]["nessun carry"]); msp = FD.stat(res5["tp"]["spot"]) + mpe = FD.stat(res5["tp"]["perp"]) + print(f" (1) funding evitato {mnc['drift']-mpe['drift']:>+7.2%}/anno " + f"(perp {mpe['drift']:.2%} -> senza-carry {mnc['drift']:.2%})") + print(f" (2) base del perpetual {msp['drift']-mnc['drift']:>+7.2%}/anno " + f"(senza-carry {mnc['drift']:.2%} -> indice {msp['drift']:.2%})") + print(" ❌ IPOTESI MIA, NATA E REFUTATA NELLA STESSA SESSIONE: mi aspettavo che il") + print(" secondo pezzo fosse una TERZA conferma della tesi condizionale — «quando siamo") + print(" long il perpetual e' a premio e quel premio decade». La prima stesura lo") + print(" misurava a +0,32%/anno e ci avrei creduto: era il MISALLINEAMENTO di un'ora") + print(" fra indice e barre (vedi [ALLINEAMENTO] in testa). Allineato, il pezzo vale") + print(" +0,07%/anno: il VERSO e' quello della tesi e l'IC95 esclude lo zero per un") + print(" soffio su entrambi gli asset, ma la TAGLIA e' il 3% del pezzo (1). Un verso") + print(" giusto a un trentesimo della grandezza non e' una conferma, e' un residuo:") + print(" tutto il vantaggio dello spot e' il funding evitato, e va detto cosi'.") + print(" 📌 E il modo in cui l'artefatto si e' fatto vedere e' la cosa da ricordare: non") + print(" dal numero, che era plausibile e nel verso atteso, ma dalla VOLATILITA' — la") + print(" variante spot mostrava +1,07pp di vol su una strategia identica, e uno strumento") + print(" che traccia lo stesso sottostante non puo' aggiungere un punto di vol.") + rngz = np.random.default_rng(823) + for a in ASSETS: + idx = pd.DatetimeIndex(dg[a]["idx"]); pos = pd.Series(dg[a]["pos"], index=idx) + c = pd.Series(FD.daily_funding(SR[a], idx), index=idx) + d = (pos * c).values + nb = max(1, len(d) // 20) + bs = [np.mean(np.concatenate([d[x:x+20] for x in + rngz.integers(0, max(1, len(d)-20), nb)])) + for _ in range(1000)] + lo, hi = np.percentile(bs, [2.5, 97.5]) * 365.25 + print(f" {a}: base del perpetual pagata da TP01 {d.mean()*365.25:+.3%}/anno " + f"IC95 a blocchi [{lo:+.3%}, {hi:+.3%}]") + + m0 = FD.stat(res5["tp"]["perp"]); m1 = FD.stat(res5["tp"]["spot+esec"]) + print(f"\n TP01: drift {m0['drift']:.2%} -> {m1['drift']:.2%} ({m1['drift']-m0['drift']:+.2%}/anno), " + f"Sharpe {m0['full']:.3f} -> {m1['full']:.3f}, maxDD {m0['dd']:.1%} -> {m1['dd']:.1%}") + print(" 📌 E' l'unica variante che alza il drift SENZA alzare la volatilita' (vol di TP01") + print(" 12,16% sul perpetual e 12,16% sullo spot, alla terza cifra): lo spot non ha una") + print(" base che si riprezza. Il datato invece scambia carry contro varianza, ed e' per") + print(" questo che il suo Sharpe non migliora mentre il suo drift si'.") + print("\n ⚠️ MA C'E' UNA DOMANDA APERTA CHE VALE PIU' DEL RISPARMIO, e non e' risolvibile") + print(" con un backtest. CLAUDE.md la registra gia' (07/08): i DERIVATI su cripto") + print(" potrebbero qualificarsi `c-quater` (26%) invece di `c-sexies` (33%). Se e'") + print(" cosi', spostare TP01 dal perpetual (derivato) allo SPOT (cripto-attivita')") + print(" sposta il suo P&L nel comparto al 33%.") + cagr = FD.stat(res5["var"]["A) perp (IL LIBRO DI OGGI)"])["cagr"] + print(f" Ordine di grandezza: 7 punti di aliquota su un CAGR di libro del {cagr:.1%} " + f"valgono ~{0.07*cagr:.2%}/anno,") + print(" cioe' PIU' del risparmio di carry che lo spot porta. La risposta e' del") + print(" commercialista, non del backtest — ed e' esattamente la stessa domanda gia'") + print(" aperta nel progetto, che ora ha un secondo motivo per essere posta.") + if V: + xs = {} + for i in (f"{a}_USDC" for a in ASSETS): + xs[i] = np.mean([x for x in (walk_cost_bps(V["books"].get(i), u, sd) + for u in TICKETS for sd in ("buy", "sell")) + if x is not None]) if V["books"].get(i) else float("nan") + for i in (f"{a}_USDC-PERPETUAL" for a in ASSETS): + xs[i] = np.mean([x for x in (walk_cost_bps(V["books"].get(i), u, sd) + for u in TICKETS for sd in ("buy", "sell")) + if x is not None]) if V["books"].get(i) else float("nan") + _, dgz = FD.tp01_realistic(ALLOC * W_TP, rate) + now, later = [], [] + for a in ASSETS: + to = float(np.abs(np.diff(dgz[a]["pos"], prepend=0.0)).sum()) / \ + ((pd.DatetimeIndex(dgz[a]["idx"])[-1] + - pd.DatetimeIndex(dgz[a]["idx"])[0]).days / 365.25) + base = xs[f"{a}_USDC-PERPETUAL"] * 1e-4 + FEE_REALE + now.append(to * (xs[f"{a}_USDC"] * 1e-4 + 0.0 - base)) + later.append(to * (xs[f"{a}_USDC"] * 1e-4 + FEE_REALE - base)) + print(f"\n ⚠️ E se la fee spot smettesse di essere zero: il sovracosto d'esecuzione di") + print(f" TP01 passa da {np.mean(now):+.3%}/anno a {np.mean(later):+.3%}/anno, cioe' " + f"mangia il {(np.mean(later)-np.mean(now))/0.0207:.0%} del vantaggio") + print(f" (che a livello di sleeve vale ~2,07%/anno). Sopravviverebbe, ma va") + print(f" sorvegliato: `fee_watch` oggi guarda i perpetual, non lo spot.") + print(" ⚠️ Secondo limite dichiarato: qui lo spot e' modellato con l'INDICE Deribit, non") + print(" col libro spot vero (che non ha storia in questo progetto). Sul carry e'") + print(" corretto per costruzione (l'indice E' il sottostante); sull'esecuzione no, e") + print(" infatti il mezzo spread e' preso dall'istantanea del libro vero.") + + +# =========================================================================== +# 10) GATE +# =========================================================================== +def sezione9(rate: dict, DR: dict, res5: dict, grid: dict) -> None: + sez("9) GATE — esito di ciascuno; quelli non girati sono DICHIARATI, non omessi") + + # causalita': controllo POSITIVO (un test che non puo' fallire non e' un test) + a = "BTC" + _, dg = FD.tp01_realistic(ALLOC * W_TP, rate) + idx = pd.DatetimeIndex(dg[a]["idx"]); pos = pd.Series(dg[a]["pos"], index=idx) + c = pd.Series(FD.daily_funding(DR[a], idx), index=idx) + base = float((pos * c).mean()) * 365.25 + ahead = float((pos * c.shift(-1).fillna(0.0)).mean()) * 365.25 + lag = float((pos * c.shift(1).fillna(0.0)).mean()) * 365.25 + print(f" causality : il carry dell'ora t si applica alla posizione tenuta in t,") + print(f" e il contratto e' scelto alla chiusura di t-1. Controllo") + print(f" POSITIVO: usando il carry di DOMANI {ahead:.3%} e di IERI") + print(f" {lag:.3%} contro {base:.3%} -> il test ha POTENZA " + f"({abs(ahead-base)*1e4:.0f}/{abs(lag-base)*1e4:.0f} bps di scarto).") + + # sensibilita' al parametro d'esecuzione (griglia dichiarata) + print(f" griglia del roll : BUF in {BUF_GRID} giorni (parametro d'ESECUZIONE, e un") + print(" parametro d'esecuzione scelto guardando il risultato e'") + print(" selezione come ogni altra — regola 27/07). Canonico BUF=5") + print(" dichiarato PRIMA. Δdrift di libro per cella:") + for b in BUF_GRID: + DRb = {x: dated_rate(x, grid[x], buf=b) for x in ASSETS} + tpb = FD.tp01_realistic(ALLOC * W_TP, DRb)[0] + d = (float(book(rate, tp_series=tpb).mean()) + - float(res5["var"]["A) perp (IL LIBRO DI OGGI)"].mean())) * 365.25 + print(f" BUF={b:>2}g -> Δdrift {d:>+7.3%}" + f"{' <- canonico' if b == BUF_CANON else ''}") + print(" -> piatto: la scelta non e' dove vive il risultato.") + + # de-levering null + A = res5["var"]["A) perp (IL LIBRO DI OGGI)"] + D = res5["var"]["D) C + sovracosto d'esecuzione"] + sa, sd = FD.stat(A), FD.stat(D) + k = sa["vol"] / sd["vol"] + rk = D.values.astype(float) * k + shk = float(np.mean(rk) / np.std(rk) * np.sqrt(365.25)) + print(f" null del de-levering : NON APPLICABILE nella forma consueta (nessun claim di") + print(f" «meno drawdown»: il maxDD si muove di {sd['dd']-sa['dd']:+.2%}).") + print(f" Girato il suo SPECULARE — a ISO-VOLATILITA' il candidato") + print(f" deve restare migliore: k={k:.3f} -> Sharpe {shk:.3f} contro") + print(f" {sa['full']:.3f} del baseline -> " + f"{'PASSA' if shk > sa['full'] else 'FALLISCE'} (il datato compra drift PAGANDOLO in") + print(" varianza, ed e' esattamente quello che il gate deve vedere).") + + E = res5["var"]["E) TP01 spot + SKH01 perp"] + se = FD.stat(E) + ke = sa["vol"] / se["vol"] + rke = E.values.astype(float) * ke + she = float(np.mean(rke) / np.std(rke) * np.sqrt(365.25)) + print(f" Stesso gate sulla variante SPOT: k={ke:.3f} -> Sharpe " + f"{she:.3f} contro {sa['full']:.3f} -> " + f"{'PASSA' if she > sa['full'] else 'FALLISCE'} (lo spot alza il drift senza") + print(" comprare varianza: e' l'unica delle tre che sopravvive).") + + ns = int(sum(len(FD.skh_daily(x, 0, "hourly", rate)[1]) for x in ASSETS)) + nl = int(sum(np.sum(np.array([t["gross"] for t in FD.skh_daily(x, 0, "hourly", rate)[1]]) <= 0) + for x in ASSETS)) + ip = al.implausible_sharpe(D, n_trades=ns, n_losing_trades=nl) + print(f" implausible_sharpe : implausible={ip['implausible']} (Sharpe {ip['sharpe']:.2f}, " + f"maxDD {ip['maxdd']:.1%}, Calmar {ip['calmar']:.2f}) -> {ip['reasons'] or 'nessuna firma'}") + print(f" anchor_luck (banda) : GIRATO, 23 offset, mediana delle differenze APPAIATE " + f"(sez. 5).") + print(" replica bit-exact : PASSATA — il -2,16%/anno del funding e' riprodotto in sez. 1") + print(" e l'assert ferma lo script se non torna.") + print(" marginal_vs_tp01 / deflated_sharpe / study_family_honest / weights_tilt_null:") + print(" NON GIRATI, e non per budget. Qui non c'e' un CANDIDATO:") + print(" il segnale e' congelato, i pesi non si toccano, non c'e'") + print(" una famiglia da cui selezionare. C'e' un COSTO che si") + print(" sposta cambiando strumento. Un `marginal_vs_tp01` su TP01") + print(" eseguito diversamente misurerebbe TP01 contro se stesso.") + + +def main() -> None: + print("=" * 100) + print("r0822e — FUNDING-AVOID: il 2,16%/anno si evita cambiando strumento?") + print(f"branch research/wave-0822 · {pd.Timestamp.now(tz='UTC')} · rete={'SI' if _NET else 'NO'}") + print("=" * 100) + print(" IPOTESI A PRIORI, registrata prima di misurare: il basis di un datato e il funding") + print(" di un perpetual sono lo STESSO premio (arbitraggio), quindi in livello devono") + print(" costare uguale; se il datato vince e' solo perche' il funding si riprezza contro") + print(" chi e' long e il basis no. Attesa: differenza piccola, forse nulla, e comunque") + print(" mangiata dal roll o dall'ineseguibilita'.") + + V = venue_snapshot() + d0 = sezione0(V) + + F = {} + for instr, (start, asset, kind) in FD.INSTR.items(): + d = FD.fetch_funding(instr, start).copy() + d["dt"] = pd.to_datetime(d["ts"], unit="ms", utc=True) + F[instr] = d.drop_duplicates(subset="ts").sort_values("dt").set_index("dt") + rate = FD.build_rate(F) + grid = {a: pd.DatetimeIndex(rate[a]["series"].index) for a in ASSETS} + + verifica_allineamento(grid) + + print(f"\n{clock()} sezione 1...") + sezione1(rate) + + DR = {a: dated_rate(a, grid[a], buf=BUF_CANON, log=True) for a in ASSETS} + SR = {a: spot_rate(a, grid[a], log=True) for a in ASSETS} + print(f"\n copertura del front datato sulla griglia del funding: " + " · ".join( + f"{a} {DR[a]['valid'].mean():.4f}" for a in ASSETS)) + + print(f"\n{clock()} sezione 2...") + sezione2(rate, grid) + print(f"\n{clock()} sezione 3...") + sezione3(rate, DR) + print(f"\n{clock()} sezione 4...") + ex = sezione4(rate, DR, V, grid) + print(f"\n{clock()} sezione 5...") + res5 = sezione5(rate, DR, SR, ex) + print(f"\n{clock()} sezione 6...") + sezione6(res5) + sezione7(V) + sezione8(V, res5, rate, SR) + print(f"\n{clock()} sezione 9...") + sezione9(rate, DR, res5, grid) + + sez("SINTESI") + A = FD.stat(res5["var"]["A) perp (IL LIBRO DI OGGI)"]) + for k in ("B) tutto sui datati", "C) TP01 datato + SKH01 perp", + "D) C + sovracosto d'esecuzione", "E) TP01 spot + SKH01 perp"): + m = res5["st"][k] + print(f" {k:<32} Δdrift {m['drift']-A['drift']:>+7.2%} ΔShFULL " + f"{m['full']-A['full']:>+7.3f} ΔShHOLD {m['hold']-A['hold']:>+7.3f} " + f"ΔmaxDD {m['dd']-A['dd']:>+6.2%}") + c_perp = res5["st"]["F) nessun carry (irreale)"]["drift"] - A["drift"] + print(f"\n costo del carry per il libro, sul perpetual: {c_perp:.2%}/anno " + f"(il -2,16% replicato)") + dD = res5["st"]["D) C + sovracosto d'esecuzione"]["drift"] - A["drift"] + tot = c_perp - dD + print(f" ... e su TP01-datato/SKH01-perp, tutto compreso (carry + mezzo spread + roll):" + f" {tot:.2%}/anno") + mD = res5["st"]["D) C + sovracosto d'esecuzione"] + print(f" risparmio netto: {dD:+.2%}/anno di drift, pagato con " + f"{mD['full']-A['full']:+.3f} di Sharpe FULL e {mD['hold']-A['hold']:+.3f} di hold-out.") + D_ = res5["st"]["D) C + sovracosto d'esecuzione"] + E_ = res5["st"]["E) TP01 spot + SKH01 perp"] + costo_dat = (c_perp - dD) * 100 + sez("VERDETTO") + print(" IPOTESI: esprimere il segnale congelato su un future DATATO invece che sul") + print(" perpetual costa meno del funding, perche' il basis e' fissato all'ingresso") + print(" mentre il funding si riprezza contro chi e' long. Attesa PRIMA di misurare:") + print(" differenza piccola o nulla, e comunque mangiata dal roll o dall'ineseguibilita'.") + print(" GRIGLIA DICHIARATA (al RIALZO): 2 estimatori (aritmetico/log) x 4 buffer di roll") + print(" (2/5/10/20 g) x 3 strumenti (perp/datato/spot) x 2 sleeve = 48 valutazioni di") + print(" carry; 6 varianti di libro x 23 offset d'ancora = 138 valutazioni di libro;") + print(" 4 muri x 26 passi di bisezione x 2.000 path. NESSUNA e' una SELEZIONE: il") + print(" segnale e i pesi sono congelati, non c'e' una cella scelta guardando il") + print(" risultato -> nessun deflated-Sharpe da calcolare.") + print(f" NUMERI (netti fee, a $600 di allocazione, lente `hourly` del libro live):") + print(f" A) perpetual, oggi Sh {A['full']:.3f} / hold {A['hold']:.3f} / " + f"maxDD {A['dd']:.1%} / CAGR {A['cagr']:.2%} / drift {A['drift']:.2%}") + print(f" D) TP01 datato, tutto dentro Sh {D_['full']:.3f} / hold {D_['hold']:.3f} / " + f"maxDD {D_['dd']:.1%} / CAGR {D_['cagr']:.2%} / drift {D_['drift']:.2%}") + print(f" E) TP01 spot, tutto dentro Sh {E_['full']:.3f} / hold {E_['hold']:.3f} / " + f"maxDD {E_['dd']:.1%} / CAGR {E_['cagr']:.2%} / drift {E_['drift']:.2%}") + print(f" haircut a $600: gia' dentro (le serie girano a $600 con min-order $5); il") + print(f" ticket massimo per asset e' $240 e resta nei primi livelli del libro datato.") + print(" VERDETTO: **SCARTATO** per i datati (il risparmio di drift si paga in varianza:") + print(f" hold-out {D_['hold']-A['hold']:+.3f}, negativo a 0/23 offset, e il gate") + print(" iso-volatilita' fallisce) — **LEAD** per lo SPOT su TP01, con la domanda") + print(" fiscale aperta che vale piu' del risparmio.") + print(" COSA MI SMENTIREBBE: (a) sui datati — un campione in cui il basis condizionale") + print(" NON si riprezza meno del funding, cioe' un regime in cui l'hold-out smette di") + print(" essere negativo: qui il vantaggio e' tutto 2019-2023 e vale ~0 dal 2024;") + print(" (b) sullo spot — che il fisco tratti i derivati come le cripto-attivita' (33%") + print(" entrambi): allora il LEAD diventa un candidato d'esecuzione; oppure che la fee") + print(" spot 0 sparisca col collegamento Coinbase (misurato in sez. 8: il vantaggio") + print(" si riduce ma non si annulla);") + print(" (c) su entrambi — che il mio proxy inverse per il carry dei datati non valga:") + print(" si smentisce raccogliendo qualche mese di quote della linea USDC datata e") + print(" confrontando i basis, oggi a 0,4-0,9 bps l'uno dall'altro.") + print(" SCRIPT: scripts/research/r0822e_funding_avoid.py") + print() + print(f"ESPRIMERE IL LIBRO SUI DATATI: COSTA {costo_dat:.2f} %/ANNO CONTRO 2,16% DEL PERP " + f"-> NON CONVIENE, perche' i {dD*100:.2f} punti risparmiati sul drift si pagano con " + f"{D_['hold']-A['hold']:+.3f} di Sharpe hold-out (negativo a 0/23 offset d'ancora) e " + f"il gate iso-volatilita' fallisce; NON e' INESEGUIBILE (il muro «tutti i datati " + f"sono INVERSE» e' FALSO: 16 datati USDC esistono da 2026-05-07, min-order e fee " + f"identici al perpetual) ma la linea USDC ha 3,5 mesi di vita e zero roll mai " + f"eseguiti.") + print(f"\n{clock()} fine.") + + +if __name__ == "__main__": + main()