Files
PythagorasGoal/scripts/research/r0726_wall_fixedpoint.py
T
Adriano Dal Pastro 3a44171402 research: il muro come punto fisso — la previsione del follow-up era SBAGLIATA
Il follow-up dichiarato stamattina diceva: "i muri usano il book a 2 sleeve da $600
estrapolato a $272k; il book diversificato ha Sharpe piu' alto -> IL MURO VERO E' PIU'
BASSO". Misurato: FALSO. A pari nozionale il muro e' $273.900 contro i $272.061
pubblicati (+1%): l'estrapolazione col book a 2 sleeve era giusta PER CASO.

STRUTTURA: il muro e' un PUNTO FISSO — serve capitale C per girare il book che determina
il muro C, quindi si itera C_{n+1} = muro(book(C_n)). Converge in 1 iterazione perche' il
muro cade SOPRA la soglia XS01 ($117k) e la composizione non cambia; la struttura conta
solo se il muro atterra vicino a una soglia, ma va iterato per saperlo.

BOOK DEPLOYABLE (non il "migliore"): TP01 38 / SKH01 23 / GTAA01 23 / XS01 17. VRP01
escluso per regola permanente (niente short-vol da modello in deploy), XSR01 escluso per
gate pre-registrato 23/10. Costi capital-aware, fattore d'ancora x0.860 misurato su
QUESTO book. Sharpe 1.94, vol 8.9%, CAGR 18.3%.

PERCHE' NON SCENDE: diversificare alza lo Sharpe (1.64 -> 1.94) ma abbassa drift e vol
INSIEME, e la rendita perpetua vive sul DRIFT -> 10.91% -> 10.84%, invariata. Il guadagno
di Sharpe va in meno rischio, non in piu' reddito. E' il fatto gia' misurato il 25/07 §3,
dimenticato scrivendo il follow-up.

E STAVO VIOLANDO UNA REGOLA GIA' CODIFICATA: "un diversificatore a basso CAGR si giudica
a ISO-RISCHIO, mai a iso-nozionale" (25/07 §3). A iso-rischio (leva 1.28x): rendita
13.35%, muro $222.406 = -18% -> replica indipendente del -19% misurato il 25/07 con
macchineria e book diversi. Vale solo se la leva e' disponibile e a costo < uplift: $222k
e' un TETTO, non una stima.

BUG CATTURATO PRIMA DI PUBBLICARE: la prima corsa dava Sharpe 0.95 e muro $854k
("diversificare triplica il muro" — spettacolare e falso). CC.gtaa_banded ritorna la
storia GTAA dal 1996 mentre lo sleeve di produzione tronca a GTAA_BOOK_ACTIVATION; con la
rinormalizzazione per-riga di combine_outer il 75% del campione era GTAA01 DA SOLO al
100%. Preso non da un test ma perche' la somma pesata dei componenti (~18%) non tornava
col drift del combinato (6.8%). Diagnostica decisiva: la copertura per colonna
(TP01 24.6% / SKH01 24.6% / GTAA01 100.0% / XS01 8.6%).

Book, pesi, cron, config INVARIATI.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-26 18:56:07 +00:00

243 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python
"""r0726_wall_fixedpoint.py — il muro di capitale come PUNTO FISSO, col book giusto alla taglia.
FOLLOW-UP dichiarato il 2026-07-26 e chiuso qui.
IL DIFETTO. `r0726_capwall_refresh.py` (e prima `r0725_capcurve`) calcola il muro con
`book_series(alloc=$600)` = **book a 2 sleeve**, e lo estrapola fino a $272.061. Ma a $272k non
gireresti quel book: a ~$13k si accende GTAA01 (IB), a ~$118k XS01 (Hyperliquid). Il book
diversificato ha Sharpe piu' alto → **rendita perpetua piu' alta → muro piu' basso**.
E la correzione ha una struttura: **il muro e' un PUNTO FISSO**. Serve capitale C per girare il
book che determina il muro C. Si itera C_{n+1} = muro(book(C_n)) fino a convergenza.
REGOLE DI AMMISSIONE RISPETTATE (non e' il book "migliore", e' quello DEPLOYABILE):
* **VRP01 ESCLUSO** — regola permanente del progetto: *niente short-vol da modello in deploy*
(premio modellato su DVOL ATM, skew non esplicito, f di stress reale non catturato);
* **XSR01 ESCLUSO** — non ammesso: gate pre-registrato al 2026-10-23, oggi forward-monitor;
* pesi = quelli del book ATTIVO rinormalizzati sugli sleeve inclusi;
* costi **capital-aware**: TP01 con haircut min-order, GTAA01 con commissione IB reale a banda
e cadenza settimanale, SKH01 sul **path live** (lente hourly);
* soglie: GTAA01 alloc >= $3.000 (`GTAA_MIN_CAPITAL`), XS01 alloc >= $20.000 (19 gambe).
DE-LUCK: fattore d'ancora **misurato sul book deployable stesso** (estrazioni congiunte sulle
serie d'ancora in cache), non ereditato da un altro book.
uv run python scripts/research/r0726_wall_fixedpoint.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"))
sys.path.insert(0, str(ROOT / "scripts" / "research" / "alt"))
import r0724_skh_live_weight as skl # noqa: E402
import r0725_capcurve as CC # noqa: E402
import r0726_capwall_refresh as CW # noqa: E402
import r0726_loo_deluck as L # noqa: E402
from src.portfolio.gtaa import GTAA_MIN_CAPITAL # noqa: E402
from src.portfolio.portfolio import combine_outer, metrics # noqa: E402
from src.portfolio.sleeves import (GTAA_BOOK_ACTIVATION, # noqa: E402
_xsec_returns)
DPY = 365.25
XS_MIN_CAPITAL = 20_000.0 # 19 gambe market-neutral (soglia documentata)
# book ATTIVO senza VRP01 (regola: niente short-vol da modello in deploy), rinormalizzato su 0.88
W_DEPLOY = {"TP01": 0.33 / 0.88, "SKH01": 0.20 / 0.88,
"GTAA01": 0.20 / 0.88, "XS01": 0.15 / 0.88}
# nome deployable -> nome nella griglia d'ancora di r0726_loo_deluck
ANCHOR_NAME = {"TP01": "TP01_trend_1d", "SKH01": "SKH01_skyhook",
"GTAA01": "GTAA01_eq_trend", "XS01": "XS01_xsec_hl"}
MIN_ALLOC = {"TP01": 0.0, "SKH01": 0.0, "GTAA01": GTAA_MIN_CAPITAL, "XS01": XS_MIN_CAPITAL}
def _daily(s: pd.Series) -> pd.Series:
s = s.dropna().sort_index()
if s.index.tz is None:
s.index = s.index.tz_localize("UTC")
return s
def included_at(capital: float) -> dict:
"""Sleeve eseguibili a quel capitale, con pesi rinormalizzati fra i soli inclusi."""
inc = {k: w for k, w in W_DEPLOY.items() if capital * w >= MIN_ALLOC[k]}
tot = sum(inc.values())
return {k: w / tot for k, w in inc.items()}
def anchor_factor(weights: dict, draws: int = 800) -> float:
"""Fattore d'ancora sul DRIFT, misurato su QUESTO book (non ereditato)."""
S = L.build_all()
names = [ANCHOR_NAME[k] for k in weights]
wmap = {ANCHOR_NAME[k]: v for k, v in weights.items()}
canon = combine_outer({n: S[n][L.SPEC[n][2]] for n in names}, wmap)
d_can = float(canon.dropna().values.mean() * DPY)
rng = np.random.default_rng(L.SEED)
ds = []
for _ in range(draws):
cfg = {n: int(rng.choice(L.SPEC[n][1])) for n in names}
s = combine_outer({n: S[n][cfg[n]] for n in names}, wmap)
ds.append(float(s.dropna().values.mean() * DPY))
return float(np.median(ds) / d_can) if d_can else float("nan")
_SKH = None
_XS = None
def realistic_book(capital: float, weights: dict) -> pd.Series:
"""Book DEPLOYABLE a quel capitale, coi costi reali di ciascun venue."""
global _SKH, _XS
if _SKH is None:
_SKH = _daily(skl.skh_book(0, "hourly"))
if _XS is None:
_XS = _daily(_xsec_returns()).resample("1D").apply(lambda x: (1 + x).prod() - 1)
cols = {}
for k, w in weights.items():
alloc = capital * w
if k == "TP01":
cols[k] = CC.tp01_realistic(alloc)
elif k == "SKH01":
cols[k] = _SKH
elif k == "GTAA01":
# ⚠️ `CC.gtaa_banded` ritorna la storia PIENA dal 1996; lo sleeve di produzione
# (`sleeves._gtaa_daily_returns`) tronca a GTAA_BOOK_ACTIVATION. Senza questo
# troncamento il 75% del campione (1996-2019) e' GTAA01 DA SOLO al 100% dopo la
# rinormalizzazione per-riga di combine_outer -> il "book a 4 sleeve" diventa uno
# sleeve a basso CAGR e il muro esce grottescamente alto. Bug catturato in sessione.
g = _daily(CC.gtaa_banded(alloc, CC.GTAA_BAND_USD, CC.GTAA_EVERY))
cols[k] = g[g.index >= GTAA_BOOK_ACTIVATION]
elif k == "XS01":
cols[k] = _XS
return combine_outer(cols, weights)
def wall_of(capital: float, deluck: float, k: float = 1.0) -> tuple[float, float, dict]:
"""(muro, tasso di rendita perpetua, metriche) del book deployable a quel capitale."""
w = included_at(capital)
b = realistic_book(capital, w)
r = (b - (1.0 - deluck) * float(b.mean())).values.astype(float)
gross = CC.TARGET_EUR_DAY * 365 * CC.EURUSD / (1 - CC.TAX_RATE)
_swr, perp, cap = CW.perp_and_wall(r, k, gross)
return cap, perp, dict(sharpe=metrics(b)["sharpe"], cagr=metrics(b)["cagr"],
vol=float(b.std() * np.sqrt(DPY)), sleeves=w)
def main() -> None:
print("=" * 100)
print(" r0726 — IL MURO COME PUNTO FISSO (book giusto alla taglia). Chiude il follow-up.")
print("=" * 100)
print(" Difetto corretto: il muro pubblicato ($272k) usa il book a 2 sleeve da $600")
print(" estrapolato fino a $272k. A quella taglia gireresti un book diverso.")
# --- fattore d'ancora del book deployable, misurato su se stesso -----------
w_full = included_at(1_000_000.0)
f = anchor_factor(w_full)
print(f"\n Fattore d'ancora sul drift, MISURATO su questo book "
f"({'+'.join(w_full)}): ×{f:.3f}")
print(f" (per confronto: ×0.874 book 5-sleeve, ×0.890 book live 2-sleeve)")
# --- soglie ---------------------------------------------------------------
print("\n SOGLIE DI SBLOCCO (peso deployable × capitale >= minimo dello sleeve)")
for k, w in sorted(W_DEPLOY.items(), key=lambda x: -MIN_ALLOC[x[0]]):
need = MIN_ALLOC[k] / w if MIN_ALLOC[k] > 0 else 0.0
print(f" {k:<8} peso {w:.1%} min alloc ${MIN_ALLOC[k]:>9,.0f} "
f"-> serve capitale ${need:>10,.0f}" if need else
f" {k:<8} peso {w:.1%} nessuna soglia")
# --- punto fisso ----------------------------------------------------------
print("\n" + "-" * 100)
print(" ITERAZIONE AL PUNTO FISSO C_{n+1} = muro( book(C_n) )")
print("-" * 100)
print(f" {'iter':>5}{'capitale ipotesi':>20}{'sleeve':>34}{'Sharpe':>8}"
f"{'rendita':>9}{'-> muro':>14}")
C = 272_061.0
for i in range(6):
cap, perp, m = wall_of(C, f)
names = "+".join(m["sleeves"])
print(f" {i:>5}{'$'+format(C, ',.0f'):>20}{names:>34}{m['sharpe']:>8.2f}"
f"{perp:>9.2%}{'$'+format(cap, ',.0f'):>14}")
if abs(cap - C) / max(C, 1) < 0.02:
C = cap
print(f"\n CONVERGE a ${C:,.0f} (variazione < 2%)")
break
C = cap
else:
print(f"\n non converge in 6 iterazioni, ultimo valore ${C:,.0f}")
# --- confronto ------------------------------------------------------------
print("\n" + "=" * 100)
print(" CONFRONTO COI NUMERI PUBBLICATI")
print("=" * 100)
cap2, perp2, m2 = 272_061.0, 0.1091, None
print(f" {'lente':<52}{'rendita':>10}{'muro':>14}")
print(f" {'25/07: 2 sleeve, de-luck ×0.60 (a occhio)':<52}{6.00:>9.2f}%"
f"{'$494,758':>14}")
print(f" {'26/07: 2 sleeve, de-luck ×0.89 (misurato)':<52}{perp2*100:>9.2f}%"
f"{'$'+format(cap2, ',.0f'):>14}")
capf, perpf, mf = wall_of(C, f)
print(f" {'26/07-bis: book DEPLOYABLE alla taglia, punto fisso':<52}"
f"{perpf*100:>9.2f}%{'$'+format(capf, ',.0f'):>14}")
print(f"\n Sharpe del book al punto fisso: {mf['sharpe']:.2f} "
f"(vol {mf['vol']*100:.1f}%, CAGR {mf['cagr']*100:.1f}%)")
print(f" sleeve attivi: {', '.join(f'{k} {v:.0%}' for k, v in mf['sleeves'].items())}")
delta = capf / cap2 - 1.0
print(f"\n >>> il muro si sposta di {delta*100:+.0f}% rispetto al numero pubblicato")
# ---------------- perche': diagnostica per sleeve --------------------------
print("\n" + "-" * 100)
print(" PERCHE' — contributo di ciascuno sleeve al punto fisso")
print("-" * 100)
w = included_at(C)
print(f" {'sleeve':<9}{'peso':>7}{'drift/a':>10}{'vol/a':>9}{'Sharpe':>9}")
for k, wk in w.items():
s = _daily(realistic_book(C, {k: 1.0}))
print(f" {k:<9}{wk:>6.0%}{s.mean()*DPY*100:>9.1f}%{s.std()*np.sqrt(DPY)*100:>8.1f}%"
f"{metrics(s)['sharpe']:>9.2f}")
b2 = _daily(CC.book_series("hourly"))
print(f"\n book 2-sleeve (riferimento) drift {b2.mean()*DPY*100:.1f}% "
f"vol {b2.std()*np.sqrt(DPY)*100:.1f}% Sharpe {metrics(b2)['sharpe']:.2f}")
print(" -> diluire in sleeve a BASSO CAGR abbassa drift E vol insieme: a pari")
print(" nozionale il muro SALE. E' il fatto gia' misurato il 25/07 (§3).")
# ---------------- il confronto ONESTO: iso-rischio -------------------------
print("\n" + "=" * 100)
print(" CONFRONTO A ISO-RISCHIO — la regola del progetto (25/07 §3)")
print("=" * 100)
print(" 'Un diversificatore a basso CAGR si giudica a ISO-RISCHIO, mai a iso-nozionale")
print(" (e' il null de-levering al contrario).' Il confronto sopra e' iso-nozionale:")
print(" secondo la regola del progetto e' quello SBAGLIATO. Qui quello giusto.")
bd = _daily(realistic_book(C, w))
vol_d = float(bd.std() * np.sqrt(DPY))
vol_2 = float(b2.std() * np.sqrt(DPY))
k_iso = vol_2 / vol_d if vol_d > 0 else 1.0
print(f"\n vol book deployable {vol_d*100:.1f}% | vol book 2-sleeve {vol_2*100:.1f}%"
f" -> leva iso-rischio k = {k_iso:.2f}x")
cap_iso, perp_iso, _ = wall_of(C, f, k=k_iso)
print(f"\n {'lente':<52}{'rendita':>10}{'muro':>14}")
print(f" {'2 sleeve, ×0.89 (pubblicato)':<52}{perp2*100:>9.2f}%"
f"{'$'+format(cap2, ',.0f'):>14}")
print(f" {'deployable, ISO-NOZIONALE (leva 1.0x)':<52}{perpf*100:>9.2f}%"
f"{'$'+format(capf, ',.0f'):>14}")
print(f" {f'deployable, ISO-RISCHIO (leva {k_iso:.2f}x)':<52}{perp_iso*100:>9.2f}%"
f"{'$'+format(cap_iso, ',.0f'):>14}")
print("\n ⚠️ L'iso-rischio vale SOLO se la leva e' (a) disponibile e (b) a costo minore")
print(" dell'uplift — lezione del 25/07 (addendum IB 10k): su ETF a IB c'e' Reg-T 2x")
print(" (portfolio margin da ~$110k) e il margine costa ~5.5%/anno. Il numero sopra")
print(" NON sconta il costo del margine: e' un TETTO, non una stima.")
print("\n ⚠️ VRP01 e XSR01 restano ESCLUSI per regola, non per risultato: includerli")
print(" abbasserebbe ancora il muro ma violerebbe 'niente short-vol da modello in")
print(" deploy' e il gate pre-registrato di XSR01.")
if __name__ == "__main__":
main()