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 Risikokontrollen 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, Nachrichtenformate 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 Strategielogik 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.