每一个与加密货币交易所打交道的应用,最后都会写出同样的两段管道代码。第一段在套接字断开后恢复你的行情订阅。第二段放慢你的 REST 调用速度,好让交易所不会对你限流。这两段代码都算不上有趣,都很容易在细微处出错,而且直到现在都还得由应用自己来做。
sgcWebSockets 2026.7 把它们内置了进来。交易所 API 客户端可以在重连后重放自己的订阅,而每个现成的 REST 客户端现在都带有一个客户端限流器,用于限制你在一个时间窗口内发送的请求数量。两者都是选择性启用、默认关闭的,所以在你把它们打开之前,升级不会改变任何行为。
重连之后,你什么都没订阅
WatchDog 一直很擅长把连接找回来。网线被拔掉、代理超时、交易所重启了一个网关,几秒钟后客户端又连上了。这部分是有效的。
回不来的是你的订阅。交易所对它们毫无记忆。新的套接字就是一个空白会话,因此你在旧套接字上请求的成交和订单簿数据流干脆就不存在了。这个症状很讨厌,因为看上去什么都没坏:客户端报告为已连接,没有抛出任何错误,只是消息事件不再到达。你的图表就这样悄悄地过时了。
通常的解决办法是自己维护一份订阅清单,并在 OnConnect 中重放它,这意味着要记住你请求过的每一个交易对、每一个频道、每一个数据流,还要记得在代码库其他地方每次订阅或取消订阅时更新这份清单。这是一堆和你的策略毫无关系的记账工作。
启用自动重新订阅
API 客户端本来就知道你订阅了什么,因为你正是通过它们订阅的。在 2026.7 中,它们可以保存这份清单,并在重连后自行重放。这只是交易所选项对象上的一个布尔值,名为 Resubscribe。
下面是当今大多数交易应用里手写的那个版本,以及取代它的写法:
uses
sgcWebSocket, sgcWebSocket_API_Binance;
// Before: keep your own list and replay it on every reconnect.
procedure TForm1.OnBinanceConnect(Connection: TsgcWSConnection);
begin
sgcWSAPI_Binance1.SubscribeTrades('btcusdt');
sgcWSAPI_Binance1.SubscribeTicker('btcusdt');
sgcWSAPI_Binance1.SubscribeTrades('ethusdt');
// ... and every other symbol you had open, in the right order.
end;
// After: the API client replays them for you.
sgcWSAPI_Binance1.Binance.Resubscribe := True;
启用 Resubscribe 后,客户端会在你每次订阅时记录下来,等 WatchDog 把套接字恢复之后,再在新的会话上重新发送这些订阅。取消订阅会删除对应的条目,因此你有意关闭的数据流不会在下一次重连后死灰复燃。
它是与 WatchDog 搭配使用的,而不是取代它。WatchDog 恢复传输层,Resubscribe 在其之上恢复会话:
uses
sgcWebSocket, sgcWebSocket_API_Kraken;
begin
sgcWebSocketClient1.WatchDog.Enabled := True;
sgcWebSocketClient1.WatchDog.Timeout := 5; // retry the socket every 5 seconds
sgcWSAPI_Kraken1.Client := sgcWebSocketClient1;
sgcWSAPI_Kraken1.Kraken.Resubscribe := True; // and restore the streams on top
sgcWebSocketClient1.Active := True;
sgcWSAPI_Kraken1.SubscribeTrade(['XBT/USD', 'ETH/USD']);
end;
默认值是 False。已经手工重新订阅的现有应用会像以前一样继续工作,不会出现重复订阅,直到你删掉自己的代码并打开这个开关。
支持哪些交易所
Resubscribe 位于每个交易所客户端的选项对象上。它在 Binance、Bitfinex、Bitget、BitMEX、Bitstamp、Bybit、CEX、CEX Plus、Coinbase、Crypto.com、Deribit、Gate.io、Huobi、Kraken、KuCoin、MEXC 和 OKX 上均可用,也可用于基于会话的 XTB 流式行情。这个属性的拼写始终一致,因此根据你在窗体上放的是哪个组件,这一行会写成 Coinbase.Resubscribe、Bybit.Resubscribe、OKX.Resubscribe 等等。
Lightstreamer 客户端(IG 及其他券商在用)在内部也做同样的事。会话重新绑定之后,服务器没有此前订阅的任何记录,因此客户端会从本地集合中重放它们,无需你开口。
从客户端一侧看限流
交易应用的另一半是 REST 侧:下单、撤单、加载余额、加载交易对列表。每家交易所都为这些端点公布了一份请求预算,通常是每个 IP 或每个密钥每分钟多少次请求或多少权重。超出就会拿到 429,如果你还继续超,就会被临时封禁,那可就是糟得多的一天了。
2026.7 为 TsgcHTTPAPI_client 增加了一个客户端限流器,因此每个现成的 REST 客户端都会继承它。它是一个滑动窗口:在任意 IntervalMs 窗口内最多 MaxRequests 个请求。你要根据交易所公布的规则来配置它,因为只有交易所的限制才算数:
uses
sgcHTTP_API, sgcHTTP_API_Binance;
var
oBinance: TsgcHTTP_API_Binance_Rest;
begin
oBinance := TsgcHTTP_API_Binance_Rest.Create(nil);
try
oBinance.RateLimit.Enabled := True;
oBinance.RateLimit.IntervalMs := 60000; // one minute window
oBinance.RateLimit.MaxRequests := 1000; // stay under the published budget
oBinance.RateLimit.Behavior := rlbWait; // default
// Every request through this component now takes a slot first.
oBinance.GetAccountInformation;
finally
oBinance.Free;
end;
end;
让自己被限流的经典方式,是启动时的那一波突发请求。应用启动了,然后它一口气去加载交易对列表、余额、未成交订单,以及四十个品种的最新 K 线,全都发生在第一秒内。这一切本身都不算过分,可交易所照样把它算作一秒内的四十个请求。限流器抚平的正是这种形状的流量,你不必在初始化代码里到处撒 Sleep 调用。
限流器是惰性创建的。如果你从不去碰 RateLimit,就不会分配任何对象,请求路径与以前逐字节完全相同,所以不用它的时候不需要付出任何代价。
等待还是拒绝
当前窗口的预算耗尽时,你可以选择下一次调用会发生什么。这就是 Behavior,它是一个真正的抉择,而不是一个调优旋钮。
rlbWait 是默认值,它会阻塞该调用,直到有空闲名额释放出来再发送。在加载历史数据或对账持仓的后台工作线程中,这正是你想要的。工作会多花一点时间,但全部都能完成。
rlbReject 会立即抛出 EsgcHTTPRateLimit 并且什么都不发送。当一个迟到的请求比根本没有请求更糟糕时,这正是你想要的。一个在你决定发送三秒之后才到达的订单,可能已经是你不再想要的订单了,把它扔进一个正在变动的市场,比大声失败更糟糕:
uses
SysUtils, sgcHTTP_API, sgcHTTP_API_Binance;
begin
oBinance.RateLimit.Enabled := True;
oBinance.RateLimit.IntervalMs := 1000;
oBinance.RateLimit.MaxRequests := 10;
oBinance.RateLimit.Behavior := rlbReject;
try
oBinance.NewOrder('BTCUSDT', 'BUY', 'LIMIT', ...);
except
on E: EsgcHTTPRateLimit do
// Budget exhausted. Drop the order rather than send it stale.
LogWarning('rate limit hit, order not sent: ' + E.Message);
end;
end;
常见的配置是两者兼用,使用两个组件:负责批量加载的组件用 rlbWait,负责下单的组件用 rlbReject,各自有各自的预算。
这不是服务器端限流器
有必要明确说清楚,因为两个名字很接近。sgcWebSockets 2026.5 发布了 TsgcWSRateLimiter,那是一个服务器端限流器:它通过限制入站连接允许发给你的数据量,来保护你的服务器不被你的客户端拖垮。而本文中的这个方向正好相反。它是一个客户端限流器,通过限制你的应用向交易所发出的请求量,来保护你不触碰别人的限制。两者互不相干,一个既是服务器又是交易所消费者的应用完全可以同时使用它们。
不只是加密货币
RateLimit 位于 HTTP API 基类客户端上,因此库中每一个现成的 REST 客户端都会继承它,而不只是交易所客户端。AI 客户端也有完全同类的请求限制,所以 OpenAI、Anthropic 和 Gemini 都有它,Stripe 以及其余的 API 集成同样如此。这个属性位置相同,设置项也是同样的四个:
uses
sgcHTTP_API, sgcHTTP_API_OpenAI;
begin
oOpenAI.RateLimit.Enabled := True;
oOpenAI.RateLimit.IntervalMs := 60000;
oOpenAI.RateLimit.MaxRequests := 500; // your tier's requests per minute
oOpenAI.RateLimit.Behavior := rlbWait;
end;
如果你的账户处于每分钟请求数上限较低的等级,这是阻止并行批处理任务触发限制的最省事的办法。
可用性
自动重新订阅和客户端限流器已在 sgcWebSockets 2026.7 中提供,覆盖 Delphi 7 至 13 以及 C++Builder,支持 Win32/Win64、Linux64、macOS、Android 和 iOS。两者默认都是关闭的,所以升级是即插即用的。
拥有有效订阅的客户可以从客户专区下载新版本。试用用户可以在 esegece.com/products/websockets/download 获取更新后的安装程序。
有疑问、建议,或者需要帮助把它接入你的交易应用?联系我们,回复你的将是编写这些代码的人。
