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>
This commit is contained in:
Adriano Dal Pastro
2026-07-26 18:56:07 +00:00
parent 29626210e6
commit 3a44171402
4 changed files with 512 additions and 3 deletions
+242
View File
@@ -0,0 +1,242 @@
#!/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()