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.