mirror of
https://github.com/JKorf/CryptoExchange.Net.git
synced 2026-10-14 23:21:48 +00:00
Updated rate limiting margins
This commit is contained in:
@@ -3,6 +3,7 @@ using CryptoExchange.Net.RateLimiting;
|
|||||||
using CryptoExchange.Net.RateLimiting.Filters;
|
using CryptoExchange.Net.RateLimiting.Filters;
|
||||||
using CryptoExchange.Net.RateLimiting.Guards;
|
using CryptoExchange.Net.RateLimiting.Guards;
|
||||||
using CryptoExchange.Net.RateLimiting.Interfaces;
|
using CryptoExchange.Net.RateLimiting.Interfaces;
|
||||||
|
using CryptoExchange.Net.RateLimiting.Trackers;
|
||||||
using CryptoExchange.Net.UnitTests.Implementations;
|
using CryptoExchange.Net.UnitTests.Implementations;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using System;
|
using System;
|
||||||
@@ -364,5 +365,30 @@ namespace CryptoExchange.Net.UnitTests
|
|||||||
RateLimitingBehaviour.Fail, null, 1.0, default);
|
RateLimitingBehaviour.Fail, null, 1.0, default);
|
||||||
Assert.That(overHardLimit.Error, Is.TypeOf<ClientRateLimitError>());
|
Assert.That(overHardLimit.Error, Is.TypeOf<ClientRateLimitError>());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[TestCase(0.1, 10)]
|
||||||
|
[TestCase(1, 50)]
|
||||||
|
[TestCase(5, 250)]
|
||||||
|
[TestCase(60, 250)]
|
||||||
|
public void RateLimitSafetyMargin_DefaultIsProportionalAndCapped(double periodSeconds, int expectedMarginMilliseconds)
|
||||||
|
{
|
||||||
|
var margin = WindowTrackerHelpers.GetDefaultSafetyMargin(TimeSpan.FromSeconds(periodSeconds));
|
||||||
|
|
||||||
|
Assert.That(margin, Is.EqualTo(TimeSpan.FromMilliseconds(expectedMarginMilliseconds)));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void RateLimitGuard_ExplicitSafetyMarginIsUsed()
|
||||||
|
{
|
||||||
|
var guard = new RateLimitGuard(
|
||||||
|
RateLimitGuard.PerHost,
|
||||||
|
new LimitItemTypeFilter(RateLimitItemType.Request),
|
||||||
|
1,
|
||||||
|
TimeSpan.FromSeconds(1),
|
||||||
|
RateLimitWindowType.Sliding,
|
||||||
|
safetyMargin: TimeSpan.Zero);
|
||||||
|
|
||||||
|
Assert.That(guard.SafetyMargin, Is.EqualTo(TimeSpan.Zero));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -45,11 +45,18 @@ public interface ICallResult<T> : ICallResult
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Exchange call result
|
/// Exchange call result
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <typeparam name="T">Result data type</typeparam>
|
public interface IExchangeCallResult : ICallResult
|
||||||
public interface IExchangeCallResult<T> : ICallResult, ICallResult<T>
|
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Exchange name
|
/// Exchange name
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string Exchange { get; set; }
|
public string Exchange { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Exchange call result
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">Result data type</typeparam>
|
||||||
|
public interface IExchangeCallResult<T> : IExchangeCallResult, ICallResult<T>
|
||||||
|
{
|
||||||
}
|
}
|
||||||
@@ -36,6 +36,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards
|
|||||||
private readonly Dictionary<string, IWindowTracker> _trackers;
|
private readonly Dictionary<string, IWindowTracker> _trackers;
|
||||||
private readonly RateLimitWindowType _windowType;
|
private readonly RateLimitWindowType _windowType;
|
||||||
private readonly double? _decayRate;
|
private readonly double? _decayRate;
|
||||||
|
private readonly TimeSpan _safetyMargin;
|
||||||
private readonly int? _connectionWeight;
|
private readonly int? _connectionWeight;
|
||||||
private readonly Func<RequestDefinition, string?, string> _keySelector;
|
private readonly Func<RequestDefinition, string?, string> _keySelector;
|
||||||
private readonly SemaphoreSlim? _sharedGuardSemaphore;
|
private readonly SemaphoreSlim? _sharedGuardSemaphore;
|
||||||
@@ -54,6 +55,10 @@ namespace CryptoExchange.Net.RateLimiting.Guards
|
|||||||
/// The time period for the limit
|
/// The time period for the limit
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public TimeSpan TimeSpan { get; }
|
public TimeSpan TimeSpan { get; }
|
||||||
|
/// <summary>
|
||||||
|
/// Additional time to wait after a rate limit window expires to account for latency and timing differences
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan SafetyMargin => _safetyMargin;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this guard is shared between multiple gates
|
/// Whether this guard is shared between multiple gates
|
||||||
@@ -68,11 +73,12 @@ namespace CryptoExchange.Net.RateLimiting.Guards
|
|||||||
/// <param name="limit">Limit per period</param>
|
/// <param name="limit">Limit per period</param>
|
||||||
/// <param name="timeSpan">Timespan for the period</param>
|
/// <param name="timeSpan">Timespan for the period</param>
|
||||||
/// <param name="windowType">Type of rate limit window</param>
|
/// <param name="windowType">Type of rate limit window</param>
|
||||||
|
/// <param name="safetyMargin">Additional time to wait after a window expires, or null to use the default margin</param>
|
||||||
/// <param name="decayPerTimeSpan">The decay per timespan if windowType is DecayWindowTracker</param>
|
/// <param name="decayPerTimeSpan">The decay per timespan if windowType is DecayWindowTracker</param>
|
||||||
/// <param name="connectionWeight">The weight of a new connection</param>
|
/// <param name="connectionWeight">The weight of a new connection</param>
|
||||||
/// <param name="shared">Whether this guard is shared between multiple gates</param>
|
/// <param name="shared">Whether this guard is shared between multiple gates</param>
|
||||||
public RateLimitGuard(Func<RequestDefinition, string?, string> keySelector, IGuardFilter filter, int limit, TimeSpan timeSpan, RateLimitWindowType windowType, double? decayPerTimeSpan = null, int? connectionWeight = null, bool shared = false)
|
public RateLimitGuard(Func<RequestDefinition, string?, string> keySelector, IGuardFilter filter, int limit, TimeSpan timeSpan, RateLimitWindowType windowType, TimeSpan? safetyMargin = null, double? decayPerTimeSpan = null, int? connectionWeight = null, bool shared = false)
|
||||||
: this(keySelector, new[] { filter }, limit, timeSpan, windowType, decayPerTimeSpan, connectionWeight, shared)
|
: this(keySelector, new[] { filter }, limit, timeSpan, windowType, safetyMargin, decayPerTimeSpan, connectionWeight, shared)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -84,10 +90,11 @@ namespace CryptoExchange.Net.RateLimiting.Guards
|
|||||||
/// <param name="limit">Limit per period</param>
|
/// <param name="limit">Limit per period</param>
|
||||||
/// <param name="timeSpan">Timespan for the period</param>
|
/// <param name="timeSpan">Timespan for the period</param>
|
||||||
/// <param name="windowType">Type of rate limit window</param>
|
/// <param name="windowType">Type of rate limit window</param>
|
||||||
|
/// <param name="safetyMargin">Additional time to wait after a window expires, or null to use the default margin</param>
|
||||||
/// <param name="decayPerTimeSpan">The decay per timespan if windowType is DecayWindowTracker</param>
|
/// <param name="decayPerTimeSpan">The decay per timespan if windowType is DecayWindowTracker</param>
|
||||||
/// <param name="connectionWeight">The weight of a new connection</param>
|
/// <param name="connectionWeight">The weight of a new connection</param>
|
||||||
/// <param name="shared">Whether this guard is shared between multiple gates</param>
|
/// <param name="shared">Whether this guard is shared between multiple gates</param>
|
||||||
public RateLimitGuard(Func<RequestDefinition, string?, string> keySelector, IEnumerable<IGuardFilter> filters, int limit, TimeSpan timeSpan, RateLimitWindowType windowType, double? decayPerTimeSpan = null, int? connectionWeight = null, bool shared = false)
|
public RateLimitGuard(Func<RequestDefinition, string?, string> keySelector, IEnumerable<IGuardFilter> filters, int limit, TimeSpan timeSpan, RateLimitWindowType windowType, TimeSpan? safetyMargin = null, double? decayPerTimeSpan = null, int? connectionWeight = null, bool shared = false)
|
||||||
{
|
{
|
||||||
_filters = filters;
|
_filters = filters;
|
||||||
_trackers = new Dictionary<string, IWindowTracker>();
|
_trackers = new Dictionary<string, IWindowTracker>();
|
||||||
@@ -97,6 +104,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards
|
|||||||
SharedGuard = shared;
|
SharedGuard = shared;
|
||||||
_keySelector = keySelector;
|
_keySelector = keySelector;
|
||||||
_decayRate = decayPerTimeSpan;
|
_decayRate = decayPerTimeSpan;
|
||||||
|
_safetyMargin = safetyMargin ?? WindowTrackerHelpers.GetDefaultSafetyMargin(timeSpan); ;
|
||||||
_connectionWeight = connectionWeight;
|
_connectionWeight = connectionWeight;
|
||||||
|
|
||||||
if (SharedGuard)
|
if (SharedGuard)
|
||||||
@@ -205,9 +213,9 @@ namespace CryptoExchange.Net.RateLimiting.Guards
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected IWindowTracker CreateTracker()
|
protected IWindowTracker CreateTracker()
|
||||||
{
|
{
|
||||||
return _windowType == RateLimitWindowType.Sliding ? new SlidingWindowTracker(Limit, TimeSpan)
|
return _windowType == RateLimitWindowType.Sliding ? new SlidingWindowTracker(Limit, TimeSpan, _safetyMargin)
|
||||||
: _windowType == RateLimitWindowType.Fixed ? new FixedWindowTracker(Limit, TimeSpan)
|
: _windowType == RateLimitWindowType.Fixed ? new FixedWindowTracker(Limit, TimeSpan, _safetyMargin)
|
||||||
: _windowType == RateLimitWindowType.FixedAfterFirst ? new FixedAfterStartWindowTracker(Limit, TimeSpan) :
|
: _windowType == RateLimitWindowType.FixedAfterFirst ? new FixedAfterStartWindowTracker(Limit, TimeSpan, _safetyMargin) :
|
||||||
new DecayWindowTracker(Limit, TimeSpan, _decayRate ?? throw new InvalidOperationException("Decay rate not provided"));
|
new DecayWindowTracker(Limit, TimeSpan, _decayRate ?? throw new InvalidOperationException("Decay rate not provided"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards
|
|||||||
private readonly Dictionary<string, IWindowTracker> _trackers;
|
private readonly Dictionary<string, IWindowTracker> _trackers;
|
||||||
private readonly RateLimitWindowType _windowType;
|
private readonly RateLimitWindowType _windowType;
|
||||||
private readonly double? _decayRate;
|
private readonly double? _decayRate;
|
||||||
|
private readonly TimeSpan _safetyMargin;
|
||||||
private readonly int _limit;
|
private readonly int _limit;
|
||||||
private readonly TimeSpan _period;
|
private readonly TimeSpan _period;
|
||||||
private readonly Func<RequestDefinition, string?, string> _keySelector;
|
private readonly Func<RequestDefinition, string?, string> _keySelector;
|
||||||
@@ -34,6 +35,11 @@ namespace CryptoExchange.Net.RateLimiting.Guards
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public string Description => _windowType == RateLimitWindowType.Decay ? $"Endpoint limit of {_limit} with a decay rate of {_decayRate}" : $"Limit of {_limit} per {_period}";
|
public string Description => _windowType == RateLimitWindowType.Decay ? $"Endpoint limit of {_limit} with a decay rate of {_decayRate}" : $"Limit of {_limit} per {_period}";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Additional time to wait after a rate limit window expires to account for latency and timing differences
|
||||||
|
/// </summary>
|
||||||
|
public TimeSpan SafetyMargin => _safetyMargin;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ctor
|
/// ctor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -41,6 +47,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards
|
|||||||
int limit,
|
int limit,
|
||||||
TimeSpan period,
|
TimeSpan period,
|
||||||
RateLimitWindowType windowType,
|
RateLimitWindowType windowType,
|
||||||
|
TimeSpan? safetyMargin = null,
|
||||||
double? decayRate = null,
|
double? decayRate = null,
|
||||||
Func<RequestDefinition, string?, string>? keySelector = null)
|
Func<RequestDefinition, string?, string>? keySelector = null)
|
||||||
{
|
{
|
||||||
@@ -48,6 +55,7 @@ namespace CryptoExchange.Net.RateLimiting.Guards
|
|||||||
_period = period;
|
_period = period;
|
||||||
_windowType = windowType;
|
_windowType = windowType;
|
||||||
_decayRate = decayRate;
|
_decayRate = decayRate;
|
||||||
|
_safetyMargin = safetyMargin ?? WindowTrackerHelpers.GetDefaultSafetyMargin(period);
|
||||||
_keySelector = keySelector ?? Default;
|
_keySelector = keySelector ?? Default;
|
||||||
_trackers = new Dictionary<string, IWindowTracker>();
|
_trackers = new Dictionary<string, IWindowTracker>();
|
||||||
}
|
}
|
||||||
@@ -84,8 +92,8 @@ namespace CryptoExchange.Net.RateLimiting.Guards
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected IWindowTracker CreateTracker()
|
protected IWindowTracker CreateTracker()
|
||||||
{
|
{
|
||||||
return _windowType == RateLimitWindowType.Sliding ? new SlidingWindowTracker(_limit, _period)
|
return _windowType == RateLimitWindowType.Sliding ? new SlidingWindowTracker(_limit, _period, _safetyMargin)
|
||||||
: _windowType == RateLimitWindowType.Fixed ? new FixedWindowTracker(_limit, _period) :
|
: _windowType == RateLimitWindowType.Fixed ? new FixedWindowTracker(_limit, _period, _safetyMargin) :
|
||||||
new DecayWindowTracker(_limit, _period, _decayRate ?? throw new InvalidOperationException("Decay rate not provided"));
|
new DecayWindowTracker(_limit, _period, _decayRate ?? throw new InvalidOperationException("Decay rate not provided"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -17,15 +17,18 @@ namespace CryptoExchange.Net.RateLimiting.Trackers
|
|||||||
private int _currentWeight = 0;
|
private int _currentWeight = 0;
|
||||||
private DateTime? _nextReset;
|
private DateTime? _nextReset;
|
||||||
|
|
||||||
/// <summary>
|
private readonly TimeSpan _safetyMargin;
|
||||||
/// Additional wait time to apply to account for time offset between server and client
|
|
||||||
/// </summary>
|
|
||||||
private static TimeSpan _fixedWindowBuffer = TimeSpan.FromMilliseconds(1000);
|
|
||||||
|
|
||||||
public FixedAfterStartWindowTracker(int limit, TimeSpan period)
|
public FixedAfterStartWindowTracker(int limit, TimeSpan period)
|
||||||
|
: this(limit, period, WindowTrackerHelpers.GetDefaultSafetyMargin(period))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedAfterStartWindowTracker(int limit, TimeSpan period, TimeSpan safetyMargin)
|
||||||
{
|
{
|
||||||
Limit = limit;
|
Limit = limit;
|
||||||
TimePeriod = period;
|
TimePeriod = period;
|
||||||
|
_safetyMargin = safetyMargin;
|
||||||
_entries = new Queue<LimitEntry>();
|
_entries = new Queue<LimitEntry>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,7 +60,7 @@ namespace CryptoExchange.Net.RateLimiting.Trackers
|
|||||||
{
|
{
|
||||||
// Remove requests no longer in time period from the history
|
// Remove requests no longer in time period from the history
|
||||||
var checkTime = DateTime.UtcNow;
|
var checkTime = DateTime.UtcNow;
|
||||||
if (_nextReset != null && checkTime > _nextReset)
|
if (_nextReset != null && checkTime >= _nextReset + _safetyMargin)
|
||||||
RemoveBefore(_nextReset.Value);
|
RemoveBefore(_nextReset.Value);
|
||||||
|
|
||||||
if (Current == 0)
|
if (Current == 0)
|
||||||
@@ -130,7 +133,7 @@ namespace CryptoExchange.Net.RateLimiting.Trackers
|
|||||||
private TimeSpan DetermineWaitTime()
|
private TimeSpan DetermineWaitTime()
|
||||||
{
|
{
|
||||||
var checkTime = DateTime.UtcNow;
|
var checkTime = DateTime.UtcNow;
|
||||||
var result = (_nextReset!.Value - checkTime) + _fixedWindowBuffer;
|
var result = _nextReset!.Value + _safetyMargin - checkTime;
|
||||||
if (result < TimeSpan.Zero)
|
if (result < TimeSpan.Zero)
|
||||||
return TimeSpan.Zero;
|
return TimeSpan.Zero;
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -16,15 +16,18 @@ namespace CryptoExchange.Net.RateLimiting.Trackers
|
|||||||
private readonly Queue<LimitEntry> _entries;
|
private readonly Queue<LimitEntry> _entries;
|
||||||
private int _currentWeight = 0;
|
private int _currentWeight = 0;
|
||||||
|
|
||||||
/// <summary>
|
private readonly TimeSpan _safetyMargin;
|
||||||
/// Additional wait time to apply to account for time offset between server and client
|
|
||||||
/// </summary>
|
|
||||||
private static readonly TimeSpan _fixedWindowBuffer = TimeSpan.FromMilliseconds(1000);
|
|
||||||
|
|
||||||
public FixedWindowTracker(int limit, TimeSpan period)
|
public FixedWindowTracker(int limit, TimeSpan period)
|
||||||
|
: this(limit, period, WindowTrackerHelpers.GetDefaultSafetyMargin(period))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public FixedWindowTracker(int limit, TimeSpan period, TimeSpan safetyMargin)
|
||||||
{
|
{
|
||||||
Limit = limit;
|
Limit = limit;
|
||||||
TimePeriod = period;
|
TimePeriod = period;
|
||||||
|
_safetyMargin = safetyMargin;
|
||||||
_entries = new Queue<LimitEntry>();
|
_entries = new Queue<LimitEntry>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +59,11 @@ namespace CryptoExchange.Net.RateLimiting.Trackers
|
|||||||
{
|
{
|
||||||
// Remove requests no longer in time period from the history
|
// Remove requests no longer in time period from the history
|
||||||
var checkTime = DateTime.UtcNow;
|
var checkTime = DateTime.UtcNow;
|
||||||
RemoveBefore(checkTime.AddTicks(-(checkTime.Ticks % TimePeriod.Ticks)));
|
var startCurrentWindow = checkTime.AddTicks(-(checkTime.Ticks % TimePeriod.Ticks));
|
||||||
|
var resetBoundary = checkTime >= startCurrentWindow + _safetyMargin
|
||||||
|
? startCurrentWindow
|
||||||
|
: startCurrentWindow - TimePeriod;
|
||||||
|
RemoveBefore(resetBoundary);
|
||||||
|
|
||||||
if ((Current + weight) / (double)Limit > allowedRateRatio)
|
if ((Current + weight) / (double)Limit > allowedRateRatio)
|
||||||
{
|
{
|
||||||
@@ -124,8 +131,9 @@ namespace CryptoExchange.Net.RateLimiting.Trackers
|
|||||||
{
|
{
|
||||||
var checkTime = DateTime.UtcNow;
|
var checkTime = DateTime.UtcNow;
|
||||||
var startCurrentWindow = checkTime.AddTicks(-(checkTime.Ticks % TimePeriod.Ticks));
|
var startCurrentWindow = checkTime.AddTicks(-(checkTime.Ticks % TimePeriod.Ticks));
|
||||||
var wait = startCurrentWindow.Add(TimePeriod) - checkTime;
|
var result = checkTime < startCurrentWindow + _safetyMargin
|
||||||
var result = wait.Add(_fixedWindowBuffer);
|
? startCurrentWindow + _safetyMargin - checkTime
|
||||||
|
: startCurrentWindow + TimePeriod + _safetyMargin - checkTime;
|
||||||
if (result < TimeSpan.Zero)
|
if (result < TimeSpan.Zero)
|
||||||
return TimeSpan.Zero;
|
return TimeSpan.Zero;
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -16,15 +16,18 @@ namespace CryptoExchange.Net.RateLimiting.Trackers
|
|||||||
private readonly List<LimitEntry> _entries;
|
private readonly List<LimitEntry> _entries;
|
||||||
private int _currentWeight = 0;
|
private int _currentWeight = 0;
|
||||||
|
|
||||||
/// <summary>
|
private readonly TimeSpan _safetyMargin;
|
||||||
/// Additional wait time to apply to account for fluctuating request times
|
|
||||||
/// </summary>
|
|
||||||
private static readonly TimeSpan _slidingWindowBuffer = TimeSpan.FromMilliseconds(1000);
|
|
||||||
|
|
||||||
public SlidingWindowTracker(int limit, TimeSpan period)
|
public SlidingWindowTracker(int limit, TimeSpan period)
|
||||||
|
: this(limit, period, WindowTrackerHelpers.GetDefaultSafetyMargin(period))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public SlidingWindowTracker(int limit, TimeSpan period, TimeSpan safetyMargin)
|
||||||
{
|
{
|
||||||
Limit = limit;
|
Limit = limit;
|
||||||
TimePeriod = period;
|
TimePeriod = period;
|
||||||
|
_safetyMargin = safetyMargin;
|
||||||
_entries = new List<LimitEntry>();
|
_entries = new List<LimitEntry>();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,7 +59,7 @@ namespace CryptoExchange.Net.RateLimiting.Trackers
|
|||||||
public TimeSpan GetWaitTime(int weight, double allowedRateRatio)
|
public TimeSpan GetWaitTime(int weight, double allowedRateRatio)
|
||||||
{
|
{
|
||||||
// Remove requests no longer in time period from the history
|
// Remove requests no longer in time period from the history
|
||||||
RemoveBefore(DateTime.UtcNow - TimePeriod);
|
RemoveBefore(DateTime.UtcNow - TimePeriod - _safetyMargin);
|
||||||
|
|
||||||
if ((Current + weight) / (double)Limit > allowedRateRatio)
|
if ((Current + weight) / (double)Limit > allowedRateRatio)
|
||||||
{
|
{
|
||||||
@@ -99,7 +102,7 @@ namespace CryptoExchange.Net.RateLimiting.Trackers
|
|||||||
{
|
{
|
||||||
for (var i = 0; i < _entries.Count; i++)
|
for (var i = 0; i < _entries.Count; i++)
|
||||||
{
|
{
|
||||||
if (_entries[i].Timestamp < time)
|
if (_entries[i].Timestamp <= time)
|
||||||
{
|
{
|
||||||
var entry = _entries[i];
|
var entry = _entries[i];
|
||||||
_entries.Remove(entry);
|
_entries.Remove(entry);
|
||||||
@@ -127,7 +130,7 @@ namespace CryptoExchange.Net.RateLimiting.Trackers
|
|||||||
removedWeight += entry.Weight;
|
removedWeight += entry.Weight;
|
||||||
if (removedWeight >= weightToRemove)
|
if (removedWeight >= weightToRemove)
|
||||||
{
|
{
|
||||||
var result = entry.Timestamp + TimePeriod + _slidingWindowBuffer - DateTime.UtcNow;
|
var result = entry.Timestamp + TimePeriod + _safetyMargin - DateTime.UtcNow;
|
||||||
if (result < TimeSpan.Zero)
|
if (result < TimeSpan.Zero)
|
||||||
return TimeSpan.Zero;
|
return TimeSpan.Zero;
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
using System;
|
||||||
|
|
||||||
|
namespace CryptoExchange.Net.RateLimiting.Trackers
|
||||||
|
{
|
||||||
|
internal static class WindowTrackerHelpers
|
||||||
|
{
|
||||||
|
private static readonly TimeSpan _minimumSafetyMargin = TimeSpan.FromMilliseconds(10);
|
||||||
|
private static readonly TimeSpan _maximumSafetyMargin = TimeSpan.FromMilliseconds(250);
|
||||||
|
|
||||||
|
public static TimeSpan GetDefaultSafetyMargin(TimeSpan period)
|
||||||
|
{
|
||||||
|
var margin = TimeSpan.FromTicks(period.Ticks / 20);
|
||||||
|
if (margin < _minimumSafetyMargin)
|
||||||
|
return _minimumSafetyMargin;
|
||||||
|
if (margin > _maximumSafetyMargin)
|
||||||
|
return _maximumSafetyMargin;
|
||||||
|
return margin;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -23,7 +23,7 @@ namespace CryptoExchange.Net.SharedApis
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request">Request info</param>
|
/// <param name="request">Request info</param>
|
||||||
/// <param name="ct">Cancellation token</param>
|
/// <param name="ct">Cancellation token</param>
|
||||||
Task<IExchangeCallResultSharedFuturesTriggerOrder>> GetFuturesTriggerOrderAsync(GetOrderRequest request, CancellationToken ct = default);
|
Task<IExchangeCallResult<SharedFuturesTriggerOrder>> GetFuturesTriggerOrderAsync(GetOrderRequest request, CancellationToken ct = default);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
Reference in New Issue
Block a user