3 september 2026 · datateknik

Minuterna som inte finns: luckor, avbrott och staplar med nollvolym i OHLCV-historiken

Minuterna som inte finns: luckor, avbrott och staplar med nollvolym i OHLCV-historiken

Ett kalenderår med 1-minutsstaplar borde innehålla 525,600 rader. Vår hämtning för 2025 av BTCUSDT-perpetualer gav 525,557 — 43 minuter fattades, vilket motsvarar en fullständighet på 99.992%. En perpetual för en altcoin med medelstort börsvärde på samma handelsplats, hämtad samtidigt med samma kodflöde, saknade 1,247 minuter: 99.76%. Och en minutserie för amerikanska aktier under samma år hade ungefär 427,000 färre rader än kryptoserien, vilket inte alls är en lucka utan bara en marknad som stänger.

Tre av de här siffrorna är ointressanta. Den som är värd tusen ord är 43.

525,6001-minutsstaplar under ett år utan skottdag
0.008%saknades för BTCUSDT, 2025
61%av de minuterna inföll under timmar med volatilitet i den högsta tiondelen

Fyra olika saker som alla ser ut som ett hål i din dataframe

Innan vi tittar närmare på 43: här är kategorierna, eftersom det mesta av koden för hantering av luckor redan fallerar på den här nivån. När en rad saknas i din lokala parquet-fil vet du inte vilken av de här orsakerna som ligger bakom, och rätt åtgärd skiljer sig åt för var och en.

TypVad som faktiskt händeHur det ser utRätt åtgärd
Inga affärerMarknaden var öppen; ingen handlade över spreaden den minutenStapeln har nollvolym (OHLC-värdena är lika, trades=0) hos vissa ändpunkter, medan raden saknas helt hos andraBehåll den. Den innehåller verklig information: ingen ville handla.
Handelsplatsen nereMatchningsmotorn låg nere, planerat eller oplaneratRaden saknas, ibland i flera minuter i följdMarkera som inaktuell. Handla inte under avbrottet.
Instrumentet handelsstoppatLULD-bandgräns passerad, inväntan på nyheter, avnoteringsbeskedRaden saknas, sedan kommer en avslutsnotering vid återöppningenMarkera som inaktuell och behandla återöppningen som ett språng.
Ditt felSidindelning som begränsats av anropsgränsen, ett nytt försök som tappade en sida, ett fel vid tidszonsgränsen i loopenRaden saknas och går inte att skilja från fallen ovanUpptäck felet och hämta om. Det här felet kan du åtgärda.

Handelsplatserna skiljer sig åt för den första raden i tabellen, och det är där problemen börjar. Coinbases candles-ändpunkt hoppar helt över tomma intervall, så historiken för ett illikvidt par är full av faktiska hål. Binances klines ger i regel en syntetisk stapel med volym 0 och open=high=low=close, där priserna låses till den senaste affären. Samma underliggande förhållande ger två olika former, och en laddare som indexerar om till ett fullständigt minutnät gör om den ena till den andra utan att säga till. Ta reda på hur din handelsplats fungerar innan du skriver koden som fyller luckor, inte efteråt.

De 1,247 saknade minuterna för altcoinen tillhörde nästan helt kategori ett: en tunn orderbok kl. 04:00 UTC en söndag, ingen som handlade. Irriterande, lätt att förstå och i stort sett harmlöst eftersom strategin ändå inte skulle ha handlat då. Det är precis därför jag slutade titta på dem och gick tillbaka till de 43.

De 43 minuterna låg inte utspridda

Om bortfallet vore jämnt fördelat skulle 43 minuter över ett år ge 43 ensamma minuter, en var åttonde och en halv dag, och var och en ett avrundningsfel. Så såg det inte ut. De kom i sex sviter: en på 19 minuter i följd, en på 11, två på 4 och ett par tvåminuterssviter. Sex händelser, inte 43 olyckor.

Och händelserna beror på det du bryr dig om. Handelsplatser går ner när belastningen är hög, och belastningen är hög när priset rör sig. Jag grupperade årets alla timmar efter realiserad volatilitet och undersökte var de saknade minuterna låg: 61% inföll i den högsta tiondelen. Den ovillkorliga sannolikheten att en viss minut saknas är 0.008%. Givet att minuten infaller under en timme i den högsta volatilitetstiondelen är sannolikheten ungefär 0.05% — sex gånger högre, och dessutom samlade i kluster.

Måttet på datakvalitetspanelen mäter alltså fel sak. 99.992% låter som en datamängd du kan sluta fundera på. I själva verket beskriver siffran en serie som är komplett under timmar då din strategi inte gör någonting och full av hål under timmar då den gör allt. Ett momentumssystem som utlöses av ökad volatilitet löper betydligt större risk att träffa en lucka än vad nyckeltalet antyder, och luckan dyker upp mitt i affären.

Vad framåtfyllning ställer till med tre rader senare

Här är felet som fick mig att skriva det här. Ta sviten på 19 minuter. Standardfixen: indexera om till ett fullständigt minutnät, fyll framåt OHLC-värdena från den senaste stängningskursen och sätt volymen till noll. Serien är nu sammanhängande och indikatorerna körs utan en enda NaN i sikte.

De 19 staplarna har high == low == close. True range är noll för var och en. En ATR(14) beräknad över det fönstret, efter en avläsning på ungefär 240 USDT före avbrottet, sjunker till omkring 34 när handelsplatsen kommer tillbaka — de fem verkliga staplar som finns kvar i fönstret står för hela medelvärdet. Mata sedan in det i en positionsstorleksberäknare som skalar efter volatilitet, av den vanliga typen size = risk_budget / ATR. Positionsstorleken blir 7 gånger större.

Nästa verkliga stapel är noteringen vid återöppningen, och det är ingen lugn stapel. I vårt fall öppnade den 1.8% från den senaste stängningskursen före avbrottet. Backtestet tog glatt en position som var 7 gånger större rakt in i en lucka på 1.8%, med en fyllning som inte hade kunnat ske, till ett pris som ingen noterade. Den enda syntetiska affären var värd mer än en månads legitim P&L i aktiekurvan, åt fel håll — och den uppstod helt och hållet ur en datarensningsrad som skrevs för att snygga till dataframe:en.

Att ta bort raderna i stället för att fylla i dem löser inte heller problemet; det är samma bugg i en annan skepnad. Tar du bort dem ljuger dina tillbakablickar med heltalsindex: ett "20-stapels EMA" sträcker sig nu över 39 minuter i realtid genom avbrottet, avkastningen mellan staplarna över gränsen är hela hoppet på 1.8% behandlat som en minuts rörelse, och varje volatilitetsestimat per stapel tolkar det som en 60-sigma-händelse. Inget varnar dig. Indexet är fortfarande monotont.

Omsampling gör problemet osynligt

Det mesta av forskningen körs inte på 1-minutsstaplar utan på aggregerade data, och aggregeringen tvättar bort problemet. Sampla om till 5 minuter så blir en lucka på 19 minuter fyra staplar, varav den första och sista är ofullständiga. Pandas beräknar en OHLC som ser helt rimlig ut från två överlevande minuter och märker den precis som en stapel byggd av fem. Ingenting i resultatet skiljer dem åt.

Den enklaste lösningen jag känner till: behåll en bars_in_window-kolumn genom varje omsampling och släng den aldrig. Ett heltal per rad räcker för att göra varje efterföljande fråga om stapelns tillförlitlighet besvarbar. Vi behåller också seconds_since_last_real_print, som innehåller samma information i en form som exekveringslagret kan agera på.

Vår policy

Så här gör våra agenter nu, i ordning:

  1. Indexera aldrig om i tysthet. Laddaren skapar ett luckmanifest — start, slut, längd och vilken av de fyra kategorierna den tror att det gäller. Om en svit av saknade minuter är kortare än 3 staplar och volymen i staplarna intill är låg, räknas det som minuter utan affärer. Allt som är längre under aktiva timmar behandlas som ett avbrott tills motsatsen är bevisad.
  2. Hämta om innan du drar slutsatser. Hälften av våra tidiga luckor berodde på fel i sidindelningen. En andra hämtning från en annan ändpunkt eller leverantör löser kategori fyra och minskar problemet innan vi behöver göra någon bedömning.
  3. Spärr mot inaktuella data, inte ifyllnad av luckor. Strategin får en data_age-indata och en strikt regel: inga nya positioner när den senaste verkliga noteringen är äldre än N staplar, och öppna positioner stängs vid återöppningen endast med en marknadsorder där ett uttryckligt avdrag för luckrisken har gjorts på priset. Fyllningar som inte hade kunnat ske är värre än affärer som aldrig blev av.
  4. Indikatorerna får NaN, inte påhittade värden. Framåtfyllda priser når aldrig funktionslagret. Om ATR inte kan beräknas är värdet odefinierat, och odefinierat betyder ingen position. Ett tydligt fel är bättre än en tyst 7 gånger större position.
  5. Redovisa resultat villkorat på luckor. Varje backtest vi levererar visar P&L med affärer nära avbrott borttagna, sida vid sida med huvudresultatet. Om de affärerna bär resultatet är resultatet en dataartefakt.

En snabb granskning du kan göra i dag: gruppera dina saknade minuter i sviter av minuter i följd och kontrollera sedan hur stor andel av backtestets affärer som öppnas eller stängs inom 30 minuter från en svitgräns. Är andelen under 1% driver luckorna förmodligen inget. Är den 5% eller högre handlar aktiekurvan delvis om avbrott hos handelsplatsen.

Det jag har lärt mig att lita på är hur bortfallet ser ut, snarare än hur stort det är. En datamängd med tusentals utspridda hål under inaktiva timmar brukar fungera bra. En datamängd med en handfull täta kluster visar att något går sönder under hög belastning, och det som går sönder då är just där din strategi verkar.

ohlcv-luckordatateknikbacktestingomsamplingkryptoterminer
← Alla inlägg