26 august 2026 · date de piață

Anatomia unui bar de 1 minut: tot ce nu-ți spune OHLCV despre backtestul tău

Anatomia unui bar de 1 minut: tot ce nu-ți spune OHLCV despre backtestul tău

Iată un bar. Binance USDⓈ-M perpetual, BTCUSDT, minutul care începe la 13:46:00 UTC. A venit de la endpointul klines ca un array simplu de douăsprezece valori, și este cea mai comună unitate de input în cercetarea cuantitativă retail și adiacentă acesteia. Aproape fiecare strategie pe care o generăm atinge ceva cu această formă.

Așadar, haideți să-l desfacem, indice cu indice, și să vedem câte lucruri înțelege greșit un backtest.

IndiceValoareNume
01773495960000timp de deschidere (ms)
1"84120.50"deschidere
2"84177.90"maxim
3"84098.10"minim
4"84163.40"închidere
5"38.417"volum de bază (BTC)
61773496019999timp de închidere (ms)
7"3232108.94"volum quote (USDT)
81204numărul de tranzacții
9"21.883"volum de bază cumpărat de taker
10"1841203.55"volum quote cumpărat de taker
11"0"ignorat

[0] și [6]: care e minutul acesta și după al cui ceas

Timp de deschidere 1773495960000, timp de închidere 1773496019999. Observați al doilea: se termină în 19999, cu o milisecundă mai puțin decât deschiderea barului următor. Fereastra este semi-deschisă, iar exchange-ul vă spune asta explicit. Jumătate din bugurile de date pe care le-am vânat au început cu cineva care a tratat ambele capete ca fiind incluse și a numărat de două ori o tranzacție de la graniță, sau a aliniat un resample astfel încât fiecare bar de 5 minute a împrumutat o milisecundă de la vecinul lui.

Marcajele temporale sunt cele ale motorului de matching al exchange-ului. Nu ale ceasului vostru, nu ale ceasului de ingestie al furnizorului vostru, și nu același ceas pe care îl folosește snapshot-ul de funding. Când combinați o serie de kline cu o serie de funding rate sau una de open interest preluată de la un endpoint diferit, combinați surse care sunt de acord în limita a aproximativ o secundă, în majoritatea timpului. Pentru o strategie de 1 minut, asta înseamnă o eroare de sincronizare de 1,7% pe fiecare rând combinat. Pentru orice e sub-minut, e fatal.

Iar acest bar este marcat la deschiderea lui. Informația din el nu poate fi cunoscută până la 13:46:59.999. Fiecare întrebare despre convenția de indexare — decalez cu unu, folosesc marcaje temporale de închidere sau de deschidere a barului ca timp al evenimentului — este de fapt aceeași întrebare despre lookahead, deghizată.

[1] "84120.50": deschiderea pe care n-o puteți tranzacționa

Deschiderea este prețul primei tranzacții care s-a executat în interiorul ferestrei. Este o tranzacție deja finalizată între alte două persoane. Până când barul există ca rând în dataframe-ul vostru, prețul acela are deja șaizeci de secunde vechime.

Barul anterior s-a închis la 84109.80, deci există o mișcare de preț de 10,70 $ în decalajul dintre două baruri de 1 minut adiacente. Asta înseamnă 1,3 puncte de bază, invizibil pe un grafic, și aproximativ un sfert din comisionul de taker. Bine. Dar mergeți și priviți aceeași serie pe un alt perp altcoin în timpul unei publicări CPI din SUA și decalajele acelea inter-bar ajung la 15 până la 40 de puncte de bază. Un backtest care generează semnal la închiderea barului și execută la deschiderea barului următor presupune, pe tăcute, că decalajul e zero, iar el e zero exact atunci când nu contează.

[2] și [3]: "84177.90" maxim, "84098.10" minim

Aici mor cele mai multe motoare de execuție, așa că această secțiune e cea lungă.

Maximul și minimul sunt prețuri atinse extreme. Nu poartă nicio dimensiune și nicio durată. Din banda brută de tranzacții pentru acest minut, tot ce s-a tranzacționat la sau sub 84100,00 a însumat 0,62 BTC în nouă execuții, într-o fereastră de 1,4 secunde. Deci un stop de backtest la 84100 „se execută” — iar dacă poziția voastră e de 3 BTC, ați consumat tot book-ul vizibil pe drumul în jos, iar restul dimensiunii voastre s-a executat undeva pe parcursul revenirii 84105–84130. Barul spune că minimul a fost 84098.10. Barul nu spune că doar 52.000 $ notional s-au tranzacționat acolo.

1204tranzacții în minutul respectiv
0,032 BTCdimensiunea medie a unei execuții (~2,7k $)
0,62 BTCtotal tranzacționat la sau sub tick-ul minim
57%din volum a fost inițiat de cumpărători

Direcția cealaltă e mai rea, pentru că vă flatează. Să presupunem că aveați un ordin limită de vânzare în așteptare la 84175. Maximul barului e 84177.90, deci un motor naiv vă execută la 84175 și înregistrează rebate de maker în loc de cost de taker. Dacă execuția aceea s-a întâmplat cu adevărat depinde de poziția în coadă la nivelul de preț respectiv, lucru pe care barul nu-l poate ști, iar voi probabil nu l-ați înregistrat niciodată. Atins nu înseamnă executat.

Regula la care am ajuns în motorul nostru de execuție: un ordin limită în așteptare se execută doar dacă barul tranzacționează prin nivelul respectiv, nu doar până la el. Execuțiile la tick-ul extrem exact necesită dovezi de volum-la-preț din banda de tranzacții, altfel sunt respinse. A eliminat aproximativ 6% din tranzacții dintr-un book tipic de mean-reversion și a redus Sharpe-ul de backtest al unui candidat de la 1,9 la 1,1. Candidatul acela nu a fost niciodată real; regula de execuție a fost doar primul lucru destul de onest ca să spună asta.

O capcană înrudită: traiectoria intra-bar. Dacă intervalul unui bar acoperă atât stop-ul cât și take-profit-ul vostru, OHLCV nu vă poate spune care a venit primul. Fiecare motor trebuie să aleagă o convenție. Al nostru presupune întotdeauna stop-ul primul, ceea ce e pesimist și ocazional greșit, dar nu generează niciodată un câștig fals. Dacă motorul vostru presupune take-profit-ul primul, barurile cu interval larg vor fabrica profit din ambiguitate, iar barurile cu interval larg sunt exact cele care domină distribuția voastră de PnL.

[4] "84163.40": cel mai puțin robust număr din bar

Închiderea e ultima execuție din fereastră. Atât. Ar putea fi un lot impar de 0,002 BTC de la un bot care rotunjește o poziție la 13:46:59,8. Acest singur tick, structural arbitrar, este ceea ce majoritatea pipeline-urilor de cercetare folosesc pentru a calcula fiecare semnal, a marca fiecare poziție și a evalua fiecare ieșire.

La perp-urile BTC aproape că nu contează; la un perp altcoin subțire la ora 04:00 UTC contează enorm, iar diferența dintre închiderile a două platforme pentru același minut poate depăși întregul vostru avantaj per tranzacție. Când PnL-ul unei strategii depinde specific de închidere, o rulăm din nou marcând la prețul mark al exchange-ului, care este derivat din index și mult mai greu de manipulat. Dacă rezultatele diverg, strategia tranzacționa artefactul.

[5] și [7]: "38.417" și "3232108.94", volum în ce unități

Volumul de bază e în BTC; volumul quote e în USDT. Ambele aici, ceea ce e o curtoazie pe care nu toate platformele o oferă. Motivul pentru care contează e agregarea între platforme. Contractele coin-margined sunt cotate în contracte de 100 $ notional. Unele feed-uri de acțiuni raportează loturi rotunde. Platformele de piețe predictive raportează numere de acțiuni în care o acțiune e o creanță binară denominată în dolari. Însumarea „volumului” pe un univers mixt fără normalizare la o unitate notională unică produce un clasament de lichiditate care e pur nonsens, iar acesta va fi nonsens într-un mod stabil aparent, care supraviețuiește unei revizuiri.

Normalizați totul la notional quote la ingestie. Păstrați și câmpul brut, dar nu lăsați niciodată o strategie să-l vadă.

[8] 1204: câmpul care ar trebui să vă seteze modelul de impact

Numărul de tranzacții împărțit la volumul de bază dă o execuție medie de 0,032 BTC, adică aproximativ 2.700 $. Dacă strategia voastră candidată vrea să intre cu 250.000 $ dintr-o dată, cere să fie de aproximativ 92 de ori dimensiunea tranzacției tipice din minutul acela. Acel număr, nu vreo constantă generică de slippage de 5 puncte de bază, ar trebui să vă determine termenul de impact în rădăcină pătrată. Calculăm rata de participare per bar ca o coloană de sine stătătoare și respingem strategiile a căror intrare mediană depășește câteva procente din notionalul barului, pentru că tot ce e în aval de asta e ficțiune.

Numărul de tranzacții semnalizează și minutele ciudate, ieftin. Volum normal, număr de tranzacții prăbușit la 11? Cineva a făcut un block. Volum normal, număr de tranzacții la 9.000? E o cascadă de lichidări mestecată în bucăți minuscule.

[9] și [10]: "21.883", câmpul pe care toată lumea îl aruncă

Volumul de bază cumpărat de taker. 21,883 din 38,417 BTC din acest bar au fost inițiate de cumpărători, deci delta de volum semnat e +5,349 BTC, iar împărțirea agresorilor e 57/43 în favoarea cumpărătorilor. Exchange-ul vă oferă dezechilibrul fluxului de ordine, gratuit, într-un câmp pe care majoritatea oamenilor nu-l citesc niciodată pentru că pandas nu le-a denumit coloana.

Nu pretind că prezice singur randamentele; strategiile naive bazate pe delta sunt printre cele mai sigure moduri de a dona la registrul de comisioane. Dar este o măsurătoare cu adevărat diferită de preț, este disponibilă în același request pe care oricum îl făceați deja, și vă permite să distingeți un rally care a fost cumpărat de unul care s-a întâmplat pentru că vânzătorii s-au retras. Cele două arată identic în OHLC și se comportă diferit zece minute mai târziu. Agentul nostru de cercetare tratează o ipoteză care ignoră împărțirea taker pe o platformă care o publică drept lăsare de dovezi pe masă.

[11] "0": ignorat — și tot ce nu e aici

Indicele 11 e un câmp deprecat, permanent zero. Mai interesantă e lista lucrurilor pe care acest bar nu le conține: fără bid, fără ask, fără spread, fără adâncime de book, fără funding rate, fără open interest, fără lichidări, fără preț mark, fără preț index. Și, esențial, nicio modalitate de a ști dacă ordinul vostru ar fi fost maker sau taker, ceea ce e diferența dintre a plăti 0,045% și a câștiga 0,01% pe această platformă.

Deci orice model de comisioane construit doar pe klines e o presupunere îmbrăcată în hainele unui număr. Rezolvăm asta forțând fiecare strategie să-și declare stilul de execuție dinainte, apoi percepem taker pe orice nu poate dovedi altfel.

Barul care nu a apărut niciodată

Ultima bucată, și cea care mușcă cel mai tare în afara majors-urilor. Un minut cu zero tranzacții nu produce niciun kline. Furnizorii și bibliotecile îl completează adesea prin forward-fill: deschidere = maxim = minim = închidere = închiderea anterioară, volum 0. Indicatorul vostru calculează fericit. Strategia voastră vede un rând valid și poate genera un semnal într-un minut în care nimeni pe lume nu a tranzacționat instrumentul respectiv.

Pe un perp mid-cap pe care l-am ingestat, 4,1% din barurile de 1 minut într-o fereastră de douăsprezece luni au avut zero tranzacții. Un candidat de mean-reversion pe simbolul respectiv își plasa 38% din intrări pe baruri sintetice, pentru că prețurile sintetice plate sunt catnip pentru orice măsoară deviația de la o medie mobilă. S-a comportat frumos în backtest. Tranzacționa golurile din date.

De aceea stratul de ingestie poartă acum un boolean synthetic per bar, propagat prin fiecare resample, iar traseul de verificare respinge orice strategie ale cărei tranzacții se grupează pe el. Coloană ieftină. A ucis mai mulți candidați decât orice indicator pe care l-am scris vreodată.

Douăsprezece valori. Patru dintre ele interpretate greșit în mod curent, două dintre ele aruncate în mod curent, și o categorie întreagă absentă din rând și imaginată de motor. Înainte de următorul vostru backtest, mergeți și extrageți un bar brut din propriul vostru depozit de date și citiți fiecare câmp cu voce tare față de ce presupune logica voastră de execuție despre el. E un exercițiu de douăzeci de minute și n-am văzut niciodată pe cineva să-l facă și să nu găsească nimic.

ohlcvmicrostructura piețeiinginerie de datebacktestingcrypto futures
← Toate articolele