Files
PythagorasGoal/docs/diary/2026-08-07-gate-gtaa-e-storia-troncata.md
T
Adriano Dal Pastro 963776e5d2 research(gtaa): il gate (A) non misurava cio' che dichiarava — e TLT ha 13.5 anni in meno
Il test del gate sulla banda GTAA01 aveva smesso di passare senza che il codice fosse
cambiato (data/raw/ e' gitignored, IB rivede ADJUSTED_LAST all'indietro ogni notte).
Invece di allentare la soglia, misurata la risoluzione del criterio.

`rank_in <= rank_oos` lo passano 14/30 celle (47%) PER COSTRUZIONE: la somma dei ranghi
e' la stessa nelle due finestre. E sulla proposta si decideva su 0.00116 di Sharpe contro
uno spread di griglia di 0.3124. Il 27/07 quel criterio passava, e passava per caso.

Criterio sostituito con quello decidibile: la proposta e' una BANDA (la cadenza settimanale
e' gia' produzione), quindi a cadenza fissa la banda scelta sui soli dati pre-2015 e' 25%
= la proposta, margine +0.0151 (13x il vecchio); chi avesse scelto sull'hold-out avrebbe
preso 40%. Controllo positivo incluso. Regge sull'universo coerente a 5 gambe.

TROVATO PER STRADA: TLT parte dal 2016-02-03 invece che dalla quotazione (2002-07-22) →
GTAA01 gira su CINQUE gambe prima del 2016, e quella assente e' la gamba obbligazionaria.
Non e' di oggi (cosi' dal primo giro nel cron log del 24/06, prima della validazione) e non
e' un fetch da rifare: una richiesta retro esplicita a IB ritorna 0 barre.

Nessuna certificazione l'aveva visto perche' tutte guardano DENTRO la serie: una serie
troncata e' integra, senza gap, senza spike, senza duplicati. Cablate due guardie in
fetch_ib_equities.certify — TRONCATO (storia persa vs disco; il file NON viene sovrascritto
ne' fuso, ADJUSTED_LAST e' ri-aggiustato all'indietro e il giunto creerebbe un salto) e
STORIA-CORTA (parte dopo la quotazione, con distinzione dal tetto della richiesta 30Y).
Controlli negativi obbligatori: giro normale, serie al cap, ETF giovane, simbolo fuori
tabella.

Produzione INVARIATA: REBAL_BAND_USD resta $50, GTAA01 resta non deployabile (PRIIPs).

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

181 lines
9.3 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 — Un gate che falliva, un criterio che non misurava, e una gamba con 13 anni in meno
**Book, pesi, cron, config: INVARIATI.** `REBAL_BAND_USD` resta $50; GTAA01 resta non deployabile
(PRIIPs) e sotto `GTAA_MIN_CAPITAL`. Cambia un test, cambia un'affermazione in CLAUDE.md, e si
aggiunge una guardia di certificazione che mancava.
Script: `scripts/research/r0807_gtaa_gate_resolution.py`.
Test: `tests/test_eq_history_guard.py` (11) + `tests/test_gtaa_band_gate.py` (16, criterio (A)
sostituito).
---
## Il punto di partenza
Il 07/08, facendo il giro dei test, `test_la_proposta_non_e_selezionata_sull_hold_out` falliva:
```
la proposta e' 8a sull'hold-out ma 9a in-sample: sta meglio dove non doveva essere guardata
```
Il gate (A) del 27/07 (`r0727_gtaa_band_gate.py`) era stato registrato cosi': «proposta 4/30
in-sample, 5/30 hold-out → il rango NON migliora sull'hold-out, quindi non e' selection-on-holdout».
Il codice non era stato toccato (`git log src/portfolio/gtaa.py` fermo al 26/07). Erano cambiati i
**dati**: `data/raw/` e' gitignored e il cron ri-scarica ogni notte i sei ETF con `ADJUSTED_LAST`,
che IB rivede all'indietro a ogni dividendo.
La tentazione ovvia era allentare la soglia o mettere un `xfail`. Sarebbe stato mettere a tacere
esattamente il segnale. La domanda giusta e' un'altra: **il criterio misura cio' che dichiara?**
---
## (0) Il difetto trovato per strada: TLT ha 13.5 anni in meno
Prima ancora di guardare il criterio, la copertura delle sei gambe:
| gamba | prima barra | quotato dal | mancano | barre | pre-2015 | 2015+ |
|---|---|---|---|---|---|---|
| SPY | 1996-08-14 | 1993-01-22 | 3.6a | 7540 | 4625 | 2915 |
| QQQ | 1999-03-10 | 1999-03-10 | 0.0a | 6896 | 3981 | 2915 |
| IWM | 2000-05-26 | 2000-05-22 | 0.0a | 6586 | 3671 | 2915 |
| **TLT** | **2016-02-03** | **2002-07-22** | **13.5a** | 2642 | **0** | 2642 |
| GLD | 2004-11-18 | 2004-11-18 | 0.0a | 5460 | 2545 | 2915 |
| HYG | 2007-04-11 | 2007-04-04 | 0.0a | 4861 | 1946 | 2915 |
(SPY parte dal 1996 perche' la richiesta chiede `durationStr="30 Y"`: e' il **tetto**, non un
difetto. La distinzione conta, senza di essa una guardia segnalerebbe ogni serie lunga.)
**GTAA01 gira su CINQUE gambe prima del 2016**, e la gamba assente e' proprio quella che
diversifica — le obbligazioni. Conseguenza diretta sul gate (A): l'in-sample (pre-2015) e
l'hold-out (2015+) **non sono la stessa strategia**.
Non e' successo il 07/08: nel `logs/cron_daily.log` TLT parte dal 2016-02-03 fin dal primo giro
registrato (24/06), quindi da **prima** della validazione del 27/07. E non e' un fetch da rifare:
una richiesta retro esplicita (`endDateTime=2016-01-01`, `durationStr="5 Y"`) su questo conto IB
ritorna **0 barre**. La storia non c'e'.
**Perche' nessuna certificazione l'ha vista.** Il feed equity ha guardie su integrita', gap lunghi,
spike, split non aggiustati (25/07) e cross-check col gemello UCITS (26/07). **Tutte guardano
DENTRO la serie.** Una serie troncata e' perfettamente integra: non ha buchi, non ha salti, non ha
duplicati. E' la terza volta in due settimane che una guardia tarata su una classe di difetto non
sorveglia le altre (soglia 50% cieca allo split 2:1; soglia sulla deviazione cieca alla
contaminazione EUR/USD; ora: ogni controllo cieco a cio' che la serie ha perso).
### Cablato
`fetch_ib_equities.certify(sym, df, prev)` ora ha **due** guardie, perche' i due difetti non si
vedono nello stesso modo:
- **`TRONCATO`** — la serie ha perso storia *rispetto al disco* (parte >10 giorni dopo, o ha >5
barre in meno). Prende una troncatura il giorno in cui compare; e' cieca a una gia' presente.
**Il file NON viene sovrascritto** — e neppure fuso: `ADJUSTED_LAST` e' ri-aggiustato
all'indietro a ogni dividendo, quindi incollare una vintage vecchia a una nuova creerebbe un
salto sul giunto, un difetto peggiore di quello che si voleva evitare.
- **`STORIA-CORTA`** — la serie parte >1 anno dopo la quotazione dello strumento, e non al tetto
della richiesta. Prende anche una troncatura presente da sempre, che e' il caso di TLT.
Riferimento: `PRIMA_QUOTAZIONE`, sei simboli, **fonte secondaria dichiarata**.
Controlli positivi obbligatori nei test: il giro normale di ogni notte non deve scattare, una
serie al tetto 30Y non deve scattare, un ETF giovane (HYG) non deve scattare, un simbolo fuori
tabella non viene giudicato. *Una guardia che non segnala mai e' indistinguibile da una rotta.*
---
## (1) Il criterio non aveva risoluzione — misurato, non argomentato
| | valore |
|---|---|
| rango della proposta | 9/30 in-sample · 8/30 hold-out |
| distanza dal rango precedente | **0.00116** di Sharpe in-sample |
| spread dell'intera griglia | 0.3124 |
| il verdetto si decideva su | **0.37% dello spread** |
E la misura che chiude la questione — **il criterio applicato a ogni cella della griglia passa
14/30 (47%)**. Non e' un caso: la somma dei ranghi e' la stessa nelle due finestre, quindi
`rank_in <= rank_oos` e' vero per circa **meta' delle celle per costruzione**, qualunque cosa la
griglia contenga.
> Un gate che una cella a caso passa il 47% delle volte non distingue una proposta onesta da una
> selezionata sull'hold-out. E' una moneta.
Contorno: lo spostamento tipico fra le due finestre e' di **8 ranghi** (massimo 25), e lo Spearman
IS/OOS e' **+0.05**. Su questa griglia il rango in-sample non porta informazione sul rango
hold-out — il che rende il confronto fra i due ranghi doppiamente privo di senso.
---
## (2) Il criterio decidibile
**La proposta del 27/07 e' una BANDA, non una cadenza.** `REBAL_EVERY=5` e' gia' la produzione e
non era in discussione. Quindi la domanda sulla provenienza della scelta e': *a cadenza di
produzione, quale banda si sceglie guardando solo il pre-2015?*
| banda | Sh in-sample | Sh hold-out |
|---|---|---|
| 0% | 0.5396 | 0.8261 |
| 5% | 0.5714 | 0.8640 |
| 10% | 0.5956 | 0.8629 |
| **25%** | **0.6398** ← argmax | 0.8529 |
| 40% | 0.6247 | **0.9081** ← argmax |
| 60% | 0.5874 | 0.7573 |
- banda scelta **sui soli dati pre-2015**: **25%** = la proposta, con margine **+0.0151** sulla 2ª;
- banda scelta **sull'hold-out**: **40%** — diversa. *(Controllo positivo: se coincidessero, il
gate non avrebbe potenza e non andrebbe citato come validazione.)*
- cella scelta al buio su **tutta** la griglia: cadenza 1, banda **25%** — stessa banda.
**La proposta e' l'esatto contrario di una selezione-sull'hold-out**: e' la cella che si sceglie
senza guardare l'hold-out, e chi avesse guardato l'hold-out ne avrebbe scelta un'altra. E il
margine e' **0.0151** contro i **0.00116** su cui si decideva il criterio a ranghi: **13×**.
⚠️ **Cio' che questo criterio NON dice:** che la banda scelta in-sample sia la migliore
sull'hold-out. Non lo e'. Con lo Spearman IS/OOS a ~0, nessuna cella di questa griglia lo sarebbe
in modo affidabile. Il gate (A) risponde alla domanda sulla **provenienza** della scelta, non a
quella sulla **previsione**. Confonderle e' esattamente il modo in cui si finisce a selezionare
sull'hold-out credendo di validare.
---
## (3) Robustezza al difetto (0)
Rifatto tutto sull'universo a **5 gambe** (senza TLT), coerente fra le due finestre:
blind **25%**, hold-out **40%**, margine **+0.0164**. Verdetto identico. Il criterio a ranghi
resta una moneta anche li' (passa 14/30).
Quindi: il difetto dei dati **non e' cio' che decide questo verdetto** — ma resta un difetto, e va
riparato per suo conto. Che e' cio' che si e' fatto al punto (0).
---
## Cosa cambia
- `tests/test_gtaa_band_gate.py`: il criterio a ranghi e' **ritirato** e sostituito da
`test_la_banda_proposta_e_quella_scelta_al_buio` + il suo controllo positivo
(`test_chi_guardasse_l_hold_out_sceglierebbe_una_banda_DIVERSA`) + un test sul margine.
Il **motivo** del ritiro e' congelato in
`test_il_confronto_fra_ranghi_e_una_moneta_ed_e_per_questo_che_e_stato_RITIRATO` — si congela il
motivo, non l'esito, che dipende dai dati e si muove da solo.
- `r0727_gtaa_band_gate.py`: il gate (A) stampa il criterio decidibile; il confronto fra ranghi
resta come descrizione, con la data del ritiro.
- CLAUDE.md: l'affermazione «proposta 4/30 in-sample, 5/30 hold-out → il rango NON migliora» e'
corretta.
- `fetch_ib_equities.py`: le due guardie sulla storia + il riepilogo finale che le elenca.
---
## Regole
1. **Un test che fallisce senza che il codice sia cambiato sta segnalando che i dati non sono
versionati.** Prima di toccarlo, si guarda cosa e' cambiato sotto.
2. **Un criterio va misurato sulla sua risoluzione prima che sul suo esito.** Se decide su una
frazione di percento dello spread, il verdetto e' rumore in entrambi i versi — anche quando
passa. Il 27/07 quel criterio *passava*, e passava per caso.
3. **Un gate si valida contando quante volte lo passa un candidato a caso.** Qui: 47%. Il numero
si poteva calcolare il 27/07 senza dati nuovi.
4. **Una certificazione che guarda solo dentro la serie non vede cio' che la serie ha perso.**
Una serie troncata e' integra, senza gap, senza spike, senza duplicati: passa tutto.
5. **Distinguere «giovane», «al tetto della richiesta» e «troncato».** Sono tre cose diverse e
solo la terza e' un difetto; senza la distinzione la guardia segnala sempre e viene ignorata.
6. **Provenienza e previsione sono due domande diverse.** Un gate anti-selezione dice da dove
viene la scelta, non se funzionera'.