2 september 2026 · mikrostruktur

Din limitorder fylldes inte: ett brev om köposition, maker-backtester och Sharpekvoten du hittade på

Din limitorder fylldes inte: ett brev om köposition, maker-backtester och Sharpekvoten du hittade på

Du skickade över notebooken förra veckan: samma signal, samma universum, samma 14 månader med BTCUSDT perp-data. Den enda ändringen gällde exekveringen. Du slutade korsa spreaden och började lägga vilande köpbud en tick innanför, och Sharpekvoten steg från 0.42 till 2.14. Du frågade om det var på riktigt eller om du hade haft sönder något.

Du hade haft sönder något. Jag vill visa dig exakt var, för buggen består av en enda rad och lärdomen bakom den är mycket större än raden.

Här är din fyllnadsregel, kopierad från din motor:

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

Det betyder: om marknaden handlade till eller under mitt pris under den här minuten fylldes min order till mitt pris. Det regeln faktiskt kodar är att priset nuddade din nivå. Att nudda är inte samma sak som att fyllas. Mellan de två finns en kö, och du har modellerat kön som tom.

Vad som står framför dig

När du lägger ett köpbud på 84,120.0 i BTCUSDT perp hamnar du sist i kön av det som redan ligger vilande där. På den kontraktets bästa köpprisnivå handlar det oftast om någonstans mellan 4 och 30 BTC beroende på tid på dygnet; medianen i ditt urval ligger runt 12 BTC. Din order är på 0.4 BTC. För att du ska få en affär måste säljare slå mot den nivån med tillräcklig sammanlagd volym för att beta av alla som kom före dig, och de måste göra det innan nivån dras tillbaka under dig eller marknaden rör sig uppåt därifrån.

Frågan ditt backtest borde ställa är alltså inte ”nådde priset 84,120.0?” utan ”handlades minst 12.4 BTC i marknadssäljorder på 84,120.0 medan min order låg där?” Det är två helt olika händelser. I dina data skiljer de sig åt med en faktor på ungefär tre.

12.4 BTCmedianvolymen framför dig när priset nuddar nivån
100%fyllnadsgrad som ditt backtest antar
31%av beröringarna där kön framför dig betades av
4,180affärer som blev ungefär 1,300

Tre fyllnadsregler, tre olika strategier

Jag körde om din signal med samma entries och tre exekveringsmodeller. Samma alpha, samma avgifter, samma funding. Det enda som ändrades var fyllnadslogiken.

FyllnadsregelFyllningarGenomsnittlig edge 60s efter fyllningSharpekvot
Beröring: low <= limit4,180+2.6 bp2.14
Strikt genomhandel: low < limit - 1 tick1,712+0.4 bp0.61
Kösimulering med volym på nivån1,306+0.9 bp0.77

Mittraden är den enkla lösning som alla brukar testa först: räkna bara en fyllning om marknaden handlade strikt förbi ditt pris, utifrån tanken att om priset gick förbi dig måste din order ha blivit uppäten. Det är rätt riktning och tar bort det mesta av fantasiföreställningen. Men den är också skev på ett obehagligt sätt, vilket jag kommer till.

Nedersta raden är den du bör bygga. Den kräver ingen L3-feed och ingen rekonstruktion affär för affär. Du har redan det mesta du behöver.

En kösimulering du kan bygga från aggTrades

Hämta den aggregerade handelsströmmen i stället för klines. Varje avslut innehåller pris, kvantitet, tidsstämpel och en flagga för makersidan som visar om den aggressiva parten köpte eller sålde. Det räcker för en användbar simulering av din egen passiva order:

  1. När din order läggs, ta en ögonblicksbild av den vilande volymen på ditt pris. Om du bara har en orderboksfeed med 100ms upplösning använder du den senaste ögonblicksbilden; felet är litet jämfört med felet du rättar till.
  2. Sätt queue_ahead = resting_size. Var pessimistisk och anta att du står sist. Det gör du, såvida det inte är du som skapar nivån.
  3. Gå igenom handelsströmmen. Varje avslut där säljaren är aggressor på eller under ditt pris minskar queue_ahead med avslutets kvantitet. När värdet blir negativt fylls din order, till ditt pris och vid den tidsstämpeln.
  4. Om priset flyttar sig en tick bort ska du inte återställa kön till noll. Låt den minska. Vissa order framför dig makuleras när nivån blir inaktuell, andra ligger kvar. En minskning på 30-40% för varje hel sekund som nivån inte är bästa pris stämde bättre med mina rekonstruktioner än något av ytterlighetsantagandena.
  5. Om din strategi skulle makulera ordern och lägga en ny ska du modellera den nya som en ny order sist i kön på den nya nivån. Det här steget hoppar folk över, och här gömmer sig resten av fantasiföreställningen.

Steg 2 är där du kommer vilja invända. Ja, ibland står du långt fram eftersom du lade ordern precis när nivån bildades. Visst — mät det, anta det inte. Logga den vilande volymen när ordern läggs och låt data visa hur stor andel av dina order som faktiskt kommer in tidigt. I din strategi var andelen 11%, eftersom din signal utlöses efter en rörelse, vilket betyder att nivån du ansluter dig till redan finns och redan har en kö.

Fyllningarna du får är de du helst hade sluppit

Nu kommer det som faktiskt spelar roll, och förklarar varför regeln med strikt genomhandel är skev.

Tänk på när ditt köpbud blir helt uppätet. Det händer när säljtrycket är tillräckligt stort för att beta av hela nivån. Det är alltså just då marknaden rör sig ned genom ditt pris. De fyllningar du kan vara säkrast på är de där du omedelbart hamnar på fel sida av marknaden.

Dela upp fyllningarna efter hur de uppstod och mät avkastningen efter 60 sekunder:

FyllnadstypAndel av fyllningarnaAvkastning efter 60s
Handel på nivån, priset studsade upp38%+3.1 bp
Handel på nivån, priset var oförändrat21%+0.2 bp
Priset handlades förbi med 2+ tick41%−2.4 bp

Ditt naiva backtest gav dig alla tre grupper och behandlade dem som kostnadsfria. Regeln med strikt genomhandel ger dig nästan uteslutande den tredje gruppen, vilket är anledningen till att edgen sjönk mer än i kösimuleringen. Inget av det är rätt. Kösimuleringen ger dig en realistisk blandning, och det är hela spelet: passiv exekvering tjänar in spreaden och betalar tillbaka den genom negativt urval; förhållandet mellan de två är din faktiska strategi.

Den gamla inramningen från aktiehandelsbordet håller fortfarande: en maker-order är en köpoption du ställer ut till marknaden utan premie. Någon utnyttjar den när det lönar sig. Ditt backtest tog hem premien och glömde att optionen också har en utbetalningsdel.

Affärerna du inte fick ändrar strategin, inte bara kostnaden

Det här är den del jag helst vill att du funderar på. När du modellerar taker-exekvering dåligt får du rätt affärer till fel pris, och en avgiftsjustering rättar till det mesta. När du modellerar maker-exekvering dåligt får du helt fel uppsättning affärer. Ungefär 2,900 av dina 4,180 entries ägde aldrig rum. En del av dem var dina bästa signaler, på ljus som rusade igenom priset och vände; precis i sådana lägen drog marknaden iväg utan dig.

Så grenen för ofyllda order behöver riktig logik. Vad gör strategin när entryn inte har fyllts innan signalen blir inaktuell? Jagar du med en taker-order och betalar spreaden plus marknadspåverkan? Lägger du en ny order lägre och accepterar ett annat ingångspris? Hoppar du över affären och står utanför marknaden? Varje val ger en märkbart annorlunda aktiekurva, och inget av dem är ”anta att ordern fylldes”. I våra körningar återvann en realistisk regel för att jaga priset — korsa spreaden efter 20 sekunder utan fyllning, med slippage begränsad till 3 bp — ungefär en tredjedel av de uteblivna affärerna och ungefär hälften av skillnaden mellan Sharpekvoten i det naiva backtestet och kösimuleringen. Det är ett riktigt intressant resultat, och det visar sig först när fyllnadsmodellen är tillräckligt verklighetstrogen för att frågan ska vara meningsfull.

En enkel rimlighetskontroll, 10 minuter: ta loggen från din live-pappershandel och ditt backtest för samma tidsperiod. Jämför fyllnadsgraden, inte PnL. Om backtestet fyller 100% av vilande order och pappershandeln fyller 34% har du ingen strategijämförelse, utan en bugg i fyllnadsmodellen. Rätta till det innan du tittar på en enda avkastningssiffra.

Två mindre saker när jag ändå har dig här

Avvisade post-only-order. Om du använder post-only för att garantera maker-avgiftsnivån och orderboken hinner flytta sig mellan ditt beslut och börsens bekräftelse kommer ordern tillbaka som avvisad i stället för att läggas vilande. I våra pappershandelsloggar gäller det 3-6% av försöken på BTCUSDT under normala timmar och över 15% under minuten efter att amerikansk KPI-statistik publicerats. En avvisad order är varken en fylld order eller en vilande, ofylld order; det är en affär som aldrig existerade. Om ditt backtest saknar det tillståndet blåser du upp antalet affärer just i de marknadslägen du bryr dig mest om.

Skydd mot egenhandel och ditt eget marknadsavtryck. Med 0.4 BTC påverkar du inte BTCUSDT, så strunta i det. Men du nämnde att du vill köra det här på en altcoin-perp med medelstort börsvärde, där det nominella värdet i bästa orderboken ofta är under $15k. Där är din order en betydande andel av kön, och siffran för volymen framför dig som du tar en ögonblicksbild av påverkas av att din senaste order finns där. När din storlek överstiger ungefär 10% av den vilande volymen måste simuleringen ta hänsyn till att andra deltagare reagerar på dig. Och ärligt talat skulle jag då lita mer på pappershandel än på någon simulering som du eller jag kan skriva.

Kör om den med kösimuleringen och skicka mig tabellen över avkastningen efter fyllning, uppdelad efter fyllnadstyp. Om studsgruppen fortfarande står för det mesta av edgen och gruppen som handlas igenom inte äter upp allt kan du ha något som är värt att testa på papper. Om allt byggde på de 2,900 fyllningar du aldrig skulle ha fått är det bättre att upptäcka det nu än efter fyra veckor av att se ett papperskonto bete sig annorlunda än notebooken lovade.

limitorderköpositionnegativt urvalbacktestningkryptoterminer
← Alla inlägg