2. september 2026 · mikrostruktur

Limitordren din ble ikke fylt: et brev om køposisjon, maker-backtester og Sharpe-forholdet du fant på

Limitordren din ble ikke fylt: et brev om køposisjon, maker-backtester og Sharpe-forholdet du fant på

Du sendte over notebooken forrige uke: samme signal, samme univers, de samme 14 månedene med BTCUSDT perp-data. Den eneste endringen var eksekveringen. Du sluttet å krysse spreaden og begynte å legge inn bud én tick innenfor, og Sharpe gikk fra 0.42 til 2.14. Du spurte om det var reelt, eller om du hadde ødelagt noe.

Du ødela noe. Jeg vil gå gjennom nøyaktig hvor, for feilen ligger i én linje, og lærdommen bak den er mye større enn selve linjen.

Her er fyllregelen din, kopiert ut av motoren:

if bar.low <= limit_price: fill(limit_price)

Det betyr: Hvis markedet handlet på eller under prisen min i løpet av dette minuttet, ble ordren min fylt til prisen min. Det den faktisk koder, er at kursen berørte nivået ditt. Å berøre nivået er ikke det samme som å fylle ordren. Mellom disse to tingene står det en kø, og du har modellert køen som tom.

Hva som står foran deg

Når du legger inn et bud på 84,120.0 på BTCUSDT perp, havner du bakerst i køen av det som allerede ligger der. På øverste nivå i ordreboken for denne kontrakten er det vanligvis et sted mellom 4 og 30 BTC, avhengig av tidspunkt på dagen; medianen i utvalgsperioden din ligger rundt 12 BTC. Ordren din er på 0.4 BTC. For at du skal få en handel, må selgere slå inn i nivået med nok samlet volum til å tømme alle som kom før deg, før nivået kanselleres under deg eller markedet løfter seg bort.

Spørsmålet backtesten din burde stille, er altså ikke «nådde kursen 84,120.0», men «ble minst 12.4 BTC i markedssalg utført på 84,120.0 mens ordren min lå der». Dette er vidt forskjellige hendelser. I dataene dine forekommer de med omtrent en faktor tre i forskjell.

12.4 BTCmedianvolum foran deg når kursen berører nivået
100%fyllingsgrad backtesten din forutsetter
31%av berøringene som tømte køen din
4,180handler, som ble til omtrent 1,300

Tre fyllregler, tre forskjellige strategier

Jeg kjørte signalet ditt på nytt med de samme inngangene og tre eksekveringsmodeller. Samme alpha, samme gebyrer, samme funding. Bare fyllogikken ble endret.

FyllregelFyllingerGjennomsnittlig 60s edge etter fyllingSharpe
Berøring: low <= limit4,180+2.6 bp2.14
Strengt gjennombrudd: low < limit - 1 tick1,712+0.4 bp0.61
Køsimulering med volum på nivået1,306+0.9 bp0.77

Den midterste raden er den enkle løsningen alle tyr til først: Tell bare en fylling hvis markedet handlet strengt forbi prisen din, ut fra tanken om at hvis kursen gikk forbi deg, må den ha spist seg gjennom ordren din. Det er riktig retning, og det fjerner det meste av fantasien. Men det er også skjevt på en uheldig måte, som jeg kommer tilbake til.

Den nederste raden er den du bør bygge. Den krever verken L3-feed eller rekonstruksjon av hver enkelt ordre. Du har allerede det meste av det du trenger.

En køsimulering du kan bygge fra aggTrades

Hent den aggregerte handelsstrømmen i stedet for klines. Hver handel inneholder pris, mengde, tidsstempel og et maker-side-flagg som viser om aggressoren kjøpte eller solgte. Det holder til en brukbar simulering av din egen passive ordre:

  1. Når ordren din legges ut, tar du et øyeblikksbilde av volumet som ligger på prisen din. Hvis du bare har en ordrebok-feed på 100ms, bruk det siste øyeblikksbildet; feilen er liten sammenlignet med feilen du retter opp i.
  2. Sett queue_ahead = resting_size. Vær pessimistisk og anta at du ligger sist. Det gjør du, med mindre du selv oppretter nivået.
  3. Gå gjennom handelsstrømmen. Hver handel initiert av en selger, på eller under prisen din, reduserer queue_ahead med handelsvolumet. Når verdien blir negativ, blir ordren din fylt til prisen din på det tidsstempelet.
  4. Hvis kursen flytter seg én tick unna, ikke nullstill køen. La den avta. Noen av ordrene foran deg kanselleres når nivået blir utdatert, andre blir liggende. En reduksjon på 30-40% per helt sekund uten berøring stemte bedre med rekonstruksjonene mine enn noen av ytterpunktene.
  5. Hvis strategien din ville kansellert og lagt inn ordren på nytt, modellerer du den nye ordren bakerst i det nye nivået. Dette trinnet hopper folk over, og her skjuler den gjenværende fantasien seg.

Det er i trinn 2 du kommer til å ville diskutere med meg. Ja, noen ganger ligger du langt fremme fordi du la inn ordren idet nivået ble dannet. Greit — mål det, ikke anta det. Logg volumet som ligger der når du legger inn ordren, og la dataene vise hvilken andel av ordrene dine som faktisk kommer tidlig. I strategien din var det 11%, fordi signalet utløses etter en bevegelse. Det betyr at nivået du går inn i, allerede finnes og allerede har en kø.

Fyllingene du får, er dem du skulle ønske du ikke hadde fått

Nå kommer den delen som faktisk betyr noe, og grunnen til at regelen om strengt gjennombrudd er skjev.

Tenk på når budet ditt blir helt spist opp. Det skjer når salgspresset er stort nok til å spise hele nivået. Det betyr, per konstruksjon, at markedet beveger seg ned gjennom prisen din. Fyllingene du er mest sikker på, er de der du umiddelbart havner på feil side av markedet.

Del fyllingene etter hvordan de skjedde, og mål avkastningen etter 60 sekunder:

FyllingstypeAndel av fyllingeneAvkastning etter 60s
Nivået ble handlet, kursen spratt opp38%+3.1 bp
Nivået ble handlet, kursen var flat21%+0.2 bp
Kursen handlet gjennom med 2+ ticks41%−2.4 bp

Den naive backtesten ga deg alle tre gruppene og behandlet dem alle som gratis. Regelen om strengt gjennombrudd gir deg nesten utelukkende den tredje gruppen, og derfor falt edgen mer enn med køsimuleringen. Ingen av dem er riktig. Køsimuleringen gir deg en realistisk blanding, og blandingen er hele spillet: Passiv eksekvering lar deg tjene spreaden, men du betaler for det gjennom ugunstig seleksjon. Forholdet mellom de to er den faktiske strategien din.

Den gamle måten aksjedeskene beskrev det på, gjelder fortsatt: En maker-ordre er en gratis opsjon du har skrevet til markedet. Noen utøver den når det lønner seg. Backtesten din tok inn premien og glemte at opsjonen også hadde en utbetalingsside.

Handlene du ikke fikk, endrer strategien, ikke bare kostnaden

Dette er poenget jeg helst vil at du skal tenke grundig over. Når du modellerer taker-eksekvering dårlig, får du de riktige handlene til feil pris, og en justering av gebyrene retter opp det meste. Når du modellerer maker-eksekvering dårlig, får du helt feil sett med handler. Omtrent 2,900 av de 4,180 inngangene dine skjedde aldri. Noen av dem var de beste signalene dine, på candles som skjøt opp og snudde. Det er nettopp et slikt mønster der markedet løp fra deg.

Derfor trenger grenen for uutfylte ordrer ekte logikk. Hva gjør strategien når inngangen ikke blir fylt før signalet blir utdatert? Jager du med en taker-ordre og betaler spreaden pluss markedspåvirkning? Legger du inn en ny ordre lavere og godtar et annet inngangsgrunnlag? Hopper du over handelen og holder deg flat? Hvert valg gir en vesentlig forskjellig egenkapitalkurve, og ingen av dem er «anta at ordren ble fylt». I kjøringene våre hentet en realistisk regel for å følge etter — kryss etter 20 sekunder uten fylling, med slippage begrenset til 3 bp — tilbake omtrent en tredjedel av de tapte handlene og omtrent halvparten av avstanden mellom naiv Sharpe og Sharpe fra køsimuleringen. Det er et veldig interessant resultat, og det dukker først opp når fyllmodellen er realistisk nok til at spørsmålet gir mening.

En enkel rimelighetssjekk, tar ti minutter: Ta loggen fra papirhandelen din og backtesten over samme tidsperiode. Sammenlign fyllingsgraden, ikke PnL. Hvis backtesten fyller 100% av hvilende ordrer og papirhandelen fyller 34%, har du ikke en strategisammenligning, men en feil i fyllmodellen. Rett det før du ser på et eneste avkastningstall.

To mindre ting når jeg først har deg her

Avviste post-only-ordrer. Hvis du bruker post-only for å sikre maker-gebyrnivået, og ordreboken flytter seg mellom beslutningen din og bekreftelsen fra børsen, blir ordren avvist i stedet for å bli liggende. I papirloggene våre gjelder det 3-6% av forsøkene på BTCUSDT i normal handelstid og over 15% i minuttet etter en amerikansk KPI-publisering. En avvist ordre er verken en fylt ordre eller en hvilende ordre som ikke ble fylt. Det er en handel som aldri fant sted. Hvis backtesten din ikke har denne tilstanden, blåses antallet handler opp nettopp i regimene du bryr deg mest om.

Forebygging av egenhandel og ditt eget fotavtrykk. Med 0.4 BTC flytter du ikke BTCUSDT, så det kan du se bort fra. Men du nevnte at du ville kjøre dette på en perp-kontrakt for en altcoin med middels markedsverdi, der nominell verdi på øverste nivå i ordreboken ofte er under $15k. Der er ordren din en betydelig andel av køen, og køvolumet du måler opp foran deg, inkluderer effekten av at den forrige ordren din allerede lå der. Når størrelsen din er mer enn omtrent 10% av volumet som ligger på nivået, må simuleringen ta høyde for at andre deltakere reagerer på deg. Og helt ærlig ville jeg da stolt mer på papirhandel enn på enhver simulering du eller jeg kan skrive.

Kjør den på nytt med køsimuleringen, og send meg tabellen over avkastning etter fylling, delt inn etter fyllingstype. Hvis gruppen der kursen spratt opp fortsatt står for det meste av edgen, og gruppen der kursen handlet gjennom ikke spiser opp hele gevinsten, kan det hende du har noe som er verdt å prøve på papiret. Hvis hele resultatet skyldtes de 2,900 fyllingene du aldri ville fått, er det bedre å lære det nå enn etter fire uker med en papirkonto som ikke gjør det notebooken lovet.

limitordrerkøposisjonugunstig seleksjonbacktestingkryptofutures
← Alle innlegg