diff --git a/CryptoExchange.Net/Clients/RestApiClient.cs b/CryptoExchange.Net/Clients/RestApiClient.cs index d27ba38..e04748f 100644 --- a/CryptoExchange.Net/Clients/RestApiClient.cs +++ b/CryptoExchange.Net/Clients/RestApiClient.cs @@ -223,7 +223,7 @@ namespace CryptoExchange.Net.Clients if (definition.RateLimitGate == null) throw new Exception("Ratelimit gate not set when request weight is not 0"); - if (ClientOptions.RatelimiterEnabled) + if (ClientOptions.RateLimiterEnabled) { var limitResult = await definition.RateLimitGate.ProcessAsync(_logger, requestId, RateLimitItemType.Request, definition, baseAddress, ApiOptions.ApiCredentials?.Key ?? ClientOptions.ApiCredentials?.Key, requestWeight, ClientOptions.RateLimitingBehaviour, cancellationToken).ConfigureAwait(false); if (!limitResult) @@ -237,7 +237,7 @@ namespace CryptoExchange.Net.Clients if (definition.RateLimitGate == null) throw new Exception("Ratelimit gate not set when endpoint limit is specified"); - if (ClientOptions.RatelimiterEnabled) + if (ClientOptions.RateLimiterEnabled) { var limitResult = await definition.RateLimitGate.ProcessSingleAsync(_logger, requestId, RateLimitItemType.Request, definition, baseAddress, ApiOptions.ApiCredentials?.Key ?? ClientOptions.ApiCredentials?.Key, requestWeight, ClientOptions.RateLimitingBehaviour, cancellationToken).ConfigureAwait(false); if (!limitResult) @@ -515,7 +515,7 @@ namespace CryptoExchange.Net.Clients if (gate == null) throw new Exception("Ratelimit gate not set when request weight is not 0"); - if (ClientOptions.RatelimiterEnabled) + if (ClientOptions.RateLimiterEnabled) { var limitResult = await gate.ProcessAsync(_logger, requestId, RateLimitItemType.Request, new RequestDefinition(uri.AbsolutePath.TrimStart('/'), method) { Authenticated = signed }, uri.Host, ApiOptions.ApiCredentials?.Key ?? ClientOptions.ApiCredentials?.Key, requestWeight, ClientOptions.RateLimitingBehaviour, cancellationToken).ConfigureAwait(false); if (!limitResult) @@ -576,7 +576,7 @@ namespace CryptoExchange.Net.Clients if (response.StatusCode == (HttpStatusCode)418 || response.StatusCode == (HttpStatusCode)429) { var rateError = ParseRateLimitResponse((int)response.StatusCode, response.ResponseHeaders, accessor); - if (rateError.RetryAfter != null && gate != null && ClientOptions.RatelimiterEnabled) + if (rateError.RetryAfter != null && gate != null && ClientOptions.RateLimiterEnabled) { _logger.RestApiRateLimitPauseUntil(request.RequestId, rateError.RetryAfter.Value); await gate.SetRetryAfterGuardAsync(rateError.RetryAfter.Value).ConfigureAwait(false); @@ -666,7 +666,7 @@ namespace CryptoExchange.Net.Clients return false; if ((int?)callResult.ResponseStatusCode == 429 - && ClientOptions.RatelimiterEnabled + && ClientOptions.RateLimiterEnabled && ClientOptions.RateLimitingBehaviour != RateLimitingBehaviour.Fail && gate != null) { diff --git a/CryptoExchange.Net/Clients/SocketApiClient.cs b/CryptoExchange.Net/Clients/SocketApiClient.cs index 356aeb8..7f29850 100644 --- a/CryptoExchange.Net/Clients/SocketApiClient.cs +++ b/CryptoExchange.Net/Clients/SocketApiClient.cs @@ -519,7 +519,7 @@ namespace CryptoExchange.Net.Clients { KeepAliveInterval = KeepAliveInterval, ReconnectInterval = ClientOptions.ReconnectInterval, - RateLimiter = ClientOptions.RatelimiterEnabled ? RateLimiter : null, + RateLimiter = ClientOptions.RateLimiterEnabled ? RateLimiter : null, RateLimitingBehaviour = ClientOptions.RateLimitingBehaviour, Proxy = ClientOptions.Proxy, Timeout = ApiOptions.SocketNoDataTimeout ?? ClientOptions.SocketNoDataTimeout diff --git a/CryptoExchange.Net/Objects/Options/ExchangeOptions.cs b/CryptoExchange.Net/Objects/Options/ExchangeOptions.cs index 332af8c..2d55808 100644 --- a/CryptoExchange.Net/Objects/Options/ExchangeOptions.cs +++ b/CryptoExchange.Net/Objects/Options/ExchangeOptions.cs @@ -31,7 +31,7 @@ namespace CryptoExchange.Net.Objects.Options /// /// Whether or not client side rate limiting should be applied /// - public bool RatelimiterEnabled { get; set; } = true; + public bool RateLimiterEnabled { get; set; } = true; /// /// What should happen when a rate limit is reached /// diff --git a/CryptoExchange.Net/Objects/Options/RestExchangeOptions.cs b/CryptoExchange.Net/Objects/Options/RestExchangeOptions.cs index 0537ba9..9368b70 100644 --- a/CryptoExchange.Net/Objects/Options/RestExchangeOptions.cs +++ b/CryptoExchange.Net/Objects/Options/RestExchangeOptions.cs @@ -33,7 +33,7 @@ namespace CryptoExchange.Net.Objects.Options ApiCredentials = ApiCredentials?.Copy(), Proxy = Proxy, RequestTimeout = RequestTimeout, - RatelimiterEnabled = RatelimiterEnabled, + RateLimiterEnabled = RateLimiterEnabled, RateLimitingBehaviour = RateLimitingBehaviour }; } diff --git a/CryptoExchange.Net/Objects/Options/SocketExchangeOptions.cs b/CryptoExchange.Net/Objects/Options/SocketExchangeOptions.cs index 20d46f1..36e28f4 100644 --- a/CryptoExchange.Net/Objects/Options/SocketExchangeOptions.cs +++ b/CryptoExchange.Net/Objects/Options/SocketExchangeOptions.cs @@ -67,7 +67,7 @@ namespace CryptoExchange.Net.Objects.Options Proxy = Proxy, RequestTimeout = RequestTimeout, RateLimitingBehaviour = RateLimitingBehaviour, - RatelimiterEnabled = RatelimiterEnabled, + RateLimiterEnabled = RateLimiterEnabled, }; } } diff --git a/docs/index.html b/docs/index.html index 02bdc53..05cf802 100644 --- a/docs/index.html +++ b/docs/index.html @@ -881,7 +881,7 @@ else
var client = new CoinExRestClient();
-var tickersResult = await client.SpotApi.ExchangeData.GetTickersAsync();
+var tickersResult = await client.SpotApiV2.ExchangeData.GetTickersAsync();
 if (!tickersResult.Success)
 {
   // Handle error, tickersResult.Error contains more information
@@ -1115,12 +1115,12 @@ if (!subscribeResult.Success)
 			  
var client = new CoinExSocketClient();
-var subscribeResult = await client.SpotApi.SubscribeToTickerUpdatesAsync("ETHUSDT", update => {
-  // Handle the data update, update.Data will contain the actual data
+var subscribeResult = await sclient.SpotApiV2.SubscribeToTickerUpdatesAsync(new[] { "ETHUSDT" }, update => {
+    // Handle the data update, update.Data will contain the actual data
 });
 if (!subscribeResult.Success)
 {
-  // Handle error, subscribeResult.Error contains more information on why the subscription failed
+    // Handle error, subscribeResult.Error contains more information on why the subscription failed
 }
 // Subscribing was successfull, the data will now be streamed into the data handler
@@ -1809,6 +1809,16 @@ var client = new OKXRestClient();
When enabled the originally received string data will be available as well as the deserialized object. For REST API client calls the data will be in the WebCallResult.OriginalData property, for Websocket API client subscriptions the data will be available in the DataEvent.OriginalData property when receiving an update. false + + RateLimiterEnabled + Whether or not client side rate limiting should be applied. Note that not all libraries have ratelimiting implemented, if it's not implemented this flag does nothing + true + + + RateLimitingBehaviour + What should happen when a rate limit is reached. RateLimitingBehaviour.Wait: the request waits until it can be send while staying within the limits, RateLimitingBehaviour.Fail: the request will return an error + RateLimitingBehaviour.Wait + Environment The environment the library should connect to. Some exchanges have testnet/sandbox environments which can be used instead of the real exchange. The environment option can be used to switch between different trade environments @@ -1831,16 +1841,6 @@ var client = new OKXRestClient(); The interval of how often the time synchronization between client and server should be executed TimeSpan.FromHours(1) - - [API].RateLimiters - A list of IRateLimiters to use - Dependent on the library - - - [API].RateLimitingBehaviour - What should happen when a rate limit is reached - RateLimitingBehaviour.Wait - [API].ApiCredentials Same as the in the base options, allows overriding per sub-API @@ -1905,11 +1905,6 @@ var client = new OKXRestClient(); The time to wait before sending messages after connecting to the server null - - [API].RateLimiters - A list of IRateLimiters to use - Dependent on the library - [API].SocketNoDataTimeout Same as the in the base websocket client options, allows overriding per sub-API @@ -2275,32 +2270,81 @@ var binanceClient = new BinanceRestClient(new HttpClient(), logFactory, options

Ratelimiting

- The client libraries have build in rate limiting. These rate limits can be configured per client. Some client implementations where the exchange has clear rate limits will also have a default rate limiter already set up. -Rate limiting is configured in the client options, and can be set on a specific client or for all clients by either providing it in the constructor for a client, or by using the SetDefaultOptions on a client. - -

What to do when a limit is reached can be configured with the RateLimitingBehaviour client options, either Fail or Wait.
+ The client libraries have build in support for rate limiting. Rate limiting in this case means that requests are throttled (or failed before sending based on configuration) when the client detects a server rate limit will be exceeded. Whether or not rate limiting is applied can be configured in the DI registration or client options. Not all libraries currently have rate limiting configured. + +
What to do when a limit is reached can be configured with the RateLimitingBehaviour client options, either Fail for returning an error or Wait to wait until the request can safely be send.

- Ratelimit configuration
- A rate limiter can be configured in the options like so: -

new ClientOptions
-{
-    RateLimitingBehaviour = RateLimitingBehaviour.Wait,
-    RateLimiters = new List
-    {
-        new RateLimiter()
-            .AddTotalRateLimit(50, TimeSpan.FromSeconds(10))
-    }
-}
- -This will add a rate limiter for 50 requests per 10 seconds. -A rate limiter can have multiple limits: -
new RateLimiter()
-            .AddTotalRateLimit(50, TimeSpan.FromSeconds(10))
-            .AddEndpointLimit("/api/order", 10, TimeSpan.FromSeconds(2))
-This adds another limit of 10 requests per 2 seconds for the order endpoint in addition to the 50 requests per 10 seconds limit. + Client side rate limiting can only correctly work if there is only a single program talking to the exchange. When multiple different application send requests at the same time it's impossible for the client side to keep track of the rate limits. When using multiple concurrent applications it is advised to turn off rate limiting.

+ +

Client side rate limiting is currently implemented for the following libraries:

+
+ +
+
+
services.AddBinance(x =>
+    x.RatelimiterEnabled = true;
+    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
+}, x =>
+{
+    x.RatelimiterEnabled = true;
+    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
+});
+

To be notified of when a rate limit is hit the static BinanceExchange.RateLimiter exposes an event which triggers when a rate limit is reached

+
BinanceExchange.RateLimiter.RateLimitTriggered += (rateLimitEvent) => Console.WriteLine("Limit triggered: " + rateLimitEvent);
+
+// Output: Limit triggered: RateLimitEvent { ApiLimit = Spot Socket, LimitDescription = Limit of 6000 per 00:01:00, RequestDefinition = GET 1, Host = wss://ws-api.binance.com, Current = 5752, RequestWeight = 250, Limit = 6000, TimePeriod = 00:01:00, DelayTime = 00:00:38.7784145, Behaviour = Wait }
+
+
+
+
services.AddKraken(x =>
+    x.RatelimiterEnabled = true;
+    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
+}, x =>
+{
+    x.RatelimiterEnabled = true;
+    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
+});
+

To be notified of when a rate limit is hit the static KrakenExchange.RateLimiter exposes an event which triggers when a rate limit is reached

+
KrakenExchange.RateLimiter.RateLimitTriggered += (rateLimitEvent) => Console.WriteLine("Limit triggered: " + rateLimitEvent);
+
+// Output: Limit triggered: RateLimitEvent { ApiLimit = Spot Rest, LimitDescription = Limit of 15 with a decay rate of 0,33, RequestDefinition = POST 0/private/TradesHistory authenticated, Host = api.kraken.com, Current = 14, RequestWeight = 2, Limit = 15, TimePeriod = 00:00:01, DelayTime = 00:00:04, Behaviour = Wait }
+ +

Kraken applies different rate limits based on the account verification tier. By default the rate limit is set to the most conservative Starter tier. To change the rate limit tier call the Configure method

+
KrakenExchange.RateLimiter.Configure(Kraken.Net.Enums.RateLimitTier.Pro);
+
+
+
services.AddKucoin(x =>
+    x.RatelimiterEnabled = true;
+    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
+}, x =>
+{
+    x.RatelimiterEnabled = true;
+    x.RateLimitingBehaviour = RateLimitingBehaviour.Wait;
+});
+

To be notified of when a rate limit is hit the static KucoinExchange.RateLimiter exposes an event which triggers when a rate limit is reached

+
KucoinExchange.RateLimiter.RateLimitTriggered += (rateLimitEvent) => Console.WriteLine("Limit triggered: " + rateLimitEvent);
+
+// Output: Limit triggered: RateLimitEvent { ApiLimit = Public Rest, LimitDescription = Limit of 2000 per 00:00:30, RequestDefinition = GET api/v1/market/stats, Host = https://api.kucoin.com/, Current = 1995, RequestWeight = 15, Limit = 2000, TimePeriod = 00:00:30, DelayTime = 00:00:19.8111238, Behaviour = Wait }
+ +

Kucoin applies different rate limits based on the account VIP level. By default the rate limit is set to the most conservative VIP0 tier. To change the rate limit tier call the Configure method

+
KucoinExchange.RateLimiter.Configure(Kucoin.Net.Enums.VipLevel.Vip5);
+
+
+
+

@@ -2382,7 +2426,7 @@ await spotClient.GetSymbolsAsync();
await bybitClient.V5Api.ExchangeData.GetSpotSymbolsAsync();
-
await coinExClient.SpotApi.ExchangeData.GetSymbolsAsync();
+
await coinExClient.SpotApiV2.ExchangeData.GetSymbolsAsync();
await huobiClient.SpotApi.ExchangeData.GetSymbolsAsync();
@@ -2476,7 +2520,7 @@ await spotClient.GetTickerAsync(spotClient.GetSymbolName("BTC", "USDT"));
await bybitClient.V5Api.ExchangeData.GetSpotTickersAsync("BTCUSDT");
-
await coinExClient.SpotApi.ExchangeData.GetTickerAsync("BTCUSDT");
+
await coinExClient.SpotApiV2.ExchangeData.GetTickersAsync(new[] { "BTCUSDT" });
await huobiClient.SpotApi.ExchangeData.GetTickerAsync("BTCUSDT");
@@ -2570,7 +2614,7 @@ await spotClient.GetBalancesAsync();
await bybitClient.V5Api.Account.GetBalancesAsync(AccountType.Spot);
-
await coinExClient.SpotApi.Account.GetBalancesAsync();
+
await coinExClient.SpotApiV2.Account.GetBalancesAsync();
// Need an account id, you probably want to already have done this before placing the order
@@ -2668,7 +2712,7 @@ await spotClient.PlaceOrderAsync(spotClient.GetSymbolName("BTC", "USDT"), Common
 		
await bybitClient.V5Api.Trading.PlaceOrderAsync(Category.Spot, "BTCUSDT", OrderSide.Buy, NewOrderType.Limit, 0.1m, price: 50000);
-
await coinExClient.SpotApi.Trading.PlaceOrderAsync("BTCUSDT", OrderSide.Buy, OrderType.Limit, 0.1m, 50000);
+
await coinExClient.SpotApiV2.Trading.PlaceOrderAsync("BTCUSDT", AccountType.Spot, OrderSide.Buy, OrderTypeV2.Limit, 0.1m, 50000);
// Need an account id, you probably want to already have done this before placing the order
@@ -2768,7 +2812,7 @@ var result = await huobiClient.SpotApi.Trading.PlaceOrderAsync(account.Id, "BTCU
 });
-
await coinExSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETHUSDT", data => {
+		
await coinExSocketClient.SpotApiV2.SubscribeToTickerUpdatesAsync(new[] { "ETHUSDT" }, data => {
     // Handle update
 });
@@ -2905,7 +2949,7 @@ _ = Task.Run(async () => { });
-
await coinExSocketClient.SpotApi.SubscribeToOrderUpdatesAsync(data => {
+		
await coinExSocketClient.SpotApiV2.SubscribeToOrderUpdatesAsync(data => {
     // Handle update
 });