3 Settembre 2026 · ingegneria dei dati

I minuti che non esistono: lacune, sospensioni e barre a volume zero nella cronologia OHLCV

I minuti che non esistono: lacune, sospensioni e barre a volume zero nella cronologia OHLCV

Un anno solare di barre da 1 minuto dovrebbe contenere 525,600 righe. La nostra estrazione del 2025 dei perpetual di BTCUSDT ne ha restituite 525,557: 43 minuti in meno, con un tasso di completezza del 99.992%. Un perpetual di un'altcoin a media capitalizzazione sulla stessa piattaforma, con la stessa estrazione e lo stesso codice, ne aveva 1,247 in meno: 99.76%. E una serie al minuto di azioni statunitensi per lo stesso anno aveva circa 427,000 righe in meno rispetto a quella crypto: non è una lacuna, semplicemente il mercato chiude.

Tre di questi numeri non hanno nulla di speciale. Quello che vale mille parole è 43.

525,600barre da 1 minuto in un anno non bisestile
0.008%mancanti su BTCUSDT, nel 2025
61%di quei minuti cadevano nelle ore con volatilità del decile più alto

Quattro cose diverse che nel tuo dataframe sembrano tutte una lacuna

Prima di parlare dei 43 minuti, facciamo chiarezza sulle categorie, perché gran parte del codice che gestisce le lacune sbaglia già a questo livello. Quando manca una riga nel tuo parquet locale, non sai quale di questi casi l'abbia causata, e la risposta giusta cambia per ciascuno.

TipoCos'è successo davveroCome si presentaRisposta corretta
Nessuno scambioIl mercato era aperto; in quel minuto nessuno ha attraversato lo spreadBarra a volume zero (OHLC tutti uguali, trades=0) su alcuni endpoint, riga assente su altriConservala. È informazione reale: nessuno voleva negoziare.
Piattaforma fuori servizioMotore di matching offline, con o senza preavvisoRiga assente, a volte per una serie di minutiSegnala dati obsoleti. Non negoziare durante l'interruzione.
Sospensione dello strumentoSuperamento della banda LULD, notizie in attesa, avviso di delistingRiga assente, poi una transazione all'asta di riaperturaSegnala dati obsoleti e tratta la riapertura come una discontinuità.
Errore tuoPaginazione limitata dal rate limit, un tentativo ripetuto che ha perso una pagina, un bug di confine del fuso orario nel cicloRiga assente, indistinguibile dai casi precedentiRilevala e ripeti l'estrazione. È l'unico caso che puoi correggere.

Le piattaforme non si comportano allo stesso modo nel primo caso della tabella, ed è lì che nascono i problemi. L'endpoint delle candele di Coinbase salta del tutto gli intervalli vuoti, perciò la cronologia di una coppia poco liquida è piena di lacune vere e proprie. Le klines di Binance invece di solito forniscono una barra sintetica con volume 0 e open=high=low=close fissati all'ultima transazione. Stesso fatto di fondo, due forme diverse; un loader che reindicizza su una griglia completa al minuto trasforma l'una nell'altra senza avvisarti. Prima di scrivere il codice per colmare le lacune, controlla come si comporta la tua piattaforma.

I 1,247 minuti mancanti sull'altcoin rientravano quasi tutti nella prima categoria: book scarno alle 04:00 UTC di domenica, nessuno scambio. Fastidiosi, facili da interpretare e in gran parte innocui, perché la strategia non avrebbe comunque operato in quei momenti. Ed è proprio per questo che ho smesso di guardarli e sono tornato ai 43.

I 43 minuti non erano sparsi

Se i minuti mancanti fossero distribuiti uniformemente, i 43 minuti dell'anno sarebbero 43 singoli isolati, uno ogni otto giorni e mezzo, ciascuno trascurabile. Non è quello che abbiamo trovato. Erano concentrati in 6 intervalli: uno di 19 minuti consecutivi, uno di 11, due di 4 e un paio di intervalli da 2. Sei eventi, non 43 incidenti.

E questi eventi dipendono da ciò che ti interessa. Le piattaforme si bloccano quando sono sotto carico, e sono sotto carico quando il prezzo si muove. Ho suddiviso tutte le ore dell'anno in base alla volatilità realizzata e ho controllato dove cadevano i minuti mancanti: il 61% era nel decile più alto. La probabilità incondizionata che manchi un minuto qualsiasi è 0.008%. Se invece consideriamo solo le ore con volatilità nel decile più alto, è circa 0.05%: 6 volte tanto, e per di più i minuti sono raggruppati.

Quindi la metrica di completezza nella dashboard della qualità dei dati misura la cosa sbagliata. 99.992% sembra indicare un dataset a cui non serve più pensare. In realtà descrive una serie completa nelle ore in cui la tua strategia non fa nulla e piena di lacune in quelle in cui fa tutto. Un sistema momentum che si attiva quando aumenta la volatilità ha una probabilità molto più alta di imbattersi in una lacuna di quanto suggerisca il dato complessivo, e ci si imbatte a operazione già aperta.

Cosa fa il forward-fill tre righe dopo

Ecco il problema che mi ha spinto a scrivere questo articolo. Prendi l'intervallo di 19 minuti. La normale pulizia dei dati: reindicizzare sulla griglia completa al minuto, applicare il forward-fill all'OHLC usando l'ultima chiusura e impostare il volume a zero. La serie ora è continua e gli indicatori funzionano senza un NaN in vista.

Quelle 19 barre hanno high == low == close. Il true range è zero in ognuna. Un ATR(14) calcolato su quella finestra, partendo da un valore di circa 240 USDT prima dell'interruzione, scende a circa 34 quando la piattaforma torna operativa: le 5 barre reali superstiti nella finestra determinano l'intera media. A quel punto, passalo a un dimensionatore di posizioni basato sulla volatilità, del normale tipo size = risk_budget / ATR. La dimensione aumenta di 7 volte.

La barra reale successiva è quella della riapertura, e non è affatto tranquilla. Nel nostro caso ha aperto 1.8% sopra l'ultima chiusura prima dell'interruzione. Il backtest ha aperto senza problemi una posizione 7 volte più grande prima di un gap dell'1.8%, con un'esecuzione impossibile e a un prezzo che nessuno quotava. Quel singolo trade sintetico valeva più di un mese di P&L legittimo sulla curva del capitale, ma nella direzione sbagliata: era nato interamente da una riga di pulizia dei dati, scritta per rendere ordinato il dataframe.

Eliminare le righe invece di riempirle non risolve il problema: è lo stesso bug con un altro travestimento. Una volta eliminate, i lookback con indice intero mentono: una "EMA a 20 barre" copre ora 39 minuti di orologio attraverso l'interruzione, il rendimento tra una barra e l'altra sul confine è l'intero salto dell'1.8% trattato come movimento di un minuto e qualsiasi stima della volatilità per barra lo legge come un evento da 60 sigma. E niente ti avvisa. L'indice è ancora monotono.

Con il ricampionamento il problema diventa invisibile

La maggior parte della ricerca non usa barre da 1 minuto, ma dati aggregati, e l'aggregazione maschera il problema. Ricampiona a 5 minuti e una lacuna di 19 minuti diventa 4 barre, la prima e l'ultima delle quali sono parziali. Pandas calcola un OHLC dall'aspetto del tutto ragionevole usando 2 minuti superstiti e gli assegna la stessa etichetta di una barra costruita con 5 minuti. Nell'output, nulla permette di distinguerle.

La soluzione più semplice che conosco: conserva una colonna bars_in_window in ogni ricampionamento e non eliminarla mai. Un intero per riga, che permette di rispondere a tutte le domande successive sull'affidabilità di una barra. Conserviamo anche seconds_since_last_real_print, che contiene le stesse informazioni in un formato su cui il livello di esecuzione può agire.

La nostra policy, per quel che vale

Ecco cosa applicano ora i nostri agenti, nell'ordine:

  1. Mai reindicizzare senza avvisare. Il loader genera un manifest delle lacune: inizio, fine, durata e la categoria tra le 4 a cui ritiene appartengano. Se mancano meno di 3 barre consecutive e il volume delle barre circostanti è basso, si tratta di un minuto senza scambi. Qualsiasi intervallo più lungo durante le ore di attività viene considerato un'interruzione finché non si dimostra il contrario.
  2. Ripeti l'estrazione prima di interpretare. La metà delle prime lacune dipendeva da bug di paginazione. Una seconda estrazione da un endpoint o fornitore diverso risolve i casi della categoria 4 e riduce il problema prima che serva formulare un giudizio.
  3. Controllo dei dati obsoleti, niente riempimento delle lacune. La strategia riceve un input data_age e una regola inderogabile: niente nuove entrate se l'ultima transazione reale risale a più di N barre prima; le posizioni aperte vengono chiuse alla riapertura solo con un ordine a mercato, il cui prezzo include un margine esplicito per il rischio di gap. Le esecuzioni impossibili sono peggio dei trade mai fatti.
  4. Gli indicatori vedono NaN, non dati inventati. I prezzi riempiti con il forward-fill non raggiungono mai il livello delle feature. Se non è possibile calcolare l'ATR, il suo valore è indefinito, e indefinito significa restare fuori mercato. Un errore esplicito è meglio di un aumento silenzioso di 7 volte.
  5. Riporta i risultati condizionati alle lacune. Ogni backtest che pubblichiamo mostra il P&L escludendo i trade vicini alle interruzioni, accanto al risultato complessivo. Se sono quei trade a determinare il risultato, allora il risultato è un artefatto dei dati.

Un controllo rapido da fare oggi: raggruppa i minuti mancanti in intervalli consecutivi, poi verifica quale percentuale dei trade del backtest si apre o si chiude entro 30 minuti dal confine di un intervallo. Se è meno dell'1%, probabilmente le lacune non incidono. Se è il 5% o più, la curva del capitale racconta in parte la storia dei tempi di inattività delle piattaforme.

Ho imparato a fidarmi più della forma dei dati mancanti che della loro quantità. Un dataset con migliaia di lacune sparse nelle ore morte di solito va bene. Un dataset con pochi gruppi molto ravvicinati ti sta dicendo che qualcosa si rompe sotto carico; ed è proprio lì, dove qualcosa si rompe sotto carico, che opera la tua strategia.

lacune OHLCVingegneria dei datibacktestingricampionamentofutures crypto
← Tutti gli articoli