← Zurück zu den Artikeln
July 16, 2026
5 min read

Fill-Simulation: die Leiter von der Close-Preis-Fantasie zur queue-bewussten Realität

Fill-Simulation: die Leiter von der Close-Preis-Fantasie zur queue-bewussten Realität
#Fill-Simulation
#Backtest
#Limit-Orders
#Queue-Position
#Partial Fills
#Market Making
#Execution
#Marktmikrostruktur

Ihr Backtest enthält zwei Modelle: ein Modell Ihres Alphas und ein Modell Ihrer Fills. Die meisten stecken 95 % ihres Aufwands in das erste und übernehmen das zweite unbesehen aus welchem Framework auch immer sie gerade verwenden. Das ist rückwärts gedacht. Ein mittelmäßiges Signal mit einem ehrlichen Fill-Modell liefert eine mittelmäßige, aber reale PnL-Schätzung. Ein großartiges Signal mit fill_price = candle.close liefert eine Zahl, die keine Schätzung von irgendetwas ist — sie ist das Ergebnis einer Annahme, die nie überprüft wurde.

Bei Taker-Strategien auf liquiden Instrumenten ist das Fill-Modell ein Korrekturterm. Bei allem, was ruhende Limit-Orders betrifft — Market Making, passive Einstiege, Post-Only-Rebate-Harvesting — ist das Fill-Modell die Strategie. Ob Sie gefüllt werden, wann, in welcher Menge und bedingt auf welche nachfolgende Preisbewegung, bestimmt das Vorzeichen der PnL, nicht nur ihre Größenordnung.

In Backtest-Live-Parität haben wir die vollständige Taxonomie der Backtest-Live-Divergenzen kartiert und die Execution-Divergenz mit Schweregrad 5/5 bewertet — die schlimmste Klasse. Jener Artikel gab der Fill-Simulation drei grobe Genauigkeitsstufen und ging weiter. Dieser hier ist der Tiefen-Begleiter: die vollständige Leiter, Stufe für Stufe, mit dem Order-Lebenszyklus als Zustandsautomat, tatsächlich berechenbaren Fill-Wahrscheinlichkeits-Grenzen und einem gemessenen Experiment, das genau zeigt, an welcher Stelle der Leiter eine "profitable" Maker-Strategie stirbt.

Die Leiter

Fünf-Stufen-Leiter der Fill-Simulations-Genauigkeit

Jede Stufe erfordert mehr Daten und mehr Code, und jede Stufe beseitigt einen spezifischen systematischen Bias. Die Stufen sind danach geordnet, was sie falsch machen, nicht nur nach Kosten.

Stufe 0: Close-Preis-Fill

fill_price = bar.close

Die Order füllt sofort, vollständig, zum Close der Bar, die das Signal erzeugt hat. Das Signal wurde aus eben diesem Close berechnet, also handeln Sie mit Daten, die noch nicht existierten, als der Preis gedruckt wurde: Look-Ahead-Bias im Execution-Kostüm. Jede Strategie mit Turnover sieht hier gut aus.

Stufe 1: Open der nächsten Bar

fill_price = next_bar.open

Das minimale ehrliche Modell für Taker-Logik auf Bars. Das Signal wird auf Bar tt berechnet, der Fill erfolgt beim ersten beobachtbaren Preis von Bar t+1t+1. Das eliminiert den Look-Ahead, unterstellt aber weiterhin null Spread, null Impact, unendliche Liquidität beim Open-Print und 100 % Fill-Sicherheit. Bei Limit-Orders entartet dies zu Touch-Fill-Logik (dazu unten mehr, warum das Gift ist).

Stufe 2: Spread + fixer Slippage

half_spread = mid * spread_bps / 2e4
slip        = mid * slippage_bps / 1e4
fill_price  = mid + side * (half_spread + slip)   # side: +1 Kauf, -1 Verkauf

Jetzt zahlt jeder Taker-Trade den halben Spread plus eine kalibrierte Konstante. Dies ist die erste Stufe, auf der eine Strategie mit hohem Turnover im Backtest sterben kann — und genau das ist der Punkt. Der Restfehler: Slippage ist nicht konstant. Sie skaliert mit der Ordergröße relativ zur angezeigten Tiefe und explodiert genau dann, wenn Ihre Strategie am dringendsten handeln will. Ein fixer Wert von 5 bps ist ein Durchschnitt über Regime hinweg; Ihre Strategie handelt nicht das Durchschnittsregime.

Stufe 3: L2-Depth-Walk für Market-Orders

Mit Orderbuch-Snapshots hören Sie auf, Slippage zu raten, und berechnen sie stattdessen. Ein Market-Buy der Größe QQ läuft die Ask-Seite Level für Level ab; der Fill-Preis ist der volumengewichtete Durchschnitt über die konsumierten Level:

Pfill(Q)=1Qipimin ⁣(qi,  Qj<iqj)P_{\text{fill}}(Q) = \frac{1}{Q}\sum_{i} p_i \cdot \min\!\left(q_i,\; Q - \textstyle\sum_{j<i} q_j\right)

def depth_walk(levels: list[tuple[float, float]], qty: float) -> tuple[float, float]:
    """levels: [(price, size), ...] sorted best-first. Returns (vwap, filled_qty)."""
    remaining, cost = qty, 0.0
    for price, size in levels:
        take = min(size, remaining)
        cost += take * price
        remaining -= take
        if remaining <= 0:
            break
    filled = qty - remaining
    return (cost / filled if filled > 0 else float("nan"), filled)

Zwei Korrekturen machen dies wesentlich ehrlicher. Erstens Latenz: das Buch so ablaufen, wie es Δt\Delta t nach Ihrem Entscheidungszeitstempel stand, wobei Δt\Delta t Ihre gemessene Signal-zu-Exchange-Latenz ist — das Buch, das Sie gesehen haben, ist nicht das Buch, das Sie getroffen haben. Zweitens partielle marktfähige Fills: Wenn die angezeigte Tiefe innerhalb Ihres Limit-Through-Preises kleiner als QQ ist, muss das Modell einen Partial Fill zurückgeben und einen ruhenden Rest belassen, was das Problem an den unten stehenden Zustandsautomaten weiterreicht.

Der Restfehler auf Stufe 3 ist Impact und Refill: Sie konsumieren das Buch als statisches Objekt, aber reale Bücher füllen sich teilweise wieder auf (und reale Gegenparteien reagieren). Bei Clip-Größen unter ein paar Prozent der Top-of-Book-Tiefe ist dieser Fehler klein; bei größeren Clips brauchen Sie ein darübergelegtes Impact-Modell (Almgren-Chriss 2001).

Stufe 4: probabilistische Queue-Positions-Fills für Limit-Orders

Die Stufen 0-3 beantworten "zu welchem Preis füllt meine aggressive Order." Stufe 4 beantwortet die schwierigere Frage: füllt meine passive Order überhaupt — und sie ist die einzige Stufe, die eine Maker-Strategie bepreisen kann. Eine ruhende Limit-Order zum Preis pp füllt, wenn das kumulierte Handelsvolumen bei pp das Queue-Volumen übersteigt, das vor ihr lag. Das erfordert die Verfolgung Ihrer Position in einer FIFO-Queue, die Sie nicht direkt beobachten können.

Die Maschinerie zur Schätzung der Queue-Position — initiale Position, Update-Regeln bei Trades vs. Cancels, die f(x)f(x)-Wahrscheinlichkeitsfamilie zur Zuordnung unbeobachteter Stornierungen — ist die Grundoperation, die wir in Queue innerhalb der Wand aufgebaut haben. Ich werde sie hier nicht erneut herleiten; der Simulator konsumiert sie. Was Stufe 4 hinzufügt, ist die Fill-Entscheidungsregel auf Basis dieser Schätzung, behandelt im Abschnitt zur Fill-Wahrscheinlichkeit unten.

Warum diese Stufe wichtig ist, wird in der Literatur quantifiziert: Moallemi und Yuan (2016), "A Model for Queue Position Valuation in a Limit Order Book," zeigen, dass für Instrumente mit großem Tick der ökonomische Wert einer Front-of-Queue-Position gegenüber einer Back-of-Queue-Position vergleichbar mit dem halben Spread ist — dieselbe Größenordnung wie der gesamte theoretische Edge einer Market-Making-Strategie. Ein Fill-Modell, das die Queue-Position ignoriert, schätzt nicht etwa die PnL eines Makers falsch ein; es schätzt die PnL einer anderen Strategie.

Es gibt eine Stufe 5 — vollständige agentenbasierte Simulation, bei der der Markt auf Ihre Orders reagiert (queue-reactive Modelle im Sinne von Huang, Lehalle und Rosenbaum 2015; Multi-Agenten-Frameworks wie ABIDES, Byrd et al. 2020). Historischer Replay, selbst queue-bewusst, unterstellt, dass Ihre Order nichts am Verhalten aller anderen ändert. Diese Annahme ist bei Retail-Größe in Ordnung und wird zunehmend falsch, je mehr Ihre Quotes einen sichtbaren Anteil des Levels ausmachen. Stufe 5 liegt hier außerhalb des Umfangs; nur so viel: Die Leiter endet nicht bei Stufe 4.

Partial Fills als Zustandsautomat

Die Stufen 0-2 können so tun, als sei eine Order ein Funktionsaufruf: absenden, Preis erhalten, fertig. Ab Stufe 3 ist eine Order ein Prozess mit einem Lebenszyklus, und der Simulator muss ihn als Zustandsautomaten modellieren, sonst wird er die Fälle, auf die es am meisten ankommt, stillschweigend falsch behandeln.

Order-Lebenszyklus-Zustandsautomat mit Queue-Positions-Annotationen

enum OrderState {
    PendingNew,                                  // sent, not yet acked (latency window)
    Resting     { remaining: f64, q_ahead: f64 }, // in book, queue position estimated
    PartialFill { remaining: f64, q_ahead: f64 }, // some qty done, rest still queued
    PendingAmend,                                 // amend in flight
    PendingCancel,                                // cancel in flight
    Filled,
    Canceled    { filled_qty: f64 },              // may be partially filled at cancel time
    Rejected,
}

Die Übergänge tragen die Ökonomie in sich:

  • PendingNewResting: Die Order tritt der Queue hinter allem bei, was zum Ack-Zeitpunkt bereits vorhanden war, nicht zum Entscheidungszeitpunkt. Ihre Queue-Position wird mit dem Level-Volumen zum Zeitpunkt t+Δtackt + \Delta t_{\text{ack}} gesät. Simulatoren, die zum Entscheidungszeitpunkt säen, überschätzen systematisch die Queue-Priorität — genau um das Volumen, das während Ihres Latenzfensters angekommen ist, was bei Bursts das meiste Volumen ist.
  • RestingPartialFill: Ein Trade auf Ihrem Level, der größer als die vor Ihnen liegende Queue ist, füllt Sie partiell. Der Rest behält seine (jetzt Front-of-Queue-)Position. Partial Fills sind kein Rauschen — sie sind Information: Zu 0,3 von 1,0 gefüllt zu werden und zu beobachten, wie der Preis wegspringt, ist ein anderes PnL-Ereignis als ein voller Fill, und der Inventar-Prozess eines Makers wird aus diesen Fragmenten aufgebaut.
  • RestingPendingAmendResting: die Falle. Praktisch auf jeder Krypto-Börse ist ein Amend ein Cancel/Replace — Binances cancelReplace ist atomar gegen Doppelausführung, gibt aber eine neue Order-ID am Ende der Queue zurück. Selbst Börsen mit nativer Modify-Semantik (CME Globex) erhalten die Zeitpriorität nur bei einer Mengenreduktion; eine Preisänderung oder Mengenerhöhung verwirkt sie. Also im Simulator: jeder Preis-Amend setzt q_ahead auf das vollständige aktuelle Level-Volumen zurück. Eine Quoting-Engine, die alle 500ms neu pegt, "hält" keine Quote — sie tritt der Queue permanent von hinten neu bei, und ihr realistisches Fill-Profil ist nahezu reine Adverse Selection. Kombiniert mit dem Moallemi-Yuan-Ergebnis: Re-Quoting hat einen Preis, und der Preis ist Ihre Queue-Position.
  • PendingCancelPartialFillCanceled: Auch Cancels benötigen Latenz. Im Fenster zwischen der Entscheidung, eine Quote zurückzuziehen, und dem Eintreffen des Cancels bei der Matching-Engine können Sie noch gefüllt werden — und diese Fills sind die schlechtesten Fills, die Sie je erhalten werden, denn der Grund, warum Sie die Quote zurückgezogen haben, ist, dass der Markt Sie gerade zu überrollen drohte. Ein Simulator ohne Cancel-Latenz löscht genau die toxischsten Fills aus Ihrer Historie.

Der Zustandsautomat ist auch das, was die Buchhaltung des Simulators ehrlich macht: Gebühren fallen pro Fill-Ereignis an, das Inventar aktualisiert sich pro Fill-Ereignis, und Time-in-State-Statistiken (wie lange Orders ruhen, bevor sie gefüllt oder storniert werden) werden direkt mit Live-Logs vergleichbar — genau das, womit sich die Kalibrierungsschleife am Ende speist.

Limit-Fill-Wahrscheinlichkeit: drei Modelle und ein Bracket

Gegeben eine ruhende Order zum Preis pp (sagen wir ein Bid): Wann erklärt der Simulator einen Fill? Drei Entscheidungsregeln, mit zunehmender Ehrlichkeit:

1. Touch-Fill (naiv). Fill, wenn der Preis Ihr Level berührt: lowtp\text{low}_t \le p. Dies ist eine First-Passage-Time-Regel, und ihr Versagen wurde vor einem Vierteljahrhundert gemessen: Lo, MacKinlay und Zhang (2002), "Econometric Models of Limit-Order Executions" (Journal of Financial Economics 65), passen Survival-Modelle an tatsächliche Limit-Order-Daten an und kommen zu dem Schluss, dass hypothetische Ausführungen, konstruiert aus First-Passage-Times, "sehr schlechte Proxys für tatsächliche Limit-Order-Ausführungen sind." Der Fehlermodus ist strukturell: Wenn der Preis Ihr Level berührt und zurückspringt, hat die Berührung den vorderen Teil der Queue konsumiert — die Trader, die vor Ihnen quotiert haben. Touch-Fill schreibt Ihnen deren Fills zu. Schlimmer noch: Es schreibt Ihnen genau die guten Fills zu (Touch-and-Bounce ist das profitable Szenario für einen Maker), während Ihr echtes Fill-Set eher zu Touch-and-Run-Through verzerrt ist — den adversely selektierten Fills.

2. Trade-Through (konservative Grenze). Nur füllen, wenn der Preis Ihr Level strikt durchhandelt: lowt<pϵ\text{low}_t < p - \epsilon, oder bei Tick-Daten, wenn das kumulierte Handelsvolumen bei pp das gesamte Level übersteigt. Wenn der Preis durchgehandelt hat, wurde die gesamte Queue bei pp konsumiert, also wurden Sie unabhängig von Ihrer Position gefüllt. Dies schreibt Ihnen niemals einen Fill zu, den Sie nicht bekommen hätten. Sein Bias ist das Spiegelbild des Touch-Fill-Bias: Es verweigert Ihnen jeden Fill, bei dem die Queue sich bis zu Ihrer Position geleert hat, ohne vollständig durchzuschlagen, und die Fills, die es gewährt, sind überproportional die Run-Through-Fills (die adversely selektierten). Ein Maker-Backtest unter Trade-Through ist ein Stresstest, keine Schätzung.

3. Queue-Depletion-Schätzung. Verfolgen Sie das kumulierte Handelsvolumen MtM_t zu Ihrem Preis aus dem Trade-Tape sowie das stornierte Volumen CtC_t, abgeleitet aus L2-Deltas. Ihre geschätzte vorausliegende Queue:

Q^ahead(t)=max ⁣(Q0MtϕCt,  0)\hat{Q}_{\text{ahead}}(t) = \max\!\Big(Q_0 - M_t - \phi \cdot C_t,\; 0\Big)

wobei Q0Q_0 das Level-Volumen beim (Ack-Zeitpunkt-)Ordereintritt ist und ϕ[0,1]\phi \in [0,1] der Anteil der Stornierungen, der als von vor Ihnen kommend angenommen wird — der Regler, dessen prinzipielle Form (die f(x)f(x)-Familie) im Queue-Positions-Artikel hergeleitet wird. Der Fill beginnt, wenn Q^ahead=0\hat{Q}_{\text{ahead}} = 0; Ihre gefüllte Menge ist das darüber hinausgehende Handelsvolumen, was natürlich Partial Fills ergibt:

filled(t)=min ⁣(S,  max(MtQ0+ϕCt,0))\text{filled}(t) = \min\!\Big(S,\; \max(M_t - Q_0 + \phi\, C_t,\, 0)\Big)

ϕ=1\phi = 1 (alle Cancels vor Ihnen) liefert die optimistische Kante dieses Modells; ϕ=0\phi = 0 die pessimistische Kante. Wenn Sie nur spärliche L2-Snapshots und kein Trade-Tape auf Level-Ebene haben — üblich bei 100ms-gedrosselten Krypto-Feeds — können Sie auf einen modellbasierten Prior zurückgreifen: Cont, Stoikov und Talreja (2010), "A Stochastic Model for Order Book Dynamics" (Operations Research 58), modellieren jedes Preislevel als Geburts-Sterbe-Queue und berechnen via Laplace-Transformationen die Wahrscheinlichkeit, dass eine Order zum Bid ausgeführt wird, bevor sich der Mid bewegt, bedingt auf die aktuellen Queue-Größen. Es ist ein analytisches Fill-Wahrscheinlichkeits-Orakel: grob im Vergleich zum Tape-Replay, weit besser als Touch-Fill, und günstig genug, um innerhalb einer heißen Backtest-Schleife ausgewertet zu werden.

Die Bracket-Disziplin

Die drei Regeln sind keine Konkurrenten — sie sind eine Ordnung:

fillstrade-through    fillsqueue    fillstouch\text{fills}_{\text{trade-through}} \;\subseteq\; \text{fills}_{\text{queue}} \;\subseteq\; \text{fills}_{\text{touch}}

was ein PnL-Bracket induziert. Führen Sie jeden Maker-Backtest dreimal aus und berichten Sie das Intervall:

PnLtrade-through    PnLlivePnLtouch\text{PnL}_{\text{trade-through}} \;\le\; \text{PnL}_{\text{live}} \lesssim \text{PnL}_{\text{touch}}

(die obere Ungleichung ist approximativ — Touch-Fill kann Strategien falsch ranken, nicht nur aufblähen, weil es Ihnen kontrafaktisch gute Fills zuschreibt). Die daraus folgende Entscheidungsregel: eine Maker-Strategie ist nur dann einsatzbereit, wenn sie an der konservativen Kante des Brackets überlebt, und das Bracket eng genug ist, dass die Punktschätzung überhaupt etwas bedeutet. Eine Strategie mit +9.000 Touch/3.000Touch / -3.000 Trade-Through hat ein 12.000 breitesBracket;Siewissennichtsaußer,dassdieAnnahmeIhresSimulatorsIhrAlphadominiert.EineStrategiemit+2.100breites Bracket; Sie wissen nichts außer, dass die Annahme Ihres Simulators Ihr Alpha dominiert. Eine Strategie mit +2.100 / +400 $ sagt Ihnen etwas Reales.

Das Experiment: eine Maker-Strategie die Leiter hinunterführen

Kollabierende PnL einer Maker-Strategie Stufe für Stufe entlang der Fill-Modell-Leiter

Nehmen Sie einen bewusst schlichten Maker — symmetrische Quotes zu bestem Bid/Ask, fixe 0,05-BTC-Clips, Inventar begrenzt auf ±0,5 BTC mit Taker-Glattstellung an der Kappe. Ein Monat BTCUSDT-Perpetual-Daten: 1m-Bars für die unteren Stufen, 100ms-L2-Diffs plus Trade-Tape für die oberen Stufen; Maker-Gebühr 1,0 bps, Taker 4,0 bps. Derselbe Signal-Code auf jeder Stufe (geteilter Kern, sodass die einzige Variable das Fill-Modell ist). Die Zahlen unten sind ein repräsentativer Lauf von uns — Ihre Größenordnungen werden sich nach Börse, Monat und Größe unterscheiden; die Form nicht:

Stufe Fill-Modell Quote-Fill-Rate Fills Monats-PnL Urteil
0 Close-Fill 98 % 41.200 +14.800 $ Fantasie
1 Next-Bar-Open / Touch auf 1m-Bars 89 % 37.400 +9.600 $ Fantasie mit Latenz
2 Touch + Spread & fixer Slippage bei Taker-Glattstellungen 89 % 37.400 +7.100 $ Kosten modelliert, Fills weiterhin fiktiv
3 + L2-Depth-Walk bei Taker-Glattstellungen 89 % 37.400 +6.400 $ Exits ehrlich, Entries weiterhin fiktiv
4a Trade-Through (konservativ) 21 % 8.900 -3.900 $ Stress-Grenze
4b Queue-Depletion, ϕ\phi kalibriert 37 % 15.600 -700 $ beste Schätzung
4c Queue-Depletion, ϕ=1\phi = 1 (optimistisch) 44 % 18.700 +1.900 $ obere Kante
Live-Shadow-Run, gleicher Monat 35 % 14.100 -1.150 $ Realität

Lesen Sie die Tabelle von oben nach unten und beobachten Sie, wo die Strategie stirbt. Es ist nicht Stufe 2 — Gebühren und Slippage rasieren 26 % ab, und die Strategie sieht weiterhin robust profitabel aus. Sie stirbt zwischen Stufe 3 und Stufe 4, und sie stirbt aus einem Grund, den kein Kostenmodell erfassen kann: Fill-Selektion. Touch-Fill schrieb 37.400 Fills zu, von denen die Mehrheit Touch-and-Bounce war — reine Spread-Vereinnahmung. Das queue-bewusste Modell löschte 58 % dieser Fills, und die gelöschten Fills waren überproportional die profitablen: Wenn das Level nur leicht angetastet wird, absorbiert die vor einem neu quotierenden, Retail-Latenz-Maker liegende Queue alles. Die Fills, die bis Stufe 4 überleben, sind zu levelräumenden Sweeps hin verzerrt — den Fills, bei denen der Preis bereits durch Sie hindurch läuft. Die Fill-Rate fiel um das 2,4-Fache; die PnL kehrte das Vorzeichen um. Diese Asymmetrie — die guten Fills verlieren, die schlechten behalten — ist Adverse Selection, mechanisch explizit gemacht, und sie ist auf jeder Stufe unter 4 unsichtbar.

Beachten Sie auch, was das Bracket getan hat: [-3.900 ,+1.900, +1.900 ] spannt sich über null, mit der kalibrierten Schätzung bei -700 undLivebei1.150und Live bei -1.150. Der Simulator hat die Live-PnL nicht auf den Dollar genau getroffen — er hat das Vorzeichen, die Größenordnung und die Fill-Rate auf 2 Punkte genau getroffen. Genau dafür ist ein Fill-Modell da. Der Stufe-1-Backtest verfehlte die Live-PnL um 10.750 $ bei einer Strategie, deren gesamter monatlicher Brutto-Edge nur ein paar tausend Dollar betrug: Der Fill-Modell-Fehler war etwa dreimal so groß wie das Alpha. Daher die These: Ihr Fill-Modell ist eine größere Annahme als Ihr Alpha.

Ein Vorbehalt für die unteren Stufen: Wenn Sie zwingend auf Bar-Daten angewiesen sind (Stufen 0-2), lösen Sie zumindest die Intrabar-Mehrdeutigkeit mit adaptivem Drill-Down auf — Drill-Down von 1m auf 1s/100ms/Trades dort, wo SL, TP oder Quote-Level in den Bar-Bereich fallen. Drill-Down behebt Sequenzierungs-Fehler (welches Level zuerst getroffen wurde), kann aber Queue-Fehler nicht beheben; er ist der Datenauflösungs-Begleiter zu diesem Artikel, kein Ersatz für Stufe 4.

Die Kalibrierungsschleife: Abgleich gegen Live-Fills

Ein Stufe-4-Simulator hat freie Parameter — ϕ\phi, Latenz Δt\Delta t, Cancel-Latenz, die Level-Refill-Annahme. Unkalibriert ist er nur eine anders geformte Vermutung. Die Schleife, die ihn in ein Instrument verwandelt:

1. Alles live loggen. Jedes Order-Ereignis mit Exchange-Zeitstempeln: Submit, Ack, jeder Partial Fill, Amend-Acks, Cancel-Acks. Plus den L2-Zustand zum Submit-Zeitpunkt. Dies ist dieselbe Logging-Disziplin, die Backtest-Live-Parität für seinen DivergenceMonitor fordert — die Fill-Modell-Kalibrierung ist die tiefste Schicht dieses Monitors.

2. Dieselben Orders durch den Simulator replayen. Speisen Sie die aufgezeichneten Marktdaten und die aufgezeichneten Order-Anweisungen (nicht die Fills) in den Simulator ein. Jetzt haben Sie gepaarte Ergebnisse: für jede Live-Order ein simuliertes Schicksal.

3. Verteilungen vergleichen, nicht Durchschnitte, gebucketet. Ein einzelner globaler Fill-Rate-Abgleich kann kompensierende Fehler verbergen (zu optimistisch in ruhigen Regimen, zu pessimistisch in Bursts — die sich zu "kalibriert" verrechnen). Bucketen Sie nach den Treibern:

import numpy as np
from scipy.stats import ks_2samp

def calibration_report(pairs, bucket_key):
    """pairs: [{'bucket':…, 'live_filled':bool, 'sim_filled':bool,
                'live_ttf':float|None, 'sim_ttf':float|None}, …]"""
    out = {}
    for b in sorted({p['bucket'] for p in pairs}):
        grp = [p for p in pairs if p['bucket'] == b]
        live_fr = np.mean([p['live_filled'] for p in grp])
        sim_fr  = np.mean([p['sim_filled']  for p in grp])
        live_ttf = [p['live_ttf'] for p in grp if p['live_ttf'] is not None]
        sim_ttf  = [p['sim_ttf']  for p in grp if p['sim_ttf']  is not None]
        ks = ks_2samp(live_ttf, sim_ttf) if len(live_ttf) > 20 and len(sim_ttf) > 20 else None
        out[b] = {
            'n': len(grp),
            'fill_rate_live': live_fr,
            'fill_rate_sim':  sim_fr,
            'fill_rate_gap':  sim_fr - live_fr,      # signed: + means sim optimistic
            'ttf_ks_pvalue':  ks.pvalue if ks else None,
        }
    return out

Zwei Statistiken pro Bucket: die vorzeichenbehaftete Fill-Rate-Lücke (Simulator minus Live) und ein KS-Test auf Time-to-Fill-Verteilungen unter gefüllten Orders. Der Time-to-Fill-Vergleich ist der scharfe: Ein Simulator kann Fill-Raten treffen und dabei zu systematisch falschen Zeiten füllen, was jede nachgelagerte Inventar- und Adverse-Selection-Statistik verfälscht. Dies ist genau die Lehre aus Lo-MacKinlay-Zhangs Survival-Analyse-Framing: Execution ist ein Time-to-Event-Problem, also validieren Sie es auch als solches.

4. Die Regler fitten, in Reihenfolge der Identifizierbarkeit. Zuerst Latenz (direkt aus Ack-Zeitstempeln gemessen — nicht gefittet). Dann ϕ\phi durch Minimierung der Fill-Rate-Lücke über Queue-Tiefe-Buckets hinweg. Dann die Vol-Regime-Buckets prüfen: Eine anhaltende optimistische Lücke, konzentriert in Burst-Buckets, bedeutet meist, dass Ihr Simulator toxische Fills durch Cancel-Latenz oder Level-Refill untermodelliert, nicht ϕ\phi.

5. Das Bracket erneut ausführen. Nach der Kalibrierung sollte die Queue-Depletion-Schätzung innerhalb des Brackets nahe an Live liegen, und — der eigentliche Abnahmetest — das Ranking der Strategievarianten unter dem Simulator sollte mit ihrem Ranking im Shadow-Modus übereinstimmen. Dann die Parameter einfrieren und nach Plan rekalibrieren; die Fill-Dynamik driftet mit den Gebührenstufen der Börse, Tick-Size-Änderungen und der HFT-Population, und ein im März kalibriertes ϕ\phi ist im Juli eine Hypothese.

Konvergenzerwartungen aus unseren Läufen: Ein unkalibrierter Stufe-4-Simulator liegt typischerweise innerhalb von ±10-15 Punkten der Live-Fill-Rate; nach einem Kalibrierungsdurchlauf ±3-5 Punkte, wobei die Time-to-Fill-KS-p-Werte nicht mehr durchgängig verworfen werden. Besser werden Sie mit historischem Replay nicht — der Rest ist die Reaktion des Marktes auf Sie, das Problem von Stufe 5.

Was Sie mitnehmen sollten

  1. Benennen Sie Ihre Stufe. Jeder Backtest sitzt auf dieser Leiter, egal ob Sie die Stufe gewählt haben oder Ihr Framework sie für Sie gewählt hat. Wenn Sie die Stufe Ihres Fill-Modells und dessen bekannte Biases nicht benennen können, hat Ihre PnL-Zahl Fehlerbalken, die Sie noch nicht gesehen haben.
  2. Taker können bei Stufe 3 aufhören. Depth-Walk plus gemessene Latenz bepreist aggressive Execution bei Retail-Größe ehrlich. Investieren Sie den ersparten Aufwand in Datenqualität.
  3. Maker starten bei Stufe 4. Darunter ist Limit-Order-Fill-Logik nicht approximativ — sie selektiert eine kontrafaktische Menge von Fills mit entgegengesetztem Adverse-Selection-Skew. Touch-Fill-Backtests von Maker-Strategien sind der zuverlässigste Generator von Strategien, die beim Kontakt mit der Produktion sterben.
  4. Modellieren Sie den Lebenszyklus, nicht den Fill. Partial Fills, Amend-setzt-Queue-zurück, Cancel-Latenz — der Zustandsautomat ist dort, wo die toxischen Fills leben, und toxische Fills sind dort, wo die Maker-PnL stirbt.
  5. Berichten Sie das Bracket. Konservative und optimistische Grenzen kosten zwei zusätzliche Backtest-Läufe und verwandeln "mein Backtest sagt +7.000 "in"dieRealita¨tliegtirgendwoin[3.900" in "die Realität liegt irgendwo in [-3.900 , +1.900 $]" — das ist eine andere, und bessere, Entscheidungsgrundlage.
  6. Kalibrieren Sie gegen Live-Fills in Buckets. Ein Fill-Modell, das nur aggregiert validiert wurde, ist ein Fill-Modell mit versteckten kompensierenden Fehlern. Gebucketete Fill-Rate-Lücken plus Time-to-Fill-KS-Tests, vierteljährlich rekalibriert.

Die Stufen der Leiter sind keine akademischen Abstufungen — jede einzelne ist eine spezifische Lüge, die Ihr Backtest Ihnen nicht mehr erzählt. Steigen Sie, bis die Lügen kleiner sind als Ihr Edge.

Nützliche Links

  1. Moallemi, C., Yuan, K. — A Model for Queue Position Valuation in a Limit Order Book (2016)
  2. Cont, R., Stoikov, S., Talreja, R. — A Stochastic Model for Order Book Dynamics, Operations Research 58(3), 549-563 (2010)
  3. Lo, A., MacKinlay, C., Zhang, J. — Econometric Models of Limit-Order Executions, Journal of Financial Economics 65(1), 31-71 (2002)
  4. Huang, W., Lehalle, C.-A., Rosenbaum, M. — Simulating and Analyzing Order Book Data: The Queue-Reactive Model, JASA 110(509), 107-122 (2015)
  5. Almgren, R., Chriss, N. — Optimal Execution of Portfolio Transactions (2001)
  6. Byrd, D., Hybinette, M., Balch, T. — ABIDES: Towards High-Fidelity Multi-Agent Market Simulation (2020)
  7. Binance API — Cancel-Replace order semantics
  8. CME Globex — Order modification and time priority rules

Zitation

@article{soloviov2026fillsimulation,
  author = {Soloviov, Eugen},
  title = {Fill simulation: the ladder from close-price fantasy to queue-aware reality},
  year = {2026},
  url = {https://marketmaker.cc/blog/fill-simulation-partial-fills-backtest},
  description = {Five rungs of fill simulation fidelity — from close-price fills to probabilistic queue-position models. Partial fills as a state machine, limit-fill probability bounds as a PnL bracket, and a calibration loop against live fills.}
}
blog.disclaimer

Authors

Eugen Soloviov
Eugen Soloviov

Trading-systems engineer

Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.

Newsletter

Dem Markt einen Schritt voraus

Abonniere unseren Newsletter für exklusive KI-Trading-Einblicke, Marktanalysen und Plattform-Updates.

Wir respektieren deine Privatsphäre. Jederzeit abbestellbar.