Eine Spot-Matching-Engine ist ein kleiner Zustandsautomat, umgeben von harten Randbedingungen: Price-Time Priority, Teilausführungen, ein Latency-Tail, der sich unter Burst-Last nicht bewegt. So werden Order Book, Matching-Loop, Journal und Replay-Harness gebaut - und so weit hilft die Sprache.
Eine Matching Engine an einer Spot-Börse hat eine eng umrissene Aufgabe. Sie nimmt einen geordneten Strom von Commands - neue Order, Cancel, Replace -, wendet sie unter Price-Time Priority auf ein Order Book an und gibt einen geordneten Strom von Events aus: Trades, Buch-Updates, Rejects, Bestätigungen.
Alles Schwierige daran kommt aus drei Randbedingungen, die auf dieser Aufgabe liegen: Das Ergebnis muss bei jedem Replay derselben Eingabe identisch sein, der Rest einer teilweise ausgeführten Order muss seinen Platz in der Queue behalten, und der Latency-Tail darf sich bei einem Burst nicht bewegen.
Das Buch besteht aus zwei Seiten, jede eine preisgeordnete Sammlung von Leveln. Ein Level ist keine Zahl - es ist eine Queue ruhender Orders zu diesem Preis, in Ankunftsreihenfolge. Matching berührt das beste Level ständig und die tiefen Level selten, deshalb wird die Struktur nach diesem Zugriffsmuster gewählt und nicht nach Eleganz.
Die Folge intrusiver Queues und Index-Handles ist, dass eine ruhende Order sich zu Lebzeiten nie im Speicher bewegt. Ihre Queue-Position ist eine Eigenschaft ihrer Verweise und nicht davon, wo sie zufällig liegt - genau das macht Teilausführungen später billig.
Eine eingehende aggressive Order läuft die Gegenseite vom besten Preis nach innen ab. Auf jedem Level durchläuft sie die FIFO-Queue von vorn. Sie stoppt, wenn der Levelpreis für die eingehende Order nicht mehr akzeptabel ist oder wenn die eingehende Menge null erreicht.
Eine teilweise ausgeführte ruhende Order behält ihren Platz. Ihr Rest bleibt mit seiner ursprünglichen Ankunftssequenz vorn in seiner Queue, denn eine Ausführung ändert eine Menge und sonst nichts. Eine teilweise ausgeführte aggressive Order, die ein einfaches Limit ist, wird zu einer ruhenden Order am Ende ihres eigenen Preislevels, mit neuer Ankunftssequenz - sie kam jetzt an, nicht früher.
Die Semantik der Ordertypen wird an der Grenze dieser Schleife entschieden, nicht in ihr. Immediate-or-Cancel verwirft den Rest, statt ihn ruhen zu lassen. Fill-or-Kill macht zuerst einen Trockendurchlauf und führt entweder ganz aus oder lehnt ab. Post-Only lehnt ab, wenn die Order bei Ankunft kreuzen würde. Diese außerhalb der Schleife zu halten heißt, dass die Schleife der einzige Ort bleibt, an dem sich der Buchzustand ändert.
Self-Trade Prevention, Mindestmengen sowie Tick- und Lot-Validierung gehören ebenfalls vor die Schleife. Eine Order, die das Matching erreicht, ist bereits als wohlgeformt erwiesen, sodass die Schleife keine Fehlerzweige hat, die sie bremsen oder uneins machen könnten.
Die durchschnittliche Latenz ist selten das Problem. Das Problem ist die schlechteste Messung während eines Bursts - also dann, wenn die Engine am meisten zählt und eine Stop-the-World-Pause am wahrscheinlichsten hineinfällt. Unter einer Managed Runtime mit Garbage Collector wird diese Pause vom Collector geplant und nicht von Ihnen, und sie landet mitten im Burst, der den Müll erzeugt hat.
Manuelle Allokation ist eine kleinere Ausgabe desselben Problems. Ein Allzweck-Allokator kann Free Lists durchlaufen, ein Lock nehmen oder den Kernel um mehr Speicher bitten, und genau dieser Aufruf taucht im Tail auf. Die Lösung ist in beiden Fällen dieselbe: auf dem Hot Path überhaupt nicht allokieren.
Eine Engine ist deterministisch, wenn dieselbe Eingabefolge dieselbe Ausgabefolge erzeugt, Byte für Byte, auf einer anderen Maschine und ein Jahr später. Alles, was im Matching-Pfad die Wall-Clock-Zeit, das Thread-Scheduling oder die Iterationsreihenfolge von Hashes liest, zerstört diese Eigenschaft.
Zeitstempel sind daher eine Eingabe und nichts, was die Engine selbst liest. Der Sequencer stempelt ein Command, wenn er es annimmt, und die Matching-Schleife behandelt den Stempel als Daten. Zufall, falls nötig, kommt aus einem Generator, dessen Seed Teil des Journals ist.
Wiederherstellung ist kein Feature, das nach dem Matching angeschraubt wird. Die Engine schreibt ein Append-only-Journal der angenommenen Commands in Sequenzreihenfolge, und das Buch im Speicher ist nichts anderes als das Ergebnis des Faltens dieses Journals. Der Wiederaufbau nach einem Absturz heißt, es wieder einzuspielen.
Die Engine schreibt das Journal, aber Dauerhaftigkeit ist eine Eigenschaft des Speicherpfads und der Frage, wie viele Maschinen den Eintrag haben, bevor die Bestätigung hinausgeht. Das ist eine Replikations- und Hardwareentscheidung, und dort wird die Wiederherstellungszeit tatsächlich gewonnen oder verloren.
Determinismus macht die Engine testbar. Weil dieselbe Eingabe dieselbe Ausgabe liefert, ist eine aufgezeichnete Session ein Regressionstest, und ein einmal gefundener Fehler lässt sich exakt reproduzieren, statt ihm nachzujagen.
Ein Referenzmodell ist mehr wert, als es aussieht. Zwei Implementierungen nach derselben Spezifikation weichen genau dort voneinander ab, wo die Spezifikation mehrdeutig war, und Matching-Regeln stecken an den Rändern voller Mehrdeutigkeit - gekreuzte Limits, Nullreste, Cancels im Rennen mit Fills.
Rust nimmt eine Klasse von Problemen weg, statt die Schleife selbst schneller zu machen. Es gibt keinen Garbage Collector, also wird hinter Ihrem Rücken keine Pause eingeplant. Ownership macht die Single-Writer-Disziplin zu etwas, das der Compiler durchsetzt, statt zu etwas, das ein Code-Review bemerken muss. Slab-Handles und intrusive Verweise, die in einer Sprache ohne Lifetimes fehleranfällig sind, sind hier prüfbar. Panics bei Integer-Überlauf in Debug-Builds fangen eine Fehlerklasse ab, die ein Buch still beschädigt.
Die ehrliche Grenze ist: Das meiste, was die Tail-Latenz bestimmt, ist nicht die Sprache:
Rust kostet auch etwas. Der Borrow Checker bremst die ersten Wochen eines Designs, das noch in Bewegung ist, das Ökosystem für börsenspezifische Protokolle ist dünner als in älteren Sprachen, und unsafe-Blöcke um lock-freie Strukturen brauchen dieselbe Review-Disziplin wie vergleichbarer Code anderswo. Die Wahl ist eine Entscheidung über den Latency-Tail und über Speichersicherheit in einem Single-Writer-Kern, keine Entscheidung über Entwicklerkomfort.
amBrain baut seit 2019 in Jerewan, Armenien, Trading-Infrastruktur mit Hot Paths in Rust - Marktdaten in unter 5 ms ausgeliefert, Pre-Trade-Risk-Checks in unter 1 ms. Wenn Sie eine Matching Engine entwerfen und Buchstruktur, Journalformat oder Replay-Harness durchgehen möchten, lohnt sich dieses Gespräch, bevor die erste Zeile des Hot Path geschrieben ist.
Unser Engineering-Team ist auf FinTech-Lösungen spezialisiert. Lassen Sie uns besprechen, wie wir Ihr Projekt umsetzen.