August 26, 2026 · markedsdata

Anatomien til én 1-minutts bar: alt OHLCV ikke forteller backtesten din

Anatomien til én 1-minutts bar: alt OHLCV ikke forteller backtesten din

Her er én bar. Binance USDⓈ-M perpetual, BTCUSDT, minuttet som begynner 13:46:00 UTC. Den kom fra klines-endepunktet som et rent array med tolv verdier, og det er den aller vanligste inputenheten i retail-nær kvantforskning. Nesten hver strategi vi genererer, berører noe formet som dette.

Så la oss ta den fra hverandre, indeks for indeks, og se hvor mye av den en backtest får feil.

IndeksVerdiNavn
01773495960000åpningstid (ms)
1"84120.50"åpning
2"84177.90"høy
3"84098.10"lav
4"84163.40"lukking
5"38.417"basisvolum (BTC)
61773496019999lukketid (ms)
7"3232108.94"kvotevolum (USDT)
81204antall handler
9"21.883"taker buy-basisvolum
10"1841203.55"taker buy-kvotevolum
11"0"ignorer

[0] og [6]: hvilket minutt er dette, og hvems klokke sier det

Åpningstid 1773495960000, lukketid 1773496019999. Legg merke til den siste: den ender på 19999, ett millisekund kortere enn neste bars åpning. Vinduet er halvåpent, og børsen forteller deg det eksplisitt. Halvparten av databuggene jeg har jaktet på, startet med at noen behandlet begge endepunktene som inkluderte og dobbelttalte en handel ved grensen, eller justerte en resampling slik at hver 5-minuttersbar lånte ett millisekund fra naboen sin.

Tidsstemplene er børsens matching-engine-tid. Ikke klokken din, ikke leverandørens innhentingsklokke, og ikke samme klokke som funding-øyeblikksbildet bruker. Når du kobler en kline-serie mot en funding rate-serie eller en open interest-serie hentet fra et annet endepunkt, kobler du sammen kilder som stemmer overens innenfor omtrent ett sekund, det meste av tiden. For en 1-minuttsstrategi er det en tidsfeil på 1.7% på hver sammenkoblede rad. For alt under ett minutt er det fatalt.

Og denne baren er tidsstemplet ved sin åpning. Informasjonen i den er ikke kjent før 13:46:59.999. Ethvert spørsmål om indekseringskonvensjon — skal jeg forskyve med én, skal jeg bruke bar-lukke- eller bar-åpne-tidsstempler som hendelsestid — er egentlig det samme lookahead-spørsmålet i forkledning.

[1] "84120.50": åpningen du ikke kan handle

Åpningen er prisen på den første handelen som ble notert innenfor vinduet. Det er en fullført transaksjon mellom to andre personer. Når baren eksisterer som en rad i dataframen din, er den prisen seksti sekunder gammel.

Forrige bar lukket på 84109.80, så det er $10.70 med prisbevegelse liggende i gapet mellom to tilstøtende 1-minuttsbarer. Det er 1.3 basispunkter, usynlig på et diagram, og omtrent en fjerdedel av taker-avgiften. Greit nok. Men se på den samme serien på en alt-perp under en amerikansk KPI-rapport, og de mellombar-gapene ligger på 15 til 40 bps. En backtest som gir signal ved bar-lukking og fyller ved neste bars åpning, antar stilltiende at gapet er null — og det er null akkurat når det ikke spiller noen rolle.

[2] og [3]: "84177.90" høy, "84098.10" lav

Dette er de to feltene der de fleste fill-motorer går for å dø, så denne seksjonen er den lange.

Høy og lav er ekstreme priser som ble berørt. De bærer ingen størrelse og ingen varighet. Fra den rå handelstapen for det samme minuttet var alt til og med 84100.00 totalt 0.62 BTC fordelt på ni noteringer innenfor et vindu på 1.4 sekunder. Så en backtest-stopp på 84100 «fylles» — og hvis posisjonen din er 3 BTC, spiste du hele den synlige boken på vei ned, og resten av størrelsen ble gjort et sted i 84105–84130-tilbaketrekningen. Baren sier at laveste var 84098.10. Baren sier ikke at bare $52,000 i pålydende verdi ble omsatt der.

1204handler i minuttet
0.032 BTCgjennomsnittlig noteringsstørrelse (~$2.7k)
0.62 BTCtotalt omsatt til og med laveste tick
57%av volumet var kjøperinitiert

Den andre retningen er verre, fordi den smigrer deg. Anta at du hadde en hvilende limit-selgeordre på 84175. Barens høyeste er 84177.90, så en naiv motor fyller deg på 84175 og bokfører maker-rabatt i stedet for taker-kostnad. Om den fyllingen faktisk skjedde, avhenger av køposisjon på det prisnivået, noe baren ikke kan vite og du sannsynligvis aldri registrerte. Berørt er ikke det samme som fylt.

Regelen vi landet på i fill-motoren vår: en hvilende limit-ordre fylles bare hvis baren handler gjennom nivået, ikke bare til det. Fyllinger på selve ytterpunkttick-en krever volum-ved-pris-bevis fra handelstapen, ellers avvises de. Det fjernet omtrent 6% av handlene fra en typisk mean-reversion-bok og kuttet backtestet Sharpe for én kandidat fra 1.9 til 1.1. Den kandidaten var aldri ekte; fill-regelen var bare det første som var ærlig nok til å si det.

Beslektet felle: intrabar-sti. Hvis en bars spenn dekker både stoppen din og take-profiten din, kan ikke OHLCV fortelle deg hvilken som kom først. Hver motor må velge en konvensjon. Vår antar alltid stopp-først, som er pessimistisk og av og til feil, men aldri genererer en falsk gevinst. Hvis motoren din antar take-profit-først, vil bredspenn-barer produsere profitt ut av tvetydighet, og bredspenn-barer er nettopp de som dominerer PnL-fordelingen din.

[4] "84163.40": det minst robuste tallet i baren

Lukkingen er den siste noteringen i vinduet. Det er alt. Det kan være en 0.002 BTC odd-lot fra en bot som runder av en posisjon klokken 13:46:59.8. Denne ene, strukturelt vilkårlige tick-en er det de fleste forskningspipeliner bruker til å beregne hvert signal, markere hver posisjon og evaluere hver exit.

På BTC-perper spiller det knapt noen rolle; på en tynn altcoin-perp klokken 04:00 UTC spiller det enormt mye, og forskjellen mellom to plattformers lukkinger for det samme minuttet kan overstige hele per-trade-edgen din. Når en strategis PnL avhenger spesifikt av lukkingen, kjører vi den på nytt med markering mot børsens mark price i stedet, som er indeksutledet og langt vanskeligere å dytte på. Hvis resultatene divergerer, handlet strategien artefakten.

[5] og [7]: "38.417" og "3232108.94", volum i hvilke enheter

Basisvolum er BTC; kvotevolum er USDT. Begge her, noe som er en høflighet ikke alle plattformer tilbyr. Grunnen til at det er viktig, er aggregering på tvers av plattformer. Coin-marginerte kontrakter er notert i kontrakter à $100 pålydende. Enkelte aksjefeeder rapporterer runde lotter. Prediksjonsmarkedsplattformer rapporterer antall andeler, der en andel er et dollardenominert binært krav. Å summere «volum» på tvers av et blandet univers uten å normalisere til én felles pålydende enhet gir en likviditetsrangering som er ren nonsens — og den vil være nonsens på en stabil-utseende måte som overlever gjennomgang.

Normaliser alt til kvote-pålydende ved innhenting. Lagre råfeltet også, men la aldri en strategi se det.

[8] 1204: feltet som burde styre impact-modellen din

Antall handler delt inn i basisvolum gir en gjennomsnittlig notering på 0.032 BTC, omtrent $2,700. Hvis kandidatstrategien din vil gå inn med $250,000 på én gang, ber den om å være omtrent 92 ganger størrelsen på den typiske transaksjonen i det minuttet. Det tallet — ikke en generisk 5-bps slippage-konstant — er det som bør drive kvadratrot-impact-leddet ditt. Vi beregner deltakelsesrate per bar som en førsteklasses kolonne og avviser strategier der medianinngangen overstiger noen få prosent av barens pålydende, fordi alt nedstrøms fra det er fiksjon.

Antall handler flagger også de rare minuttene billig. Normalt volum, men antall handler kollapset til 11? Noen gjorde en blokkhandel. Normalt volum, men antall handler på 9,000? Det er en likvidasjonskaskade som tygges gjennom i bittesmå biter.

[9] og [10]: "21.883", feltet alle kaster bort

Taker buy-basisvolum. 21.883 av 38.417 BTC i denne baren var kjøperinitiert, så det signerte volumdeltaet er +5.349 BTC, og aggressor-fordelingen er 57/43 mot kjøpere. Børsen gir deg order flow imbalance, gratis, i et felt de fleste aldri leser fordi pandas ikke navnga kolonnen for dem.

Jeg påstår ikke at det forutsier avkastning på egen hånd; naive delta-strategier er blant de mer pålitelige måtene å donere til avgiftsregnskapet på. Men det er en genuint annen måling enn pris, den er tilgjengelig i den samme forespørselen du allerede gjorde, og den lar deg skille et rally som ble kjøpt fra et rally som skjedde fordi selgerne trakk seg unna. De to ser identiske ut i OHLC og oppfører seg forskjellig ti minutter senere. Vår forskningsagent behandler en hypotese som ignorerer taker-fordelingen på en plattform som publiserer den, som å la bevis ligge igjen på bordet.

[11] "0": ignorer — og alt annet som ikke er her

Indeks 11 er et utfaset felt, permanent null. Mer interessant er listen over ting denne baren ikke inneholder: ingen bud, intet salgstilbud, ingen spread, ingen bokdybde, ingen funding rate, ingen open interest, ingen likvidasjoner, ingen mark price, ingen indekspris. Og avgjørende: ingen måte å vite om din ordre ville vært maker eller taker, som er forskjellen mellom å betale 0.045% og tjene 0.01% på denne plattformen.

Så enhver avgiftsmodell bygget på klines alene er en antagelse kledd i et talls klær. Vi løser det ved å tvinge hver strategi til å erklære utførelsesstilen sin på forhånd, og deretter belaste taker-avgift på alt som ikke kan bevise noe annet.

Baren som aldri dukket opp

Siste del, og den som biter hardest utenfor de store parene. Et minutt med null handler produserer ingen kline i det hele tatt. Leverandører og biblioteker forward-fyller det ofte: åpning = høy = lav = lukking = forrige lukking, volum 0. Indikatoren din beregner lykkelig videre. Strategien din ser en gyldig rad og kan generere et signal på et minutt der ingen i verden handlet det instrumentet.

På én mid-cap-perp vi hentet inn, hadde 4.1% av 1-minuttsbarene i et tolvmåneders vindu null handler. En mean-reversion-kandidat på det symbolet plasserte 38% av inngangene sine på syntetiske barer, fordi flate syntetiske priser er catnip for alt som måler avvik fra et glidende gjennomsnitt. Den backtestet vakkert. Den handlet hullene i dataene.

Derfor bærer nå innhentingslaget en synthetic-boolsk verdi per bar, videreført gjennom hver resampling, og verifiseringsløypa feller enhver strategi hvis handler klumper seg på den. Billig kolonne. Den har drept flere kandidater enn noen indikator vi noensinne har skrevet.

Tolv verdier. Fire av dem rutinemessig feiltolket, to av dem rutinemessig kastet bort, og en hel kategori av dem fraværende fra raden og innbilt av motoren. Før neste backtest, hent én rå bar fra ditt eget lager og les hvert felt høyt opp mot det fill-logikken din antar om det. Det er en tjue-minutters øvelse, og jeg har aldri sett noen gjøre det og ikke finne noe.

ohlcvmarkedsmikrostrukturdataingeniørarbeidbacktestingkryptofutures
← All posts