Toda exchange publica limites de taxa, e toda exchange os aplica. A Binance responde a uma chamada REST que estourou o limite com um HTTP 429 e um cabeçalho Retry-After que se espera que você respeite, e fecha uma conexão WebSocket que lhe envia mais de 5 mensagens por segundo. Dois relatos de clientes chegaram com poucos dias de diferença, um sobre cada metade disso, e juntos revelaram três problemas que valia a pena corrigir de verdade.
Os três já estão corrigidos. Um deles acabou afetando muito mais do que a Binance.
Uma requisição com falha jogava fora seus cabeçalhos
Quando uma requisição feita por um dos clientes de API prontos falhava com um status de erro HTTP, o componente levantava uma exceção que carregava o código de status, o texto do status e o corpo. Os cabeçalhos da resposta desapareciam. Não ficavam ocultos, desapareciam. O objeto HTTP era destruído enquanto a exceção ainda subia pela pilha, e os cabeçalhos iam junto.
Isso torna impossível construir sobre o componente um backoff em conformidade com a Binance. O Retry-After informa exatamente quanto tempo esperar, e ignorá-lo faz o 429 escalar para um HTTP 418 e um banimento temporário de IP. Os contadores de limite de taxa que a Binance devolve, como o X-MBX-USED-WEIGHT-1M, estavam igualmente inacessíveis, então a aplicação também não conseguia ajustar seu próprio ritmo antes de bater na parede.
A exceção levantada agora é a EsgcHTTPAPIProtocolException, e ela carrega consigo os cabeçalhos da resposta. Ela descende da exceção levantada antes, então os tratadores existentes continuam capturando-a e nada precisa mudar, a menos que você queira a nova informação.
try
vResponse := oBinance.GetAggregateTrades('BTCUSDT');
except
on E: EsgcHTTPAPIProtocolException do
begin
if E.ErrorCode = 429 then
Sleep(E.RetryAfterMs);
vWeight := E.GetHeader('X-MBX-USED-WEIGHT-1M');
// E.ResponseHeaders holds the complete list
end;
end;
RetryAfterMs devolve o atraso em milissegundos e entende as duas formas que o cabeçalho pode assumir, um número simples de segundos e uma data HTTP. Devolve -1 quando o cabeçalho não está presente. A correção vale para todos os oito métodos de requisição, não apenas para Get, e o parâmetro ResponseHeaders que Post e Query já aceitavam agora é preenchido quando uma requisição falha, que é justamente quando você precisa dele.
Vale saber junto com isso: desde a 2026.7.0 o cliente HTTP também pode fazer a espera por você.
oBinance.RetryOptions.Enabled := True;
oBinance.RetryOptions.MaxRetries := 3;
// HonorRetryAfter is already True by default
Um frame em vez de doze
O protocolo WebSocket da Binance aceita uma lista de streams em um único frame SUBSCRIBE. O componente não usava isso. Cada auxiliar Subscribe* escrevia um frame carregando exatamente um stream, imediatamente, então uma lista de acompanhamento comum, com seis símbolos em dois canais, enviava doze frames em poucos milissegundos. A Binance fechava a conexão, exatamente como está documentado.
Agora existem métodos de agrupamento que enviam a lista inteira como um único frame:
oBinance.SubscribeStreams(['btcusdt@aggTrade', 'btcusdt@depth',
'ethusdt@aggTrade', 'ethusdt@depth']);
Um frame fica muito abaixo do limite de 8 KB por requisição mesmo com centenas de streams, então, na prática, uma lista de acompanhamento é sempre um frame só. UnsubscribeStreams é a imagem espelhada.
A parte que ninguém conseguia contornar
Esse foi o problema que mais importou, e nenhum dos dois relatos o capturou por completo.
Toda API de exchange guarda os canais que você assinou, para poder restaurá-los depois de uma reconexão. Essa repetição enviava um frame por stream, um atrás do outro, sem ritmo nenhum, em uma conexão com poucos milissegundos de vida. Qualquer lista de acompanhamento de tamanho realista fazia a conexão recém-criada ser fechada de imediato. O WatchDog então reconectava, repetia a mesma rajada e era fechado de novo. Uma única desconexão passageira virava um laço de reconexão permanente.
Como essa rajada é gerada dentro do componente, a aplicação não tinha como evitá-la. Escrever seus próprios frames em vez de usar os auxiliares não ajudava, já que a repetição não é algo que você chama.
Uma auditoria no restante da biblioteca mostrou que o mesmo laço de repetição tinha sido copiado para 17 APIs de exchanges: Binance, Bitfinex, Bitget, Bitmex, Bitstamp, Bybit, Cex, CexPlus, Coinbase, CryptoCom, Deribit, GateIO, Huobi, Kraken, Kucoin, MEXC e OKX. Cada uma contra o limite da sua própria exchange.
Agora existe uma opção Throttle nos clientes de API WebSocket:
oBinance.Throttle.Enabled := True; // paces your own Subscribe calls
oBinance.Throttle.MaxMessages := 4; // default, leaves room for PING/PONG
oBinance.Throttle.IntervalMs := 1000; // default
O padrão merece uma explicação, porque é deliberadamente assimétrico. Throttle.Enabled é False, então o ritmo das chamadas que a sua aplicação faz não muda a menos que você peça. Mas Throttle.PaceResubscribe é True, então a repetição na reconexão já vem ritmada de fábrica.
O raciocínio é simples. Você controla o seu próprio laço de assinatura e pode agrupá-lo, então o ritmo ali é decisão sua. Você não controla a repetição, por isso ela é ritmada para você. O laço de reconexão fica corrigido em toda aplicação existente, sem nenhuma mudança de código. Na Binance a repetição também é enviada como frames combinados, então uma lista de acompanhamento com doze streams agora é repetida como um único frame, e não como doze. Defina PaceResubscribe como False se quiser o comportamento anterior.
Um detalhe se você usa várias exchanges: o orçamento padrão de 4 mensagens por segundo combina com a Binance, cujo limite documentado é 5. A OKX documenta 3, então o cliente OKX usa 3 por padrão. Verifique o limite da sua exchange antes de aumentar o MaxMessages.
Atualização
É substituição direta. A nova exceção descende da que era levantada antes, os métodos de agrupamento são acréscimos, e o Throttle vem desligado por padrão para tudo, exceto para a repetição que estava quebrando as reconexões. Baixe a versão mais recente na página de download do sgcWebSockets.
Os dois relatos vieram de clientes que tiveram o trabalho de ler o código-fonte, reproduzir o problema e descrevê-lo com precisão. Foi assim que o terceiro e pior bug foi encontrado. Se algo nos componentes se comportar de um jeito que você não consegue explicar, avise-nos.
Dúvidas, comentários ou ajuda na migração? Entre em contato, você receberá resposta das pessoas que escreveram o código.
