Et kalenderår med 1-minuttsstolper skal inneholde 525,600 rader. Uttrekket vårt for BTCUSDT-perpetualer i 2025 ga 525,557 — 43 minutter manglet, en fullstendighetsgrad på 99.992%. En perpetual for en altcoin med middels markedsverdi fra samme handelsplass, samme uttrekk og samme kodeløp manglet 1,247 minutter: 99.76%. En minuttserie for amerikanske aksjer for det samme året hadde rundt 427,000 færre rader enn kryptoserien. Det er ikke et datagap, men et marked som stenger.
Tre av disse tallene er kjedelige. Det ene som er verdt tusen ord, er 43.
Fire ulike ting som alle ser ut som et hull i datarammen din
Før vi ser på de 43, må vi gå gjennom kategoriene, for det meste av koden som håndterer datagap, er allerede feil på dette nivået. Når en rad mangler i den lokale parquet-filen din, vet du ikke hvilken av disse årsakene som ligger bak, og riktig håndtering er forskjellig for hver av dem.
| Type | Hva som faktisk skjedde | Hvordan det ser ut | Riktig håndtering |
|---|---|---|---|
| Ingen handler | Markedet var åpent, men ingen krysset spreaden det minuttet | Nullvolumsstolpe (OHLC-verdiene er like, trades=0) på noen endepunkter, manglende rad på andre | Behold den. Det er ekte informasjon: ingen ønsket å handle. |
| Handelsplassen nede | Matchingsmotoren var frakoblet, planlagt eller ikke | Manglende rad, noen ganger en hel rekke | Merk som foreldet. Ikke handle gjennom avbruddet. |
| Handelspapir stanset | Brudd på LULD-bånd, nyheter på vei, melding om avnotering | Manglende rad, etterfulgt av en auksjonshandel ved gjenåpning | Merk som foreldet, og behandle gjenåpningen som et brudd i serien. |
| Din feil | Sideinndeling begrenset av rate limit, et nytt forsøk som droppet en side, en feil i løkken ved tidssonegrensen | Manglende rad, umulig å skille fra tilfellene over | Oppdag feilen og hent dataene på nytt. Dette er den eneste kategorien du kan rette opp. |
Handelsplasser håndterer det første punktet i tabellen ulikt, og det er der problemene oppstår. Candles-endepunktet til Coinbase utelater tomme intervaller helt, så historikken til et illikvidt par er full av faktiske hull. Binance sine klines gir som regel en syntetisk stolpe med volum 0 og open=high=low=close satt lik forrige handel. Samme underliggende forhold, to ulike former. En laster som indekserer dataene på nytt mot et komplett minuttgitter, gjør den ene formen om til den andre uten å fortelle deg det. Finn ut hva handelsplassen gjør før du skriver kode som fyller hullene, ikke etterpå.
Nesten alle de 1,247 manglende minuttene for altcoinen tilhørte kategori én: en tynn ordrebok klokken 04:00 UTC på en søndag, ingen handlet. Irriterende, lett å forstå og stort sett ufarlig, for strategien ville uansett ikke ha handlet da. Nettopp derfor sluttet jeg å se på det og gikk tilbake til de 43.
De 43 minuttene var ikke spredt utover
Hvis manglene var jevnt fordelt, ville 43 minutter i løpet av et år ha dukket opp som 43 enkeltstående hull, ett omtrent hver åttende og en halv dag, hvert så lite at det kunne avskrives som avrunding. Slik var det ikke. De kom i seks perioder: én på 19 sammenhengende minutter, én på 11, to på 4 og et par på 2. Seks hendelser, ikke 43 enkelttilfeller.
Og hendelsene henger sammen med det du bryr deg om. Handelsplasser går ned når belastningen er høy, og belastningen er høy når prisen beveger seg. Jeg grupperte alle årets timer etter realisert volatilitet og undersøkte hvor de manglende minuttene lå: 61% av dem var i øverste desil. Den ubetingede sannsynligheten for at et gitt minutt mangler, er 0.008%. Gitt at det ligger i en time med volatilitet i øverste desil, er sannsynligheten omtrent 0.05% — seks ganger høyere, og manglene kommer dessuten i klynger.
Dermed måler målet på datakvalitetspanelet ditt feil ting. 99.992% høres ut som et datasett du kan slutte å tenke på. I virkeligheten beskriver det en serie som er komplett i timene strategien din ikke gjør noe, og full av hull i timene den gjør alt. Et momentumsystem som utløses av økende volatilitet, har vesentlig større sjanse for å treffe et datagap enn hovedtallet tilsier — og det skjer midt i en handel.
Hva forward-fill gjør tre linjer senere
Her er feilen som fikk meg til å skrive dette. Ta perioden på 19 minutter. Vanlig opprydding: indekser på nytt mot et komplett minuttgitter, fyll OHLC fremover fra siste sluttkurs, sett volumet til null. Serien er nå sammenhengende, og indikatorene dine kjører uten et eneste NaN.
Disse 19 stolpene har high == low == close. True range er null i hver eneste stolpe. ATR(14) beregnet over dette vinduet, med en verdi på rundt 240 USDT før avbruddet, synker til rundt 34 når handelsplassen er tilbake — de fem ekte stolpene som overlevde i vinduet, utgjør hele gjennomsnittet. Bruk dette i en posisjonsstørrelsesberegner skalert etter volatilitet, den vanlige typen size = risk_budget / ATR, og posisjonsstørrelsen blir sju ganger større.
Den neste ekte stolpen er handelen ved gjenåpning, og den er ikke rolig. I vårt tilfelle åpnet den 1.8% unna sluttkursen før avbruddet. Backtesten tok gladelig en posisjon som var 7x større, rett inn i et prisgap på 1.8%, med en fylling som ikke kunne ha funnet sted, til en pris ingen la ut. Denne ene syntetiske handelen var mer verdt enn en måneds legitimt resultat i aksjekurven, i feil retning — og den oppsto utelukkende på grunn av en datavaskelinje som skulle gjøre datarammen ryddig.
Å slette radene i stedet for å fylle dem løser heller ikke problemet; det er den samme feilen i en annen forkledning. Sletter du dem, blir oppslagsvinduer basert på heltallsindekser misvisende: en «20-stolpers EMA» strekker seg nå over 39 minutter i klokketid på tvers av avbruddet, avkastningen fra stolpe til stolpe over grensen behandler hele hoppet på 1.8% som en bevegelse på ett minutt, og ethvert volatilitetsestimat per stolpe leser det som en 60-sigma-hendelse. Ingenting varsler deg. Indeksen er fortsatt monotonisk.
Ved resampling blir problemet usynlig
Det meste av analysen kjøres ikke på 1-minuttsstolper, men på aggregerte data, og aggregeringen vasker bort problemet. Resampler du til 5-minuttersstolper, blir et hull på 19 minutter til fire stolper, der den første og siste er delvise. Pandas beregner OHLC-verdier som ser helt rimelige ut fra to gjenværende minutter og merker stolpen på samme måte som en stolpe bygget av fem minutter. Ingenting i resultatet skiller dem fra hverandre.
Den enkleste løsningen jeg kjenner: ta med en bars_in_window-kolonne gjennom hver resampling, og kast den aldri. Én verdi per rad gjør at alle senere spørsmål om hvorvidt en stolpe er til å stole på, kan besvares. Vi tar også med seconds_since_last_real_print, som inneholder den samme informasjonen i en form utførelseslaget kan handle på.
Retningslinjene våre, så langt de rekker
Dette bruker agentene våre nå, i denne rekkefølgen:
- Aldri indekser på nytt i det skjulte. Lasteren lager et manifest over datagapene — start, slutt, lengde og hvilken av de fire kategoriene den antar at de tilhører. Hvis en rekke manglende minutter er kortere enn 3 stolper og volumet i stolpene rundt er lavt, regnes det som minutter uten handler. Alt som varer lenger i aktive timer, behandles som et avbrudd til det motsatte er bevist.
- Hent dataene på nytt før du tolker dem. Halvparten av de tidlige datagapene våre skyldtes feil i sideinndelingen. Et nytt uttrekk fra et annet endepunkt eller en annen dataleverandør avklarer kategori fire og reduserer omfanget av problemet før vi trenger å vurdere noe.
- Bruk en foreldelsesgrense, ikke fyll hull. Strategien får et
data_age-inndata og en streng regel: ingen nye innganger når siste ekte handel er eldre enn N stolper, og åpne posisjoner lukkes ved gjenåpningen bare med en markedsordre priset med et eksplisitt fradrag for gaprisiko. Fyllinger som ikke kunne ha funnet sted, er verre enn handler som aldri ble gjennomført. - Indikatorene får NaN, ikke oppdiktede verdier. Fremoverfylte priser når aldri frem til funksjonslaget. Hvis ATR ikke kan beregnes, er verdien udefinert, og udefinert betyr flat posisjon. En tydelig feil er bedre enn et ubemerket 7x.
- Rapporter resultater betinget av datagap. Hver backtest vi leverer, viser P&L både med og uten handler nær avbrudd, i tillegg til hovedresultatet. Hvis disse handlene står for resultatet, skyldes det en dataartefakt.
En rask kontroll du kan gjøre i dag: grupper de manglende minuttene i sammenhengende perioder, og sjekk deretter hvor stor andel av handlene i backtesten som åpnes eller lukkes innen 30 minutter fra en periodes start eller slutt. Er andelen under 1%, er det sannsynligvis ikke datagapene som driver resultatet. Er den 5% eller høyere, handler aksjekurven delvis om nedetid på handelsplassen.
Det jeg har lært å stole på, er formen på manglene snarere enn omfanget. Et datasett med tusenvis av spredte hull i rolige timer er som regel greit. Et datasett med noen få tette klynger forteller deg at noe svikter under belastning, og strategien din lever nettopp der belastningen får noe til å svikte.
← Alle innlegg
