3a44171402
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>
103 lines
4.4 KiB
Python
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}"
|