Files
PythagorasGoal/scripts/research/r0825_xsr_rendita.py
T
Adriano Dal Pastro f10d847816 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
2026-08-26 06:56:40 +00:00

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()