Das Versprechen eines No-Code-Trading-Bots ist einfach: Sie beschreiben eine Strategie in einem Formular, und die Software führt sie für Sie aus. Das Schwierige ist nicht das Formular. Es ist, dass jede Vereinfachung, die Sie dem Nutzer bieten, an irgendeiner Stelle im System bezahlt werden muss, meist an einer Stelle, die Sie nicht erwartet haben.
Das sind drei Dinge, bei denen ich mich dabei geirrt habe, und was uns jedes einzelne tatsächlich gelehrt hat. Sie sind danach geordnet, wie stark ich sie falsch eingeschätzt habe.
Das Wichtigste
- Börsen-APIs versagen häufiger still als laut. Ein HTTP 200 mit weniger Daten als angefordert ist der Fehlerfall, gegen den Sie sich absichern müssen.
- Vertrauen Sie einem Backtest-Ergebnis nie, bevor Sie den Simulator selbst getestet haben. Eine sauber aussehende Kapitalkurve kann aus einem fehlerhaften Durchlauf stammen.
- Fließkomma-Arithmetik bei Ordermengen erzeugt Fehler um einen ganzen Schritt, die nur bei bestimmten Paaren auftreten.
- Die Entscheidung, niemals Nutzergelder zu verwahren, beseitigt eine ganze Klasse von Problemen und schafft ein kleineres, schärferes: Ihr System muss korrekt sein, ohne jemals etwas rückgängig machen zu können.
- Jeder Komfort, den Sie dem Nutzer bieten, wird irgendwo in der Engine zu einer Einschränkung. Dieser Tausch lohnt sich meist, aber er ist nie kostenlos.
1. Ich nahm an, eine fehlgeschlagene Anfrage würde wie ein Fehler aussehen
Dies ist die Sache, die am meisten Zeit gekostet hat, und die, vor der ich jeden Entwickler zuerst warnen würde.
Wir holen historische Kerzen von Börsen, um Backtests durchzuführen. Das naive Denkmodell ist, dass eine Anfrage entweder gelingt oder fehlschlägt: Sie bekommen Ihre Daten, oder Sie bekommen einen Nicht-200-Status und behandeln ihn. Echte Börsen-APIs verhalten sich unter Last nicht so. Sie liefern HTTP 200 mit weniger Kerzen als angefordert zurück, und keinen Hinweis darauf, dass etwas schiefgelaufen ist.
Hier ist ein realer Fall aus unseren Logs, und die Zahlen sind exakt. Ein Backtest über einen langen Zeitraum forderte rund 536.000 Kerzen an. Um 16:34 Uhr erhielt er 58.000. Status 200. Kein Fehlerfeld. Keine Warnung. Dieselbe Anfrage, unverändert, erhielt um 17:54 Uhr die vollen 535.000.
Überlegen Sie, was das bedeutet, wenn Sie nicht prüfen. Die Engine simuliert eine Strategie bereitwillig über elf Prozent der beabsichtigten Historie, erzeugt eine plausibel aussehende Kapitalkurve und meldet ein Ergebnis. Der Nutzer liest eine Zahl, die auf eine Weise falsch ist, die nichts in der Ausgabe verrät. Das ist schlimmer als ein Absturz. Ein Absturz ist ehrlich.
Die Lösung ist konzeptionell langweilig und ließ sich leicht überspringen: Prüfen Sie nach dem Abruf, ob die erhaltenen Daten den angeforderten Zeitraum tatsächlich abdecken, und behandeln Sie einen Fehlbetrag als Fehler, auch wenn der Transport meldete, alles sei in Ordnung. Abdeckung ist eine Eigenschaft, die Sie zusichern müssen, nicht eine, die Sie annehmen können.
Hinter der ersten verbarg sich eine Lektion zweiter Ordnung. Sobald wir begannen, Abschneidungen zu erkennen, lag der naheliegende Schritt darin, den Durchlauf scheitern zu lassen. Aber die obigen Zahlen zeigen, dass diese Abschneidungen vorübergehend sind: Die identische Anfrage lieferte eine Stunde später vollständige Daten. Ein sofortiges Scheitern hätte das Kontingent des Nutzers für das aufgebraucht, was letztlich nur ein zeitweiliger Aussetzer auf Börsenseite ist. Das korrekte Verhalten ist also, einmal erneut zu versuchen, bevor man scheitert, und das bedeutete, den Fehlerpfad umzubauen, damit eine wiederholbare Bedingung auf dem Weg nach draußen nicht dauerhaft als Fehler markiert wird.
Zwei Regeln gingen daraus hervor, und beide würde ich in jedes Projekt mitnehmen, das mit einer Drittanbieter-API spricht:
- Validieren Sie die Form einer erfolgreichen Antwort, nicht nur ihren Statuscode. Der Statuscode sagt Ihnen, dass die Anfrage abgeschlossen wurde. Er sagt Ihnen nicht, dass die Antwort das ist, was Sie angefordert haben.
- Unterscheiden Sie vorübergehende von dauerhaften Fehlern, bevor Sie entscheiden, was zu tun ist. Sie sehen im Moment ihres Auftretens identisch aus und verdienen entgegengesetzte Reaktionen.
2. Ich nahm an, ein Backtest-Ergebnis sei ein Ergebnis
Verwandt, aber ein eigener Fehler, und er geht tiefer als das Abschneidungsproblem.
Wenn Sie einen Simulator bauen, stecken Sie Ihren Testaufwand in Strategien. Verhält sich die DCA-Logik korrekt? Lösen die Ausstiege dort aus, wo sie sollen? Das ist die interessante Frage, also fließt die Aufmerksamkeit natürlicherweise dorthin.
Das Problem ist, dass ein fehlerhafter Simulator eine Ausgabe erzeugt, die genau wie die eines funktionierenden aussieht. Ein Durchlauf, der still nur einen Bruchteil der Daten verarbeitet hat, liefert trotzdem eine Zahl. Ein Durchlauf, dessen Ausstiegsbedingungen nie ausgewertet wurden, liefert trotzdem eine Zahl, und eine verdächtig flache. Wir haben Ergebnisse gesehen, die mit exakt +0.00% und null Trades zurückkamen, was das lauteste denkbare Signal dafür ist, dass etwas mit der Mechanik nicht stimmt, und zugleich das am leichtesten als „diese Strategie tut nichts" fehlgedeutete.
Die Wende, die das behob, bestand darin, zu ändern, was wir testen. Testen Sie den Simulator, nicht die Strategie. Konkret heißt das, Dinge zuzusichern, die die Strategie nicht verändern können sollte: dass die Anzahl der verarbeiteten Kerzen der Anzahl der abgerufenen entspricht, dass ein Durchlauf über einen bekannten Zeitraum diesen Zeitraum tatsächlich abdeckt, dass eine garantiert auslösende Konfiguration Trades erzeugt. Wenn diese Invarianten halten, ist ein seltsam aussehendes Ergebnis ein echter Befund über die Strategie. Wenn nicht, ist das Ergebnis Rauschen im feinen Zwirn.
Die allgemeine Form davon, für alles, was simuliert: Ein Simulator, der nicht laut scheitern kann, wird still scheitern, und stilles Scheitern in einem System, das Zahlen erzeugt, ist der schlimmste Fehlerfall überhaupt. Menschen handeln nach Zahlen. Sie handeln nicht nach Stack-Traces.
3. Ich unterschätzte, wie viel Präzisionsarbeit die Ordergrößenberechnung wirklich ist
Diese Sache ist intellektuell nicht schwer. Sie ist nur viel größer, als sie aussieht, und sie versteckt sich an einer Stelle, an der Fehler teuer sind.
Börsen akzeptieren keine beliebigen Mengen. Jedes Handelspaar hat eine Schrittgröße (step size) für die Menge und eine Tick-Größe (tick size) für den Preis, und Orders, die nicht dazu passen, werden abgelehnt. Also muss jede Menge, die das System berechnet, vor dem Versand auf einen gültigen Schritt abgerundet werden. Das klingt nach einer einzigen Hilfsfunktion.
Dann kommt die Fließkomma-Arithmetik ins Spiel. Nehmen Sie das Abrunden von 0.29 auf einen Schritt von 0.01. Skalieren Sie den Wert hoch, und IEEE-754 liefert Ihnen 28.999999999999996, was auf 28 abrundet, was zurück auf 0.28 skaliert. Der Wert war bereits am Schritt ausgerichtet. Die Arithmetik hat ihn um einen vollen Schritt nach unten verschoben, und zwar still, in Richtung einer kleineren Order. Die Behebung erfordert ein Epsilon während des Skalierungsschritts, klein genug, dass ein tatsächlich darunterliegender Wert wie 0.289999 weiterhin korrekt abrundet, groß genug, um das Darstellungsrauschen aufzufangen.
Das ist eine einzeilige Korrektur, deren Auffinden echtes Debugging erforderte, weil das Symptom keine Ausnahme ist. Es ist eine Order, die etwas kleiner als beabsichtigt ausfällt, bei manchen Paaren und bei anderen nicht, je nachdem, wo die Schrittgröße relativ zur Binärdarstellung liegt.
Der weiterreichende Punkt, und der Grund, warum ich das als etwas Unterschätztes aufführe: Präzision in einem Trading-System ist keine Hilfsfunktion, sondern eine ganze Kategorie von Arbeit. Schrittgrößen und Tick-Größen unterscheiden sich je Paar, je Börse und zwischen Spot und Futures. Mindestorderwerte sind nicht eine einzige Zahl, die Sie nachschlagen und fest verdrahten können. Sie kommen von der Börse je Symbol, und an manchen Handelsplätzen hängen sie vom aktuellen Preis ab. Immer wenn Sie sich dabei ertappen, eine einzelne Konstante für etwas zu schreiben, das die Börse je Paar definiert, schreiben Sie einen Bug mit Zeitzünder.
Wir zeigen dies den Nutzern, statt es zu verbergen, denn die Alternative ist eine Order, die aus Gründen abgelehnt wird, die sie nicht sehen können.
4. Die Einschränkung, die mir wider Erwarten gefiel
Der letzte Punkt ist kein Fehler. Es ist eine Entscheidung, deren Folgen ich im positiven Sinne unterschätzt habe.
Freya verwahrt niemals Handelsgelder der Nutzer. Bots handeln über Börsen-API-Schlüssel mit der Berechtigung, zu lesen und zu handeln, niemals aber abzuheben. Das Geld bleibt durchgehend auf dem Börsenkonto des Nutzers.
Ich hatte erwartet, dass dies ein Marketing-Argument sein würde. Es stellte sich als ein architektonisches heraus, und es machte das System kleiner. Es gibt kein Verwahrungs-Ledger, keinen internen Saldo, der gegen einen externen abgeglichen werden muss, keine Auszahlungswarteschlange, keine Cold-Wallet-Richtlinie für Handelskapital. Eine ganze Kategorie der schwierigsten Probleme in Finanzsoftware ist schlicht abwesend, weil wir das, was sie erzeugt, nie auf uns genommen haben.
Was an seine Stelle tritt, ist enger und schärfer: Ihr System muss in Echtzeit korrekt sein, weil es nichts rückgängig machen kann. Bei einer Verwahrung können Sie eine interne Überweisung im Prinzip zurückdrehen. Eine Order auf dem Börsenkonto eines anderen zu platzieren ist endgültig, sobald sie ausgeführt wird. Das verlagert das gesamte technische Gewicht auf die Korrektheit vor dem Versand der Anfrage statt auf den Abgleich danach, und das ist ein Tausch, den ich wieder eingehen würde.
Was ich mir zu Beginn sagen würde
- Gehen Sie davon aus, dass jede Drittanbieter-API manchmal fehlerhaft erfolgreich ist, und bauen Sie die Erkennung dafür, bevor Sie sie brauchen.
- Schreiben Sie Invarianten-Tests für Ihren Simulator, bevor Sie Zusicherungen über Strategien schreiben. Die Strategie-Tests sind wertlos, solange die Simulator-Tests nicht bestehen.
- Behandeln Sie Präzision als eigene Domäne, nicht als Helfer.
- Wählen Sie Ihre Einschränkungen früh. Die, die Sie von vornherein akzeptieren, sind viel billiger als die, die Sie später entdecken.
Keines davon waren exotische Probleme. Es waren gewöhnliche technische Probleme, die ich bei der Planung als „klein" abgelegt hatte, und genau so wurden sie teuer.
