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

9.3 KiB
Raw Blame History

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'.