f10d847816
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
373 lines
22 KiB
Python
373 lines
22 KiB
Python
"""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()
|