Delphi で sgcWebSockets + Binance を使ったリアルタイム取引ボットを構築

· 機能

これから作るもの

このチュートリアルが終わる頃には、Binance からライブの取引とオーダーブックデータをストリームし、シンプルなブレイクアウト戦略を実行し、REST API 経由で実注文を発注し、リスク制御(最大ポジションサイズ、日次損失限度、キルスイッチ)を強制する動作する Delphi VCL アプリケーションが完成します。同じ配管は、sgcWebSockets がサポートする任意の取引所 — Coinbase、Kraken、OKX、Bybit、Bitfinex — で動作し、変わるのは資格情報とシンボルマッピングだけです。

これはリファレンスである sgcTrader サンプルで使用されているのと同じアーキテクチャです。完全な UI、チャート、マルチ取引所ルーティングを備えたより大きな出発点が欲しい場合はそちらを取得してください。以下のウォークスルーは、約 300 行のコードの内部で何が起きているかを示します。

始める前に 2 つの前提条件。第 1 に、Binance API キーを取得(Account > API Management)。開発用には、「Enable Reading」と「Enable Spot Trading」のみのキーを生成し、IP をホワイトリストに登録してください。出金有効なキーをコードに置かないでください。第 2 に、まず Binance のテストネット(testnet.binance.vision)ですべてを実行してください。エンドポイント、メッセージ形式、署名アルゴリズムは本番と同一ですが、資金は偽物です。「自分の戦略は正しいはず」で実マネーを失った回数は、テストネットで最初にテストしなかった回数と正確に等しいです。

1 つの図でのアーキテクチャ

3 つのスレッド、2 つのコンポーネント、1 つのリスクゲートキーパー:

コンポーネント 役割 スレッド
TsgcWSAPI_Binance WebSocket ストリーム: 取引、デプス、ローソク足、ユーザーデータ I/O ワーカー
TsgcHTTP_API_Binance REST: 注文発注、キャンセル、口座スナップショット トレーダーワーカー
戦略キュー 切り離し: マーケットイベント → 判断 → 注文 戦略ワーカー
リスクゲート 各注文をブロック/縮小/許可 トレーダー内インライン

ステップ 1: マーケットデータをストリーム

フォームに TsgcWSAPI_Binance をドロップします。すでに Binance 結合ストリームプロトコルを話します — 必要なチャネルを購読するだけです。

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;

これがマーケットデータ取り込み層の全部です。ドロップ時の再接続、死亡リンク検出のためのハートビート、戦略キューへのノンブロッキングプッシュ。

コンポーネントが行ってくれること、そうでなければ自分で書く必要があること: Binance の結合ストリーム URL は /stream?streams=name1/name2/name3 で、接続をドロップせずにストリームを追加・削除したい場合は、同じソケットで JSON-RPC subscribe/unsubscribe メッセージを送る必要があります。TsgcWSAPI_Binance は、JSON-RPC ハンドシェイクを処理する SubscribeStreamUnsubscribeStream メソッドを公開します。ユーザーが UI で新しい銘柄を選んだとき便利です — 再接続なし、メッセージ損失なし。

また: Binance は IP ごとおよびストリームごとの制限を課します。すべての USDT ペアの完全なデプスを取ると、メッセージレート制限にすぐに達します。実際に必要なものだけを購読し、広いマーケットビューが必要なときはシンボルごとのストリームより集約ストリーム(!miniTicker@arr)を優先してください。

ステップ 2: 最小限の戦略

戦略は自身のスレッドで実行されます。1 分ローソク足ストリームから最後の N の終値のローリングウィンドウを維持し、価格が 20 期間の高値を超えたらロングします。純粋に説明用 — 実マネーの前に置かないでください。

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;

ステップ 3: リスクゲート

戦略が取引所と直接話すことを決して許してはいけません。すべての意図を、あなたの制限を知るゲートを通じて漏斗に通してください。

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;

ステップ 4: REST 経由で注文を発注

注文ワーカーは検証済みの意図を引き出し、リクエストに署名し、Binance に POST します。

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;

ステップ 4b: REST 認証と署名

TsgcHTTP_API_Binance コンポーネントは API シークレットを使ってリクエストに署名してくれます。背後で正規クエリ文字列を構築し、シークレットで HMAC-SHA256 を計算し、signature パラメーターとして付加します。起動時に一度キーとシークレットを提供します。

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

テストネットに対して実行したいなら、BinanceOptions.Testnet := True を設定してください — コンポーネントは REST ベース URL と WebSocket ホストの両方を自動で切り替えます。テストネットでビルドしてテスト、1 つのフラグを反転、本番にデプロイ。Binance API ドキュメントは 2 つの環境間でそれ以外同一です。

ステップ 5: ユーザーデータストリーム

同じ WebSocket コンポーネントはプライベートユーザーデータストリーム — 口座更新、注文イベント、ポジション変更 — も購読します。これがボットの外で起きた約定(Web UI からの手動キャンセル、清算など)を調整する方法です。

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;

バックテストについての一言

上記のいずれも「この戦略は実際に利益が出るのか?」に答えていません。それがバックテストの目的です — 同じ戦略を過去データに対してリプレイし、将来のパフォーマンスを推定する。上記のアーキテクチャはこれをほぼ無料にします: 戦略スレッドは、マーケットイベントがライブ WebSocket から来るのか CSV リーダーから来るのかを気にしません。ディスクからローソク足を読み、同じキューに供給する合成イベントソースを構築すれば、戦略コードは何年分もの過去データに対して変更なしで動作します。

避けるべき 2 つの落とし穴。先読みバイアス: 戦略が処理中のタイムスタンプで利用できなかったはずのデータポイントを覗き見ることを許さないでください。生存者バイアス: 今日まで生き残った「成功した」シンボルの厳選リストではなく、当時存在したシンボルの宇宙でトレーニングしてテストしてください。両方が、すべてのコーディングバグを合計するよりも多くの戦略を本番で殺してきました。

運用チェックリスト

関心事 処理場所
Wi-Fi 切断時の再接続 WatchDog.Enabled := True
死亡リンク検出 HeartBeat.Enabled := True
時刻同期(Binance はずれた署名を拒否) OS の NTP、加えてサーバー時刻エンドポイントへの日次呼び出し
注文の冪等性 すべての注文で newClientOrderId を使用
レート制限 ヘッダーを追跡。制限の 90% 以内になったらバックオフ
キルスイッチ UI または監視プロセスから反転される単一ブール
監査ログ すべての意図、すべての約定、すべての拒否、追記専用

Binance を超えて

TsgcWSAPI_BinanceTsgcWSAPI_CoinbaseTsgcWSAPI_Kraken、または他の 20 以上の取引所コンポーネントのいずれかに交換してください。戦略、リスクゲート、注文ワーカーは変わりません — 資格情報設定とシンボル命名のみ。チャート、ポジション管理、注文ルーティング UI をそのまま備えた本番グレードのマルチ取引所トレーダーには、sgcTrader サンプルを見てください。

実マルチ取引所システムは、ここで見たものの上にもう 1 層追加します: シンボル正規化器。Binance は BTCUSDT、Coinbase は BTC-USD、Kraken は XBT/USD と呼びます。正規名と取引所ごとのエイリアスを持つ内部シンボルモデルを構築し、API 境界で翻訳します。前もって 5 分の作業、下流で節約される無限のバグ。

マルチ取引所運用に追加するもう 1 つは、起動時のクロックスキューチェックです。Binance、Coinbase、その他は、サーバーから 1000 ms 以上ずれたタイムスタンプの署名済みリクエストを拒否します。NTP は通常その範囲内によく収めますが、誤構成された VPS は 1 時間で数秒ドリフトすることがあります。起動時にサーバー時刻エンドポイントを照会し、オフセットを記録し、500 ms を超えたら取引を拒否してください。

なぜこれに Delphi なのか?

「なぜ Python で書かないのか?」は当然の疑問です。本番からの 3 つの答え。第 1 に、JIT ウォームアップと GIL のため、CPython は低レイテンシイベントループに不向きです — Delphi で 0.8 ms 中央値レイテンシに達する同じ戦略は、真剣な努力なしには CPython では 6 ms かかります。第 2 に、デプロイのストーリーが単純です: 署名された exe 1 つ vs インストール時に C コンパイラを必要とするものを半分含む 100 個の wheel を持つ virtualenv。第 3 に、既存のバックオフィスは Delphi にあります。これらのクラス — 口座台帳、P&L 計算機、ジャーナル、監査ログ — を別言語で再実装する代わりに新しいボットで再利用することで、調整バグのカテゴリ全体を排除します。

純粋なリサーチとノートブックスタイルのバックテストでは、Python が容易に勝ちます — pandas、statsmodels、vectorbt 等のエコシステムは比肩できません。ほとんどのショップで動く分割: リサーチは Python、本番は Delphi。戦略ロジックを小さな状態機械としてエクスポートし、1 度ポートし、実戦試験済みの Delphi ランタイムで実行。2 つの半分は、結果を共有するために言語を共有する必要はありません。

次に何を読むか

これを VPS で 24/7 実行する予定なら、次に パフォーマンスチューニング を読んでください。最もよくある落とし穴を避けるには、10 のよくある間違い を参照。まだ sgcWebSockets をインストールしていなければ、はじめにハブ が 5 分でライブに導きます。

免責事項: 本稿の戦略は教育目的です。暗号通貨の取引には実質的なリスクが伴います。実資本でテストされていないコードをデプロイしないでください。