fix(gtaa): costo IB reale (pavimento fisso) + esecuzione a banda — lo sleeve era cieco al capitale

DIFETTO. src/portfolio/gtaa.py modellava il costo come 2bps PROPORZIONALI e il modulo si
dichiarava "eseguibile a basso capitale, switch mensile/basso turnover". Entrambe false:
il vol-target e' CONTINUO (l'esposizione cambia ogni giorno, su 6 gambe) e IB non ha una
fee proporzionale ma un PAVIMENTO FISSO per ordine, min(max($0.35, $0.0035/az), 1% del
controvalore) = ~$530/anno indipendenti dal capitale.

Il modello vecchio era quindi CIECO AL CAPITALE: dava Sharpe 0.64 sia a $50.000 sia a $600.
Alla taglia grande era sostanzialmente giusto (0.64 vs 0.59-0.66 reali); a $600 la realta'
e' -0.55 di Sharpe e -3.5%/anno di CAGR. L'errore era tutto concentrato dove lo sleeve
verrebbe realmente deployato.

FIX. Il modulo ora:
  * modella la commissione IB reale (ib_commission);
  * esegue a BANDA + CADENZA (settimanale, $50/gamba) — config scelta sul PLATEAU del sweep
    (weekly/monthly x banda 25-100 -> Sh 0.45-0.64 a ogni capitale), non sull'argmax
    in-sample (banda $50 daily a $600 = cella isolata che crolla a banda 100);
  * prende il CAPITALE allocato come parametro: gtaa_returns(capital=...);
  * dichiara la soglia di deployabilita': GTAA_MIN_CAPITAL=$3.000 + gtaa_is_deployable();
  * espone gtaa_rebalance_plan(held, capital) per l'esecutore — salta le gambe il cui
    nozionale non supera la banda (un ordine da $12 costa $0.35 = 2.9%).
sleeves.py dichiara esplicitamente il capitale assunto (GTAA_DEFAULT_CAPITAL=$10.000 allocati
= book ~$50k al peso 20%) invece di nasconderlo.

IMPATTO SUL BOOK (misurato sostituendo la sola gamba GTAA, non citato):
  GTAA01 standalone  Sh 0.64 -> 0.61   CAGR 3.76% -> 3.65%
  book 5 sleeve      FULL 2.22 -> 2.22  HOLD 2.36 -> 2.38  maxDD 6.2% -> 6.0%
Trascurabile alla taglia assunta: il fix conta per il DEPLOY (a $600-2k passa da -3.5%/anno
a +3.0%/anno). PESI INVARIATI -> nessun weights_tilt_null richiesto.

CORREZIONE A UN ERRORE DI ANALISI DELLA SESSIONE. Il diario citava il modello vecchio a
"Sharpe 0.77 / CAGR 5.5%": artefatto di annualizzazione: la serie GTAA grezza ha ~252 barre
/anno (soli giorni di borsa) e metrics() annualizza a 365 con years=n/365.25 -> Sharpe x1.20
e CAGR x1.45. Le righe per-capitale erano gia' su calendario 365, quindi era falsata solo la
riga di riferimento. Corretti diario, CLAUDE.md e r0725_capcurve.py.
LEZIONE: una serie su giorni di borsa non si passa a metrics() senza to_daily().

Test: 221 pass (+4 in tests/test_gtaa_sleeve.py: pavimento non proporzionale, dipendenza dal
capitale + soglia, senza-banda-e-distruttivo, piano che salta le gambe sotto banda).
Smoke: scripts/live/paper_combo.py gira invariato.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Adriano Dal Pastro
2026-07-25 17:25:06 +00:00
parent 3266f3a09a
commit 607cd94ecd
6 changed files with 248 additions and 42 deletions
+37 -10
View File
@@ -25,11 +25,25 @@ Entrambe le cose sono false come codificate:
| capitale allocato a GTAA | CAGR reale | Sharpe reale | drag commissioni |
|---|---|---|---|
| $600 | **3.5%** | **0.55** | 9.0%/anno |
| $2.000 | 0.7% | 0.09 | 6.2%/anno |
| $5.000 | +1.5% | 0.28 | 4.0%/anno |
| $50.000 | +3.4% | 0.59 | 2.1%/anno |
| *modellato (2bps)* | *+5.5%* | *0.77* | — |
| $600 | **3.5%** | **0.55** | 7.2%/anno |
| $2.000 | 0.7% | 0.09 | 4.4%/anno |
| $5.000 | +1.5% | 0.28 | 2.2%/anno |
| $50.000 | +3.4% | 0.59 | 0.2%/anno |
| *modello vecchio (2bps propor.)* | *+3.8%* | *0.64* | — |
⚠️ **Correzione a un errore MIO, trovata implementando il fix.** La prima stesura di questa
sezione citava il modello vecchio a *Sharpe 0.77 / CAGR 5.5%*: era un **artefatto di
annualizzazione** del mio script di analisi — metricavo la serie GTAA grezza (solo giorni di
borsa, ~252/anno) con `metrics()`, che annualizza a 365 e calcola `years = n/365.25` → Sharpe
gonfiato di √(365/252) = 1.20× e CAGR di ~1.45×. Le righe per-capitale erano già su calendario
365 (reindex + weekend a zero), quindi **il confronto era falsato solo sulla riga di riferimento**.
Valore corretto del modello vecchio: **0.64 / 3.8%**.
**La formulazione giusta del difetto cambia di conseguenza.** Non è "il modello sovrastimava lo
Sharpe di 0.13": alla taglia grande il modello vecchio era sostanzialmente **giusto** (0.64 contro
0.59-0.66 reali). Il difetto è che era **CIECO AL CAPITALE** — dava 0.64 sia a $50.000 sia a $600,
mentre a $600 la realtà è **0.55**. Un modello di costo proporzionale non può descrivere un
pavimento fisso, e l'errore è tutto concentrato dove lo sleeve verrebbe realmente deployato.
**La banda lo salva.** Con costo fisso la banda ottimale non è il minimo eseguibile (come su
Deribit) ma un valore molto più alto. Plateau robusto = **cadenza settimanale × banda $25-100**
@@ -38,11 +52,24 @@ dà **Sh 0.52 / CAGR 3.0%** invece di 0.55/3.5%. (La cella argmax a $600 e
a 0.57, ma è isolata — crolla a 0.28 a banda 100 → scartata: il parametro è di costo, si sceglie
sul plateau, non sull'argmax in-sample.)
**Due conseguenze.**
1. Se GTAA01 va live su IB **deve** avere banda+cadenza cablate. Come sta ora, a $600-2k è un
distruttore di capitale.
2. Lo Sharpe **onesto** di GTAA01 è **~0.55-0.64, non 0.77** → i numeri headline del book a 5
sleeve vanno citati con questo haircut (in aggiunta all'anchor-luck già documentato).
**Conseguenza operativa: CABLATO** (stessa sessione, `src/portfolio/gtaa.py`). Il modulo ora
modella il costo IB reale (`ib_commission`), esegue a **banda + cadenza** (settimanale, $50/gamba),
prende il **capitale allocato** come parametro (`gtaa_returns(capital=...)`), dichiara la soglia
di deployabilità (`GTAA_MIN_CAPITAL = $3.000`, `gtaa_is_deployable`) ed espone
**`gtaa_rebalance_plan(held, capital)`** — il piano che l'esecutore deve usare per non ribilanciare
ogni giorno per pochi dollari. `sleeves.py` dichiara esplicitamente il capitale assunto
(`GTAA_DEFAULT_CAPITAL = $10.000` allocati ≈ book $50k al peso 20%) invece di nasconderlo.
**Impatto sul book, misurato prima/dopo** (non citato: ricalcolato sostituendo la sola gamba GTAA):
| | GTAA01 standalone | book 5 sleeve FULL | HOLD | maxDD |
|---|---|---|---|---|
| prima (2bps, continuo) | Sh 0.64 / CAGR 3.76% | 2.22 | 2.36 | 6.2% |
| dopo (IB reale + banda) | Sh 0.61 / CAGR 3.65% | 2.22 | **2.38** | **6.0%** |
Alla taglia assunta l'impatto sul book è **trascurabile** — il fix non è cosmetico per i numeri
storici, lo è per il **deploy**: è a $600-2.000 che cambia tutto (da 3.5%/anno a +3.0%/anno).
Pesi **invariati** (nessun cambio pesi → nessun `weights_tilt_null` richiesto).
**Regola nuova (generale): il costo di un venue va modellato nella sua FORMA — fisso vs
proporzionale — non solo nel suo livello.** Due venue con lo stesso "costo medio" danno