Files
PythagorasGoal/tests/test_wall_fixedpoint.py
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

103 lines
4.4 KiB
Python

"""Test per r0726_wall_fixedpoint.py — il muro come punto fisso col book giusto alla taglia.
Il test che conta e' `test_gtaa_e_troncato_all_era_book`: e' il bug catturato in sessione.
`CC.gtaa_banded` ritorna la storia GTAA dal **1996**, mentre lo sleeve di produzione tronca a
`GTAA_BOOK_ACTIVATION`. Senza troncamento la rinormalizzazione per-riga di `combine_outer`
rende il 75% del campione (1996-2019) **GTAA01 da solo al 100%** — e il "book a 4 sleeve"
diventa uno sleeve a basso CAGR: Sharpe 0.95 invece di 1.94, muro $854k invece di $274k.
Un numero grottescamente sbagliato e perfettamente plausibile a prima vista.
"""
from __future__ import annotations
import sys
from pathlib import Path
import numpy as np
import pandas as pd
import pytest
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT))
sys.path.insert(0, str(ROOT / "scripts" / "research"))
sys.path.insert(0, str(ROOT / "scripts" / "research" / "alt"))
import r0726_wall_fixedpoint as WF # noqa: E402
# ===========================================================================
# il bug: troncamento di GTAA01
# ===========================================================================
def test_gtaa_e_troncato_all_era_book():
from src.portfolio.sleeves import GTAA_BOOK_ACTIVATION
g = WF.realistic_book(300_000.0, {"GTAA01": 1.0})
assert g.index[0] >= GTAA_BOOK_ACTIVATION, (
f"GTAA01 parte da {g.index[0]}: storia pre-book inclusa, il book combinato sarebbe "
"GTAA01 da solo per la maggior parte del campione")
def test_il_book_deployable_non_parte_dal_1996():
"""Guardia sull'effetto, non solo sulla causa: se una gamba futura reintroducesse storia
antecedente, questo test la prende anche se il test sopra non la copre."""
b = WF.realistic_book(300_000.0, WF.included_at(300_000.0))
assert b.index[0] >= pd.Timestamp("2019-01-01", tz="UTC"), (
f"il book parte da {b.index[0]}: qualche gamba porta storia pre-era-crypto")
def test_il_book_a_4_sleeve_ha_sharpe_superiore_al_singolo_gtaa():
"""Controllo di plausibilita': un book diversificato non puo' avere lo Sharpe di GTAA01
da solo. Con il bug del troncamento faceva esattamente quello."""
from src.portfolio.portfolio import metrics
C = 300_000.0
b = WF.realistic_book(C, WF.included_at(C))
g = WF.realistic_book(C, {"GTAA01": 1.0})
assert metrics(b)["sharpe"] > metrics(g)["sharpe"] + 0.3
# ===========================================================================
# soglie e pesi
# ===========================================================================
def test_soglie_di_sblocco_degli_sleeve():
assert set(WF.included_at(1_000.0)) == {"TP01", "SKH01"}
assert set(WF.included_at(50_000.0)) == {"TP01", "SKH01", "GTAA01"}
assert set(WF.included_at(300_000.0)) == {"TP01", "SKH01", "GTAA01", "XS01"}
@pytest.mark.parametrize("cap", [600.0, 5_000.0, 50_000.0, 300_000.0])
def test_i_pesi_si_rinormalizzano_a_uno(cap):
w = WF.included_at(cap)
assert sum(w.values()) == pytest.approx(1.0)
assert all(v > 0 for v in w.values())
def test_vrp01_e_xsr01_sono_esclusi_per_regola():
"""VRP01: 'niente short-vol da modello in deploy'. XSR01: gate pre-registrato 2026-10-23.
Se qualcuno li aggiunge qui senza passare dai gate, il test lo dice."""
assert "VRP01" not in WF.W_DEPLOY
assert "XSR01" not in WF.W_DEPLOY
assert set(WF.W_DEPLOY) == {"TP01", "SKH01", "GTAA01", "XS01"}
def test_i_pesi_deployable_derivano_dal_book_attivo():
"""W_DEPLOY deve essere il book attivo meno VRP01, rinormalizzato — non pesi inventati."""
from src.portfolio.sleeves import active_sleeves
live = {s.name: s.weight for s in active_sleeves()}
atteso = {
"TP01": live["TP01_trend_1d"], "SKH01": live["SKH01_skyhook"],
"GTAA01": live["GTAA01_eq_trend"], "XS01": live["XS01_xsec_hl"]}
tot = sum(atteso.values())
for k, v in atteso.items():
assert WF.W_DEPLOY[k] == pytest.approx(v / tot, rel=1e-6), (
f"{k}: peso disallineato da active_sleeves()")
# ===========================================================================
# fattore d'ancora
# ===========================================================================
def test_anchor_factor_e_un_haircut_plausibile():
"""Deve essere <1 (l'ancora canonica e' un punto alto) ma non assurdo."""
f = WF.anchor_factor(WF.included_at(300_000.0), draws=40)
assert 0.6 < f < 1.05, f"fattore d'ancora implausibile: {f}"