Vier getallen, allemaal gemeten op onze eigen paper-trading-stack, één strategie, 1-minuut BTCUSDT perpetual-signalen, 31 dagen en 1,418 orders. Mediane tijd van bar-close tot order-ack door de exchange: 195 ms. 99e percentiel: 2,970 ms. Mediaan in de 1% van de minuten met de hoogste gerealiseerde volatiliteit: 840 ms. En de latentieconstante die het grootste deel van het voorgaande jaar in de backtest-config stond: 0.
Drie van die getallen zijn saai. Iedereen weet dat de nul fout is, iedereen vervangt hem uiteindelijk door iets, en dat iets is bijna altijd een constante die iemand op gevoel heeft gekozen. 100 ms, 250 ms, "laten we conservatief zijn, 500." Het getal dat er écht toe doet is 840, omdat het zegt dat je latentie een functie is van de markttoestand, en de markttoestanden waarin die ontspoort zijn precies de markttoestanden waarin je signaal denkt iets te hebben gevonden.
Waar de 195 milliseconden naartoe gaan
Hier is het volledige budget, fase voor fase gemeten met monotonic-clock-timestamps die op elke hop zijn weggeschreven. De box is een AWS-instance in ap-northeast-1, dezelfde regio als de endpoints van de venue, wat zo'n beetje het beste is wat een niet-gecollocate, retail-achtige setup kan krijgen.
| Fase | p50 | p95 | p99 |
|---|---|---|---|
| Bar-close → WS kline-closed event ontvangen | 128 ms | 402 ms | 1,940 ms |
| Parsen + feature-state bijwerken | 3 ms | 9 ms | 28 ms |
| Beslissing: model-eval + risicocontroles | 11 ms | 22 ms | 60 ms |
| Order verzenden → exchange-ack | 47 ms | 180 ms | 1,100 ms |
| End-to-end (gemeten, niet opgeteld) | 195 ms | 690 ms | 2,970 ms |
Twee dingen om op te merken voordat het interessante deel komt. Ten eerste tellen de medianen niet op: 128 + 3 + 11 + 47 = 189, en de gemeten end-to-end-mediaan is 195. Percentielen zijn niet additief, en als je je latentieschatting opbouwt door per-fase-p99's op te tellen, krijg je 3,128 ms terwijl de echte p99 2,970 is. Hier dicht genoeg, hopeloos ernaast wanneer de fasen de andere kant op gecorreleerd zijn. Meet het hele pad.
Ten tweede, kijk naar de bovenste rij. Twee derde van het mediane budget gaat op aan wachten tot de exchange je vertelt dat de bar gesloten is. Niet het netwerk, niet je code, niet order routing. De venue aggregeert trades tot een kline, besluit dat de minuut voorbij is, en pusht. Die push heeft een wachtrij ervoor, en die wachtrij wordt gedeeld met elke andere subscriber op die stream.
De 840
Verdeel elk van die 1,418 orders in buckets naar de gerealiseerde volatiliteit van de minuut die het signaal opleverde, en de verdeling valt uit elkaar.
| Volatiliteitsbucket van de signaalbar | Orders | p50 end-to-end | p99 end-to-end |
|---|---|---|---|
| Onderste kwartiel | 201 | 141 ms | 488 ms |
| Middelste twee kwartielen | 796 | 183 ms | 1,210 ms |
| Bovenste kwartiel | 407 | 312 ms | 2,880 ms |
| Top 1% van alle minuten | 14 | 840 ms | 4,100 ms |
Elke fase verslechtert tegelijk, en daarom stapelt het zich op. De aggregatie- en broadcastwachtrij van de venue wordt gevoed door matching-engine-load, dus een minuut met 11,000 trades pusht later dan een minuut met 600. Je eigen proces verwerkt in hetzelfde window meer depth-updates en meer trade prints, dus de feature-update die normaal 3 ms duurt, duurt 28. Het REST-orderpad concurreert met de paniek van iedereen anders, en je begint af en toe een 503-and-retry te zien die je op zichzelf een volle seconde kost. De correlatiecoëfficiënt tussen het aantal trades in de signaalbar en de end-to-end-latentie in onze steekproef was 0.41. Niet enorm. Genoeg om een constante te slopen.
En de strategieën die een eerste screening overleven, zijn overweldigend de strategieën die breakouts, reversals, dislocations en liquidation cascades verhandelen. Ze gaan af in precies de bucket waar de latentie het slechtst is. Een momentumsignaal dat alleen afgaat wanneer de minuut 40 bps bewoog, is een signaal dat zich structureel zo heeft ingericht dat het altijd door de traagste versie van je stack wordt bediend.
We hebben ooit een dag besteed aan het najagen van een 3.6-seconde-outlier die elke nacht ongeveer op hetzelfde tijdstip opdook. Het was logrotate, die met copy-truncate een 2 GB-bestand afkapte op hetzelfde EBS-volume waar het proces naartoe schreef, waardoor de event loop vastliep. De markt was onschuldig. Meet eerst, geef daarna de microstructuur de schuld.
Wat dat met een fill doet
De prijs drijft terwijl je wacht. In een willekeurige minuut was de gemiddelde adverse move over onze 195 ms mediaan ongeveer 1.1 bps tegen de signaalrichting in. In de bovenste-kwartielminuten bij 312 ms: 5.8 bps. In de top-1%-minuten bij 840 ms: grofweg 14 bps adverse drift voordat de order überhaupt het book bereikt.
Op BTC-perps doet de spread hier nauwelijks aan mee: één tick op een 84,000-quote is 0.012 bps, dus de schade is adverse drift in plaats van crossing cost. Verplaats dezelfde strategie naar een mid-cap-perp en de tweede term wordt wakker — de top-of-book-spread in onze steekproef ging van 1.4 bps in kalme minuten naar 6.2 bps in het bovenste kwartiel, en de depth op die touch dunnerde met meer dan de helft. Je betaalt de drift én de bredere spread én de slechtere depth, allemaal tegelijk, allemaal omdat ze een oorzaak delen.
Toen we dezelfde 31 dagen opnieuw afspeelden met conditionele latentie per bar in plaats van een vlakke 200 ms, verschoof de gemiddelde entry-prijs van de backtest 3.1 bps slechter, bijna volledig geconcentreerd in 9% van de trades. De backtest-versus-paper-divergentie in entry-prijs daalde van 4.2 bps naar 0.9 bps. De in-sample Sharpe van die strategie ging van 1.7 naar 1.1, en dat is het eerlijke getal, en dat was het altijd al.
Dus wat zet je dan écht in de config
Geen scalar. Wat we nu draaien:
- Samplen, niet vastzetten. Trek de latentie voor elke gesimuleerde order uit een empirische verdeling, geconditioneerd op de eigen activiteit van die bar — het aantal trades en het absolute rendement volstaan, en beide zitten al in een standaard kline. Fit één keer per venue per ordertype, refit maandelijks.
- Houd de staart. Fit geen lognormaal en noem het klaar; de p99 leeft in retry-storms en GC-pauzes die geen gladde verdeling reproduceert. Wij gebruiken de empirische kwantielen met een geresamplede staart boven p95.
- Latentie geldt ook voor cancels. Een stop-replacement of een hedge-cancel is onderhevig aan dezelfde vertraging, en in snelle markten racet je cancel tegen een fill. Als je simulator instant cancelt, is je risicomodel fictie.
- Voeg een staleness-guard toe en simuleer die. De onze verwerpt elk signaal waarvan de bar bij aankomst meer dan 1.5 seconden oud is. Dat is echt gedrag met echte kosten — het verwijdert stilletjes trades, en het moet in de backtest dezelfde trades verwijderen.
- Herijk tegen paper. Paper trading is waar je de ground truth vandaan haalt. Log de vier hop-timestamps bij elke order en diff wekelijks de verdelingen tegen wat de simulator aannam.
Andere asset classes, dezelfde ziekte. US-equities: de consolidated tape voegt zijn eigen aggregatievertraging toe bovenop die van je broker, en die loopt op onder message-rate-druk om dezelfde structurele reden. Prediction markets: REST-only order entry op de meeste venues, en we hebben sub-100 ms-medianen gemeten die naar meerdere seconden gaan wanneer een resolution event toeslaat en iedereen tegelijk aankomt. Crypto-opties: de quote waarop je prijst, kan twee seconden stale zijn in precies de minuut dat de underlying bewoog.
De algemene vorm van de fout is een executieparameter behandelen als een onafhankelijke constante terwijl het een functie is van dezelfde toestandsvariabele die je signaal aandrijft. Fees gedragen zich; funding gedraagt zich; latentie niet. Als je één getal uit dit alles in je hoofd wilt meenemen, neem de ratio: onze p99 is vijftien keer onze mediaan, en ons signaal komt onevenredig vaak aan de p99-kant terecht.
Instrumenteer de vier hops. Het is een middag werk en het vertelt je meer over je strategie dan nóg een week parameter search.
← Alle artikelen


