Einen Echtzeit-Trading-Bot in Delphi mit sgcWebSockets + Binance bauen

· Features

Was wir bauen

Am Ende dieses Tutorials hast du eine funktionierende Delphi-VCL-Anwendung, die Live-Trade- und Order-Book-Daten von Binance streamt, eine einfache Breakout-Strategie fährt, echte Orders über die REST-API platziert und Risiko­kontrollen erzwingt (maximale Positionsgröße, Tagesverlustlimit, Kill-Switch). Dieselbe Plumbing-Schicht funktioniert für jede von sgcWebSockets unterstützte Börse — Coinbase, Kraken, OKX, Bybit, Bitfinex — es ändern sich nur Credentials und Symbol-Mapping.

Das ist die gleiche Architektur wie in unserem Referenz-Sample sgcTrader. Wenn du einen größeren Startpunkt mit voller UI, Charts und Multi-Börsen-Routing willst, hol dir das. Der folgende Walkthrough zeigt, was unter der Haube in rund 300 Zeilen Code passiert.

Zwei Voraussetzungen vorab. Erstens: Hol dir einen Binance-API-Key (Account > API Management). Für die Entwicklung erzeuge einen Key mit nur „Enable Reading“ und „Enable Spot Trading“ und whiteliste deine IP. Pack niemals einen Key mit Withdrawal-Recht in den Code. Zweitens: Mach zuerst alles auf dem Binance-Testnet (testnet.binance.vision). Endpunkte, Nachrichten­formate und Signaturalgorithmus sind identisch mit der Produktion, das Geld ist aber Fake. Wir haben „Meine Strategie ist sicher korrekt“ genau so oft mit echtem Geld verloren, wie wir nicht zuerst auf dem Testnet getestet haben.

Architektur in einem Diagramm

Drei Threads, zwei Komponenten, ein Risiko-Gate:

Komponente Rolle Thread
TsgcWSAPI_Binance WebSocket-Stream: Trades, Depth, Klines, User Data I/O-Worker
TsgcHTTP_API_Binance REST: Order-Platzierung, Cancel, Account-Snapshot Trader-Worker
Strategie-Queue Entkopplung: Marktereignisse → Entscheidungen → Orders Strategie-Worker
Risiko-Gate Order blockieren / verkleinern / erlauben Inline im Trader

Schritt 1: Marktdaten streamen

Zieh ein TsgcWSAPI_Binance auf das Formular. Es spricht bereits das Binance-Combined-Stream-Protokoll — du abonnierst nur die gewünschten Kanäle.

uses
  sgcWSAPI_Binance;

procedure TForm1.FormCreate(Sender: TObject);
begin
  oBinance := TsgcWSAPI_Binance.Create(Self);
  oBinance.WatchDog.Enabled  := True;
  oBinance.WatchDog.Interval := 5;
  oBinance.HeartBeat.Enabled := True;

  oBinance.OnBinanceMessage := DoStream;
  oBinance.OnDisconnect     := DoDisconnect;

  // Subscribe to 1-minute klines and aggregated trades for BTCUSDT
  oBinance.Streams.Add('btcusdt@kline_1m');
  oBinance.Streams.Add('btcusdt@aggTrade');

  oBinance.Active := True;
end;

procedure TForm1.DoStream(Sender: TObject; const aStream, aData: string);
begin
  // Fire-and-forget: push to the strategy queue
  oStrategyQueue.Push(TMarketEvent.Create(aStream, aData));
end;

Das ist die komplette Marktdaten-Ingestion-Schicht. Reconnect bei Abbrüchen, Heartbeat zur Erkennung toter Links und ein nichtblockierender Push in die Strategie-Queue.

Eine Sache, die die Komponente erledigt, die du sonst selbst schreiben müsstest: Die URL des Binance-Combined-Streams lautet /stream?streams=name1/name2/name3, und wenn du Streams hinzufügen oder entfernen willst, ohne die Verbindung zu trennen, musst du eine JSON-RPC-Subscribe/Unsubscribe-Nachricht über denselben Socket schicken. TsgcWSAPI_Binance bietet die Methoden SubscribeStream und UnsubscribeStream, die den JSON-RPC-Handshake für dich erledigen. Praktisch, wenn der Nutzer in der UI einen neuen Ticker auswählt — kein Reconnect, keine verlorenen Nachrichten.

Außerdem: Binance erzwingt Limits pro IP und pro Stream. Für volle Depth auf jedem USDT-Paar erreichst du das Message-Rate-Limit schnell. Abonniere nur, was du wirklich brauchst, und bevorzuge aggregierte Streams (!miniTicker@arr) gegenüber Streams pro Symbol, wenn du eine breite Marktsicht benötigst.

Schritt 2: Eine minimale Strategie

Die Strategie läuft in ihrem eigenen Thread. Sie hält ein rollendes Fenster der letzten N Closes des 1-Minuten-Kline-Streams und geht long, wenn der Kurs über das Hoch der letzten 20 Perioden ausbricht. Reine Illustration — bitte setze das nicht vor echtes Geld.

procedure TStrategyThread.Execute;
var
  oEvent : TMarketEvent;
  oJSON  : TsgcJSONObject;
  vClose : Double;
  vHigh  : Double;
begin
  while not Terminated do
  begin
    if not oStrategyQueue.Pop(oEvent, 100) then Continue;
    try
      oJSON := TsgcJSONObject.Parse(oEvent.Data);
      try
        if oEvent.Stream.EndsWith('@kline_1m') then
        begin
          vClose := oJSON.O['k'].F['c'];
          FCloses.Append(vClose);
          if FCloses.Count >= 21 then
          begin
            vHigh := FCloses.Max(20);                  // prior 20-bar high
            if (FPosition = 0) and (vClose > vHigh) then
              oOrderQueue.Push(TIntent.New(siBuy, 'BTCUSDT', 0.001))
            else if (FPosition > 0) and (vClose < FCloses.MA(20)) then
              oOrderQueue.Push(TIntent.New(siSell, 'BTCUSDT', FPosition));
          end;
        end;
      finally
        oJSON.Free;
      end;
    finally
      oEvent.Free;
    end;
  end;
end;

Schritt 3: Risiko-Gate

Lass eine Strategie nie direkt mit der Börse reden. Schleus jede Intention durch ein Gate, das deine Limits kennt.

function TRiskGate.Validate(const aIntent: TIntent;
  out aReason: string): Boolean;
begin
  Result := False;

  if FKillSwitch then
    Exit(False);

  if Abs(FDailyPnL) > FConfig.MaxDailyLoss then
  begin
    aReason := 'Daily loss limit hit';
    Exit;
  end;

  if (aIntent.Side = siBuy)
     and (FPositionUSD + aIntent.Qty * FLastPrice > FConfig.MaxPositionUSD) then
  begin
    aReason := 'Max position size';
    Exit;
  end;

  Result := True;
end;

Schritt 4: Order per REST platzieren

Der Order-Worker zieht validierte Intentionen, signiert den Request und postet ihn an Binance.

procedure TTraderThread.Execute;
var
  oIntent  : TIntent;
  vReason  : string;
  oResponse: TsgcBinanceClass_Response_NewOrder;
begin
  while not Terminated do
  begin
    if not oOrderQueue.Pop(oIntent, 100) then Continue;
    try
      if not FRisk.Validate(oIntent, vReason) then
      begin
        Log(Format('REJECT %s %s qty=%.6f reason=%s',
          [SideName(oIntent.Side), oIntent.Symbol, oIntent.Qty, vReason]));
        Continue;
      end;

      oResponse := oHttp.NewOrder(
        oIntent.Symbol,
        IfThen(oIntent.Side = siBuy, 'BUY', 'SELL'),
        'MARKET',
        oIntent.Qty,
        0  // price ignored for MARKET
      );
      try
        Log(Format('FILL  %s qty=%.6f price=%.2f id=%d',
          [oIntent.Symbol, oResponse.ExecutedQty,
           oResponse.AvgPrice, oResponse.OrderId]));
        FRisk.OnFill(oIntent.Side, oResponse.ExecutedQty, oResponse.AvgPrice);
      finally
        oResponse.Free;
      end;
    finally
      oIntent.Free;
    end;
  end;
end;

Schritt 4b: REST-Authentifizierung und Signierung

Die Komponente TsgcHTTP_API_Binance signiert Requests mit dem API-Secret für dich. Sie baut hinter den Kulissen den kanonischen Query-String, berechnet ein HMAC-SHA256 mit deinem Secret und hängt es als signature-Parameter an. Du gibst Key und Secret einmal beim Start an.

oHttp := TsgcHTTP_API_Binance.Create(Self);
oHttp.BinanceOptions.ApiKey    := FConfig.ApiKey;
oHttp.BinanceOptions.ApiSecret := FConfig.ApiSecret;
oHttp.BinanceOptions.RecvWindow := 5000;   // ms tolerance for signed requests
// Test connectivity and confirm your IP whitelist
ShowMessage('Server time: ' + IntToStr(oHttp.GetServerTime));

Wenn du gegen das Testnet fahren willst, setze BinanceOptions.Testnet := True — die Komponente schaltet automatisch sowohl REST-Basis-URL als auch WebSocket-Host um. Auf dem Testnet bauen und testen, einen Schalter umlegen, in die Produktion deployen. Die Binance-API-Doku ist zwischen beiden Umgebungen sonst identisch.

Schritt 5: User-Data-Stream

Dieselbe WebSocket-Komponente abonniert auch deinen privaten User-Data-Stream — Account-Updates, Order-Events, Positionsänderungen. So gleichst du Fills ab, die außerhalb deines Bots passiert sind (manueller Cancel über die Web-UI, Liquidation usw.).

oBinance.AuthOptions.ApiKey    := FConfig.ApiKey;
oBinance.AuthOptions.ApiSecret := FConfig.ApiSecret;
oBinance.Streams.Add('!userData');

procedure TForm1.DoStream(Sender: TObject; const aStream, aData: string);
var
  oJSON: TsgcJSONObject;
begin
  if aStream = '!userData' then
  begin
    oJSON := TsgcJSONObject.Parse(aData);
    try
      if oJSON.S['e'] = 'executionReport' then
        FRisk.ReconcileExternalFill(oJSON);
    finally
      oJSON.Free;
    end;
  end;
end;

Eine Bemerkung zum Backtesting

Nichts von dem oben beantwortet „Ist diese Strategie überhaupt profitabel?“ Genau dafür ist Backtesting da — dieselbe Strategie gegen historische Daten ablaufen lassen, um die zukünftige Performance einzuschätzen. Die obige Architektur macht das fast kostenlos: Der Strategie-Thread interessiert es nicht, ob Marktereignisse aus einem Live-WebSocket oder einem CSV-Reader kommen. Bau eine synthetische Event-Quelle, die Klines von der Platte liest und in dieselbe Queue füttert — dein Strategie-Code läuft unverändert gegen Jahre historischer Daten.

Zwei Fallstricke. Look-Ahead-Bias: Lass die Strategie keinen Datenpunkt sehen, der zum verarbeiteten Zeitstempel noch nicht verfügbar gewesen wäre. Und Survivorship-Bias: Trainiere und teste auf der Symbol-Universe, die damals existierte, nicht auf der kuratierten Liste „erfolgreicher“ Symbole, die bis heute überlebt haben. Beide haben in der Produktion mehr Strategien getötet als alle Coding-Bugs zusammen.

Operative Checkliste

Anliegen Wo damit umgehen
Reconnect bei WLAN-Aussetzer WatchDog.Enabled := True
Dead-Link-Erkennung HeartBeat.Enabled := True
Zeitsync (Binance lehnt schiefe Signaturen ab) NTP im OS, plus täglicher Aufruf des Server-Time-Endpunkts
Order-Idempotenz Bei jeder Order eine newClientOrderId verwenden
Rate-Limits Header tracken; ab 90 % des Limits zurückrudern
Kill-Switch Ein Boolean, aus UI oder Watchdog-Prozess gekippt
Audit-Log Jede Intention, jeder Fill, jeder Reject — append-only

Über Binance hinaus

Tausche TsgcWSAPI_Binance gegen TsgcWSAPI_Coinbase, TsgcWSAPI_Kraken oder eine der über 20 weiteren Börsen-Komponenten. Strategie, Risiko-Gate und Order-Worker ändern sich nicht — nur Credentials und Symbol-Benennung. Für einen produktionsreifen Multi-Börsen-Trader mit Charts, Positions-Management und Order-Routing-UI out of the box, schau dir das sgcTrader-Sample an.

Echte Multi-Börsen-Systeme fügen noch eine Schicht über dem hinzu, was du oben siehst: einen Symbol-Normalisierer. Binance nennt es BTCUSDT, Coinbase BTC-USD, Kraken XBT/USD. Bau ein internes Symbol-Modell mit kanonischem Namen und Aliassen pro Börse und übersetze an der API-Grenze. Fünf Minuten Vorarbeit, unendlich viele gesparte Bugs später.

Was Multi-Börsen-Setups zusätzlich brauchen: einen Clock-Skew-Check beim Start. Binance, Coinbase und Co. lehnen signierte Requests mit Zeitstempel über 1000 ms Abweichung zu ihrem Server ab. NTP hält dich meist deutlich darunter, ein fehlkonfiguriertes VPS kann pro Stunde aber Sekunden driften. Frag beim Start den Server-Time-Endpunkt ab, logge den Offset, verweigere den Handel, wenn er >500 ms beträgt.

Warum Delphi dafür?

„Warum nicht in Python?“ ist die naheliegende Frage. Drei Antworten aus der Produktion. Erstens: JIT-Warm-up und GIL machen CPython zu einer schlechten Wahl für latenzarme Event-Loops — dieselbe Strategie, die in Delphi 0,8 ms Median-Latenz erreicht, braucht in CPython ohne ernsthaften Aufwand 6 ms. Zweitens: Das Deployment ist einfacher — eine signierte EXE gegen ein virtualenv mit hundert Wheels, von denen die Hälfte beim Install einen C-Compiler verlangt. Drittens: Das bestehende Backoffice ist in Delphi. Diese Klassen — Account-Ledger, P&L-Rechner, Journal, Audit-Log — im neuen Bot wiederzuverwenden statt sie in einer anderen Sprache nachzubauen, eliminiert eine ganze Kategorie Abgleichs-Bugs.

Für reine Forschung und Notebook-artige Backtests gewinnt Python klar — das Ökosystem aus pandas, statsmodels, vectorbt und Co. ist unschlagbar. Die Aufteilung, die in den meisten Häusern funktioniert: Forschung in Python, Produktion in Delphi. Exportiere die Strategie­logik als kleine State-Machine, portiere sie einmal, lass sie auf einer kampferprobten Delphi-Runtime laufen. Die beiden Hälften müssen keine Sprache teilen, um Ergebnisse zu teilen.

Was als Nächstes lesen

Wenn du das 24/7 auf einem VPS fahren willst, lies als Nächstes Performance-Tuning. Um die häufigsten Fallstricke zu vermeiden, siehe 10 typische Fehler. Und falls du sgcWebSockets noch nicht installiert hast, der Getting-Started-Hub bringt dich in fünf Minuten live.

Haftungsausschluss: Die Strategie in diesem Beitrag dient Bildungszwecken. Krypto-Handel birgt erhebliches Risiko. Setze ungetesteten Code nicht mit echtem Kapital ein.