mirror of
https://github.com/JKorf/CryptoExchange.Net.git
synced 2026-08-13 17:33:02 +00:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 12ca45050f | |||
| eaf092a334 | |||
| 33c0fb26a7 | |||
| dabbb5c868 |
@@ -56,7 +56,7 @@ namespace CryptoExchange.Net.UnitTests.TestImplementations
|
|||||||
var request = new Mock<IRequest>();
|
var request = new Mock<IRequest>();
|
||||||
request.Setup(c => c.Uri).Returns(new Uri("http://www.test.com"));
|
request.Setup(c => c.Uri).Returns(new Uri("http://www.test.com"));
|
||||||
request.Setup(c => c.GetResponseAsync(It.IsAny<CancellationToken>())).Returns(Task.FromResult(response.Object));
|
request.Setup(c => c.GetResponseAsync(It.IsAny<CancellationToken>())).Returns(Task.FromResult(response.Object));
|
||||||
request.Setup(c => c.SetContent(It.IsAny<string>(), It.IsAny<string>())).Callback(new Action<string, string>((content, type) => { request.Setup(r => r.Content).Returns(content); }));
|
request.Setup(c => c.SetContent(It.IsAny<string>(), It.IsAny<Encoding>(), It.IsAny<string>())).Callback(new Action<string, Encoding, string>((content, encoding, type) => { request.Setup(r => r.Content).Returns(content); }));
|
||||||
request.Setup(c => c.AddHeader(It.IsAny<string>(), It.IsAny<string>())).Callback<string, string>((key, val) => headers.Add(key, new string[] { val }));
|
request.Setup(c => c.AddHeader(It.IsAny<string>(), It.IsAny<string>())).Callback<string, string>((key, val) => headers.Add(key, new string[] { val }));
|
||||||
request.Setup(c => c.GetHeaders()).Returns(() => headers);
|
request.Setup(c => c.GetHeaders()).Returns(() => headers);
|
||||||
|
|
||||||
|
|||||||
@@ -77,6 +77,16 @@ namespace CryptoExchange.Net.Clients
|
|||||||
{ new HttpMethod("Patch"), HttpMethodParameterPosition.InBody },
|
{ new HttpMethod("Patch"), HttpMethodParameterPosition.InBody },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Encoding/charset for the ContentType header
|
||||||
|
/// </summary>
|
||||||
|
protected Encoding? RequestBodyContentEncoding { get; set; } = Encoding.UTF8;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether to omit the ContentType header if there is no content
|
||||||
|
/// </summary>
|
||||||
|
protected bool OmitContentTypeHeaderWithoutContent { get; set; } = false;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public new RestExchangeOptions ClientOptions => (RestExchangeOptions)base.ClientOptions;
|
public new RestExchangeOptions ClientOptions => (RestExchangeOptions)base.ClientOptions;
|
||||||
|
|
||||||
@@ -374,7 +384,11 @@ namespace CryptoExchange.Net.Clients
|
|||||||
if (!string.IsNullOrEmpty(queryString) && !queryString.StartsWith("?"))
|
if (!string.IsNullOrEmpty(queryString) && !queryString.StartsWith("?"))
|
||||||
queryString = $"?{queryString}";
|
queryString = $"?{queryString}";
|
||||||
|
|
||||||
var uri = new Uri(baseAddress.AppendPath(definition.Path) + queryString);
|
var path = baseAddress.AppendPath(definition.Path);
|
||||||
|
if (definition.ForcePathEndWithSlash == true && !path.EndsWith("/"))
|
||||||
|
path += "/";
|
||||||
|
|
||||||
|
var uri = new Uri(path + queryString);
|
||||||
var request = RequestFactory.Create(ClientOptions.HttpVersion, definition.Method, uri, requestId);
|
var request = RequestFactory.Create(ClientOptions.HttpVersion, definition.Method, uri, requestId);
|
||||||
request.Accept = MessageHandler.AcceptHeader;
|
request.Accept = MessageHandler.AcceptHeader;
|
||||||
|
|
||||||
@@ -398,14 +412,14 @@ namespace CryptoExchange.Net.Clients
|
|||||||
var bodyContent = requestConfiguration.GetBodyContent();
|
var bodyContent = requestConfiguration.GetBodyContent();
|
||||||
if (bodyContent != null)
|
if (bodyContent != null)
|
||||||
{
|
{
|
||||||
request.SetContent(bodyContent, contentType);
|
request.SetContent(bodyContent, RequestBodyContentEncoding, contentType);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (requestConfiguration.BodyParameters != null && requestConfiguration.BodyParameters.Count != 0)
|
if (requestConfiguration.BodyParameters != null && requestConfiguration.BodyParameters.Count != 0)
|
||||||
WriteParamBody(request, requestConfiguration.BodyParameters, contentType);
|
WriteParamBody(request, requestConfiguration.BodyParameters, contentType);
|
||||||
else
|
else if (OmitContentTypeHeaderWithoutContent != true)
|
||||||
request.SetContent(RequestBodyEmptyContent, contentType);
|
request.SetContent(RequestBodyEmptyContent, RequestBodyContentEncoding, contentType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -646,13 +660,13 @@ namespace CryptoExchange.Net.Clients
|
|||||||
stringData = stringSerializer.Serialize(value);
|
stringData = stringSerializer.Serialize(value);
|
||||||
else
|
else
|
||||||
stringData = stringSerializer.Serialize(parameters);
|
stringData = stringSerializer.Serialize(parameters);
|
||||||
request.SetContent(stringData, contentType);
|
request.SetContent(stringData, RequestBodyContentEncoding, contentType);
|
||||||
}
|
}
|
||||||
else if (contentType == Constants.FormContentHeader)
|
else if (contentType == Constants.FormContentHeader)
|
||||||
{
|
{
|
||||||
// Write the parameters as form data in the body
|
// Write the parameters as form data in the body
|
||||||
var stringData = parameters.ToFormData();
|
var stringData = parameters.ToFormData();
|
||||||
request.SetContent(stringData, contentType);
|
request.SetContent(stringData, RequestBodyContentEncoding, contentType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
<PackageId>CryptoExchange.Net</PackageId>
|
<PackageId>CryptoExchange.Net</PackageId>
|
||||||
<Authors>JKorf</Authors>
|
<Authors>JKorf</Authors>
|
||||||
<Description>CryptoExchange.Net is a base library which is used to implement different cryptocurrency (exchange) API's. It provides a standardized way of implementing different API's, which results in a very similar experience for users of the API implementations.</Description>
|
<Description>CryptoExchange.Net is a base library which is used to implement different cryptocurrency (exchange) API's. It provides a standardized way of implementing different API's, which results in a very similar experience for users of the API implementations.</Description>
|
||||||
<PackageVersion>10.7.1</PackageVersion>
|
<PackageVersion>10.8.0</PackageVersion>
|
||||||
<AssemblyVersion>10.7.1</AssemblyVersion>
|
<AssemblyVersion>10.8.0</AssemblyVersion>
|
||||||
<FileVersion>10.7.1</FileVersion>
|
<FileVersion>10.8.0</FileVersion>
|
||||||
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
||||||
<PackageTags>OKX;OKX.Net;Mexc;Mexc.Net;Kucoin;Kucoin.Net;Kraken;Kraken.Net;Huobi;Huobi.Net;CoinEx;CoinEx.Net;Bybit;Bybit.Net;Bitget;Bitget.Net;Bitfinex;Bitfinex.Net;Binance;Binance.Net;CryptoCurrency;CryptoCurrency Exchange;CryptoExchange.Net</PackageTags>
|
<PackageTags>OKX;OKX.Net;Mexc;Mexc.Net;Kucoin;Kucoin.Net;Kraken;Kraken.Net;Huobi;Huobi.Net;CoinEx;CoinEx.Net;Bybit;Bybit.Net;Bitget;Bitget.Net;Bitfinex;Bitfinex.Net;Binance;Binance.Net;CryptoCurrency;CryptoCurrency Exchange;CryptoExchange.Net</PackageTags>
|
||||||
<RepositoryType>git</RepositoryType>
|
<RepositoryType>git</RepositoryType>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.Net.Http;
|
using System.Net.Http;
|
||||||
using System.Net.Http.Headers;
|
using System.Net.Http.Headers;
|
||||||
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
@@ -43,9 +44,7 @@ namespace CryptoExchange.Net.Interfaces
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Set string content
|
/// Set string content
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="data"></param>
|
void SetContent(string data, Encoding? encoding, string contentType);
|
||||||
/// <param name="contentType"></param>
|
|
||||||
void SetContent(string data, string contentType);
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Add a header to the request
|
/// Add a header to the request
|
||||||
|
|||||||
@@ -72,6 +72,11 @@ namespace CryptoExchange.Net.Objects
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public int? ConnectionId { get; set; }
|
public int? ConnectionId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the endpoint path should always include the trailing `/`
|
||||||
|
/// </summary>
|
||||||
|
public bool? ForcePathEndWithSlash { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ctor
|
/// ctor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -47,6 +47,7 @@ namespace CryptoExchange.Net.Objects
|
|||||||
/// <param name="arraySerialization">Array serialization type</param>
|
/// <param name="arraySerialization">Array serialization type</param>
|
||||||
/// <param name="preventCaching">Prevent request caching</param>
|
/// <param name="preventCaching">Prevent request caching</param>
|
||||||
/// <param name="tryParseOnNonSuccess">Try parse the response even when status is not success</param>
|
/// <param name="tryParseOnNonSuccess">Try parse the response even when status is not success</param>
|
||||||
|
/// <param name="forcePathEndWithSlash">Force trailing `/`</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public RequestDefinition GetOrCreate(
|
public RequestDefinition GetOrCreate(
|
||||||
HttpMethod method,
|
HttpMethod method,
|
||||||
@@ -59,8 +60,9 @@ namespace CryptoExchange.Net.Objects
|
|||||||
HttpMethodParameterPosition? parameterPosition = null,
|
HttpMethodParameterPosition? parameterPosition = null,
|
||||||
ArrayParametersSerialization? arraySerialization = null,
|
ArrayParametersSerialization? arraySerialization = null,
|
||||||
bool? preventCaching = null,
|
bool? preventCaching = null,
|
||||||
bool? tryParseOnNonSuccess = null)
|
bool? tryParseOnNonSuccess = null,
|
||||||
=> GetOrCreate(method + path, method, path, rateLimitGate, weight, authenticated, limitGuard, requestBodyFormat, parameterPosition, arraySerialization, preventCaching, tryParseOnNonSuccess);
|
bool? forcePathEndWithSlash = null)
|
||||||
|
=> GetOrCreate(method + path, method, path, rateLimitGate, weight, authenticated, limitGuard, requestBodyFormat, parameterPosition, arraySerialization, preventCaching, tryParseOnNonSuccess, forcePathEndWithSlash);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Get a definition if it is already in the cache or create a new definition and add it to the cache
|
/// Get a definition if it is already in the cache or create a new definition and add it to the cache
|
||||||
@@ -77,6 +79,7 @@ namespace CryptoExchange.Net.Objects
|
|||||||
/// <param name="arraySerialization">Array serialization type</param>
|
/// <param name="arraySerialization">Array serialization type</param>
|
||||||
/// <param name="preventCaching">Prevent request caching</param>
|
/// <param name="preventCaching">Prevent request caching</param>
|
||||||
/// <param name="tryParseOnNonSuccess">Try parse the response even when status is not success</param>
|
/// <param name="tryParseOnNonSuccess">Try parse the response even when status is not success</param>
|
||||||
|
/// <param name="forcePathEndWithSlash">Force trailing `/`</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public RequestDefinition GetOrCreate(
|
public RequestDefinition GetOrCreate(
|
||||||
string identifier,
|
string identifier,
|
||||||
@@ -90,7 +93,8 @@ namespace CryptoExchange.Net.Objects
|
|||||||
HttpMethodParameterPosition? parameterPosition = null,
|
HttpMethodParameterPosition? parameterPosition = null,
|
||||||
ArrayParametersSerialization? arraySerialization = null,
|
ArrayParametersSerialization? arraySerialization = null,
|
||||||
bool? preventCaching = null,
|
bool? preventCaching = null,
|
||||||
bool? tryParseOnNonSuccess = null)
|
bool? tryParseOnNonSuccess = null,
|
||||||
|
bool? forcePathEndWithSlash = null)
|
||||||
{
|
{
|
||||||
|
|
||||||
if (!_definitions.TryGetValue(identifier, out var def))
|
if (!_definitions.TryGetValue(identifier, out var def))
|
||||||
@@ -105,7 +109,8 @@ namespace CryptoExchange.Net.Objects
|
|||||||
RequestBodyFormat = requestBodyFormat,
|
RequestBodyFormat = requestBodyFormat,
|
||||||
ParameterPosition = parameterPosition,
|
ParameterPosition = parameterPosition,
|
||||||
PreventCaching = preventCaching ?? false,
|
PreventCaching = preventCaching ?? false,
|
||||||
TryParseOnNonSuccess = tryParseOnNonSuccess ?? false
|
TryParseOnNonSuccess = tryParseOnNonSuccess ?? false,
|
||||||
|
ForcePathEndWithSlash = forcePathEndWithSlash ?? false
|
||||||
};
|
};
|
||||||
_definitions.TryAdd(identifier, def);
|
_definitions.TryAdd(identifier, def);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,9 +19,6 @@ namespace CryptoExchange.Net.Requests
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create request object for web request
|
/// Create request object for web request
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request"></param>
|
|
||||||
/// <param name="client"></param>
|
|
||||||
/// <param name="requestId"></param>
|
|
||||||
public Request(HttpRequestMessage request, HttpClient client, int requestId)
|
public Request(HttpRequestMessage request, HttpClient client, int requestId)
|
||||||
{
|
{
|
||||||
_httpClient = client;
|
_httpClient = client;
|
||||||
@@ -55,10 +52,12 @@ namespace CryptoExchange.Net.Requests
|
|||||||
public int RequestId { get; }
|
public int RequestId { get; }
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public void SetContent(string data, string contentType)
|
public void SetContent(string data, Encoding? encoding, string contentType)
|
||||||
{
|
{
|
||||||
Content = data;
|
Content = data;
|
||||||
_request.Content = new StringContent(data, Encoding.UTF8, contentType);
|
_request.Content = new StringContent(data, encoding ?? Encoding.UTF8, contentType);
|
||||||
|
if (encoding == null)
|
||||||
|
_request.Content.Headers.ContentType!.CharSet = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ namespace CryptoExchange.Net.Testing.Implementations
|
|||||||
Content = Encoding.UTF8.GetString(data);
|
Content = Encoding.UTF8.GetString(data);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SetContent(string data, string contentType)
|
public void SetContent(string data, Encoding? encoding, string contentType)
|
||||||
{
|
{
|
||||||
Content = data;
|
Content = data;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
|
|||||||
private readonly ExchangeParameters? _exchangeParameters;
|
private readonly ExchangeParameters? _exchangeParameters;
|
||||||
private readonly bool _requiresSymbolParameterOpenOrders;
|
private readonly bool _requiresSymbolParameterOpenOrders;
|
||||||
private readonly Dictionary<string, int> _openOrderNotReturnedTimes = new();
|
private readonly Dictionary<string, int> _openOrderNotReturnedTimes = new();
|
||||||
|
private readonly TimeSpan _pollOverlapPeriod = TimeSpan.FromSeconds(3);
|
||||||
|
|
||||||
internal event Func<UpdateSource, SharedUserTrade[], Task>? OnTradeUpdate;
|
internal event Func<UpdateSource, SharedUserTrade[], Task>? OnTradeUpdate;
|
||||||
|
|
||||||
@@ -355,17 +356,17 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
|
|||||||
DateTime? fromTime = null;
|
DateTime? fromTime = null;
|
||||||
string? source = null;
|
string? source = null;
|
||||||
|
|
||||||
// Use the last timestamp we we received data from the websocket as state should be correct at that time. 1 seconds buffer
|
// Use the last timestamp we we received data from the websocket as state should be correct at that time
|
||||||
if (_lastDataTimeBeforeDisconnect.HasValue && (fromTime == null || fromTime > _lastDataTimeBeforeDisconnect.Value))
|
if (_lastDataTimeBeforeDisconnect.HasValue && (fromTime == null || fromTime > _lastDataTimeBeforeDisconnect.Value))
|
||||||
{
|
{
|
||||||
fromTime = _lastDataTimeBeforeDisconnect.Value.AddSeconds(-1);
|
fromTime = _lastDataTimeBeforeDisconnect.Value.Add(-_pollOverlapPeriod);
|
||||||
source = "LastDataTimeBeforeDisconnect";
|
source = "LastDataTimeBeforeDisconnect";
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we've previously polled use that timestamp to request data from
|
// If we've previously polled use that timestamp to request data from
|
||||||
if (_lastPollTime.HasValue && (fromTime == null || _lastPollTime.Value > fromTime))
|
if (_lastPollTime.HasValue && (fromTime == null || _lastPollTime.Value > fromTime))
|
||||||
{
|
{
|
||||||
fromTime = _lastPollTime;
|
fromTime = _lastPollTime.Value.Add(-_pollOverlapPeriod);
|
||||||
source = "LastPollTime";
|
source = "LastPollTime";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -378,7 +379,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
|
|||||||
{
|
{
|
||||||
// Could be improved by only requesting the specific open orders if there are only a few that would be better than trying to request a long
|
// Could be improved by only requesting the specific open orders if there are only a few that would be better than trying to request a long
|
||||||
// history if the open order is far back
|
// history if the open order is far back
|
||||||
fromTime = trackedOrdersMinOpenTime.Value.AddMilliseconds(-1);
|
fromTime = trackedOrdersMinOpenTime.Value.AddSeconds(-1);
|
||||||
source = "OpenOrder";
|
source = "OpenOrder";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -388,7 +389,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
|
|||||||
source = "StartTime";
|
source = "StartTime";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DateTime.UtcNow - fromTime < TimeSpan.FromSeconds(1))
|
if (DateTime.UtcNow - fromTime < TimeSpan.FromSeconds(5))
|
||||||
{
|
{
|
||||||
// Set it to at least 5 seconds in the past to prevent issues when local time isn't in sync
|
// Set it to at least 5 seconds in the past to prevent issues when local time isn't in sync
|
||||||
fromTime = DateTime.UtcNow.AddSeconds(-5);
|
fromTime = DateTime.UtcNow.AddSeconds(-5);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
|
|||||||
private readonly IFuturesOrderRestClient _restClient;
|
private readonly IFuturesOrderRestClient _restClient;
|
||||||
private readonly IUserTradeSocketClient? _socketClient;
|
private readonly IUserTradeSocketClient? _socketClient;
|
||||||
private readonly ExchangeParameters? _exchangeParameters;
|
private readonly ExchangeParameters? _exchangeParameters;
|
||||||
|
private readonly TimeSpan _pollOverlapPeriod = TimeSpan.FromSeconds(3);
|
||||||
|
|
||||||
internal Func<string[]>? GetTrackedOrderIds { get; set; }
|
internal Func<string[]>? GetTrackedOrderIds { get; set; }
|
||||||
|
|
||||||
@@ -106,22 +107,22 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
|
|||||||
|
|
||||||
private DateTime? GetTradesRequestStartTime()
|
private DateTime? GetTradesRequestStartTime()
|
||||||
{
|
{
|
||||||
// Determine the timestamp from which we need to check order status
|
// Determine the timestamp from which we need to request trades from
|
||||||
// Use the timestamp we last know the correct state of the data
|
// Use the timestamp we last know the correct state of the data
|
||||||
DateTime? fromTime = null;
|
DateTime? fromTime = null;
|
||||||
string? source = null;
|
string? source = null;
|
||||||
|
|
||||||
// Use the last timestamp we we received data from the websocket as state should be correct at that time. 1 seconds buffer
|
// Use the last timestamp we we received data from the websocket as state should be correct at that time.
|
||||||
if (_lastDataTimeBeforeDisconnect.HasValue && (fromTime == null || fromTime > _lastDataTimeBeforeDisconnect.Value))
|
if (_lastDataTimeBeforeDisconnect.HasValue && (fromTime == null || fromTime > _lastDataTimeBeforeDisconnect.Value))
|
||||||
{
|
{
|
||||||
fromTime = _lastDataTimeBeforeDisconnect.Value.AddSeconds(-1);
|
fromTime = _lastDataTimeBeforeDisconnect.Value.Add(-_pollOverlapPeriod);
|
||||||
source = "LastDataTimeBeforeDisconnect";
|
source = "LastDataTimeBeforeDisconnect";
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we've previously polled use that timestamp to request data from
|
// If we've previously polled use that timestamp to request data from
|
||||||
if (_lastPollTime.HasValue && (fromTime == null || _lastPollTime.Value > fromTime))
|
if (_lastPollTime.HasValue && (fromTime == null || _lastPollTime.Value > fromTime))
|
||||||
{
|
{
|
||||||
fromTime = _lastPollTime;
|
fromTime = _lastPollTime.Value.Add(-_pollOverlapPeriod);
|
||||||
source = "LastPollTime";
|
source = "LastPollTime";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,7 +133,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
|
|||||||
}
|
}
|
||||||
|
|
||||||
var now = DateTime.UtcNow;
|
var now = DateTime.UtcNow;
|
||||||
if (now - fromTime < TimeSpan.FromSeconds(1))
|
if (now - fromTime < TimeSpan.FromSeconds(5))
|
||||||
{
|
{
|
||||||
// Set it to at least 5 seconds in the past to prevent issues when local time isn't in sync
|
// Set it to at least 5 seconds in the past to prevent issues when local time isn't in sync
|
||||||
fromTime = DateTime.UtcNow.AddSeconds(-5);
|
fromTime = DateTime.UtcNow.AddSeconds(-5);
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
|
|||||||
private readonly ExchangeParameters? _exchangeParameters;
|
private readonly ExchangeParameters? _exchangeParameters;
|
||||||
private readonly bool _requiresSymbolParameterOpenOrders;
|
private readonly bool _requiresSymbolParameterOpenOrders;
|
||||||
private readonly Dictionary<string, int> _openOrderNotReturnedTimes = new();
|
private readonly Dictionary<string, int> _openOrderNotReturnedTimes = new();
|
||||||
|
private readonly TimeSpan _pollOverlapPeriod = TimeSpan.FromSeconds(3);
|
||||||
|
|
||||||
internal event Func<UpdateSource, SharedUserTrade[], Task>? OnTradeUpdate;
|
internal event Func<UpdateSource, SharedUserTrade[], Task>? OnTradeUpdate;
|
||||||
|
|
||||||
@@ -366,17 +367,17 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
|
|||||||
DateTime? fromTime = null;
|
DateTime? fromTime = null;
|
||||||
string? source = null;
|
string? source = null;
|
||||||
|
|
||||||
// Use the last timestamp we we received data from the websocket as state should be correct at that time. 1 seconds buffer
|
// Use the last timestamp we we received data from the websocket as state should be correct at that time.
|
||||||
if (_lastDataTimeBeforeDisconnect.HasValue && (fromTime == null || fromTime > _lastDataTimeBeforeDisconnect.Value))
|
if (_lastDataTimeBeforeDisconnect.HasValue && (fromTime == null || fromTime > _lastDataTimeBeforeDisconnect.Value))
|
||||||
{
|
{
|
||||||
fromTime = _lastDataTimeBeforeDisconnect.Value.AddSeconds(-1);
|
fromTime = _lastDataTimeBeforeDisconnect.Value.Add(-_pollOverlapPeriod);
|
||||||
source = "LastDataTimeBeforeDisconnect";
|
source = "LastDataTimeBeforeDisconnect";
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we've previously polled use that timestamp to request data from
|
// If we've previously polled use that timestamp to request data from
|
||||||
if (_lastPollTime.HasValue && (fromTime == null || _lastPollTime.Value > fromTime))
|
if (_lastPollTime.HasValue && (fromTime == null || _lastPollTime.Value > fromTime))
|
||||||
{
|
{
|
||||||
fromTime = _lastPollTime;
|
fromTime = _lastPollTime.Value.Add(-_pollOverlapPeriod);
|
||||||
source = "LastPollTime";
|
source = "LastPollTime";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,7 +390,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
|
|||||||
{
|
{
|
||||||
// Could be improved by only requesting the specific open orders if there are only a few that would be better than trying to request a long
|
// Could be improved by only requesting the specific open orders if there are only a few that would be better than trying to request a long
|
||||||
// history if the open order is far back
|
// history if the open order is far back
|
||||||
fromTime = trackedOrdersMinOpenTime.Value.AddMilliseconds(-1);
|
fromTime = trackedOrdersMinOpenTime.Value.AddSeconds(-1);
|
||||||
source = "OpenOrder";
|
source = "OpenOrder";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -399,7 +400,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
|
|||||||
source = "StartTime";
|
source = "StartTime";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DateTime.UtcNow - fromTime < TimeSpan.FromSeconds(1))
|
if (DateTime.UtcNow - fromTime < TimeSpan.FromSeconds(5))
|
||||||
{
|
{
|
||||||
// Set it to at least 5 seconds in the past to prevent issues when local time isn't in sync
|
// Set it to at least 5 seconds in the past to prevent issues when local time isn't in sync
|
||||||
fromTime = DateTime.UtcNow.AddSeconds(-5);
|
fromTime = DateTime.UtcNow.AddSeconds(-5);
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
|
|||||||
private readonly ISpotOrderRestClient _restClient;
|
private readonly ISpotOrderRestClient _restClient;
|
||||||
private readonly IUserTradeSocketClient? _socketClient;
|
private readonly IUserTradeSocketClient? _socketClient;
|
||||||
private readonly ExchangeParameters? _exchangeParameters;
|
private readonly ExchangeParameters? _exchangeParameters;
|
||||||
|
private readonly TimeSpan _pollOverlapPeriod = TimeSpan.FromSeconds(3);
|
||||||
|
|
||||||
internal Func<string[]>? GetTrackedOrderIds { get; set; }
|
internal Func<string[]>? GetTrackedOrderIds { get; set; }
|
||||||
|
|
||||||
@@ -103,22 +104,22 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
|
|||||||
|
|
||||||
private DateTime? GetTradesRequestStartTime()
|
private DateTime? GetTradesRequestStartTime()
|
||||||
{
|
{
|
||||||
// Determine the timestamp from which we need to check order status
|
// Determine the timestamp from which we need to request trades from
|
||||||
// Use the timestamp we last know the correct state of the data
|
// Use the timestamp we last know the correct state of the data
|
||||||
DateTime? fromTime = null;
|
DateTime? fromTime = null;
|
||||||
string? source = null;
|
string? source = null;
|
||||||
|
|
||||||
// Use the last timestamp we we received data from the websocket as state should be correct at that time. 1 seconds buffer
|
// Use the last timestamp we we received data from the websocket as state should be correct at that time.
|
||||||
if (_lastDataTimeBeforeDisconnect.HasValue && (fromTime == null || fromTime > _lastDataTimeBeforeDisconnect.Value))
|
if (_lastDataTimeBeforeDisconnect.HasValue && (fromTime == null || fromTime > _lastDataTimeBeforeDisconnect.Value))
|
||||||
{
|
{
|
||||||
fromTime = _lastDataTimeBeforeDisconnect.Value.AddSeconds(-1);
|
fromTime = _lastDataTimeBeforeDisconnect.Value.Add(-_pollOverlapPeriod);
|
||||||
source = "LastDataTimeBeforeDisconnect";
|
source = "LastDataTimeBeforeDisconnect";
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we've previously polled use that timestamp to request data from
|
// If we've previously polled use that timestamp to request data from
|
||||||
if (_lastPollTime.HasValue && (fromTime == null || _lastPollTime.Value > fromTime))
|
if (_lastPollTime.HasValue && (fromTime == null || _lastPollTime.Value > fromTime))
|
||||||
{
|
{
|
||||||
fromTime = _lastPollTime;
|
fromTime = _lastPollTime.Value.Add(-_pollOverlapPeriod);
|
||||||
source = "LastPollTime";
|
source = "LastPollTime";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -128,7 +129,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
|
|||||||
source = "StartTime";
|
source = "StartTime";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DateTime.UtcNow - fromTime < TimeSpan.FromSeconds(1))
|
if (DateTime.UtcNow - fromTime < TimeSpan.FromSeconds(5))
|
||||||
{
|
{
|
||||||
// Set it to at least 5 seconds in the past to prevent issues when local time isn't in sync
|
// Set it to at least 5 seconds in the past to prevent issues when local time isn't in sync
|
||||||
fromTime = DateTime.UtcNow.AddSeconds(-5);
|
fromTime = DateTime.UtcNow.AddSeconds(-5);
|
||||||
|
|||||||
@@ -67,6 +67,14 @@ Make a one time donation in a crypto currency of your choice. If you prefer to d
|
|||||||
Alternatively, sponsor me on Github using [Github Sponsors](https://github.com/sponsors/JKorf).
|
Alternatively, sponsor me on Github using [Github Sponsors](https://github.com/sponsors/JKorf).
|
||||||
|
|
||||||
## Release notes
|
## Release notes
|
||||||
|
* Version 10.8.0 - 06 Mar 2026
|
||||||
|
* Added `RequestBodyContentEncoding` and `OmitContentTypeHeaderWithoutContent` config to RestApiClient
|
||||||
|
* Added `ForcePathEndWithSlash` setting to RequestDefinition
|
||||||
|
* Added `encoding` parameter to SetContent on REST Request
|
||||||
|
|
||||||
|
* Version 10.7.2 - 02 Mar 2026
|
||||||
|
* Added small overlap in UserDataTracker polling logic to account for API endpoints not immediately having the data available
|
||||||
|
|
||||||
* Version 10.7.1 - 25 Feb 2026
|
* Version 10.7.1 - 25 Feb 2026
|
||||||
* Fixed deadlock scenario in websocket connection when subscribe and handling message concurrently
|
* Fixed deadlock scenario in websocket connection when subscribe and handling message concurrently
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user