Zbuduj bota tradingowego w czasie rzeczywistym w Delphi z sgcWebSockets + Binance

· Funkcje

Co budujemy

Pod koniec tego samouczka będziesz miał działającą aplikację Delphi VCL, która strumieniuje na żywo dane o transakcjach i książce zleceń z Binance, uruchamia prostą strategię breakout, składa prawdziwe zlecenia przez REST API i wymusza kontrolę ryzyka (maks. rozmiar pozycji, dzienny limit straty, kill switch). Ta sama hydraulika działa dla każdej giełdy obsługiwanej przez sgcWebSockets — Coinbase, Kraken, OKX, Bybit, Bitfinex — ze zmianą tylko mapowania poświadczeń i symboli.

To ta sama architektura używana przez nasz referencyjny przykład sgcTrader. Jeśli chcesz większego punktu startu z pełnym UI, wykresami i routingiem na wiele giełd, weź to. Przewodnik poniżej pokazuje, co dzieje się pod maską w około 300 liniach kodu.

Dwa warunki wstępne, zanim zaczniemy. Po pierwsze, zdobądź klucz API Binance (Account > API Management). Dla deweloperki wygeneruj klucz, który ma tylko „Enable Reading” i „Enable Spot Trading” i białą listę swojego IP. Nigdy nie umieszczaj w kodzie klucza umożliwiającego wypłatę. Po drugie, rób wszystko najpierw na testnecie Binance (testnet.binance.vision). Endpointy, formaty wiadomości i algorytm podpisu są identyczne jak w produkcji, ale fundusze są fałszywe. Straciliśmy prawdziwe pieniądze na „jestem pewien, że moja strategia jest poprawna” dokładnie tyle razy, ile razy najpierw nie testowaliśmy na testnecie.

Architektura na jednym diagramie

Trzy wątki, dwa komponenty, jeden strażnik ryzyka:

Komponent Rola Wątek
TsgcWSAPI_Binance Strumień WebSocket: transakcje, głębia, klines, dane użytkownika Pracownik I/O
TsgcHTTP_API_Binance REST: składanie zleceń, anulowanie, snapshot konta Pracownik trader
Kolejka strategii Oddzielenie: zdarzenia rynkowe → decyzje → zlecenia Pracownik strategii
Bramka ryzyka Blokuj / zmniejsz / zezwól na każde zlecenie Inline w traderze

Krok 1: Strumieniuj dane rynkowe

Upuść TsgcWSAPI_Binance na formularz. Już mówi protokołem combined stream Binance — po prostu subskrybujesz kanały, których chcesz.

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;

To cała warstwa pozyskiwania danych rynkowych. Ponowne łączenie przy zerwaniach, heartbeat do wykrywania martwych łączy i nieblokujący push do kolejki strategii.

Jedna rzecz, którą komponent robi, a którą w przeciwnym razie musiałbyś napisać sam: URL combined stream Binance to /stream?streams=name1/name2/name3, a jeśli chcesz dodać lub usunąć strumienie bez rozłączania połączenia, musisz wysłać wiadomość subscribe/unsubscribe JSON-RPC nad tym samym gniazdem. TsgcWSAPI_Binance udostępnia metody SubscribeStream i UnsubscribeStream, które obsługują handshake JSON-RPC za Ciebie. Przydatne, gdy użytkownik wybiera nowy ticker w UI — bez ponownego łączenia, bez utraconych wiadomości.

Także: Binance narzuca limity per-IP i per-strumień. Dla pełnej głębi na każdej parze USDT szybko trafisz na limit szybkości wiadomości. Subskrybuj tylko to, czego faktycznie potrzebujesz, i preferuj strumienie zagregowane (!miniTicker@arr) zamiast strumieni per-symbol, gdy potrzebujesz szerokiego widoku rynku.

Krok 2: Minimalna strategia

Strategia działa na własnym wątku. Utrzymuje przesuwające się okno ostatnich N zamknięć ze strumienia 1-minutowych klines i zajmuje długą pozycję, gdy cena przebije 20-okresowe maksimum. Czysta ilustracja — proszę nie umieszczać tego przed prawdziwymi pieniędzmi.

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;

Krok 3: Bramka ryzyka

Nigdy nie pozwól strategii rozmawiać bezpośrednio z giełdą. Przepuść każdą intencję przez bramkę, która zna Twoje limity.

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;

Krok 4: Złóż zlecenie przez REST

Pracownik zleceń pobiera zwalidowane intencje, podpisuje żądanie i wysyła post do 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;

Krok 4b: Uwierzytelnianie REST i podpisywanie

Komponent TsgcHTTP_API_Binance podpisuje żądania za Ciebie, używając API secret. Pod maską buduje kanoniczny query string, oblicza HMAC-SHA256 z Twoim sekretem i dołącza go jako parametr signature. Podajesz klucz i sekret raz przy starcie.

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));

Jeśli chcesz uruchomić przeciwko testnetowi, ustaw BinanceOptions.Testnet := True — komponent przełącza zarówno bazowy URL REST, jak i host WebSocket automatycznie. Buduj i testuj przeciwko testnetowi, przerzuć jedną flagę, wdrażaj do produkcji. Dokumentacja API Binance jest poza tym identyczna między dwoma środowiskami.

Krok 5: Strumień danych użytkownika

Ten sam komponent WebSocket subskrybuje również Twój prywatny strumień danych użytkownika — aktualizacje konta, zdarzenia zleceń, zmiany pozycji. W ten sposób uzgadniasz wypełnienia, które miały miejsce poza Twoim botem (ręczne anulowanie z UI web, likwidacja itp.).

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;

Uwaga o backtestowaniu

Nic powyżej nie odpowiada „czy ta strategia jest faktycznie opłacalna?” To do tego służy backtestowanie — odtwarzanie tej samej strategii przeciwko historycznym danym w celu oszacowania jej przyszłej wydajności. Architektura powyżej sprawia, że jest to prawie darmowe: wątek strategii nie dba o to, czy zdarzenia rynkowe pochodzą z żywego WebSocket czy z czytnika CSV. Zbuduj syntetyczne źródło zdarzeń, które czyta klines z dysku i wprowadza je do tej samej kolejki, a Twój kod strategii działa bez zmian przeciwko latom danych historycznych.

Dwie pułapki do uniknięcia. Look-ahead bias: nie pozwól strategii zaglądać do żadnego punktu danych, który nie byłby dostępny w momencie znacznika czasu, który przetwarza. I survivorship bias: trenuj i testuj na uniwersum symboli, które istniały w tym czasie, a nie wyselekcjonowanej liście „udanych” symboli, które przetrwały do dziś. Oba zabiły więcej strategii w produkcji niż wszystkie błędy w kodzie razem wzięte.

Lista kontrolna operacyjna

Zagadnienie Gdzie to obsłużyć
Ponowne łączenie przy zaniku Wi-Fi WatchDog.Enabled := True
Wykrywanie martwego łącza HeartBeat.Enabled := True
Synchronizacja czasu (Binance odrzuca przesunięte podpisy) NTP na OS plus codzienne wywołanie endpointu czasu serwera
Idempotentność zlecenia Użyj newClientOrderId na każdym zleceniu
Limity szybkości Śledź nagłówki; cofnij się przy 90% limitu
Kill switch Pojedynczy boolean, przerzucany z UI lub procesu watchdog
Dziennik audytu Każda intencja, każde wypełnienie, każde odrzucenie, append-only

Poza Binance

Wymień TsgcWSAPI_Binance na TsgcWSAPI_Coinbase, TsgcWSAPI_Kraken lub dowolny z ponad 20 innych komponentów giełdowych. Strategia, bramka ryzyka i pracownik zleceń nie zmieniają się — tylko konfiguracja poświadczeń i nazewnictwo symboli. Dla wieloprodukcyjnego tradera z wieloma giełdami, wykresami, zarządzaniem pozycjami i UI routingu zleceń od razu po instalacji, zobacz przykład sgcTrader.

Prawdziwe systemy wielogiełdowe dodają jeszcze jedną warstwę nad tym, co widziałeś tutaj: normalizator symboli. Binance nazywa to BTCUSDT, Coinbase BTC-USD, Kraken XBT/USD. Zbuduj wewnętrzny model symbolu z kanoniczną nazwą i aliasami per-giełda i tłumacz na granicy API. Pięć minut pracy z góry, nieskończoność zaoszczędzonych błędów później.

Inną rzeczą do dodania dla operacji na wielu giełdach jest sprawdzenie odchylenia zegara przy starcie. Binance, Coinbase i reszta odrzucają podpisane żądania z znacznikiem czasu więcej niż 1000 ms odbiegającym od ich serwera. NTP zwykle trzyma Cię dobrze w tym zakresie, ale błędnie skonfigurowany VPS może dryfować sekundy w godzinie. Zapytaj endpoint czasu serwera przy starcie, zaloguj offset, odmów handlu, jeśli jest >500 ms.

Dlaczego Delphi do tego?

„Dlaczego nie napisać tego w Pythonie?” to oczywiste pytanie. Trzy odpowiedzi z produkcji. Po pierwsze, rozgrzewka JIT i GIL sprawiają, że CPython jest słabym dopasowaniem do pętli zdarzeń o niskiej latencji — ta sama strategia, która osiąga 0,8 ms medianowej latencji w Delphi, zajmuje 6 ms w CPython bez poważnego wysiłku. Po drugie, historia wdrożenia jest prostsza: jeden podpisany exe vs virtualenv z setką wheels, z których połowa wymaga kompilatora C w czasie instalacji. Po trzecie, istniejący back office jest w Delphi. Ponowne wykorzystanie tych klas — księga konta, kalkulator P&L, dziennik, dziennik audytu — w nowym bocie zamiast ponownej implementacji ich w innym języku eliminuje całą kategorię błędów uzgadniania.

Do czystych badań i backtestów w stylu notebook Python wygrywa łatwo — ekosystem pandas, statsmodels, vectorbt i pokrewnych jest niedoścignięty. Podział, który działa dla większości sklepów: badania w Pythonie, produkcja w Delphi. Eksportuj logikę strategii jako małą maszynę stanów, portuj raz, uruchom na sprawdzonym w boju runtime Delphi. Dwie połówki nie muszą dzielić języka, by dzielić wyniki.

Co czytać dalej

Jeśli planujesz uruchamiać to 24/7 na VPS, przeczytaj następnie Strojenie wydajności. Aby uniknąć najczęstszych pułapek, zobacz 10 najczęstszych błędów. A jeśli jeszcze nie zainstalowałeś sgcWebSockets, hub Pierwsze kroki uruchamia Cię na żywo w pięć minut.

Zastrzeżenie: strategia w tym wpisie jest do celów edukacyjnych. Handel kryptowalutami wiąże się z znacznym ryzykiem. Nie wdrażaj nieprzetestowanego kodu z prawdziwym kapitałem.