Files
PythagorasGoal/docs/diary/2026-08-07-crescita-fisco-etf-scelta.md
T
Adriano Dal Pastro ba5ea2f5c3 docs: diario 07/08 e memoria — e un gate di GTAA01 che ORA FALLISCE
Diario 2026-08-07-crescita-fisco-etf-scelta.md + i bullet corrispondenti in
CLAUDE.md (fisco d'accumulo, confronto book/ETF, scelta 50/50, simulatore web).

Registra anche un buco trovato ricostruendo il piano: i QUATTRO risultati del
27/07 sera (r0727_3k_vs_5k, r0727_orizzonte10, r0727_tasse) non erano ne' in
CLAUDE.md ne' in un diario — vivevano solo nei messaggi di commit, e uno di essi
cambia il numero di testa del piano.

GATE GTAA01 CHE FALLISCE (test_gtaa_band_gate::test_la_proposta_non_e_selezionata
_sull_hold_out): la proposta e' 9a/30 in-sample e 8a/30 sull'hold-out contro il
4/30 e 5/30 registrato il 27/07 — cioe' migliora dove non doveva essere guardata.
Caratterizzato prima di riportarlo: i due ranghi distano 0.00116 di Sharpe su
un'ampiezza di griglia di 0.3124 (0.4%), quindi il criterio non ha mai avuto
margine; il calcolo e' deterministico (2 corse, max|diff| = 0.0) e il codice e'
invariato dal 27/07 -> e' cambiato il DATO, perche' data/raw/ e' gitignored e i
parquet equity sono riscritti ogni giorno dal cron con ADJUSTED_LAST di IB, che
e' retroattivo.

REGOLA NUOVA: un gate validato su dati sovrascritti ogni giorno non e'
ri-verificabile. Il lato cripto non ha il problema (rebuild_history.py ricostruisce
da sorgente deterministica), il lato equity si'.

Il test NON e' stato toccato: allentare una soglia perche' ha smesso di passare e'
proprio cio' che questo progetto vieta, e un xfail silenzierebbe il segnale.
Niente di operativo dipende da questo (GTAA01 non e' deployabile per il blocco
PRIIPs e non e' nel book live), ma l'affermazione "il rango NON migliora
sull'hold-out" oggi e' falsa e la decisione su cosa farne e' dell'operatore.

Book, pesi, cron, config/live.json e i gate pre-registrati: INVARIATI.

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

225 lines
13 KiB
Markdown
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.
# 2026-08-07 — La crescita del capitale, il fisco che nessuno aveva contato, e se invece fosse un ETF
**Richieste dell'operatore, in sequenza:** *«stato progetto»**«riepiloga piano»* → *«grafico della
crescita per anno con versamento e con guadagno»* → *«con 800 al mese»* → *«sistema dinamico dove
imposti valore iniziale, mensile e durata, e calcola la best curva fissando il tempo»* → *«il
guadagno come viene calcolato? e se mettessi in MSCI World o ETF SP500?»* → *«quale è la miglior
strategia?»*.
**Esito: book, pesi, cron, config INVARIATI.** Nessuna decisione operativa cambia. Cambiano tre
numeri pubblicati e cade un'affermazione su GTAA01.
---
## 0. Il buco trovato per primo: quattro risultati mai registrati
Ricostruendo il piano per l'operatore ho scoperto che **quattro commit del 27/07 sera non sono né
in CLAUDE.md né in un diario**: vivono solo nei messaggi di commit e negli script
(`r0727_3k_vs_5k.py`, `r0727_orizzonte10.py`, `r0727_tasse.py`). Uno di questi cambia il numero di
testa del piano — vedi §2. È il motivo per cui questo diario esiste prima ancora del lavoro nuovo.
---
## 1. Come si calcola il «guadagno» — la domanda che andava fatta
Nessuna traiettoria del progetto ipotizza un rendimento. Il meccanismo, ora scritto:
1. si parte dai ritorni giornalieri **veri** del book live (TP01 75% + SKH01 25%), 2.692 giorni dal
2019-03-14, già netti di commissioni;
2. si toglie la fortuna d'ancora: `r (10.89)·media(r)`;
3. si **rimescolano a blocchi di 20 giorni** (block bootstrap): blocchi, non giorni singoli, per
non distruggere il raggruppamento della volatilità;
4. si compone: `cap *= (1+r)` ogni giorno, deposito ogni 30, imposta a fine anno, su 4.000 percorsi;
5. **`guadagno = mediana(capitale) versato`**.
Drift implicito: **17,1% annuo, vol 10,9%**. Non è un'assunzione sul rendimento — è l'assunzione
che *il futuro somigli a quei 7 anni*. Che è esattamente ciò che §3 mette alla prova.
Script: `scripts/research/r0807_growth_yearly.py` (`--lump`, `--dep`, `--anni`, `--json`).
---
## 2. ⚠️ Il fisco durante l'ACCUMULO — la voce mai contata
`TAX_RATE` compariva in **un solo punto** di tutto il progetto: la lordizzazione del bersaglio in
fase di *prelievo*. L'accumulo componeva al lordo per dieci o vent'anni. Misurato il 27/07
(`r0727_tasse.py`) e riprodotto oggi su lump €5.000 + €500/mese:
| | 5 anni | 10 anni | 15 anni |
|---|---|---|---|
| costo del fisco d'accumulo | **15,7%** | **29,8%** | **42,6%** |
(27/07, lump €10k: 16,8% / 31% / 44% → replica coerente su lump diverso.)
**L'errore è composto, non una tantum.** Conseguenza da tenere presente: *tutte* le tabelle a 15-20
anni pubblicate in CLAUDE.md sono al lordo del fisco d'accumulo e vanno lette con questo sconto
sopra. Assunzioni dichiarate (33% plusvalenze, minusvalenze in carry 4 anni, 0,2% annuo sul valore),
**non un parere fiscale**.
Piano a €800/mese, 15 anni: capitale mediano **$450.107** netto contro $776.366 lordo.
📌 **Risultato contro-intuitivo:** versare di più **ritarda il sorpasso** (l'anno in cui il guadagno
cumulato supera tutto il versato): 7º anno a €500/mese, 8º a €800, perché si alza l'asticella che
il guadagno deve superare. I €300 in più non comprano il sorpasso, comprano il **traguardo**: la
mediana tocca il bersaglio al 12º anno invece che al 15º, e P(bersaglio) a 15 anni passa da 73,2% a
99,0%.
⚠️ **€800/mese non bastano per il vincolo dei 10 anni**: P(entro 10 anni) col fisco = **25,5%**.
Servono ~€880-1.050/mese (27/07), e a quel punto si versano oltre $119.000 per arrivare a $272.061.
---
## 3. E se invece fosse un ETF S&P 500 o MSCI World
Script `scripts/research/r0807_asset_compare.py`. Tre cose andavano rese comparabili:
- **la griglia** — le azioni hanno ~252 barre/anno, il book 365: portate su calendario con **0,0 a
borsa chiusa** (convenzione GTAA01). Senza, il Sharpe esce ×1,20 (lezione 25/07);
- **il fisco** — il book realizza ogni anno al 33%; un UCITS ad accumulazione paga il 26% **alla
vendita**. Il differimento è un vantaggio strutturale dell'ETF, ora nel modello (le curve ETF
sono valori di *liquidazione*, già al netto dell'imposta latente);
- **il bersaglio** — $272.061 vale per la rendita perpetua *del book* e per il 33%. Ricalcolato per
ciascuno.
| a 15 anni, €5.000 + €500/mese | book | S&P 500 | MSCI World (proxy) |
|---|---|---|---|
| rendita perpetua | 11,67% | 4,16% | 3,09% |
| capitale-rendita necessario | $254.524 | $646.172 | $869.729 |
| **lente A** — storia piena di ciascuno | $293.823 → **115%** | $204.663 → **32%** | $188.417 → 22% |
| **lente B** — stessa finestra 2019-26 | $295.540 → **118%** | $343.805 → **110%** | $296.707 → 82% |
**Il risultato che conta non è quale vince, è che le due lenti danno risposte opposte.** Sulla
storia piena il book stravince; sulla stessa finestra **l'S&P 500 accumula più del book**. Il
divario della lente A viene interamente dal fatto che i 30 anni dell'indice contengono il 2000 e il
2008, crolli che la strategia non ha mai vissuto.
📌 **E in una riga della tabella delle serie c'è il fatto più importante:** sulla stessa finestra il
rendimento è quasi identico — **17,4% contro 16,8%**. Tutta la differenza è nel rischio: vol 11,0%
contro 19,6%, maxDD 10,5% contro 33,7%. **Il book non guadagna di più, perde di meno** — che è
precisamente ciò che il progetto scrive di TP01 dal 19/06, qui misurato contro un'alternativa vera.
E la rendita perpetua vive sul drawdown: per questo il book ha bisogno di **2,5× meno capitale**.
⚠️ MSCI World è un **proxy** (70% SPY + 30% EFA): URTH/ACWI/VT non sono nell'abbonamento dati del
conto IB. Fra 50% e 80% di quota USA il drift si muove di 0,7 punti e il Sharpe di 0,05.
---
## 4. Quale strategia: book, ETF o un mix
Script `scripts/research/r0807_best_strategy.py`.
⚠️ **Difetto della prima stesura, corretto: un mix esiste solo dove esistono ENTRAMBE le serie.**
Confrontavo «storia piena» contro «stessa finestra» come due lenti, ma calcolavo i bersagli del mix
sull'intersezione in entrambe — quindi la lente «storia piena» dichiarava trent'anni e ne usava
sette (per l'ETF puro dava rendita 8,63% invece del 4,16% vero). **Il confronto fra finestre non è
disponibile per un portafoglio misto.** Rifatto su una finestra sola, con gli scenari espressi come
spostamento del **drift**, applicabile simmetricamente ai due lati.
| quota sul book | base | equity a 30 anni | book dimezzato | entrambi cauti | peggiore | **rimpianto max** |
|---|---|---|---|---|---|---|
| solo ETF | 68% | 23% | 68% | 23% | 23% | 53% |
| 25% book | 80% | 41% | 60% | 27% | 27% | 34% |
| **50% book** | **86%** | 58% | 48% | 27% | 27% | **20%** |
| 75% book | 84% | 69% | 32% | 23% | 23% | 35% |
| solo book | 75% | **75%** | 16% | 16% | 16% | 52% |
**Risposta: 50/50**, non perché vinca (vince solo nello scenario base) ma perché ha il **rimpianto
massimo più basso**. ⚠️ Il criterio del solo caso peggiore **non** distingue 25% da 50% (27,09%
contro 26,99% = pareggio dentro il rumore): a separarli è il rimpianto.
📌 **L'asimmetria fra i due stress È il risultato.** Quello sull'equity è **misurato** — trent'anni
esistono e dicono 11,3% invece di 16,5%. Quello sul book è **giudiziale** — metà del drift, scelto a
mano, perché 7,4 anni sono tutta la storia che ha e *non c'è nulla con cui stressarlo*. Si può
discutere la taglia del taglio, non lo si può togliere. La sua conseguenza è brutale: col drift
dimezzato il capitale-rendita del book puro passa da $254k a **$771.646** (rendita 11,67% → 3,85%).
**Perché il mix non è un compromesso:** correlazione book↔S&P **+0,082** nei giorni di borsa aperta,
**+0,046** nei ribassi; nel 5% di giornate peggiori dell'indice (media 2,94%) il book fa **0,11%**.
Il capitale-rendita del 50/50 è **$235.769**, più basso sia del solo ETF ($311.567) sia del solo
book ($254.524): due motori scorrelati *abbassano l'asticella*.
⚠️ Non c'è il rischio di venue (spingerebbe ancora verso il mix), e la decisione del 26/07 tiene
tutto su Deribit fino a $20k: questa analisi è **materiale per quella soglia**, non un'indicazione
di agire adesso.
---
## 5. Il simulatore nel browser, e la sua verifica
`scripts/web/` — motore di accumulo in JavaScript (`engine.js`), serie esportata da
`export_series.py`, pagine assemblate da `build.py`.
**Il motore JS deve dare la stessa risposta del Python, e lo si prova:** `test_engine.js` confronta
mediane e probabilità su due configurazioni già calcolate, verifica che il *versato* (deterministico)
coincida al centesimo, e include un **controllo positivo** (un motore col fisco spento dev'essere
rilevato fuori tolleranza — un test che non sa fallire non è un test). Il risolutore («quanto serve
al mese per centrare il bersaglio in N anni») è validato contro `dep_necessario` di `r0727_tasse.py`:
scarti 0,4% / 0,6% / 1,1% su tre configurazioni.
Misura che ha cambiato una scelta: la banda del versamento suggerito resta **±1% da 1.200 a 3.000
percorsi** → non domina il Monte Carlo ma la granularità della bisezione (~€5). Percorsi tenuti
bassi e **incertezza dichiarata**, invece di pagare tempo per una precisione che non arriva.
---
## 6. Quattro errori miei, tutti trovati da un controllo e non a occhio
Sono la parte riutilizzabile del diario.
**(a) Dati inventati in un grafico.** Le righe degli anni 1-2 di una serie erano scritte *a memoria*
perché l'output del terminale era troncato da `tail`. Le ha trovate il confronto riga-per-riga fra
il JSON e i dati incollati nella pagina. **Regola: i dati di una pagina si INIETTANO da un file, mai
si trascrivono** — ora è `build.py` a farlo, e il passaggio manuale non esiste più.
**(b) «Distorsione sistematica» che non esisteva.** Il motore JS sembrava +0,75% sopra il Python su
8 semi, tutti positivi. Ma erano **8 estrazioni contro UN solo punto Python** (seme 807), che stava
in basso nella propria distribuzione: 8 segni concordi rispetto a un punto rumoroso non sono 8
osservazioni indipendenti. Misurato bene (8 semi per parte): **0,01%, t = 0,04**; e i due
campionatori cadono entrambi entro 1,7 SE dall'**atteso analitico** della media di blocco.
**Regola: un confronto punto-contro-distribuzione non produce evidenza di distorsione.**
**(c) Un confronto impossibile per costruzione** — §4, il mix sull'intersezione.
**(d) Una pagina pubblicata rotta.** Python scrive le chiavi dei pesi come `"0.0"`/`"1.0"`, la pagina
le ricostruiva con `String(0)``"0"`: ogni lookup `undefined`, script morto alla prima riga utile.
I pesi intermedi funzionavano *per caso*. **Il controllo che usavo (`node --check`) valida solo la
SINTASSI**: ora c'è `smoke.js`, che esegue le pagine con un DOM finto e segnala sia le eccezioni sia
gli elementi riempiti con `"undefined"`. **Regola: le chiavi di un dizionario Python si LEGGONO dal
JSON, non si ricostruiscono.**
---
## 7. ⚠️ Effetto collaterale del check: un gate di GTAA01 ora FALLISCE
`tests/test_gtaa_band_gate.py::test_la_proposta_non_e_selezionata_sull_hold_out` — la proposta
(banda 25%, cadenza 5g) risulta **9ª/30 in-sample e 8ª/30 sull'hold-out**: migliora passando
all'hold-out, cioè la firma che il test intercetta. CLAUDE.md registra **4/30 e 5/30** (27/07).
Caratterizzato prima di riportarlo:
- i due ranghi sono separati da **0,00116 di Sharpe** su un'ampiezza di griglia di 0,3124 = lo
**0,4%**: il criterio non ha mai avuto margine, era una differenza di un rango;
- il calcolo è **deterministico** (due corse, `max|diff| = 0.0`) e il codice del gate è **invariato**
dal 27/07 (nessun commit su `r0727_gtaa_band_gate.py`, `gtaa.py`, `eqlib.py`, `eq_splits.py`);
- quindi è cambiato **il dato**: `data/raw/` è gitignored e i parquet equity vengono riscritti ogni
giorno dal cron con `ADJUSTED_LAST` di IB, che è retroattivo.
📌 **La lezione vale più del test: un gate validato su dati sovrascritti ogni giorno non è
ri-verificabile.** Il lato cripto non ha questo problema (`rebuild_history.py` ricostruisce da una
sorgente deterministica); il lato equity sì.
**Il test NON è stato toccato** — allentare una soglia perché ha smesso di passare è ciò che questo
progetto vieta, e un `xfail` silenzierebbe esattamente il segnale. Nulla di operativo dipende da
questo (GTAA01 non è deployabile per il blocco PRIIPs e non è nel book live), ma l'affermazione
*«il rango NON migliora sull'hold-out»* oggi è falsa: **decisione dell'operatore** se correggere
l'affermazione, ripensare il gate, o congelare uno snapshot dei parquet equity per renderlo
riproducibile.
---
## Cosa NON cambia
Book, pesi, cron, `config/live.json`, i gate pre-registrati (STATARB 27/09, XSR01 23/10,
DVOLSPREAD 24/10), la decisione di venue (100% Deribit fino a $20k), il muro pubblicato di
$272.061. Gli script di questo diario non toccano la produzione.