3. september 2026 · data engineering

De minutter, der ikke findes: huller, handelsstop og barer med nul volumen i OHLCV-historikken

De minutter, der ikke findes: huller, handelsstop og barer med nul volumen i OHLCV-historikken

Et kalenderår med 1-minutsbarer bør bestå af 525,600 rækker. Vores udtræk af BTCUSDT-perps for 2025 indeholdt 525,557 — 43 minutter manglede, svarende til en fuldstændighed på 99.992%. En perp på en altcoin med middelstor markedsværdi fra samme handelsplads, med samme udtræk og samme kodeforløb, manglede 1,247: 99.76%. Og en amerikansk aktieserie med minutdata for samme år havde omkring 427,000 færre rækker end kryptoserien, hvilket slet ikke er et hul, men blot et marked, der lukker.

Tre af de tal er kedelige. Det ene, der er tusind ord værd, er 43.

525,6001-minutsbarer i et ikke-skudår
0.008%mangler for BTCUSDT i 2025
61%af de minutter lå i timer med volatilitet i øverste decil

Fire forskellige ting, der alle ligner et hul i dit dataframe

Før vi ser på de 43, skal vi gennemgå kategorierne, for det meste kode til håndtering af huller går allerede galt her. Når en række mangler i din lokale parquet-fil, ved du ikke, hvilken af disse årsager der ligger bag, og den rigtige håndtering er forskellig fra kategori til kategori.

TypeHvad der faktisk sketeSådan viser det sigDen rigtige håndtering
Ingen handlerMarkedet var åbent; ingen krydsede spreadet det minutBar med nul volumen (OHLC er ens, trades=0) på nogle endpoints; manglende række på andreBehold den. Det er reel information: ingen ønskede at handle.
Handelsplads nedeMatchningsmotor offline, planlagt eller ejManglende række, nogle gange en hel serie af demMarkér den som forældet. Handl ikke hen over den.
Handelspause for instrumentetBrud på LULD-bånd, afventende nyheder, meddelelse om afnoteringManglende række efterfulgt af en åbningsauktionstransaktionMarkér den som forældet, og behandl genåbningen som et diskontinuitetspunkt.
Din fejlSideindlæsning begrænset af rate limit, et forsøg igen, der droppede en side, en fejl ved tidszonegrænsen i løkkenManglende række, umulig at skelne fra ovenståendeFind fejlen, og hent data igen. Det er den eneste kategori, du kan rette.

Handelspladserne håndterer den første række i tabellen forskelligt, og det er dér, problemet opstår. Coinbases candles-endpoint udelader tomme intervaller helt, så historikken for et illikvidt par er fuld af bogstavelige huller. Binances klines giver som regel en syntetisk bar med volumen 0 og open=high=low=close sat til den forrige handel. Samme underliggende forhold, to forskellige former — og en loader, der indekserer data på ny efter et komplet minutgitter, omdanner den ene form til den anden uden at fortælle dig det. Undersøg, hvad din handelsplads gør, før du skriver kode til at udfylde hullerne, ikke bagefter.

De 1,247 manglende minutter for altcoinen tilhørte næsten udelukkende kategori ét: en tynd ordrebog kl. 04:00 UTC en søndag, hvor ingen handlede. Irriterende, nemt at forstå og stort set harmløst, fordi strategien alligevel ikke ville have handlet dér. Og netop derfor holdt jeg op med at kigge på det og vendte tilbage til de 43.

De 43 minutter var ikke spredt ud

Hvis manglerne var jævnt fordelt, ville 43 minutter på et år være 43 enkeltstående huller, ét hver ottende og en halv dag, og hvert hul ville være en afrundingsfejl. Sådan så vores data ikke ud. Hullerne kom i seks sammenhængende forløb: ét på 19 minutter, ét på 11, to på 4 og et par stykker på 2. Seks hændelser, ikke 43 tilfældigheder.

Og hændelserne afhænger af det, du går op i. Handelspladser går ned, når de er belastede, og de er belastede, når prisen bevæger sig. Jeg inddelte årets timer efter realiseret volatilitet og undersøgte, hvor de manglende minutter lå: 61% af dem lå i den øverste decil. Den ubetingede sandsynlighed for, at et givent minut mangler, er 0.008%. Betinget af, at minuttet ligger i en time med volatilitet i øverste decil, er sandsynligheden omkring 0.05% — seks gange højere, og hullerne ligger ovenikøbet i klynger.

Så målet for fuldstændighed på dit dashboard for datakvalitet måler det forkerte. 99.992% lyder som et datasæt, du kan holde op med at tænke på. Men tallet beskriver faktisk en serie, der er komplet i de timer, hvor din strategi ikke gør noget, og fuld af huller i de timer, hvor den gør alt. Et momentum-system, der udløses af stigende volatilitet, har en væsentligt større sandsynlighed for at ramme et hul, end overskriftstallet antyder, og det rammer hullet midt i en handel.

Hvad forward-fill gør tre linjer senere

Her er fejlen, der fik mig til at skrive dette. Tag forløbet på 19 minutter. Standardoprydning: indeksér på ny efter det fulde minutgitter, forward-fill OHLC ud fra den seneste lukkepris, og sæt volumen til nul. Serien er nu sammenhængende, og dine indikatorer kører uden skyggen af en NaN.

De 19 barer har high == low == close. True range er nul for dem alle. En ATR(14), beregnet på dette interval med en værdi før udfaldet på omkring 240 USDT, falder til omkring 34, når handelspladsen kommer tilbage — de 5 ægte barer, der er tilbage i intervallet, udgør hele gennemsnittet. Før nu værdien ind i en volatilitetsjusteret positionsstørrelse, den almindelige size = risk_budget / ATR-type. Positionens størrelse bliver 7 gange større.

Den næste ægte bar er prisen ved genåbningen, og den er alt andet end rolig. I vores tilfælde åbnede den 1.8% fra lukkeprisen før udfaldet. Backtesten tog gladelig en position, der var 7 gange større, ind i et hul på 1.8%, baseret på en handel, der ikke kunne have fundet sted, til en pris, ingen stillede. Den ene syntetiske handel var mere værd end en måneds legitim P&L på aktiekurven, i den forkerte retning — og den opstod udelukkende på grund af en datarensningslinje, der skulle gøre dataframen pænt.

Det løser heller ikke problemet at droppe rækkerne i stedet for at udfylde dem; det er den samme fejl i forklædning. Drop dem, og dine lookbacks med heltalsindeks lyver: En "20-bar EMA" spænder nu over 39 minutter på væguret på tværs af udfaldet, afkastet fra bar til bar over grænsen er hele springet på 1.8%, behandlet som én minuts bevægelse, og ethvert estimat af volatilitet pr. bar læser det som en hændelse på 60 sigma. Intet advarer dig. Indekset er stadig monotont.

Problemet forsvinder i resampling

Det meste research kører ikke på 1-minutsbarer, men på aggregerede data, og aggregeringen vasker problemet væk. Resampler du til 5 minutter, bliver et hul på 19 minutter til 4 barer, hvoraf den første og den sidste er delvise. Pandas beregner et OHLC, der ser helt rimeligt ud, ud fra 2 overlevende minutter og mærker det på samme måde som en bar, der er bygget af 5. Intet i resultatet skelner mellem dem.

Den billigste løsning, jeg kender: Før en bars_in_window-kolonne med gennem hver eneste resampling, og smid den aldrig væk. Én værdi pr. række gør det muligt at besvare alle efterfølgende spørgsmål om, hvorvidt en bar er troværdig. Vi medfører også seconds_since_last_real_print, som indeholder den samme information i en form, eksekveringslaget kan reagere på.

Vores retningslinjer

Sådan håndterer vores agenter det nu, i denne rækkefølge:

  1. Indeksér aldrig data på ny uden at sige det. Loaderen genererer et manifest over hullerne — start, slut, varighed og hvilken af de 4 kategorier den mener, hullet tilhører. Hvis et forløb med manglende minutter er under 3 barer, og volumen i barerne omkring hullet er lav, er der tale om minutter uden handler. Alt, der varer længere under aktive handelstimer, behandles som et udfald, indtil det modsatte er bevist.
  2. Hent data igen, før du drager konklusioner. Halvdelen af hullerne i vores tidlige data skyldtes fejl ved sideindlæsning. Et nyt udtræk fra et andet endpoint eller en anden leverandør afslører kategori 4 og indsnævrer problemet, før det er nødvendigt at vurdere noget.
  3. Brug en grænse for forældede data, ikke udfyldning af huller. Strategien modtager et data_age-input og en fast regel: Ingen nye positioner, når den seneste ægte handel er ældre end N barer, og åbne positioner lukkes ved genåbningen kun via en markedsordre med et eksplicit fradrag for risikoen ved prisgabet. Handler, der ikke kunne have fundet sted, er værre end handler, der aldrig blev foretaget.
  4. Indikatorer får NaN, ikke opdigtede data.Forward-filled priser når aldrig frem til feature-laget. Hvis ATR ikke kan beregnes, er den udefineret, og udefineret betyder ingen position. En tydelig fejl er bedre end en stille 7x.
  5. Rapportér resultater betinget af huller.Alle backtests, vi udgiver, viser P&L både med og uden handler tæt på udfald, side om side med hovedtallet. Hvis disse handler bærer resultatet, skyldes resultatet en dataartefakt.

En hurtig kontrol, du kan lave i dag: Gruppér dine manglende minutter i sammenhængende forløb, og undersøg derefter, hvor stor en andel af handlerne i din backtest der åbner eller lukker inden for 30 minutter af grænsen til et forløb. Hvis andelen er under 1%, påvirker hullerne sandsynligvis ikke resultatet. Hvis den er 5% eller højere, handler aktiekurven til dels om nedetid på handelspladsen.

Det tegn, jeg har lært at stole på, er mønstret i de manglende data snarere end antallet. Et datasæt med tusindvis af spredte huller i stille timer er som regel helt fint. Et datasæt med en håndfuld tætte klynger fortæller dig, at noget bryder sammen under belastning, og strategien din opererer dér, hvor sammenbruddet sker.

ohlcv-hullerdata engineeringbacktestingresamplingkryptofutures
← Alle indlæg