26 augustus 2026 · marktdata

Anatomie van één 1-minuutbar: alles wat OHLCV je backtest niet vertelt

Anatomie van één 1-minuutbar: alles wat OHLCV je backtest niet vertelt

Hier is één bar. Binance USDⓈ-M perpetual, BTCUSDT, de minuut die begint om 13:46:00 UTC. Hij kwam van het klines-endpoint als een kale array van twaalf waarden, en het is de meest voorkomende inputeenheid in retail-en-aanverwant quant-onderzoek. Bijna elke strategie die wij genereren raakt wel iets met deze vorm.

Laten we hem dus uit elkaar halen, index voor index, en kijken hoeveel een backtest er fout aan interpreteert.

IndexWaardeNaam
01773495960000opentijd (ms)
1"84120.50"open
2"84177.90"high
3"84098.10"low
4"84163.40"close
5"38.417"basisvolume (BTC)
61773496019999sluitingstijd (ms)
7"3232108.94"quote-volume (USDT)
81204aantal trades
9"21.883"taker buy basisvolume
10"1841203.55"taker buy quote-volume
11"0"ignore

[0] en [6]: welke minuut is dit, en welke klok bepaalt dat

Opentijd 1773495960000, sluitingstijd 1773496019999. Let op dat tweede getal: het eindigt op 19999, één milliseconde vóór de opening van de volgende bar. Het venster is half-open, en de exchange vertelt je dat expliciet. Minstens de helft van de databugs die ik heb opgespoord, begon doordat iemand beide eindpunten als inclusief behandelde en een trade op de grens dubbel telde, of een resample zo uitlijnde dat elke 5-minutenbar een milliseconde leende van zijn buurman.

De timestamps zijn matching-engine-tijd van de exchange. Niet jouw klok, niet de ingestieklok van je vendor, en ook niet dezelfde klok die de funding-snapshot gebruikt. Als je een kline-reeks joint met een funding-rate-reeks of een open-interest-reeks die van een ander endpoint komt, join je bronnen die het gros van de tijd tot op ongeveer een seconde nauwkeurig overeenkomen. Voor een 1-minuutstrategie is dat een timingfout van 1,7% op elke gejoinde rij. Voor alles sub-minuut is het fataal.

En deze bar is gestempeld op zijn opening. De informatie erin is niet bekend vóór 13:46:59.999. Elke vraag over indexeringsconventies — verschuif ik met één, gebruik ik bar-close of bar-open timestamps als mijn event-tijd — is in werkelijkheid dezelfde lookahead-vraag in een ander jasje.

[1] "84120.50": de open die je niet kunt traden

De open is de prijs van de eerste trade die binnen het venster print. Het is een afgeronde transactie tussen twee andere partijen. Tegen de tijd dat de bar als rij in je dataframe bestaat, is die prijs zestig seconden oud.

De vorige bar sloot op 84109,80, dus er zit $10,70 aan prijsbeweging in de kloof tussen twee opeenvolgende 1-minuutbars. Dat is 1,3 basispunten, onzichtbaar op een grafiek en ongeveer een kwart van de taker fee. Prima. Maar kijk eens naar diezelfde reeks op een alt-perp tijdens een Amerikaanse CPI-publicatie, en die interbar-kloven lopen op tot 15 à 40 bps. Een backtest die signaleert op bar-close en fill op de open van de volgende bar, neemt stilzwijgend aan dat de kloof nul is — en die is precies nul op de momenten dat het er niet toe doet.

[2] en [3]: "84177.90" high, "84098.10" low

Bij deze twee sneuvelen de meeste fill-engines, dus dit stuk is het langste.

De high en low zijn extreme aangetikte prijzen. Ze dragen geen grootte en geen duur mee. Kijk je naar de ruwe trade tape voor diezelfde minuut, dan zie je dat alles op of onder 84100,00 samen 0,62 BTC was, verdeeld over negen prints binnen een venster van 1,4 seconde. Dus een backtest-stop op 84100 "filt" — en als jouw positie 3 BTC is, heb je op weg naar beneden het hele zichtbare orderboek opgegeten en is de rest van je size ergens tussen 84105 en 84130 afgehandeld tijdens de terugloop. De bar zegt dat de low 84098,10 was. De bar zegt niet dat daar maar $52.000 aan notional heeft verhandeld.

1204trades in de minuut
0,032 BTCgemiddelde printgrootte (~$2,7k)
0,62 BTCtotaal verhandeld op of onder de low tick
57%van het volume was koper-geïnitieerd

De andere richting is erger, want die vleit je. Stel dat je een openstaande limit sell had op 84175. De high van de bar is 84177,90, dus een naïeve engine geeft je een fill op 84175 en boekt maker rebate in plaats van taker kosten. Of die fill echt heeft plaatsgevonden hangt af van je queue-positie op dat prijsniveau — iets wat de bar niet kan weten en jij waarschijnlijk nooit hebt vastgelegd. Aangetikt is niet gevuld.

De regel waar we in onze fill-engine op uitkwamen: een openstaande limit vult alleen als de bar door het niveau heen handelt, niet slechts ertoe. Fills op precies de extreme tick vereisen bewijs van volume-op-prijs uit de trade tape, anders worden ze verworpen. Dat haalde ongeveer 6% van de trades uit een typisch mean-reversion-boek en verlaagde de gebacktestte Sharpe van één kandidaat van 1,9 naar 1,1. Die kandidaat was nooit echt; de fill-regel was alleen het eerste eerlijke ding dat dat durfde te zeggen.

Gerelateerde valkuil: het intrabar-pad. Als het bereik van een bar zowel je stop als je take-profit omvat, kan OHLCV je niet vertellen wat er eerst was. Elke engine moet een conventie kiezen. De onze gaat altijd uit van stop-eerst, wat pessimistisch is en soms fout, maar nooit een nepwinst genereert. Als jouw engine uitgaat van take-profit-eerst, fabriceren wide-range bars winst uit onduidelijkheid — en wide-range bars zijn precies degene die je PnL-verdeling domineren.

[4] "84163.40": het minst robuuste getal in de bar

De close is de laatste print in het venster. Meer niet. Het kan een 0,002 BTC odd-lot zijn van een bot die om 13:46:59,8 een positie afrondt. Deze ene, structureel willekeurige tick is waarmee de meeste onderzoekspipelines elk signaal berekenen, elke positie marken en elke exit evalueren.

Bij BTC-perps maakt het nauwelijks iets uit; bij een dunne altcoin-perp om 04:00 UTC maakt het enorm veel uit, en het verschil tussen de closes van twee venues voor dezelfde minuut kan groter zijn dan je hele edge per trade. Als de PnL van een strategie specifiek van de close afhangt, draaien we hem opnieuw met marking op de mark price van de exchange, die index-afgeleid en veel lastiger te manipuleren is. Als de resultaten uiteenlopen, handelde de strategie het artefact.

[5] en [7]: "38.417" en "3232108.94", volume in eenheden van wat

Basisvolume is BTC; quote-volume is USDT. Hier allebei aanwezig, wat een gunst is die niet alle venues verlenen. De reden om je erom te bekommeren is aggregatie over venues heen. Coin-margined contracten worden genoteerd in contracten van $100 notional. Sommige aandelenfeeds rapporteren round lots. Prediction market-venues rapporteren aantallen shares, waarbij een share een in dollars genoteerde binaire claim is. "Volume" optellen over een gemengd universum zonder normalisatie naar één notional-eenheid, levert een liquiditeitsranking op die pure onzin is — en het is onzin op een manier die er stabiel uitziet en review overleeft.

Normaliseer alles naar quote-notional bij ingest. Bewaar het ruwe veld ook, maar laat een strategie het nooit zien.

[8] 1204: het veld dat je impactmodel zou moeten bepalen

Aantal trades gedeeld door basisvolume geeft een gemiddelde print van 0,032 BTC, ongeveer $2.700. Als je kandidaat-strategie in één keer $250.000 wil binnenkomen, vraagt hij om ongeveer 92 keer de omvang van de typische transactie in die minuut. Dat getal — niet een generieke 5-bps-slippageconstante — hoort je square-root-impactterm te sturen. We berekenen de participation rate per bar als een eersteklas kolom en verwerpen strategieën waarvan de mediane entry meer dan een paar procent van de bar-notional bedraagt, want alles wat daarna komt is fictie.

Aantal trades signaleert ook goedkoop de vreemde minuten. Volume normaal, aantal trades ingestort naar 11? Iemand deed een block. Volume normaal, aantal trades op 9.000? Dat is een liquidatiecascade die in piepkleine stukjes wordt verwerkt.

[9] en [10]: "21.883", het veld dat iedereen weggooit

Taker buy basisvolume. 21,883 van de 38,417 BTC in deze bar was koper-geïnitieerd, dus de signed volume delta is +5,349 BTC en de aggressor-verdeling is 57/43 richting kopers. De exchange geeft je order flow imbalance cadeau, in een veld dat de meeste mensen nooit lezen omdat pandas de kolom niet voor ze heeft benoemd.

Ik beweer niet dat dit op zichzelf returns voorspelt; naïeve delta-strategieën behoren tot de betrouwbaarste manieren om aan het fee-grootboek te doneren. Maar het is een echt andere meting dan prijs, het zit in dezelfde request die je toch al deed, en het stelt je in staat een rally die gekocht werd te onderscheiden van een rally die ontstond doordat verkopers wegbleven. Die twee zien er identiek uit in OHLC en gedragen zich tien minuten later heel anders. Onze research-agent behandelt een hypothese die de taker-split negeert op een venue die hem publiceert, als het laten liggen van bewijs.

[11] "0": ignore — en al het andere dat hier ontbreekt

Index 11 is een verouderd veld, permanent nul. Interessanter is de lijst met dingen die deze bar níét bevat: geen bid, geen ask, geen spread, geen book depth, geen funding rate, geen open interest, geen liquidaties, geen mark price, geen index price. En cruciaal: geen manier om te weten of jouw order maker of taker zou zijn geweest — wat het verschil is tussen 0,045% betalen en 0,01% verdienen op deze venue.

Dus elk feemodel dat alleen op klines is gebouwd, is een aanname vermomd als getal. Wij lossen dat op door elke strategie vooraf zijn executiestijl te laten declareren, en vervolgens taker te rekenen voor alles wat het tegendeel niet kan bewijzen.

De bar die er nooit kwam

Laatste stuk, en degene die het hardst bijt buiten de majors. Een minuut zonder trades levert helemaal geen kline op. Vendors en libraries vullen dat vaak forward: open = high = low = close = vorige close, volume 0. Je indicator rekent vrolijk door. Je strategie ziet een geldige rij en kan een signaal genereren op een minuut waarin niemand ter wereld dat instrument heeft verhandeld.

Bij één mid-cap perp die wij ingestten, had 4,1% van de 1-minuutbars in een venster van twaalf maanden nul trades. Een mean-reversion-kandidaat op dat symbool plaatste 38% van zijn entries op synthetische bars, omdat platte synthetische prijzen catnip zijn voor alles wat afwijking van een voortschrijdend gemiddelde meet. Hij backtestte prachtig. Hij handelde de gaten in de data.

Daarom draagt de ingest-laag nu een synthetic-boolean per bar, doorgegeven bij elke resample, en de verificatietest laat elke strategie zakken waarvan de trades zich daarop clusteren. Goedkope kolom. Hij heeft meer kandidaten om zeep geholpen dan welke indicator dan ook die we ooit hebben geschreven.

Twaalf waarden. Vier ervan routinematig verkeerd geïnterpreteerd, twee ervan routinematig weggegooid, en een hele categorie ervan afwezig in de rij en verzonnen door de engine. Haal vóór je volgende backtest één ruwe bar uit je eigen store en lees elk veld hardop voor tegen wat je fill-logica erover aanneemt. Het is een oefening van twintig minuten, en ik heb nog nooit iemand gezien die dat deed en niets vond.

ohlcvmarktmicrostructuurdata engineeringbacktestingcrypto futures
← Alle artikelen