diff --git a/CryptoExchange.Net/ExchangeClient.cs b/CryptoExchange.Net/ExchangeClient.cs index f83aa94..3b37c2b 100644 --- a/CryptoExchange.Net/ExchangeClient.cs +++ b/CryptoExchange.Net/ExchangeClient.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using CryptoExchange.Net.Authentication; using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.Logging; +using CryptoExchange.Net.Objects; using CryptoExchange.Net.RateLimiter; using CryptoExchange.Net.Requests; using Newtonsoft.Json; @@ -21,6 +22,7 @@ namespace CryptoExchange.Net protected Log log; protected ApiProxy apiProxy; + protected RateLimitingBehaviour rateLimitBehaviour; protected AuthenticationProvider authProvider; private List rateLimiters; @@ -45,6 +47,7 @@ namespace CryptoExchange.Net if(apiProxy != null) log.Write(LogVerbosity.Info, $"Setting api proxy to {exchangeOptions.Proxy.Host}:{exchangeOptions.Proxy.Port}"); + rateLimitBehaviour = exchangeOptions.RateLimitingBehaviour; rateLimiters = new List(); foreach (var rateLimiter in exchangeOptions.RateLimiters) rateLimiters.Add(rateLimiter); @@ -79,8 +82,11 @@ namespace CryptoExchange.Net protected virtual async Task> ExecuteRequest(Uri uri, string method = "GET", Dictionary parameters = null, bool signed = false) where T : class { - if(signed && authProvider == null) + if (signed && authProvider == null) + { + log.Write(LogVerbosity.Warning, $"Request {uri.AbsolutePath} failed because no ApiCredentials were provided"); return new CallResult(null, new NoApiCredentialsError()); + } var request = ConstructRequest(uri, method, parameters, signed); @@ -89,9 +95,14 @@ namespace CryptoExchange.Net foreach (var limiter in rateLimiters) { - var limitedBy = limiter.LimitRequest(uri.AbsolutePath); - if (limitedBy > 0) - log.Write(LogVerbosity.Debug, $"Request {uri.AbsolutePath} was limited by {limitedBy}ms by {limiter.GetType().Name}"); + var limitResult = limiter.LimitRequest(uri.AbsolutePath, rateLimitBehaviour); + if (!limitResult.Success) + { + log.Write(LogVerbosity.Debug, $"Request {uri.AbsolutePath} failed because of rate limit"); + return new CallResult(null, limitResult.Error); + } + else if (limitResult.Data > 0) + log.Write(LogVerbosity.Debug, $"Request {uri.AbsolutePath} was limited by {limitResult.Data}ms by {limiter.GetType().Name}"); } log.Write(LogVerbosity.Debug, $"Sending {(signed ? "signed": "")} request to {request.Uri}"); diff --git a/CryptoExchange.Net/ApiProxy.cs b/CryptoExchange.Net/Objects/ApiProxy.cs similarity index 100% rename from CryptoExchange.Net/ApiProxy.cs rename to CryptoExchange.Net/Objects/ApiProxy.cs diff --git a/CryptoExchange.Net/CallResult.cs b/CryptoExchange.Net/Objects/CallResult.cs similarity index 100% rename from CryptoExchange.Net/CallResult.cs rename to CryptoExchange.Net/Objects/CallResult.cs diff --git a/CryptoExchange.Net/Objects/Enums.cs b/CryptoExchange.Net/Objects/Enums.cs new file mode 100644 index 0000000..ba0f75c --- /dev/null +++ b/CryptoExchange.Net/Objects/Enums.cs @@ -0,0 +1,8 @@ +namespace CryptoExchange.Net.Objects +{ + public enum RateLimitingBehaviour + { + Fail, + Wait + } +} diff --git a/CryptoExchange.Net/Error.cs b/CryptoExchange.Net/Objects/Error.cs similarity index 75% rename from CryptoExchange.Net/Error.cs rename to CryptoExchange.Net/Objects/Error.cs index a136407..2d5be84 100644 --- a/CryptoExchange.Net/Error.cs +++ b/CryptoExchange.Net/Objects/Error.cs @@ -38,21 +38,26 @@ public class WebError : Error { - public WebError(string message) : base(3, "Web error: " + message) { } + public WebError(string message) : base(4, "Web error: " + message) { } } public class DeserializeError : Error { - public DeserializeError(string message) : base(4, "Error deserializing data: " + message) { } + public DeserializeError(string message) : base(5, "Error deserializing data: " + message) { } } public class UnknownError : Error { - public UnknownError(string message) : base(5, "Unknown error occured " + message) { } + public UnknownError(string message) : base(6, "Unknown error occured " + message) { } } public class ArgumentError : Error { - public ArgumentError(string message) : base(5, "Invalid parameter: " + message) { } + public ArgumentError(string message) : base(7, "Invalid parameter: " + message) { } + } + + public class RateLimitError: Error + { + public RateLimitError(string message) : base(8, "Rate limit exceeded: " + message) { } } } diff --git a/CryptoExchange.Net/ExchangeOptions.cs b/CryptoExchange.Net/Objects/ExchangeOptions.cs similarity index 81% rename from CryptoExchange.Net/ExchangeOptions.cs rename to CryptoExchange.Net/Objects/ExchangeOptions.cs index b2629da..8fc75d0 100644 --- a/CryptoExchange.Net/ExchangeOptions.cs +++ b/CryptoExchange.Net/Objects/ExchangeOptions.cs @@ -2,6 +2,7 @@ using System.IO; using CryptoExchange.Net.Authentication; using CryptoExchange.Net.Logging; +using CryptoExchange.Net.Objects; using CryptoExchange.Net.RateLimiter; namespace CryptoExchange.Net @@ -36,5 +37,10 @@ namespace CryptoExchange.Net /// List of ratelimiters to use /// public List RateLimiters { get; set; } = new List(); + + /// + /// What to do when a call would exceed the rate limit + /// + public RateLimitingBehaviour RateLimitingBehaviour { get; set; } = RateLimitingBehaviour.Wait; } } diff --git a/CryptoExchange.Net/RateLimiter/IRateLimiter.cs b/CryptoExchange.Net/RateLimiter/IRateLimiter.cs index 4a1d5c9..507ecd0 100644 --- a/CryptoExchange.Net/RateLimiter/IRateLimiter.cs +++ b/CryptoExchange.Net/RateLimiter/IRateLimiter.cs @@ -1,7 +1,9 @@ -namespace CryptoExchange.Net.RateLimiter +using CryptoExchange.Net.Objects; + +namespace CryptoExchange.Net.RateLimiter { public interface IRateLimiter { - double LimitRequest(string url); + CallResult LimitRequest(string url, RateLimitingBehaviour limitBehaviour); } } diff --git a/CryptoExchange.Net/RateLimiter/RateLimiterPerEndpoint.cs b/CryptoExchange.Net/RateLimiter/RateLimiterPerEndpoint.cs index 60e56f4..9ef576e 100644 --- a/CryptoExchange.Net/RateLimiter/RateLimiterPerEndpoint.cs +++ b/CryptoExchange.Net/RateLimiter/RateLimiterPerEndpoint.cs @@ -1,4 +1,5 @@ -using System; +using CryptoExchange.Net.Objects; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Threading; @@ -27,7 +28,7 @@ namespace CryptoExchange.Net.RateLimiter this.perTimePeriod = perTimePeriod; } - public double LimitRequest(string url) + public CallResult LimitRequest(string url, RateLimitingBehaviour limitingBehaviour) { int waitTime; RateLimitObject rlo; @@ -49,6 +50,9 @@ namespace CryptoExchange.Net.RateLimiter waitTime = rlo.GetWaitTime(DateTime.UtcNow, limitPerEndpoint, perTimePeriod); if (waitTime != 0) { + if(limitingBehaviour == RateLimitingBehaviour.Fail) + return new CallResult(waitTime, new RateLimitError($"endpoint limit of {limitPerEndpoint} reached on endpoint " + url)); + Thread.Sleep(Convert.ToInt32(waitTime)); waitTime += (int)sw.ElapsedMilliseconds; } @@ -56,7 +60,7 @@ namespace CryptoExchange.Net.RateLimiter rlo.Add(DateTime.UtcNow); } - return waitTime; + return new CallResult(waitTime, null); } } } diff --git a/CryptoExchange.Net/RateLimiter/RateLimiterTotal.cs b/CryptoExchange.Net/RateLimiter/RateLimiterTotal.cs index 683c2fd..d087685 100644 --- a/CryptoExchange.Net/RateLimiter/RateLimiterTotal.cs +++ b/CryptoExchange.Net/RateLimiter/RateLimiterTotal.cs @@ -1,4 +1,5 @@ -using System; +using CryptoExchange.Net.Objects; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; @@ -28,7 +29,7 @@ namespace CryptoExchange.Net.RateLimiter this.perTimePeriod = perTimePeriod; } - public double LimitRequest(string url) + public CallResult LimitRequest(string url, RateLimitingBehaviour limitBehaviour) { var sw = Stopwatch.StartNew(); lock (requestLock) @@ -43,6 +44,9 @@ namespace CryptoExchange.Net.RateLimiter waitTime = (history.First() - (checkTime - perTimePeriod)).TotalMilliseconds; if (waitTime > 0) { + if (limitBehaviour == RateLimitingBehaviour.Fail) + return new CallResult(waitTime, new RateLimitError($"total limit of {limit} reached")); + Thread.Sleep(Convert.ToInt32(waitTime)); waitTime += sw.ElapsedMilliseconds; } @@ -50,7 +54,7 @@ namespace CryptoExchange.Net.RateLimiter history.Add(DateTime.UtcNow); history.Sort(); - return waitTime; + return new CallResult(waitTime, null); } } }