ricerca: XSR01 sotto la lente RENDITA (filone 70) + cosa compra un versamento da $3k
XSR-RENDITA (r0825_xsr_rendita.py): prima valutazione di XSR01 sul criterio della perpetua. A iso-nozionale alza il muro; a iso-rischio lo abbassa del 20,6% MA il null mostra che il meccanismo vale 0,8% (mescolare i rendimenti non cambia nulla) e un conto remunerato allo stesso tasso lo eguaglia a vol zero senza secondo venue. A drift zero il muro SALE: si compra un drift scorrelato, non la scorrelazione. L'haircut non pareggia un conto al 4% nemmeno a zero. Vincolo binding: capitale ($60k per un 25% sopra C*). Corretta in CLAUDE.md la riga Sharpe 1,82 (terza lente; la lente dei gate da' 1,79 alla scoperta / 1,56-1,63 a oggi). VERSAMENTO-3K (r0825_versamento_3k.py): $2.065 -> $5.065 appaiato sugli stessi path del piano = 5,5 mesi di versamenti anticipati; 10a $114.929 -> $122.491 (+6,6%); $15k in 1,3a e $20k in 1,9a. P(cap >= $5k al gate XSR01 del 23/10): 0% -> 100% -- il lump rende il gate leggibile senza rendere XSR01 comprabile: la decisione sulle soglie (S5.3) va presa PRIMA che il lump atterri. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01B9UyJLHzR7EJzxR3iQ3RN1
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
"""r0825_versamento_3k.py — cosa compra un versamento di $3.000 ADESSO, dal conto vero.
|
||||
|
||||
DOMANDA (operatore, 2026-08-25 sera): *"rianalizza e vediamo come procedere. potrei versare a
|
||||
breve altri 3k"*. Il conto passerebbe da ~$2.065 a ~$5.065 — esattamente a cavallo della soglia
|
||||
$5k su cui sta scritto il criterio di capitale del gate XSR01 (23/10), e a un terzo del muro
|
||||
$15-20k che e' insieme il C* vero di Hyperliquid E la soglia della decisione vincolante
|
||||
"100% Deribit fino a $20k" (26/07).
|
||||
|
||||
ASSUNZIONI DICHIARATE:
|
||||
- "3k" = 3.000 USDC = $3.000 (i versamenti precedenti dell'operatore sono in USDC);
|
||||
- il lump atterra OGGI. "A breve" puo' voler dire fra settimane: ritardarlo di un mese sposta
|
||||
i numeri di poco e sempre nello stesso verso (un po' meno beneficio). Non lo modello.
|
||||
- equity di partenza: 2.065,00 — letta dal watermark alle 18:47:13Z di oggi (l'istante
|
||||
dell'evento, non l'istante della scrittura).
|
||||
|
||||
LENTE: L3 CONGIUNTA (funding esatto + fisco d'accumulo + ancora de-luckata), la stessa di
|
||||
`r0825_piano_10a_500` — stessi semi, stessi path: ogni confronto qui e' APPAIATO (M22).
|
||||
⚠️ Stessi difetti ereditati e dichiarati la': 121 versamenti in 10 anni, `versato` scalare.
|
||||
⚠️ M23: N_PATHS 3000, blocchi 20g. La banda del muro resta [$187k – $1,14M]: i numeri assoluti
|
||||
qui sotto stanno sulla lente `hourly`+L3 e vanno letti con quella etichetta (P7).
|
||||
|
||||
uv run python scripts/research/r0825_versamento_3k.py
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
sys.path.insert(0, str(ROOT))
|
||||
sys.path.insert(0, str(ROOT / "scripts" / "research"))
|
||||
|
||||
import r0725_capcurve as CC # noqa: E402
|
||||
import r0807_piano_netto as PN # noqa: E402
|
||||
import r0822d_piano_vero as PV # noqa: E402
|
||||
import r0823_deposit_timing as DT # noqa: E402
|
||||
|
||||
START_OGGI = 2065.00 # watermark 2026-08-25T18:47:13Z
|
||||
LUMP_USD = 3000.0 # il versamento ipotizzato, in USD
|
||||
START_LUMP = START_OGGI + LUMP_USD
|
||||
DEP = 500.0 # EUR/mese, il piano dichiarato
|
||||
ORIZZONTI = (5, 10, 15, 20)
|
||||
N_PATHS = 3000
|
||||
SEED = PV.SEED_TRAJ
|
||||
BLOCK = PV.BLOCK
|
||||
GATE_XSR = 59 # giorni da oggi al 2026-10-23 (25/08 -> 23/10)
|
||||
MILESTONE = (5_000.0, 15_000.0, 20_000.0) # criterio gate · C* Hyperliquid · muro "100% Deribit"
|
||||
|
||||
|
||||
def sez(t: str) -> None:
|
||||
print(f"\n{'='*100}\n {t}\n{'='*100}")
|
||||
|
||||
|
||||
def eur_g(cap: np.ndarray, perp: float) -> np.ndarray:
|
||||
return cap * perp / 365.0 / CC.EURUSD
|
||||
|
||||
|
||||
def main() -> None:
|
||||
print("=" * 100)
|
||||
print(f" r0825 — IL VERSAMENTO DA $3.000: ${START_OGGI:,.0f} -> ${START_LUMP:,.0f}")
|
||||
print("=" * 100)
|
||||
|
||||
B = PV.costruisci_serie()
|
||||
r = PV.deluck(B["fund"].values.astype(float))
|
||||
lente = PV.Lente("L3 congiunta", r, lordizza=False)
|
||||
lente.perp, lente.muro = PV.muro_di(lente)
|
||||
print(f"\n lente L3: drift {lente.drift:.2%} · vol {lente.vol:.2%} · perpetua {lente.perp:.2%}"
|
||||
f" · muro ${lente.muro:,.0f}")
|
||||
|
||||
# ---------------------------------------------------- (1) cosa compra il lump, appaiato
|
||||
sez(f"(1) COSA COMPRA IL LUMP — €{DEP:.0f}/mese, stessi path, da ${START_OGGI:,.0f} vs "
|
||||
f"${START_LUMP:,.0f}")
|
||||
print(f" {'oriz.':>6} {'senza lump':>13} {'con lump':>13} {'guadagno':>10} "
|
||||
f"{'€/g senza':>11} {'€/g con':>9} {'P(50) senza':>12} {'P(50) con':>10}")
|
||||
print(" " + "-" * 92)
|
||||
d10 = {}
|
||||
for anni in ORIZZONTI:
|
||||
rng = np.random.default_rng(SEED)
|
||||
ii = DT.boot_idx(len(r), N_PATHS, anni * 365, BLOCK, rng)
|
||||
paths = r[ii]
|
||||
a = PN.accumula(paths, DEP, lente.muro, lente.aliq, lente.patr, start=START_OGGI)
|
||||
b = PN.accumula(paths, DEP, lente.muro, lente.aliq, lente.patr, start=START_LUMP)
|
||||
ea, eb = eur_g(a["cap"], lente.perp), eur_g(b["cap"], lente.perp)
|
||||
pa, pb = float((ea >= 50).mean()), float((eb >= 50).mean())
|
||||
if anni == 10:
|
||||
d10 = dict(a=a, b=b, ea=ea, eb=eb, pa=pa, pb=pb)
|
||||
print(f" {anni:>4}a ${np.median(a['cap']):>11,.0f} ${np.median(b['cap']):>11,.0f} "
|
||||
f"{np.median(b['cap'])/np.median(a['cap'])-1:>9.1%} {np.median(ea):>10.2f} "
|
||||
f"{np.median(eb):>8.2f} {pa:>11.1%} {pb:>9.1%}")
|
||||
lump_in_mesi = LUMP_USD / CC.EURUSD / DEP
|
||||
print(f"\n il lump equivale a {lump_in_mesi:.1f} mesi di versamenti anticipati in un colpo.")
|
||||
print(f" N3: la leva 'versare' resta la prima — questo lump E' quella leva, esercitata.")
|
||||
|
||||
# ---------------------------------------------------- (2) pietre miliari
|
||||
sez("(2) QUANDO ARRIVANO LE PIETRE MILIARI (€500/mese, primo passaggio, 10 anni di path)")
|
||||
rng = np.random.default_rng(SEED)
|
||||
ii = DT.boot_idx(len(r), N_PATHS, 10 * 365, BLOCK, rng)
|
||||
paths10 = r[ii]
|
||||
print(f" {'soglia':>9} {'cosa sblocca':<44} {'senza lump':>11} {'con lump':>9}")
|
||||
print(" " + "-" * 82)
|
||||
naming = {5_000: "criterio di capitale del gate XSR01",
|
||||
15_000: "C* vero di Hyperliquid (pavimento misurato)",
|
||||
20_000: "decisione '100% Deribit fino a $20k' si riapre"}
|
||||
for ms in MILESTONE:
|
||||
riga = []
|
||||
for st in (START_OGGI, START_LUMP):
|
||||
out = PN.accumula(paths10, DEP, ms, lente.aliq, lente.patr, start=st)
|
||||
if st >= ms:
|
||||
riga.append("gia' li'")
|
||||
continue
|
||||
d = PV.leggi(out["colpito"], anni_sim=10)
|
||||
riga.append("oltre 10a" if not np.isfinite(d["med"]) else f"{d['med']:.1f}a")
|
||||
print(f" ${ms:>7,.0f} {naming[int(ms)]:<44} {riga[0]:>11} {riga[1]:>9}")
|
||||
|
||||
# ---------------------------------------------------- (3) il gate del 23/10
|
||||
sez(f"(3) GATE XSR01 (2026-10-23, fra {GATE_XSR} giorni) — P(capitale >= $5.000 quel giorno)")
|
||||
rng = np.random.default_rng(SEED)
|
||||
ii = DT.boot_idx(len(r), N_PATHS, GATE_XSR, BLOCK, rng)
|
||||
pg = r[ii]
|
||||
for st, nome in ((START_OGGI, "senza lump"), (START_LUMP, "con lump")):
|
||||
out = PN.accumula(pg, DEP, 5_000.0, lente.aliq, lente.patr, start=st)
|
||||
p = float((out["cap"] >= 5_000.0).mean())
|
||||
print(f" {nome:<11} start ${st:>7,.0f} -> P(>= $5k al 23/10) = {p:>6.1%} "
|
||||
f"(mediana ${np.median(out['cap']):,.0f}, p10 ${np.percentile(out['cap'],10):,.0f})")
|
||||
print(f"\n ⚠️ Il criterio FORMALE diventa raggiungibile col lump — ma il C* VERO del venue e'")
|
||||
print(f" $15.000-20.000 (§5.3), il 25% a ${START_LUMP:,.0f} sono ${START_LUMP*0.25:,.0f} su")
|
||||
print(f" Hyperliquid, e la decisione vincolante del 26/07 dice 100% Deribit fino a $20k.")
|
||||
print(f" Il lump rende il gate LEGGIBILE, non rende XSR01 comprabile.")
|
||||
|
||||
# ---------------------------------------------------- (4) meccanica live a $5.065
|
||||
sez(f"(4) COSA CAMBIA NELLA MECCANICA LIVE, DA SOLO, A ${START_LUMP:,.0f}")
|
||||
print(f" cap per-asset (equity x 0,5, nessun tetto assoluto sul percorso normale):")
|
||||
print(f" ${START_OGGI*0.5:,.0f} -> ${START_LUMP*0.5:,.0f} per asset — nessuna config da toccare,")
|
||||
print(f" il profilo di rischio RELATIVO non cambia (leva lorda max resta 1,0x; realizzata")
|
||||
print(f" max 0,52x: il vincolo e' il segnale, non il cap)")
|
||||
print(f" min_order $5: passa dal {5/(START_OGGI*0.5):.2%} al {5/(START_LUMP*0.5):.2%} del cap")
|
||||
print(f" per-asset (C2: un parametro in valuta assoluta pesa meno al crescere del conto)")
|
||||
print(f" fallback a equity illeggibile: min($3.000, watermark x 0,5) = "
|
||||
f"min(3000, {START_LUMP*0.5:,.0f}) -> ${min(3000.0, START_LUMP*0.5):,.0f}")
|
||||
print(f" disaster-SL -30%: invariato — resta rotolante e simmetrico (peggior DD misurato")
|
||||
print(f" dall'ingresso -60,6%/-61,5%: il nome inganna anche a $5k)")
|
||||
print(f" ⚠️ slip-audit: misurato su fill a $597-2.067; a ${START_LUMP:,.0f} la taglia relativa")
|
||||
print(f" raddoppia -> la misura VA RIFATTA dopo i primi fill alla taglia nuova (C2/C5),")
|
||||
print(f" lo script c'e' gia' (`r0822_slip_audit.py`, normalizzazione per-fill)")
|
||||
|
||||
# ---------------------------------------------------- verdetto
|
||||
sez("VERDETTO (runtime)")
|
||||
med_a, med_b = np.median(d10["a"]["cap"]), np.median(d10["b"]["cap"])
|
||||
print(f" a 10 anni con €500/mese: ${med_a:,.0f} -> ${med_b:,.0f} ({med_b/med_a-1:+.1%}), "
|
||||
f"€/g {np.median(d10['ea']):.2f} -> {np.median(d10['eb']):.2f}")
|
||||
print(f" P(>=50 €/g a 10a): {d10['pa']:.1%} -> {d10['pb']:.1%}")
|
||||
print(f" il lump anticipa il piano di ~{lump_in_mesi:.0f} mesi e rende leggibile il gate del")
|
||||
print(f" 23/10; NON rende comprabile XSR01 (C* $15-20k) e NON riapre la scelta del venue ($20k).")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,372 @@
|
||||
"""r0825_xsr_rendita.py — XSR01 serve alla RENDITA PERPETUA? (non all'accumulo, non allo Sharpe)
|
||||
|
||||
DOMANDA (operatore, 2026-08-25): *"rendita perpetua ma non ho ancora capito se serve XSR01"*.
|
||||
|
||||
PERCHE' E' UNA MISURA NUOVA E NON UNA CITAZIONE. XSR01 e' stato giudicato tre volte — ammissione
|
||||
(25/07), gate di deploy (23/10, pre-registrato), accumulo (oggi) — e **mai sotto la lente della
|
||||
rendita**. Sono criteri diversi: l'accumulo premia il DRIFT, la rendita premia la **PERPETUA**,
|
||||
cioe' il prelievo piu' alto che sopravvive a 20 anni con P>=90% (definizione 25/07, invariata).
|
||||
La perpetua e' una funzione di drift **e** di coda: un diversificatore che non aggiunge drift puo'
|
||||
comunque alzarla, ed e' esattamente il caso che nessuno ha misurato. N6: l'obiettivo si dichiara
|
||||
prima di ottimizzarlo — qui e' dichiarato, quindi si misura quello.
|
||||
|
||||
COSA MI ASPETTO PRIMA DI MISURARE (M12: una previsione dichiarata va misurata, non assunta):
|
||||
(a) a ISO-NOZIONALE il mix perde drift (XSR01 rende 4,2%/a contro i 15,2% del libro) e quindi
|
||||
**alza il muro** anche se abbassa la vol -> XSR01 sembrera' inutile;
|
||||
(b) a ISO-RISCHIO (M6, la lente obbligatoria per un diversificatore a basso CAGR) il mix puo'
|
||||
essere ri-scalato fino alla vol del libro e allora il verdetto puo' **ribaltarsi**;
|
||||
(c) il vincolo che decide non sara' ne' (a) ne' (b) ma il **capitale**.
|
||||
Se (c) e' vero, le prime due sezioni sono aritmetica su una cosa che non si puo' comprare.
|
||||
|
||||
⚠️ TRE DIFETTI EREDITATI, DICHIARATI PRIMA DEI NUMERI (nessuno riparato qui):
|
||||
1. **La finestra comune e' corta.** Il libro ha ~7,4 anni, XSR01 ~2,6 (Hyperliquid parte 2024).
|
||||
Regola del 07/08, gia' violata una volta: **un MIX esiste solo dove esistono ENTRAMBE le
|
||||
serie**. Quindi il muro del mix NON e' confrontabile col $313k pubblicato (7,4 anni): il
|
||||
confronto valido e' **libro-sulla-finestra-comune vs mix-sulla-finestra-comune**, appaiato.
|
||||
2. **Il numero di titolo di XSR01 (Sharpe 1.82) non e' la sua serie.** XSR-REPRO (22/08): 1.82
|
||||
viene da una TERZA lente (divisore fisso 50); la lente che ha girato i gate da' **1.79** alla
|
||||
scoperta e **1.63** a oggi. Qui si usa la **lente dei gate su sole barre chiuse**, ed e' l'unica
|
||||
citabile — sempre con la coppia (lente, ultima barra chiusa).
|
||||
3. **Il forward monitor di XSR01 e' rotto** (`advance()` registra ~41 min di mercato al giorno,
|
||||
vol registrata 0,46% contro 2,74% ricalcolata): i 31 giorni di `data/paper_xsr/returns.jsonl`
|
||||
**non sono usabili** e non entrano in questo script. Qui si misura il BACKTEST, non il vivo.
|
||||
|
||||
uv run python scripts/research/r0825_xsr_rendita.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"))
|
||||
|
||||
import r0725_capcurve as CC # noqa: E402
|
||||
import r0807_piano_netto as PN # noqa: E402
|
||||
import r0822_xsr_repro as XR # noqa: E402
|
||||
import r0822d_piano_vero as PV # noqa: E402
|
||||
from src.portfolio.portfolio import weights_tilt_null # noqa: E402
|
||||
|
||||
ANN = np.sqrt(365.0)
|
||||
N_WALL = 1500 # M23: la risoluzione del Monte Carlo si dichiara — misurata in sezione 6
|
||||
SEED_WALL = PV.SEED_WALL
|
||||
BLOCK = PV.BLOCK
|
||||
PESI = (0.0, 0.05, 0.10, 0.15, 0.20, 0.25, 0.30, 0.40, 0.50)
|
||||
|
||||
|
||||
def sez(t: str) -> None:
|
||||
print(f"\n{'='*100}\n {t}\n{'='*100}")
|
||||
|
||||
|
||||
def _sh(r: np.ndarray) -> float:
|
||||
r = np.asarray(r, float)
|
||||
return float(r.mean() / r.std() * ANN) if r.std() > 0 else 0.0
|
||||
|
||||
|
||||
def _dd(r: np.ndarray) -> float:
|
||||
eq = np.cumprod(1.0 + np.asarray(r, float))
|
||||
return float((eq / np.maximum.accumulate(eq) - 1.0).min())
|
||||
|
||||
|
||||
def perpetua(r: np.ndarray, aliq: float, patr: float, n_paths: int = N_WALL,
|
||||
seed: int = SEED_WALL) -> float:
|
||||
"""Il prelievo annuo (frazione del capitale) piu' alto con P(cap a 20a >= cap iniziale) >= 90%.
|
||||
|
||||
Bisezione IDENTICA a `PV.muro_di` — non re-implementata a mano: stessa `PN.sopravvivenza`,
|
||||
stessi 13 passi, stesso criterio `p10_end >= cap0`. Cambia solo che qui la serie e' un
|
||||
argomento invece che un attributo di `Lente`, perche' vanno confrontati molti mix."""
|
||||
lo, hi = 0.0, 0.40
|
||||
for _ in range(13):
|
||||
mid = (lo + hi) / 2
|
||||
s = PN.sopravvivenza(r, 1e6, 1e6 * mid, 20, aliq, patr, n_paths=n_paths, seed=seed)
|
||||
lo, hi = (mid, hi) if s["p10_end"] >= 1e6 else (lo, mid)
|
||||
return lo
|
||||
|
||||
|
||||
def muro_da(perp: float, prelievo: float) -> float:
|
||||
return prelievo / perp if perp > 0.002 else float("inf")
|
||||
|
||||
|
||||
def se_drift(r: np.ndarray, n: int = 2000, seed: int = 20260825) -> float:
|
||||
"""SE annualizzata del drift, block-bootstrap a blocchi di 20 giorni (la stessa dipendenza
|
||||
seriale che usa il muro). Serve a dire quanto e' RISOLTA la differenza fra due mix."""
|
||||
rng = np.random.default_rng(seed)
|
||||
B = CC._boot_paths(np.asarray(r, float), n, len(r), BLOCK, rng)
|
||||
return float(B.mean(axis=1).std() * 365.0)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
print("=" * 100)
|
||||
print(" r0825 — XSR01 SERVE ALLA RENDITA PERPETUA?")
|
||||
print("=" * 100)
|
||||
|
||||
# ---------------------------------------------------------------- (0) le due serie
|
||||
sez("(0) LE DUE SERIE E LA FINESTRA IN CUI ESISTONO ENTRAMBE")
|
||||
|
||||
B = PV.costruisci_serie()
|
||||
libro_full = pd.Series(PV.deluck(B["fund"].values.astype(float)), index=B.index)
|
||||
lente_full = PV.Lente("L3 congiunta", libro_full.values, lordizza=False)
|
||||
prelievo = lente_full.prelievo
|
||||
aliq, patr = lente_full.aliq, lente_full.patr
|
||||
|
||||
# XSR01: lente dei GATE (basket_from_positions, demean) su sole barre CHIUSE.
|
||||
P, S = XR.panel(partial_last=False)
|
||||
xsr_full = XR.lens_L1(P, S).dropna()
|
||||
ultima = xsr_full.index.max()
|
||||
# l'ultima barra su disco e' quella del giorno IN CORSO (difetto §5.1): si scarta.
|
||||
xsr_full = xsr_full[xsr_full.index < pd.Timestamp.utcnow().normalize()]
|
||||
chiusa = xsr_full.index.max()
|
||||
print(f" libro L3 (75/25, funding dentro, ancora de-luckata): "
|
||||
f"{libro_full.index.min().date()} -> {libro_full.index.max().date()} "
|
||||
f"({len(libro_full)} giorni)")
|
||||
print(f" XSR01 lente-dei-gate, sole barre chiuse: "
|
||||
f"{xsr_full.index.min().date()} -> {chiusa.date()} ({len(xsr_full)} giorni)")
|
||||
print(f" (ultima barra su disco {ultima.date()} = giorno IN CORSO, scartata — difetto §5.1)")
|
||||
print(f" Sharpe XSR01 a oggi, questa lente: {_sh(xsr_full.values):.2f} "
|
||||
f"[il '1.82' pubblicato e' una TERZA lente — XSR-REPRO 22/08]")
|
||||
|
||||
ix = libro_full.index.normalize().intersection(xsr_full.index.normalize())
|
||||
lb = pd.Series(libro_full.values, index=libro_full.index.normalize()).reindex(ix)
|
||||
xs = pd.Series(xsr_full.values, index=xsr_full.index.normalize()).reindex(ix)
|
||||
lb, xs = lb.dropna(), xs.dropna()
|
||||
ix = lb.index.intersection(xs.index)
|
||||
lb, xs = lb.reindex(ix).values, xs.reindex(ix).values
|
||||
print(f"\n FINESTRA COMUNE: {ix.min().date()} -> {ix.max().date()} ({len(ix)} giorni, "
|
||||
f"{len(ix)/365:.2f} anni)")
|
||||
print(f" copre il {len(ix)/len(libro_full):.0%} della storia del libro -> il muro calcolato "
|
||||
f"qui NON e' il $313k pubblicato (7,4 anni): si confronta solo con se stesso.")
|
||||
|
||||
# ---------------------------------------------------------------- (1) le due gambe
|
||||
sez("(1) LE DUE GAMBE SULLA FINESTRA COMUNE — e quanto e' diversa da tutta la storia")
|
||||
print(f" {'serie':<34} {'drift/a':>9} {'vol/a':>8} {'Sharpe':>8} {'maxDD':>9} {'SE drift':>10}")
|
||||
print(" " + "-" * 84)
|
||||
for nome, r in (("libro L3 — TUTTA la storia", libro_full.values),
|
||||
("libro L3 — finestra comune", lb),
|
||||
("XSR01 — finestra comune", xs)):
|
||||
print(f" {nome:<34} {r.mean()*365:>8.2%} {r.std()*ANN:>7.2%} {_sh(r):>8.2f} "
|
||||
f"{_dd(r):>8.1%} {se_drift(r):>9.2%}")
|
||||
corr = float(np.corrcoef(lb, xs)[0, 1])
|
||||
print(f"\n correlazione libro <-> XSR01 sulla finestra comune: {corr:+.3f}")
|
||||
# ⚠️ la maschera dei FLAT si prende dalla serie GREZZA, non da quella de-luckata: `deluck`
|
||||
# sottrae una costante e gli zeri esatti spariscono (difetto trovato e riparato il 25/08).
|
||||
grezza = pd.Series(B["fund"].values.astype(float), index=B.index.normalize()).reindex(ix)
|
||||
flat = (grezza.abs() < 1e-12).values
|
||||
assert flat.sum() > 0, "maschera flat vuota: si sta guardando la serie sbagliata"
|
||||
print(f" giorni in cui il libro e' FLAT: {flat.mean():.1%} ({flat.sum()} giorni) "
|
||||
f"— li' XSR01 rende {xs[flat].mean()*365:+.2%}/anno annualizzato contro "
|
||||
f"{xs[~flat].mean()*365:+.2%} negli altri")
|
||||
|
||||
# ---------------------------------------------------------------- (2) iso-nozionale
|
||||
sez("(2) MIX A ISO-NOZIONALE — la lente INGENUA (w di XSR01 tolto al libro)")
|
||||
print(f" {'w XSR01':>8} {'drift/a':>9} {'vol/a':>8} {'Sharpe':>8} {'maxDD':>9} "
|
||||
f"{'perpetua':>10} {'MURO':>13} {'d muro':>10}")
|
||||
print(" " + "-" * 88)
|
||||
base_perp = perpetua(lb, aliq, patr)
|
||||
base_muro = muro_da(base_perp, prelievo)
|
||||
iso_n = {}
|
||||
for w in PESI:
|
||||
m = (1 - w) * lb + w * xs
|
||||
p = perpetua(m, aliq, patr)
|
||||
mu = muro_da(p, prelievo)
|
||||
iso_n[w] = dict(perp=p, muro=mu, drift=m.mean()*365, vol=m.std()*ANN, sh=_sh(m), dd=_dd(m))
|
||||
dmu = "n/d" if not np.isfinite(mu) or not np.isfinite(base_muro) else f"{mu/base_muro-1:+.1%}"
|
||||
print(f" {w:>7.0%} {m.mean()*365:>8.2%} {m.std()*ANN:>7.2%} {_sh(m):>8.2f} {_dd(m):>8.1%} "
|
||||
f"{p:>9.2%} ${mu:>12,.0f} {dmu:>10}")
|
||||
|
||||
# ---------------------------------------------------------------- (3) iso-rischio
|
||||
sez("(3) MIX A ISO-RISCHIO (M6) — la lente OBBLIGATORIA per un diversificatore a basso CAGR")
|
||||
print(" Ogni mix e' ri-scalato di k = vol(libro)/vol(mix) cosi' che la vol sia IDENTICA a")
|
||||
print(" quella del libro da solo. E' l'unico confronto che non regala al mix il merito di")
|
||||
print(" essere semplicemente piu' piccolo (M6, e null del de-levering M5).")
|
||||
vol_b = lb.std()
|
||||
print(f"\n {'w XSR01':>8} {'k richiesto':>12} {'drift/a':>9} {'vol/a':>8} {'Sharpe':>8} "
|
||||
f"{'maxDD':>9} {'perpetua':>10} {'MURO':>13} {'d muro':>10}")
|
||||
print(" " + "-" * 96)
|
||||
iso_r = {}
|
||||
for w in PESI:
|
||||
m = (1 - w) * lb + w * xs
|
||||
k = vol_b / m.std()
|
||||
mk = m * k
|
||||
p = perpetua(mk, aliq, patr)
|
||||
mu = muro_da(p, prelievo)
|
||||
iso_r[w] = dict(k=k, perp=p, muro=mu, drift=mk.mean()*365, sh=_sh(mk), dd=_dd(mk))
|
||||
dmu = "n/d" if not np.isfinite(mu) or not np.isfinite(base_muro) else f"{mu/base_muro-1:+.1%}"
|
||||
print(f" {w:>7.0%} {k:>11.3f}x {mk.mean()*365:>8.2%} {mk.std()*ANN:>7.2%} {_sh(mk):>8.2f} "
|
||||
f"{_dd(mk):>8.1%} {p:>9.2%} ${mu:>12,.0f} {dmu:>10}")
|
||||
print("\n ⚠️ Il k di questa colonna e' ARITMETICA, non una configurazione: in `config/live.json`")
|
||||
print(" NON esiste una chiave di scala (§3), e il gradino di leva NON e' autorizzato oggi")
|
||||
print(" (1,50x BOCCIATO, k max difendibile 1,40). Un k > 1,40 in questa tabella e' un")
|
||||
print(" numero che il libro **non puo' eseguire**.")
|
||||
|
||||
# M8: l'argmax di questa griglia sta al BORDO (il muro scende monotonamente in w) -> non e'
|
||||
# una decisione, e' il bordo. Il peso che si porta avanti e' il migliore DENTRO il k
|
||||
# autorizzato (<= 1,40, decisione operatore 23/08); il bordo resta come nota.
|
||||
bordo = max((w for w in PESI if w > 0), key=lambda w: -iso_r[w]["muro"])
|
||||
ammessi = [w for w in PESI if w > 0 and iso_r[w]["k"] <= 1.40]
|
||||
best_w = max(ammessi, key=lambda w: -iso_r[w]["muro"]) if ammessi else min(
|
||||
w for w in PESI if w > 0)
|
||||
print(f"\n ⚠️ M8 — l'argmax di questa griglia e' al BORDO (w={bordo:.0%}, k={iso_r[bordo]['k']:.2f}x):")
|
||||
print(f" il muro scende MONOTONAMENTE in w, quindi la griglia non indica un ottimo, dice")
|
||||
print(f" 'il piu' possibile'. Cio' che si sta comprando in quella direzione e' il k, non XSR01.")
|
||||
print(f" => peso portato avanti = il migliore col k AUTORIZZATO (<=1,40x): XSR01 {best_w:.0%} "
|
||||
f"(k={iso_r[best_w]['k']:.2f}x)")
|
||||
|
||||
# ------------------------------------------------------- (3b) da dove viene il guadagno?
|
||||
sez("(3b) IL NULL — il guadagno a iso-rischio e' di XSR01, o di 'una qualunque cosa scorrelata'?")
|
||||
print(" A iso-vol vale `drift = Sharpe x vol_libro`: la colonna 'drift' della sezione (3) E' la")
|
||||
print(" colonna 'Sharpe' ri-etichettata. Quindi tutto il guadagno di muro e' un guadagno di")
|
||||
print(" SHARPE da diversificazione — e va chiesto se e' XSR01 o la sua sola statistica.")
|
||||
print(" Tre sostituti, stessa w, stessa ri-scalatura a iso-vol del libro:")
|
||||
print(" N1 XSR01 MESCOLATO — stessi rendimenti in ordine casuale: stesso drift, stessa vol,")
|
||||
print(" nessuna relazione col libro giorno per giorno (null M14).")
|
||||
print(" N2 RUMORE a drift di XSR01 — gaussiano, stessa media e stessa vol, zero struttura.")
|
||||
print(" N3 RUMORE a drift ZERO — stessa vol, media 0: isola quanta parte e' pura varianza.")
|
||||
rng = np.random.default_rng(20260825)
|
||||
print(f"\n {'sostituto':<26} {'drift/a':>9} {'Sharpe':>8} {'perpetua':>10} {'MURO':>13} {'d muro':>10}")
|
||||
print(" " + "-" * 80)
|
||||
w0 = best_w
|
||||
def _riga(nome, sub):
|
||||
m = (1 - w0) * lb + w0 * sub
|
||||
m = m * (vol_b / m.std())
|
||||
pp = perpetua(m, aliq, patr)
|
||||
mm = muro_da(pp, prelievo)
|
||||
print(f" {nome:<26} {m.mean()*365:>8.2%} {_sh(m):>8.2f} {pp:>9.2%} ${mm:>12,.0f} "
|
||||
f"{mm/base_muro-1:>9.1%}")
|
||||
return mm
|
||||
m_vero = iso_r[w0]["muro"]
|
||||
print(f" {'XSR01 (vero)':<26} {iso_r[w0]['drift']:>8.2%} {iso_r[w0]['sh']:>8.2f} "
|
||||
f"{iso_r[w0]['perp']:>9.2%} ${m_vero:>12,.0f} {m_vero/base_muro-1:>9.1%}")
|
||||
mescolati = [_riga(f"N1 mescolato (seme {i})", rng.permutation(xs)) for i in range(3)]
|
||||
n2 = [_riga(f"N2 rumore, drift XSR01 ({i})", rng.normal(xs.mean(), xs.std(), len(xs)))
|
||||
for i in range(3)]
|
||||
n3 = [_riga(f"N3 rumore, drift ZERO ({i})", rng.normal(0.0, xs.std(), len(xs)))
|
||||
for i in range(3)]
|
||||
# N4: la gamba che nessuno ha messo sul tavolo — un rendimento SENZA rischio e senza venue.
|
||||
# Se il merito di XSR01 e' 'un drift scorrelato a bassa vol', allora un conto remunerato
|
||||
# (T-bill / USDC lending) lo fornisce con vol ~0, corr 0 e nessun pavimento d'esecuzione.
|
||||
n4 = {y: _riga(f"N4 conto remunerato {y:.1%}", np.full(len(xs), y / 365.0))
|
||||
for y in (0.020, 0.0372, 0.040)}
|
||||
print(f"\n LETTURA (calcolata, non asserita):")
|
||||
print(f" XSR01 vero ${m_vero:>10,.0f}")
|
||||
print(f" N1 mescolato, mediana ${np.median(mescolati):>10,.0f} "
|
||||
f"-> il MECCANISMO vale {abs(m_vero-np.median(mescolati))/base_muro:.1%} di muro")
|
||||
print(f" N2 rumore, mediana ${np.median(n2):>10,.0f} "
|
||||
f"-> (media, vol) da sole bastano: {'SI' if np.median(n2) <= m_vero else 'NO'}")
|
||||
print(f" N3 drift zero, mediana${np.median(n3):>10,.0f} "
|
||||
f"-> la sola riduzione di varianza paga: "
|
||||
f"{'SI' if np.median(n3) < base_muro else 'NO, il muro SALE'}")
|
||||
print(f" N4 conto remunerato 4,0% ${n4[0.040]:>8,.0f} "
|
||||
f"-> {'MEGLIO' if n4[0.040] <= m_vero else 'peggio'} di XSR01, "
|
||||
f"a vol ZERO e senza secondo venue")
|
||||
print(f"\n drift di XSR01 sulla finestra comune: {xs.mean()*365:+.2%}/anno · SE {se_drift(xs):.2%} "
|
||||
f"-> t = {xs.mean()*365/se_drift(xs):+.2f}")
|
||||
|
||||
# ---------------------------------------------------------------- (4) gate dei pesi
|
||||
sez("(4) `weights_tilt_null` — il gate obbligatorio per OGNI proposta di cambio pesi (§8.5)")
|
||||
print(f" proposta testata: XSR01 {best_w:.0%} (il miglior peso a k autorizzato, sezione 3).")
|
||||
print(f" ⚠️ hold-out del gate = 2025-01-01: su questa finestra la gamba in-sample e' UN SOLO")
|
||||
print(f" anno (2024). Il gate gira, ma con questa risoluzione va letto come indizio.")
|
||||
cols = {"LIBRO": pd.Series(lb, index=ix), "XSR01": pd.Series(xs, index=ix)}
|
||||
try:
|
||||
g = weights_tilt_null(cols,
|
||||
{"LIBRO": 1.0, "XSR01": 0.0}, # corrente: XSR01 fuori dal book
|
||||
{"LIBRO": 1.0 - best_w, "XSR01": best_w},
|
||||
floor=0.0, n=500, k_seen=len(PESI))
|
||||
print(f" delta_insample {g['delta_insample']:+.3f} · delta_hold {g['delta_hold']:+.3f} "
|
||||
f"· pctl_hold {g['pctl_hold']:.1f} · soglia best-of-k {g['bestofk_pctl']:.1f}")
|
||||
print(f" tilt casuali che battono CURRENT sull'hold-out: {g['frac_random_beat_hold']:.0%}")
|
||||
print(f" => gate_pass = {g['gate_pass']}")
|
||||
except Exception as e:
|
||||
g = None
|
||||
print(f" NON ESEGUIBILE su questa finestra: {type(e).__name__}: {e}")
|
||||
|
||||
# ---------------------------------------------------------------- (5) risoluzione
|
||||
sez("(5) LA RISOLUZIONE — la differenza misurata e' piu' grande del rumore che la misura?")
|
||||
se_b = se_drift(lb)
|
||||
m_best = (1 - best_w) * lb + best_w * xs
|
||||
m_best = m_best * (vol_b / m_best.std())
|
||||
se_m = se_drift(m_best)
|
||||
d_drift = m_best.mean() * 365 - lb.mean() * 365
|
||||
se_pair = se_drift(m_best - lb)
|
||||
print(f" drift libro {lb.mean()*365:>8.2%} (SE {se_b:.2%})")
|
||||
print(f" drift mix {best_w:.0%} {m_best.mean()*365:>8.2%} (SE {se_m:.2%}) iso-rischio")
|
||||
print(f" DIFFERENZA appaiata (M7: si misura la differenza, non si differenziano le mediane):")
|
||||
print(f" {d_drift:+.2%}/anno · SE della differenza {se_pair:.2%} -> "
|
||||
f"t = {d_drift/se_pair if se_pair > 0 else float('nan'):+.2f}")
|
||||
p1 = perpetua(m_best, aliq, patr, seed=SEED_WALL)
|
||||
p2 = perpetua(m_best, aliq, patr, seed=SEED_WALL + 1)
|
||||
p3 = perpetua(m_best, aliq, patr, seed=SEED_WALL + 2)
|
||||
print(f"\n risoluzione Monte Carlo del muro (M23), stesso mix a tre semi diversi:")
|
||||
print(f" perpetua {p1:.3%} / {p2:.3%} / {p3:.3%} -> spread {max(p1,p2,p3)-min(p1,p2,p3):.3%}")
|
||||
print(f" in muro: ${muro_da(p1, prelievo):,.0f} / ${muro_da(p2, prelievo):,.0f} / "
|
||||
f"${muro_da(p3, prelievo):,.0f}")
|
||||
|
||||
# ---------------------------------------------------------------- (6) il vincolo vero
|
||||
sez("(6) IL VINCOLO CHE NON E' NELLE TABELLE — si puo' COMPRARE questo mix?")
|
||||
eq = 2066.88
|
||||
print(f" capitale reale oggi: ${eq:,.0f} (Deribit). XSR01 gira su **Hyperliquid**: e' un")
|
||||
print(f" SECONDO CONTO, quindi il suo peso e' capitale che ESCE da Deribit, non che si aggiunge.")
|
||||
print(f"\n {'w XSR01':>8} {'$ su Hyperliquid':>18} {'ticket mediano/gamba':>22} {'eseguibile?':>14}")
|
||||
print(" " + "-" * 68)
|
||||
TICKET_MED = 3.33 # XSR-REPRO 22/08, misurato: mediano $3,33 · medio $5,98 · 63% sotto $5
|
||||
C_STAR = 15_000.0 # pavimento vero del venue misurato (non i ~$3.000 su cui e' tarato il gate)
|
||||
for w in (0.10, 0.20, 0.30, 0.50):
|
||||
cap_x = eq * w
|
||||
tk = TICKET_MED * cap_x / 5_000.0 # il ticket misurato era a taglia $5.000
|
||||
print(f" {w:>7.0%} ${cap_x:>17,.0f} ${tk:>21,.2f} "
|
||||
f"{'NO' if cap_x < C_STAR else 'forse':>14}")
|
||||
print(f"\n E IL COSTO DI ESECUZIONE MANGIA PROPRIO LA COSA CHE SI STA COMPRANDO.")
|
||||
print(f" Il gate 23/10 chiede 'haircut a $5.000 <= 40%'. Il drift lordo di XSR01 su questa")
|
||||
print(f" finestra e' {xs.mean()*365:.2%}/anno: e' quello, e solo quello, cio' che il null dice")
|
||||
print(f" di star comprando. Al netto dell'haircut:")
|
||||
for h in (0.0, 0.20, 0.40, 0.60):
|
||||
netto = xs.mean() * 365 * (1 - h)
|
||||
print(f" haircut {h:>4.0%} -> {netto:>6.2%}/anno netti "
|
||||
f"{'sopra' if netto > 0.040 else 'SOTTO'} un conto al 4,0% · "
|
||||
f"{'sopra' if netto > 0.020 else 'SOTTO'} un conto al 2,0%")
|
||||
h_be = 1 - 0.020 / (xs.mean() * 365)
|
||||
print(f" pareggio con un conto al 2,0%: haircut {h_be:.0%}. Con un conto al 4,0%: "
|
||||
f"NON pareggia nemmeno a haircut ZERO.")
|
||||
print(f" ⚠️ e l'haircut pubblicato ($14,41 di ticket) e' ~2,4x ottimista e senza script"
|
||||
f" che lo riproduca (XSR-REPRO 22/08).")
|
||||
print(f"\n C* misurato del venue: ${C_STAR:,.0f}-20.000 (il gate 23/10 e' tarato su ~$3.000, §5.3).")
|
||||
print(f" Capitale che serve per dare a XSR01 il {best_w:.0%} restando sopra C*: "
|
||||
f"${C_STAR/best_w:,.0f} sul conto totale.")
|
||||
|
||||
# ---------------------------------------------------------------- verdetto
|
||||
sez("VERDETTO — calcolato a runtime, non scritto a mano (N11)")
|
||||
mu_n = iso_n[best_w]["muro"]
|
||||
mu_r = iso_r[best_w]["muro"]
|
||||
print(f" base (libro solo, finestra comune): perpetua {base_perp:.2%} · muro ${base_muro:,.0f}")
|
||||
print(f" a ISO-NOZIONALE, XSR01 {best_w:.0%}: muro ${mu_n:,.0f} ({mu_n/base_muro-1:+.1%})")
|
||||
print(f" a ISO-RISCHIO, XSR01 {best_w:.0%}: muro ${mu_r:,.0f} ({mu_r/base_muro-1:+.1%}) "
|
||||
f"[k = {iso_r[best_w]['k']:.2f}x]")
|
||||
aiuta_n = mu_n < base_muro
|
||||
aiuta_r = mu_r < base_muro
|
||||
risolto = abs(d_drift) > 2 * se_pair
|
||||
eseguibile = eq * best_w >= C_STAR
|
||||
k_ok = iso_r[best_w]["k"] <= 1.40
|
||||
print(f"\n (a) abbassa il muro a iso-nozionale? {'SI' if aiuta_n else 'NO'}")
|
||||
print(f" (b) abbassa il muro a iso-rischio? {'SI' if aiuta_r else 'NO'}")
|
||||
print(f" (c) la differenza e' RISOLTA (|d| > 2 SE)? {'SI' if risolto else 'NO'}")
|
||||
print(f" (d) il k richiesto e' autorizzato (<= 1,40)? {'SI' if k_ok else 'NO'}")
|
||||
print(f" (e) e' ESEGUIBILE al capitale di oggi? {'SI' if eseguibile else 'NO'}")
|
||||
if g is not None:
|
||||
print(f" (f) `weights_tilt_null` passa? {'SI' if g['gate_pass'] else 'NO'}")
|
||||
cond = [aiuta_r, risolto, k_ok, eseguibile] + ([g["gate_pass"]] if g is not None else [])
|
||||
print(f" (g) batte un CONTO REMUNERATO al 4,0% a pari peso? "
|
||||
f"{'SI' if m_vero < n4[0.040] else 'NO'} "
|
||||
f"(XSR01 ${m_vero:,.0f} vs conto ${n4[0.040]:,.0f})")
|
||||
cond.append(m_vero < n4[0.040])
|
||||
print(f"\n => XSR01 sotto la lente RENDITA: {sum(cond)}/{len(cond)} condizioni.")
|
||||
if not eseguibile:
|
||||
print(f" => Il vincolo BINDING non e' il rendimento: e' il CAPITALE. Sotto ${C_STAR/best_w:,.0f}")
|
||||
print(f" totali la domanda 'serve XSR01?' non ha una risposta comprabile.")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user