Limites de débit Binance en Delphi : regroupement, cadencement et 429 lisibles | eSeGeCe Blog

Limites de débit Binance en Delphi : regroupement, cadencement et 429 lisibles

· Composants

Chaque plateforme d'échange publie des limites de débit, et chaque plateforme les applique. Binance répond à un appel REST limité par un HTTP 429 accompagné d'un en-tête Retry-After que vous êtes censé respecter, et elle ferme toute connexion WebSocket qui lui envoie plus de 5 messages par seconde. Deux rapports de clients sont arrivés à quelques jours d'intervalle, un pour chaque moitié du problème, et ensemble ils ont mis au jour trois défauts qui méritaient d'être corrigés correctement.

Les trois sont désormais corrigés. L'un d'eux s'est avéré concerner bien plus que Binance.

Une requête en échec jetait ses en-têtes

Lorsqu'une requête effectuée via l'un des clients d'API prêts à l'emploi échouait avec un statut d'erreur HTTP, le composant levait une exception portant le code de statut, le texte du statut et le corps. Les en-têtes de réponse avaient disparu. Pas masqués, disparus. L'objet HTTP était détruit alors que l'exception remontait encore la pile, et les en-têtes partaient avec lui.

Cela rend impossible la mise en place d'un backoff conforme aux règles de Binance par-dessus le composant. Retry-After indique exactement combien de temps attendre, et l'ignorer fait passer d'un 429 à un HTTP 418 et à un bannissement temporaire de l'adresse IP. Les compteurs de limite de débit renvoyés par Binance, comme X-MBX-USED-WEIGHT-1M, étaient tout aussi inaccessibles, si bien qu'une application ne pouvait pas non plus se cadencer avant de heurter le mur.

L'exception levée est maintenant EsgcHTTPAPIProtocolException, et elle transporte les en-têtes de réponse avec elle. Elle dérive de l'exception levée auparavant, donc les gestionnaires existants continuent de l'intercepter et rien n'a besoin de changer, sauf si vous souhaitez profiter des nouvelles informations.

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 renvoie le délai en millisecondes et comprend les deux formes que l'en-tête est autorisé à prendre, un simple nombre de secondes et une date HTTP. Il renvoie -1 lorsque l'en-tête est absent. La correction s'applique aux huit méthodes de requête, pas seulement à Get, et le paramètre ResponseHeaders que Post et Query acceptaient déjà est maintenant rempli quand une requête échoue, c'est-à-dire précisément au moment où vous en avez besoin.

Bon à savoir également : depuis la 2026.7.0, le client HTTP peut aussi attendre à votre place.

oBinance.RetryOptions.Enabled := True;
oBinance.RetryOptions.MaxRetries := 3;
// HonorRetryAfter is already True by default

Une trame au lieu de douze

Le protocole WebSocket de Binance accepte une liste de flux dans une seule trame SUBSCRIBE. Le composant ne l'utilisait pas. Chaque méthode Subscribe* écrivait immédiatement une trame portant exactement un flux, si bien qu'une liste de surveillance ordinaire de six symboles sur deux canaux envoyait douze trames en quelques millisecondes. Binance fermait la connexion, exactement comme documenté.

Il existe désormais des méthodes de regroupement qui envoient la liste entière en une seule trame :

oBinance.SubscribeStreams(['btcusdt@aggTrade', 'btcusdt@depth',
  'ethusdt@aggTrade', 'ethusdt@depth']);

Une trame reste très en dessous de la limite de requête de 8 KB même avec des centaines de flux, donc en pratique une liste de surveillance tient toujours dans une seule trame. UnsubscribeStreams en est l'image miroir.

La partie que personne ne pouvait contourner

C'est celle qui comptait le plus, et aucun des deux rapports ne l'avait entièrement identifiée.

Chaque API d'échange conserve les canaux auxquels vous vous êtes abonné, afin de pouvoir les restaurer après une reconnexion. Cette relecture envoyait une trame par flux, coup sur coup, sans aucun cadencement, sur une connexion vieille de quelques millisecondes. Une liste de surveillance de taille réaliste faisait fermer la nouvelle connexion immédiatement. Le WatchDog se reconnectait alors, rejouait la même rafale, et se faisait de nouveau fermer. Une simple déconnexion passagère se transformait en boucle de reconnexion permanente.

Comme cette rafale est générée à l'intérieur du composant, une application ne pouvait pas l'éviter. Écrire vos propres trames au lieu d'utiliser les méthodes fournies n'aidait en rien, puisque la relecture n'est pas quelque chose que vous appelez.

L'audit du reste de la bibliothèque a montré que la même boucle de relecture avait été recopiée dans 17 API d'échange : Binance, Bitfinex, Bitget, Bitmex, Bitstamp, Bybit, Cex, CexPlus, Coinbase, CryptoCom, Deribit, GateIO, Huobi, Kraken, Kucoin, MEXC et OKX. Chacune face à la limite de sa propre plateforme.

Il existe maintenant une option Throttle sur les clients d'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

La valeur par défaut mérite une explication, car elle est délibérément asymétrique. Throttle.Enabled vaut False, donc le rythme des appels effectués par votre application ne change pas, sauf si vous le demandez. En revanche, Throttle.PaceResubscribe vaut True, donc la relecture après reconnexion est cadencée d'emblée.

Le raisonnement est simple. Vous contrôlez votre propre boucle d'abonnement et pouvez la regrouper, donc le cadencement à cet endroit relève de votre décision. Vous ne contrôlez pas la relecture, elle est donc cadencée pour vous. La boucle de reconnexion est corrigée pour toutes les applications existantes sans la moindre modification de code. Sur Binance, la relecture est en outre envoyée sous forme de trames combinées, donc une liste de surveillance de douze flux se rejoue maintenant en une seule trame plutôt qu'en douze. Mettez PaceResubscribe à False si vous préférez le comportement précédent.

Un détail si vous utilisez plusieurs plateformes : le budget par défaut de 4 messages par seconde convient à Binance, dont la limite documentée est de 5. OKX documente 3, donc le client OKX utilise 3 par défaut. Vérifiez la limite de votre plateforme avant d'augmenter MaxMessages.

Mise à jour

Remplacement direct. La nouvelle exception dérive de celle qui était levée auparavant, les méthodes de regroupement sont des ajouts, et Throttle est désactivé par défaut pour tout, sauf pour la relecture qui cassait les reconnexions. Téléchargez la dernière version depuis la page de téléchargement de sgcWebSockets.

Ces deux signalements viennent de clients qui ont pris le temps de lire le code source, de reproduire le problème et de le décrire précisément. C'est ainsi que le troisième et pire bug a été trouvé. Si quelque chose dans les composants se comporte d'une façon que vous ne pouvez pas expliquer, dites-le nous.

Des questions, des retours ou besoin d'aide pour la migration ? Contactez-nous, vous obtiendrez une réponse des personnes qui ont écrit le code.