我们将构建什么
本教程结束时,您将拥有一个可工作的 Delphi VCL 应用程序,它从 Binance 流式传输实时交易和订单簿数据、运行简单的突破策略、通过 REST API 下真实订单,并执行风险控制(最大头寸大小、每日亏损限额、紧急停止开关)。相同的管道适用于 sgcWebSockets 支持的任何交易所——Coinbase、Kraken、OKX、Bybit、Bitfinex——只有凭据和符号映射会更改。
这与我们的参考 sgcTrader 示例使用的架构相同。如果您想要一个带完整 UI、图表和多交易所路由的更大起点,请获取它。下面的演练展示了大约 300 行代码下幕后发生的事情。
开始之前的两个先决条件。首先,获取 Binance API 密钥(账户 > API 管理)。对于开发,生成仅具有"启用读取"和"启用现货交易"的密钥,并将您的 IP 列入白名单。永远不要将启用提取的密钥放入代码中。其次,先在 Binance 的测试网(testnet.binance.vision)上做所有事情。端点、消息格式和签名算法与生产相同,但资金是假的。我们将真钱输给了"我确信我的策略是正确的",正好是我们首先没有在测试网上测试的次数。
一图架构
三个线程,两个组件,一个风险守门员:
| 组件 | 角色 | 线程 |
TsgcWSAPI_Binance |
WebSocket 流:交易、深度、k 线、用户数据 | 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 订阅/取消订阅消息。TsgcWSAPI_Binance 公开 SubscribeStream 和 UnsubscribeStream 方法,为您处理 JSON-RPC 握手。当用户在 UI 中选择新行情时很有用——无需重连,无丢失消息。
另外:Binance 施加每 IP 和每流限制。要获得每个 USDT 对的完整深度,您将很快达到消息速率限制。仅订阅您实际需要的,并在需要广泛市场视图时优先使用聚合流(!miniTicker@arr)而不是每符号流。
步骤 2:最小策略
策略在其自己的线程上运行。它维护 1 分钟 k 线流的最后 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。
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 主机。针对测试网构建和测试,翻转单个标志,部署到生产。Binance API 文档在这两个环境之间是相同的。
步骤 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 读取器。构建一个从磁盘读取 k 线并将其馈送到同一队列的合成事件源,您的策略代码就会针对多年的历史数据无修改地运行。
要避免的两个陷阱。前瞻偏差:不要让策略偷看任何在它正在处理的时间戳上不会可用的数据点。以及幸存者偏差:在当时存在的符号宇宙上训练和测试,而不是幸存到今天的"成功"符号的精选列表。这两者在生产中杀死的策略比所有编码错误加起来都多。
运营清单
| 关注点 | 处理位置 |
| Wi-Fi 断开时重连 | WatchDog.Enabled := True |
| 死链接检测 | HeartBeat.Enabled := True |
| 时间同步(Binance 拒绝偏斜签名) | OS 上的 NTP,加上每天对服务器时间端点的调用 |
| 订单幂等性 | 在每个订单上使用 newClientOrderId |
| 速率限制 | 跟踪标头;在限制的 90% 以内时退避 |
| 紧急停止开关 | 单个布尔值,从 UI 或 watchdog 进程翻转 |
| 审计日志 | 每个意图、每个成交、每个拒绝,仅追加 |
超越 Binance
将 TsgcWSAPI_Binance 换成 TsgcWSAPI_Coinbase、TsgcWSAPI_Kraken 或其他 20 多个交易所组件中的任意一个。策略、风险门和订单工作器不会更改——只有凭据设置和符号命名。对于开箱即用具有图表、头寸管理和订单路由 UI 的生产级多交易所交易者,请查看 sgcTrader 示例。
真实的多交易所系统在您所见之上添加一层:符号规范化器。Binance 称之为 BTCUSDT,Coinbase 称之为 BTC-USD,Kraken 称之为 XBT/USD。构建一个具有规范名称和每交易所别名的内部符号模型,并在 API 边界处翻译。前期五分钟的工作,下游无限节省的错误。
多交易所操作要添加的另一件事是启动时的时钟偏差检查。Binance、Coinbase 和其他都拒绝时间戳与其服务器相差超过 1000 ms 的已签名请求。NTP 通常将您保持在该范围内,但配置错误的 VPS 可以在一小时内漂移几秒。在启动时查询服务器时间端点,记录偏移,如果 >500 ms 拒绝交易。
为什么选 Delphi?
"为什么不用 Python 写?"是显而易见的问题。生产中的三个答案。首先,JIT 预热和 GIL 使 CPython 不适合低延迟事件循环——在 Delphi 中达到 0.8 ms 中位延迟的相同策略,在 CPython 中无需认真努力需要 6 ms。其次,部署故事更简单:一个签名 exe vs 一个带 100 个 wheels 的 virtualenv,其中一半需要在安装时使用 C 编译器。第三,现有的后台已经是 Delphi。在新机器人中重用那些类(账户分类账、P&L 计算器、日记、审计日志)而不是用另一种语言重新实现它们,消除了整个类别的协调错误。
对于纯研究和笔记本式回测,Python 轻松获胜——pandas、statsmodels、vectorbt 和朋友的生态系统是无与伦比的。适用于大多数公司的拆分:Python 中的研究,Delphi 中的生产。将策略逻辑导出为小状态机,移植一次,在经过战斗考验的 Delphi 运行时上运行。两半不必共享语言来共享结果。
接下来读什么
如果您计划在 VPS 上 24/7 运行此程序,请接下来阅读性能调优。为避免最常见的陷阱,请参阅10 个常见错误。如果您还没有安装 sgcWebSockets,开始使用中心会在五分钟内让您上线。
免责声明:本文中的策略仅用于教育目的。交易加密货币涉及重大风险。不要使用未经测试的代码部署真实资本。