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>
This commit is contained in:
@@ -0,0 +1,180 @@
|
||||
# 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'.
|
||||
Reference in New Issue
Block a user