diff --git a/CryptoExchange.Net/Interfaces/IRateLimiter.cs b/CryptoExchange.Net/Interfaces/IRateLimiter.cs index 735f3ec..df6b494 100644 --- a/CryptoExchange.Net/Interfaces/IRateLimiter.cs +++ b/CryptoExchange.Net/Interfaces/IRateLimiter.cs @@ -1,4 +1,4 @@ -using CryptoExchange.Net.Objects; +using CryptoExchange.Net.Objects; namespace CryptoExchange.Net.Interfaces { @@ -13,7 +13,8 @@ namespace CryptoExchange.Net.Interfaces /// /// /// + /// /// - CallResult LimitRequest(RestClient client, string url, RateLimitingBehaviour limitBehaviour); + CallResult LimitRequest(RestClient client, string url, RateLimitingBehaviour limitBehaviour, int credits=1); } } diff --git a/CryptoExchange.Net/RateLimiter/RateLimiterAPIKey.cs b/CryptoExchange.Net/RateLimiter/RateLimiterAPIKey.cs index 0b2dddf..8bfe2f6 100644 --- a/CryptoExchange.Net/RateLimiter/RateLimiterAPIKey.cs +++ b/CryptoExchange.Net/RateLimiter/RateLimiterAPIKey.cs @@ -34,7 +34,7 @@ namespace CryptoExchange.Net.RateLimiter } /// - public CallResult LimitRequest(RestClient client, string url, RateLimitingBehaviour limitBehaviour) + public CallResult LimitRequest(RestClient client, string url, RateLimitingBehaviour limitBehaviour, int credits = 1) { if(client.authProvider?.Credentials?.Key == null) return new CallResult(0, null); diff --git a/CryptoExchange.Net/RateLimiter/RateLimiterCredit.cs b/CryptoExchange.Net/RateLimiter/RateLimiterCredit.cs new file mode 100644 index 0000000..ef6cc2b --- /dev/null +++ b/CryptoExchange.Net/RateLimiter/RateLimiterCredit.cs @@ -0,0 +1,65 @@ +using CryptoExchange.Net.Interfaces; +using CryptoExchange.Net.Objects; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Threading; + +namespace CryptoExchange.Net.RateLimiter +{ + /// + /// Limits the amount of requests per time period to a certain limit, counts the total amount of requests. + /// + public class RateLimiterCredit : IRateLimiter + { + internal List history = new List(); + + private readonly int limit; + private readonly TimeSpan perTimePeriod; + private readonly object requestLock = new object(); + + /// + /// Create a new RateLimiterTotal. This rate limiter limits the amount of requests per time period to a certain limit, counts the total amount of requests. + /// + /// The amount to limit to + /// The time period over which the limit counts + public RateLimiterCredit(int limit, TimeSpan perTimePeriod) + { + this.limit = limit; + this.perTimePeriod = perTimePeriod; + } + + /// + public CallResult LimitRequest(RestClient client, string url, RateLimitingBehaviour limitBehaviour, int credits = 1) + { + var sw = Stopwatch.StartNew(); + lock (requestLock) + { + sw.Stop(); + double waitTime = 0; + var checkTime = DateTime.UtcNow; + history.RemoveAll(d => d < checkTime - perTimePeriod); + + if (history.Count >= limit) + { + 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; + } + } + + for (int i = 1; i <= credits; i++) + history.Add(DateTime.UtcNow); + + history.Sort(); + return new CallResult(waitTime, null); + } + } + } +} diff --git a/CryptoExchange.Net/RateLimiter/RateLimiterPerEndpoint.cs b/CryptoExchange.Net/RateLimiter/RateLimiterPerEndpoint.cs index fb62900..f137ae3 100644 --- a/CryptoExchange.Net/RateLimiter/RateLimiterPerEndpoint.cs +++ b/CryptoExchange.Net/RateLimiter/RateLimiterPerEndpoint.cs @@ -30,7 +30,7 @@ namespace CryptoExchange.Net.RateLimiter } /// - public CallResult LimitRequest(RestClient client, string url, RateLimitingBehaviour limitingBehaviour) + public CallResult LimitRequest(RestClient client, string url, RateLimitingBehaviour limitingBehaviour, int credits = 1) { int waitTime; RateLimitObject rlo; diff --git a/CryptoExchange.Net/RateLimiter/RateLimiterTotal.cs b/CryptoExchange.Net/RateLimiter/RateLimiterTotal.cs index 85919e6..a5d1792 100644 --- a/CryptoExchange.Net/RateLimiter/RateLimiterTotal.cs +++ b/CryptoExchange.Net/RateLimiter/RateLimiterTotal.cs @@ -31,7 +31,7 @@ namespace CryptoExchange.Net.RateLimiter } /// - public CallResult LimitRequest(RestClient client, string url, RateLimitingBehaviour limitBehaviour) + public CallResult LimitRequest(RestClient client, string url, RateLimitingBehaviour limitBehaviour, int credits = 1) { var sw = Stopwatch.StartNew(); lock (requestLock) diff --git a/CryptoExchange.Net/RestClient.cs b/CryptoExchange.Net/RestClient.cs index d3372fa..bc8bdd5 100644 --- a/CryptoExchange.Net/RestClient.cs +++ b/CryptoExchange.Net/RestClient.cs @@ -1,4 +1,4 @@ -using System; +using System; using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -172,7 +172,7 @@ namespace CryptoExchange.Net /// [return: NotNull] protected virtual async Task> SendRequest(Uri uri, HttpMethod method, CancellationToken cancellationToken, - Dictionary? parameters = null, bool signed = false, bool checkResult = true, PostParameters? postPosition = null, ArrayParametersSerialization? arraySerialization = null) where T : class + Dictionary? parameters = null, bool signed = false, bool checkResult = true, PostParameters? postPosition = null, ArrayParametersSerialization? arraySerialization = null, int credits=1) where T : class { var requestId = NextId(); log.Write(LogVerbosity.Debug, $"[{requestId}] Creating request for " + uri); @@ -185,7 +185,7 @@ namespace CryptoExchange.Net var request = ConstructRequest(uri, method, parameters, signed, postPosition ?? postParametersPosition, arraySerialization ?? this.arraySerialization, requestId); foreach (var limiter in RateLimiters) { - var limitResult = limiter.LimitRequest(this, uri.AbsolutePath, RateLimitBehaviour); + var limitResult = limiter.LimitRequest(this, uri.AbsolutePath, RateLimitBehaviour, credits); if (!limitResult.Success) { log.Write(LogVerbosity.Debug, $"[{requestId}] Request {uri.AbsolutePath} failed because of rate limit");