diff --git a/CryptoExchange.Net.UnitTests/RestClientTests.cs b/CryptoExchange.Net.UnitTests/RestClientTests.cs index 555cf70d..d17a4560 100644 --- a/CryptoExchange.Net.UnitTests/RestClientTests.cs +++ b/CryptoExchange.Net.UnitTests/RestClientTests.cs @@ -96,7 +96,7 @@ namespace CryptoExchange.Net.UnitTests // assert ClassicAssert.IsFalse(result.Success); Assert.That(result.Error != null); - Assert.That(result.Error is ServerError); + Assert.That(result.Error is DeserializeError); Assert.That(result.Error.Message.Contains(response)); } diff --git a/CryptoExchange.Net/ExchangeHelpers.cs b/CryptoExchange.Net/ExchangeHelpers.cs index 9cd859b4..1e200a49 100644 --- a/CryptoExchange.Net/ExchangeHelpers.cs +++ b/CryptoExchange.Net/ExchangeHelpers.cs @@ -4,6 +4,7 @@ using CryptoExchange.Net.SharedApis; using System; using System.Collections.Generic; using System.Globalization; +using System.Linq; using System.Runtime.CompilerServices; using System.Security.Cryptography; using System.Threading; @@ -310,11 +311,11 @@ namespace CryptoExchange.Net /// The request parameters /// Cancellation token /// - public static async IAsyncEnumerable> ExecutePages(Func>> paginatedFunc, U request, [EnumeratorCancellation]CancellationToken ct = default) + public static async IAsyncEnumerable> ExecutePages(Func>> paginatedFunc, U request, [EnumeratorCancellation]CancellationToken ct = default) { var result = new List(); ExchangeWebResult batch; - INextPageToken? nextPageToken = null; + PageRequest? nextPageToken = null; while (true) { batch = await paginatedFunc(request, nextPageToken, ct).ConfigureAwait(false); @@ -323,12 +324,42 @@ namespace CryptoExchange.Net break; result.AddRange(batch.Data); - nextPageToken = batch.NextPageToken; + nextPageToken = batch.NextPageRequest; if (nextPageToken == null) break; } } + /// + /// Apply filters to the data set + /// + /// Type + /// Data set + /// Time selector for the data + /// Start time filter + /// End time filter + /// Data direction + public static IEnumerable ApplyFilter( + IEnumerable data, + Func timeSelector, + DateTime? startTime, + DateTime? endTime, + DataDirection direction) + { + if (direction == DataDirection.Ascending) + data = data.OrderBy(timeSelector); + else + data = data.OrderByDescending(timeSelector); + + if (startTime != null) + data = data.Where(x => timeSelector(x) >= startTime.Value); + + if (endTime != null) + data = data.Where(x => timeSelector(x) < endTime.Value); + + return data; + } + /// /// Apply the rules (price and quantity step size and decimals precision, min/max quantity) from the symbol to the quantity and price /// diff --git a/CryptoExchange.Net/Objects/CallResult.cs b/CryptoExchange.Net/Objects/CallResult.cs index 74f79ac6..a4a02e13 100644 --- a/CryptoExchange.Net/Objects/CallResult.cs +++ b/CryptoExchange.Net/Objects/CallResult.cs @@ -531,11 +531,11 @@ namespace CryptoExchange.Net.Objects /// The exchange /// Trade mode the result applies to /// Data - /// Next page token + /// Next page request /// - public ExchangeWebResult AsExchangeResult(string exchange, TradingMode tradeMode, [AllowNull] K data, INextPageToken? nextPageToken = null) + public ExchangeWebResult AsExchangeResult(string exchange, TradingMode tradeMode, [AllowNull] K data, PageRequest? nextPageRequest = null) { - return new ExchangeWebResult(exchange, tradeMode, As(data), nextPageToken); + return new ExchangeWebResult(exchange, tradeMode, As(data), nextPageRequest); } /// @@ -545,11 +545,11 @@ namespace CryptoExchange.Net.Objects /// The exchange /// Trade modes the result applies to /// Data - /// Next page token + /// Next page token /// - public ExchangeWebResult AsExchangeResult(string exchange, TradingMode[]? tradeModes, [AllowNull] K data, INextPageToken? nextPageToken = null) + public ExchangeWebResult AsExchangeResult(string exchange, TradingMode[]? tradeModes, [AllowNull] K data, PageRequest? nextPageRequest = null) { - return new ExchangeWebResult(exchange, tradeModes, As(data), nextPageToken); + return new ExchangeWebResult(exchange, tradeModes, As(data), nextPageRequest); } /// diff --git a/CryptoExchange.Net/RateLimiting/Filters/PathStartFilter.cs b/CryptoExchange.Net/RateLimiting/Filters/PathStartFilter.cs index ba68b8b6..1001b663 100644 --- a/CryptoExchange.Net/RateLimiting/Filters/PathStartFilter.cs +++ b/CryptoExchange.Net/RateLimiting/Filters/PathStartFilter.cs @@ -17,11 +17,11 @@ namespace CryptoExchange.Net.RateLimiting.Filters /// public PathStartFilter(string path) { - _path = path; + _path = path.TrimStart('/'); } /// public bool Passes(RateLimitItemType type, RequestDefinition definition, string host, string? apiKey) - => definition.Path.StartsWith(_path, StringComparison.OrdinalIgnoreCase); + => definition.Path.TrimStart('/').StartsWith(_path, StringComparison.OrdinalIgnoreCase); } } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/INextPageToken.cs b/CryptoExchange.Net/SharedApis/Interfaces/INextPageToken.cs deleted file mode 100644 index ceb1e73d..00000000 --- a/CryptoExchange.Net/SharedApis/Interfaces/INextPageToken.cs +++ /dev/null @@ -1,111 +0,0 @@ -using System; - -namespace CryptoExchange.Net.SharedApis -{ - /// - /// A token which a request can use to retrieve the next page if there are more pages in the result set - /// - public interface INextPageToken - { - } - - /// - /// A datetime offset token - /// - public record DateTimeToken: INextPageToken - { - /// - /// Last result time - /// - public DateTime LastTime { get; set; } - - /// - /// ctor - /// - public DateTimeToken(DateTime timestamp) - { - LastTime = timestamp; - } - } - - /// - /// A current page index token - /// - public record PageToken: INextPageToken - { - /// - /// The next page index - /// - public int Page { get; set; } - /// - /// Page size - /// - public int PageSize { get; set; } - - /// - /// ctor - /// - public PageToken(int page, int pageSize) - { - Page = page; - PageSize = pageSize; - } - } - - /// - /// A id offset token - /// - public record FromIdToken : INextPageToken - { - /// - /// The last id from previous result - /// - public string FromToken { get; set; } - - /// - /// ctor - /// - public FromIdToken(string fromToken) - { - FromToken = fromToken; - } - } - - /// - /// A cursor token - /// - public record CursorToken : INextPageToken - { - /// - /// The next page cursor - /// - public string Cursor { get; set; } - - /// - /// ctor - /// - public CursorToken(string cursor) - { - Cursor = cursor; - } - } - - /// - /// A result offset token - /// - public record OffsetToken : INextPageToken - { - /// - /// Offset in the result set - /// - public int Offset { get; set; } - - /// - /// ctor - /// - public OffsetToken(int offset) - { - Offset = offset; - } - } -} diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFundingRateRestClient.cs b/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFundingRateRestClient.cs index 057cb367..0798554b 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFundingRateRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFundingRateRestClient.cs @@ -16,8 +16,8 @@ namespace CryptoExchange.Net.SharedApis /// Get funding rate records /// /// Request info - /// The pagination token from the previous request to continue pagination + /// The pagination request from the previous request result `NextPageRequest` property to continue pagination /// Cancellation token - Task> GetFundingRateHistoryAsync(GetFundingRateHistoryRequest request, INextPageToken? nextPageToken = null, CancellationToken ct = default); + Task> GetFundingRateHistoryAsync(GetFundingRateHistoryRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); } } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFuturesOrderRestClient.cs b/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFuturesOrderRestClient.cs index 5c3af62b..39632ddd 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFuturesOrderRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IFuturesOrderRestClient.cs @@ -73,14 +73,14 @@ namespace CryptoExchange.Net.SharedApis /// /// Spot get closed orders request options /// - PaginatedEndpointOptions GetClosedFuturesOrdersOptions { get; } + GetClosedOrdersOptions GetClosedFuturesOrdersOptions { get; } /// /// Get info on closed futures orders /// /// Request info - /// The pagination token from the previous request to continue pagination + /// The pagination request from the previous request result `NextPageRequest` property to continue pagination /// Cancellation token - Task> GetClosedFuturesOrdersAsync(GetClosedOrdersRequest request, INextPageToken? nextPageToken = null, CancellationToken ct = default); + Task> GetClosedFuturesOrdersAsync(GetClosedOrdersRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); /// /// Futures get order trades request options @@ -96,14 +96,14 @@ namespace CryptoExchange.Net.SharedApis /// /// Futures user trades request options /// - PaginatedEndpointOptions GetFuturesUserTradesOptions { get; } + GetUserTradesOptions GetFuturesUserTradesOptions { get; } /// /// Get futures user trade records /// /// Request info - /// The pagination token from the previous request to continue pagination + /// The pagination request from the previous request result `NextPageRequest` property to continue pagination /// Cancellation token - Task> GetFuturesUserTradesAsync(GetUserTradesRequest request, INextPageToken? nextPageToken = null, CancellationToken ct = default); + Task> GetFuturesUserTradesAsync(GetUserTradesRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); /// /// Futures cancel order request options diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IIndexPriceKlineRestClient.cs b/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IIndexPriceKlineRestClient.cs index 4c2299f2..85b78a27 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IIndexPriceKlineRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IIndexPriceKlineRestClient.cs @@ -16,8 +16,8 @@ namespace CryptoExchange.Net.SharedApis /// Get index price kline/candlestick data /// /// Request info - /// The pagination token from the previous request to continue pagination + /// The pagination request from the previous request result `NextPageRequest` property to continue pagination /// Cancellation token - Task> GetIndexPriceKlinesAsync(GetKlinesRequest request, INextPageToken? nextPageToken = null, CancellationToken ct = default); + Task> GetIndexPriceKlinesAsync(GetKlinesRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); } } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IMarkPriceKlineRestClient.cs b/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IMarkPriceKlineRestClient.cs index 0b025280..a749c166 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IMarkPriceKlineRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IMarkPriceKlineRestClient.cs @@ -16,8 +16,8 @@ namespace CryptoExchange.Net.SharedApis /// Get mark price kline/candlestick data /// /// Request info - /// The pagination token from the previous request to continue pagination + /// The pagination request from the previous request result `NextPageRequest` property to continue pagination /// Cancellation token - Task> GetMarkPriceKlinesAsync(GetKlinesRequest request, INextPageToken? nextPageToken = null, CancellationToken ct = default); + Task> GetMarkPriceKlinesAsync(GetKlinesRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); } } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IPositionHistoryRestClient.cs b/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IPositionHistoryRestClient.cs index 59059275..d51b8c2a 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IPositionHistoryRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Interfaces/Rest/Futures/IPositionHistoryRestClient.cs @@ -16,8 +16,8 @@ namespace CryptoExchange.Net.SharedApis /// Get position history /// /// Request info - /// The pagination token from the previous request to continue pagination + /// The pagination request from the previous request result `NextPageRequest` property to continue pagination /// Cancellation token - Task> GetPositionHistoryAsync(GetPositionHistoryRequest request, INextPageToken? nextPageToken = null, CancellationToken ct = default); + Task> GetPositionHistoryAsync(GetPositionHistoryRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); } } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IDepositRestClient.cs b/CryptoExchange.Net/SharedApis/Interfaces/Rest/IDepositRestClient.cs index c50758e1..9e1af83f 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IDepositRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Interfaces/Rest/IDepositRestClient.cs @@ -30,9 +30,9 @@ namespace CryptoExchange.Net.SharedApis /// Get deposit records /// /// Request info - /// The pagination token from the previous request to continue pagination + /// The pagination request from the previous request result `NextPageRequest` property to continue pagination /// Cancellation token /// - Task> GetDepositsAsync(GetDepositsRequest request, INextPageToken? nextPageToken = null, CancellationToken ct = default); + Task> GetDepositsAsync(GetDepositsRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); } } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IKlineRestClient.cs b/CryptoExchange.Net/SharedApis/Interfaces/Rest/IKlineRestClient.cs index a71ac1ea..81e1c515 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IKlineRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Interfaces/Rest/IKlineRestClient.cs @@ -17,9 +17,9 @@ namespace CryptoExchange.Net.SharedApis /// Get kline/candlestick data /// /// Request info - /// The pagination token from the previous request to continue pagination + /// The pagination request from the previous request result `NextPageRequest` property to continue pagination /// Cancellation token /// - Task> GetKlinesAsync(GetKlinesRequest request, INextPageToken? nextPageToken = null, CancellationToken ct = default); + Task> GetKlinesAsync(GetKlinesRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); } } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/ITradeHistoryRestClient.cs b/CryptoExchange.Net/SharedApis/Interfaces/Rest/ITradeHistoryRestClient.cs index c20d9ce4..327db6e3 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/ITradeHistoryRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Interfaces/Rest/ITradeHistoryRestClient.cs @@ -17,9 +17,9 @@ namespace CryptoExchange.Net.SharedApis /// Get public trade history /// /// Request info - /// The pagination token from the previous request to continue pagination + /// The pagination request from the previous request result `NextPageRequest` property to continue pagination /// Cancellation token /// - Task> GetTradeHistoryAsync(GetTradeHistoryRequest request, INextPageToken? nextPageToken = null, CancellationToken ct = default); + Task> GetTradeHistoryAsync(GetTradeHistoryRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); } } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IWithdrawalRestClient .cs b/CryptoExchange.Net/SharedApis/Interfaces/Rest/IWithdrawalRestClient .cs index c2f36000..fd21b0b2 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/IWithdrawalRestClient .cs +++ b/CryptoExchange.Net/SharedApis/Interfaces/Rest/IWithdrawalRestClient .cs @@ -17,9 +17,9 @@ namespace CryptoExchange.Net.SharedApis /// Get withdrawal records /// /// Request info - /// The pagination token from the previous request to continue pagination + /// The pagination request from the previous request result `NextPageRequest` property to continue pagination /// Cancellation token /// - Task> GetWithdrawalsAsync(GetWithdrawalsRequest request, INextPageToken? nextPageToken = null, CancellationToken ct = default); + Task> GetWithdrawalsAsync(GetWithdrawalsRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); } } diff --git a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Spot/ISpotOrderRestClient.cs b/CryptoExchange.Net/SharedApis/Interfaces/Rest/Spot/ISpotOrderRestClient.cs index c4d1a321..6a1bd1ba 100644 --- a/CryptoExchange.Net/SharedApis/Interfaces/Rest/Spot/ISpotOrderRestClient.cs +++ b/CryptoExchange.Net/SharedApis/Interfaces/Rest/Spot/ISpotOrderRestClient.cs @@ -72,14 +72,14 @@ namespace CryptoExchange.Net.SharedApis /// /// Spot get closed orders request options /// - PaginatedEndpointOptions GetClosedSpotOrdersOptions { get; } + GetClosedOrdersOptions GetClosedSpotOrdersOptions { get; } /// /// Get info on closed spot orders /// /// Request info - /// The pagination token from the previous request to continue pagination + /// The pagination request from the previous request result `NextPageRequest` property to continue pagination /// Cancellation token - Task> GetClosedSpotOrdersAsync(GetClosedOrdersRequest request, INextPageToken? nextPageToken = null, CancellationToken ct = default); + Task> GetClosedSpotOrdersAsync(GetClosedOrdersRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); /// /// Spot get order trades request options @@ -95,14 +95,14 @@ namespace CryptoExchange.Net.SharedApis /// /// Spot user trades request options /// - PaginatedEndpointOptions GetSpotUserTradesOptions { get; } + GetUserTradesOptions GetSpotUserTradesOptions { get; } /// /// Get spot user trade records /// /// Request info - /// The pagination token from the previous request to continue pagination + /// The pagination request from the previous request result `NextPageRequest` property to continue pagination /// Cancellation token - Task> GetSpotUserTradesAsync(GetUserTradesRequest request, INextPageToken? nextPageToken = null, CancellationToken ct = default); + Task> GetSpotUserTradesAsync(GetUserTradesRequest request, PageRequest? nextPageToken = null, CancellationToken ct = default); /// /// Spot cancel order request options diff --git a/CryptoExchange.Net/SharedApis/Models/ExchangeWebResult.cs b/CryptoExchange.Net/SharedApis/Models/ExchangeWebResult.cs index 7861d212..6819f79a 100644 --- a/CryptoExchange.Net/SharedApis/Models/ExchangeWebResult.cs +++ b/CryptoExchange.Net/SharedApis/Models/ExchangeWebResult.cs @@ -24,9 +24,9 @@ namespace CryptoExchange.Net.SharedApis public TradingMode[]? DataTradeMode { get; } /// - /// Token to retrieve the next page with + /// Next page request, can be passed to the next request on the same endpoint to get the next page /// - public INextPageToken? NextPageToken { get; } + public PageRequest? NextPageRequest { get; } /// /// ctor @@ -46,7 +46,7 @@ namespace CryptoExchange.Net.SharedApis string exchange, TradingMode dataTradeMode, WebCallResult result, - INextPageToken? nextPageToken = null) : + PageRequest? nextPageToken = null) : base(result.ResponseStatusCode, result.HttpVersion, result.ResponseHeaders, @@ -64,7 +64,7 @@ namespace CryptoExchange.Net.SharedApis { DataTradeMode = new[] { dataTradeMode }; Exchange = exchange; - NextPageToken = nextPageToken; + NextPageRequest = nextPageToken; } /// @@ -74,7 +74,7 @@ namespace CryptoExchange.Net.SharedApis string exchange, TradingMode[]? dataTradeModes, WebCallResult result, - INextPageToken? nextPageToken = null) : + PageRequest? nextPageRequest = null) : base(result.ResponseStatusCode, result.HttpVersion, result.ResponseHeaders, @@ -92,7 +92,7 @@ namespace CryptoExchange.Net.SharedApis { DataTradeMode = dataTradeModes; Exchange = exchange; - NextPageToken = nextPageToken; + NextPageRequest = nextPageRequest; } /// @@ -115,7 +115,7 @@ namespace CryptoExchange.Net.SharedApis ResultDataSource dataSource, [AllowNull] T data, Error? error, - INextPageToken? nextPageToken = null) : base( + PageRequest? nextPageToken = null) : base( code, httpVersion, responseHeaders, @@ -133,7 +133,7 @@ namespace CryptoExchange.Net.SharedApis { DataTradeMode = dataTradeModes; Exchange = exchange; - NextPageToken = nextPageToken; + NextPageRequest = nextPageToken; } /// @@ -144,7 +144,7 @@ namespace CryptoExchange.Net.SharedApis /// public new ExchangeWebResult As([AllowNull] K data) { - return new ExchangeWebResult(Exchange, DataTradeMode, ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, DataSource, data, Error, NextPageToken); + return new ExchangeWebResult(Exchange, DataTradeMode, ResponseStatusCode, HttpVersion, ResponseHeaders, ResponseTime, ResponseLength, OriginalData, RequestId, RequestUrl, RequestBody, RequestMethod, RequestHeaders, DataSource, data, Error, NextPageRequest); } /// diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetClosedOrdersOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetClosedOrdersOptions.cs index 1727806e..12f2f942 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetClosedOrdersOptions.cs +++ b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetClosedOrdersOptions.cs @@ -1,4 +1,5 @@ using CryptoExchange.Net.Objects; +using System; using System.Text; namespace CryptoExchange.Net.SharedApis @@ -8,24 +9,36 @@ namespace CryptoExchange.Net.SharedApis /// public class GetClosedOrdersOptions : PaginatedEndpointOptions { - /// - /// Whether the start/end time filter is supported - /// - public bool TimeFilterSupported { get; set; } - /// /// ctor /// - public GetClosedOrdersOptions(SharedPaginationSupport paginationType, bool timeFilterSupported, int maxLimit) : base(paginationType, timeFilterSupported, maxLimit, true) + public GetClosedOrdersOptions(bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit) + : base(supportsAscending, supportsDescending, timeFilterSupported, maxLimit, true) { - TimeFilterSupported = timeFilterSupported; } /// public override Error? ValidateRequest(string exchange, GetClosedOrdersRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes) { - if (!TimeFilterSupported && request.StartTime != null) - return ArgumentError.Invalid(nameof(GetClosedOrdersRequest.StartTime), $"Time filter is not supported"); + if (!SupportsAscending && request.Direction == DataDirection.Ascending) + return ArgumentError.Invalid(nameof(GetWithdrawalsRequest.Direction), $"Ascending direction is not supported"); + + if (!SupportsDescending && request.Direction == DataDirection.Descending) + return ArgumentError.Invalid(nameof(GetWithdrawalsRequest.Direction), $"Descending direction is not supported"); + + if (MaxAge.HasValue && request.StartTime < DateTime.UtcNow.Add(-MaxAge.Value)) + return ArgumentError.Invalid(nameof(GetKlinesRequest.StartTime), $"Only the most recent {MaxAge} period data is available"); + + if (!TimePeriodFilterSupport) + { + // When going descending we can still allow startTime filter to limit the results + var now = DateTime.UtcNow; + if ((request.Direction != DataDirection.Descending && request.StartTime != null) + || (request.EndTime != null && now - request.EndTime > TimeSpan.FromSeconds(5))) + { + return ArgumentError.Invalid(nameof(GetDepositsRequest.StartTime), $"Time filter is not supported"); + } + } return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes); } @@ -34,7 +47,7 @@ namespace CryptoExchange.Net.SharedApis public override string ToString(string exchange) { var sb = new StringBuilder(base.ToString(exchange)); - sb.AppendLine($"Time filter supported: {TimeFilterSupported}"); + sb.AppendLine($"Time filter supported: {TimePeriodFilterSupport}"); return sb.ToString(); } } diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetDepositsOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetDepositsOptions.cs index fee678b2..88e39052 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetDepositsOptions.cs +++ b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetDepositsOptions.cs @@ -1,4 +1,5 @@ using CryptoExchange.Net.Objects; +using System; using System.Text; namespace CryptoExchange.Net.SharedApis @@ -8,24 +9,36 @@ namespace CryptoExchange.Net.SharedApis /// public class GetDepositsOptions : PaginatedEndpointOptions { - /// - /// Whether the start/end time filter is supported - /// - public bool TimeFilterSupported { get; set; } - /// /// ctor /// - public GetDepositsOptions(SharedPaginationSupport paginationType, bool timeFilterSupported, int maxLimit) : base(paginationType, timeFilterSupported, maxLimit, true) + public GetDepositsOptions(bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit) + : base(supportsAscending, supportsDescending, timeFilterSupported, maxLimit, true) { - TimeFilterSupported = timeFilterSupported; } /// public override Error? ValidateRequest(string exchange, GetDepositsRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes) { - if (!TimeFilterSupported && request.StartTime != null) - return ArgumentError.Invalid(nameof(GetDepositsRequest.StartTime), $"Time filter is not supported"); + if (!SupportsAscending && request.Direction == DataDirection.Ascending) + return ArgumentError.Invalid(nameof(GetWithdrawalsRequest.Direction), $"Ascending direction is not supported"); + + if (!SupportsDescending && request.Direction == DataDirection.Descending) + return ArgumentError.Invalid(nameof(GetWithdrawalsRequest.Direction), $"Descending direction is not supported"); + + if (MaxAge.HasValue && request.StartTime < DateTime.UtcNow.Add(-MaxAge.Value)) + return ArgumentError.Invalid(nameof(GetKlinesRequest.StartTime), $"Only the most recent {MaxAge} period data is available"); + + if (!TimePeriodFilterSupport) + { + // When going descending we can still allow startTime filter to limit the results + var now = DateTime.UtcNow; + if ((request.Direction != DataDirection.Descending && request.StartTime != null) + || (request.EndTime != null && now - request.EndTime > TimeSpan.FromSeconds(5))) + { + return ArgumentError.Invalid(nameof(GetDepositsRequest.StartTime), $"Time filter is not supported"); + } + } return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes); } @@ -34,7 +47,7 @@ namespace CryptoExchange.Net.SharedApis public override string ToString(string exchange) { var sb = new StringBuilder(base.ToString(exchange)); - sb.AppendLine($"Time filter supported: {TimeFilterSupported}"); + sb.AppendLine($"Time filter supported: {TimePeriodFilterSupport}"); return sb.ToString(); } } diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFundingRateHistoryOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFundingRateHistoryOptions.cs index be857c25..114b6456 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFundingRateHistoryOptions.cs +++ b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetFundingRateHistoryOptions.cs @@ -1,4 +1,8 @@ -namespace CryptoExchange.Net.SharedApis +using CryptoExchange.Net.Objects; +using System; +using System.Text; + +namespace CryptoExchange.Net.SharedApis { /// /// Options for requesting funding rate history @@ -8,8 +12,43 @@ /// /// ctor /// - public GetFundingRateHistoryOptions(SharedPaginationSupport paginationType, bool timeFilterSupported, int maxLimit, bool needsAuthentication) : base(paginationType, timeFilterSupported, maxLimit, needsAuthentication) + public GetFundingRateHistoryOptions(bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit, bool needsAuthentication) + : base(supportsAscending, supportsDescending, timeFilterSupported, maxLimit, needsAuthentication) { } + + /// + public override Error? ValidateRequest(string exchange, GetFundingRateHistoryRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes) + { + if (!SupportsAscending && request.Direction == DataDirection.Ascending) + return ArgumentError.Invalid(nameof(GetWithdrawalsRequest.Direction), $"Ascending direction is not supported"); + + if (!SupportsDescending && request.Direction == DataDirection.Descending) + return ArgumentError.Invalid(nameof(GetWithdrawalsRequest.Direction), $"Descending direction is not supported"); + + if (MaxAge.HasValue && request.StartTime < DateTime.UtcNow.Add(-MaxAge.Value)) + return ArgumentError.Invalid(nameof(GetKlinesRequest.StartTime), $"Only the most recent {MaxAge} period data is available"); + + if (!TimePeriodFilterSupport) + { + // When going descending we can still allow startTime filter to limit the results + var now = DateTime.UtcNow; + if ((request.Direction != DataDirection.Descending && request.StartTime != null) + || (request.EndTime != null && now - request.EndTime > TimeSpan.FromSeconds(5))) + { + return ArgumentError.Invalid(nameof(GetDepositsRequest.StartTime), $"Time filter is not supported"); + } + } + + return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes); + } + + /// + public override string ToString(string exchange) + { + var sb = new StringBuilder(base.ToString(exchange)); + sb.AppendLine($"Time filter supported: {TimePeriodFilterSupport}"); + return sb.ToString(); + } } } diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetKlinesOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetKlinesOptions.cs index c1712591..3cf3e5d9 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetKlinesOptions.cs +++ b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetKlinesOptions.cs @@ -18,15 +18,12 @@ namespace CryptoExchange.Net.SharedApis /// Max number of data points which can be requested /// public int? MaxTotalDataPoints { get; set; } - /// - /// The max age of the data that can be requested - /// - public TimeSpan? MaxAge { get; set; } /// /// ctor /// - public GetKlinesOptions(SharedPaginationSupport paginationType, bool timeFilterSupported, int maxLimit, bool needsAuthentication) : base(paginationType, timeFilterSupported, maxLimit, needsAuthentication) + public GetKlinesOptions(bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit, bool needsAuthentication) + : base(supportsAscending, supportsDescending, timeFilterSupported, maxLimit, needsAuthentication) { SupportIntervals = new[] { @@ -50,7 +47,8 @@ namespace CryptoExchange.Net.SharedApis /// /// ctor /// - public GetKlinesOptions(SharedPaginationSupport paginationType, bool timeFilterSupported, int maxLimit, bool needsAuthentication, params SharedKlineInterval[] intervals) : base(paginationType, timeFilterSupported, maxLimit, needsAuthentication) + public GetKlinesOptions(bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit, bool needsAuthentication, params SharedKlineInterval[] intervals) + : base(supportsAscending, supportsDescending, timeFilterSupported, maxLimit, needsAuthentication) { SupportIntervals = intervals; } @@ -68,12 +66,29 @@ namespace CryptoExchange.Net.SharedApis if (!IsSupported(request.Interval)) return ArgumentError.Invalid(nameof(GetKlinesRequest.Interval), "Interval not supported"); + if (!SupportsAscending && request.Direction == DataDirection.Ascending) + return ArgumentError.Invalid(nameof(GetWithdrawalsRequest.Direction), $"Ascending direction is not supported"); + + if (!SupportsDescending && request.Direction == DataDirection.Descending) + return ArgumentError.Invalid(nameof(GetWithdrawalsRequest.Direction), $"Descending direction is not supported"); + if (MaxAge.HasValue && request.StartTime < DateTime.UtcNow.Add(-MaxAge.Value)) return ArgumentError.Invalid(nameof(GetKlinesRequest.StartTime), $"Only the most recent {MaxAge} klines are available"); if (request.Limit > MaxLimit) return ArgumentError.Invalid(nameof(GetKlinesRequest.Limit), $"Only {MaxLimit} klines can be retrieved per request"); + if (!TimePeriodFilterSupport) + { + // When going descending we can still allow startTime filter to limit the results + var now = DateTime.UtcNow; + if ((request.Direction == DataDirection.Ascending && request.StartTime != null) + || (request.EndTime != null && now - request.EndTime > TimeSpan.FromSeconds(5))) + { + return ArgumentError.Invalid(nameof(GetDepositsRequest.StartTime), $"Time filter is not supported"); + } + } + if (MaxTotalDataPoints.HasValue) { if (request.Limit > MaxTotalDataPoints.Value) @@ -93,6 +108,7 @@ namespace CryptoExchange.Net.SharedApis public override string ToString(string exchange) { var sb = new StringBuilder(base.ToString(exchange)); + sb.AppendLine($"Time filter supported: {TimePeriodFilterSupport}"); sb.AppendLine($"Supported SharedKlineInterval values: {string.Join(", ", SupportIntervals)}"); if (MaxAge != null) sb.AppendLine($"Max age of data: {MaxAge}"); diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetPositionHistoryOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetPositionHistoryOptions.cs index ffb01585..59f82f00 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetPositionHistoryOptions.cs +++ b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetPositionHistoryOptions.cs @@ -1,4 +1,8 @@ -namespace CryptoExchange.Net.SharedApis +using CryptoExchange.Net.Objects; +using System; +using System.Text; + +namespace CryptoExchange.Net.SharedApis { /// /// Options for requesting position history @@ -8,8 +12,43 @@ /// /// ctor /// - public GetPositionHistoryOptions(SharedPaginationSupport paginationType, bool timeFilterSupported, int maxLimit) : base(paginationType, timeFilterSupported, maxLimit, true) + public GetPositionHistoryOptions(bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit) + : base(supportsAscending, supportsDescending, timeFilterSupported, maxLimit, true) { } + + /// + public override Error? ValidateRequest(string exchange, GetPositionHistoryRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes) + { + if (!SupportsAscending && request.Direction == DataDirection.Ascending) + return ArgumentError.Invalid(nameof(GetWithdrawalsRequest.Direction), $"Ascending direction is not supported"); + + if (!SupportsDescending && request.Direction == DataDirection.Descending) + return ArgumentError.Invalid(nameof(GetWithdrawalsRequest.Direction), $"Descending direction is not supported"); + + if (MaxAge.HasValue && request.StartTime < DateTime.UtcNow.Add(-MaxAge.Value)) + return ArgumentError.Invalid(nameof(GetKlinesRequest.StartTime), $"Only the most recent {MaxAge} period data is available"); + + if (!TimePeriodFilterSupport) + { + // When going descending we can still allow startTime filter to limit the results + var now = DateTime.UtcNow; + if ((request.Direction != DataDirection.Descending && request.StartTime != null) + || (request.EndTime != null && now - request.EndTime > TimeSpan.FromSeconds(5))) + { + return ArgumentError.Invalid(nameof(GetDepositsRequest.StartTime), $"Time filter is not supported"); + } + } + + return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes); + } + + /// + public override string ToString(string exchange) + { + var sb = new StringBuilder(base.ToString(exchange)); + sb.AppendLine($"Time filter supported: {TimePeriodFilterSupport}"); + return sb.ToString(); + } } } diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetTradeHistoryOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetTradeHistoryOptions.cs index d07972fd..6c174dc7 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetTradeHistoryOptions.cs +++ b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetTradeHistoryOptions.cs @@ -9,34 +9,27 @@ namespace CryptoExchange.Net.SharedApis /// public class GetTradeHistoryOptions : PaginatedEndpointOptions { - /// - /// The max age of data that can be requested - /// - public TimeSpan? MaxAge { get; set; } - /// /// ctor /// - public GetTradeHistoryOptions(SharedPaginationSupport paginationType, bool timeFilterSupported, int maxLimit, bool needsAuthentication) : base(paginationType, timeFilterSupported, maxLimit, needsAuthentication) + public GetTradeHistoryOptions(bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit, bool needsAuthentication) + : base(supportsAscending, supportsDescending, timeFilterSupported, maxLimit, needsAuthentication) { } /// public override Error? ValidateRequest(string exchange, GetTradeHistoryRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes) { + if (!SupportsAscending && request.Direction == DataDirection.Ascending) + return ArgumentError.Invalid(nameof(GetWithdrawalsRequest.Direction), $"Ascending direction is not supported"); + + if (!SupportsDescending && request.Direction == DataDirection.Descending) + return ArgumentError.Invalid(nameof(GetWithdrawalsRequest.Direction), $"Descending direction is not supported"); + if (MaxAge.HasValue && request.StartTime < DateTime.UtcNow.Add(-MaxAge.Value)) - return ArgumentError.Invalid(nameof(GetTradeHistoryRequest.StartTime), $"Only the most recent {MaxAge} trades are available"); + return ArgumentError.Invalid(nameof(GetKlinesRequest.StartTime), $"Only the most recent {MaxAge} period data is available"); return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes); } - - /// - public override string ToString(string exchange) - { - var sb = new StringBuilder(base.ToString(exchange)); - if (MaxAge != null) - sb.AppendLine($"Max age of data: {MaxAge}"); - return sb.ToString(); - } } } diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetUserTradesOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetUserTradesOptions.cs new file mode 100644 index 00000000..f59d4480 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetUserTradesOptions.cs @@ -0,0 +1,54 @@ +using CryptoExchange.Net.Objects; +using System; +using System.Text; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Options for requesting user trades + /// + public class GetUserTradesOptions : PaginatedEndpointOptions + { + /// + /// ctor + /// + public GetUserTradesOptions(bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit) + : base(supportsAscending, supportsDescending, timeFilterSupported, maxLimit, true) + { + } + + /// + public override Error? ValidateRequest(string exchange, GetUserTradesRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes) + { + if (!SupportsAscending && request.Direction == DataDirection.Ascending) + return ArgumentError.Invalid(nameof(GetWithdrawalsRequest.Direction), $"Ascending direction is not supported"); + + if (!SupportsDescending && request.Direction == DataDirection.Descending) + return ArgumentError.Invalid(nameof(GetWithdrawalsRequest.Direction), $"Descending direction is not supported"); + + if (MaxAge.HasValue && request.StartTime < DateTime.UtcNow.Add(-MaxAge.Value)) + return ArgumentError.Invalid(nameof(GetKlinesRequest.StartTime), $"Only the most recent {MaxAge} period data is available"); + + if (!TimePeriodFilterSupport) + { + // When going descending we can still allow startTime filter to limit the results + var now = DateTime.UtcNow; + if ((request.Direction != DataDirection.Descending && request.StartTime != null) + || (request.EndTime != null && now - request.EndTime > TimeSpan.FromSeconds(5))) + { + return ArgumentError.Invalid(nameof(GetDepositsRequest.StartTime), $"Time filter is not supported"); + } + } + + return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes); + } + + /// + public override string ToString(string exchange) + { + var sb = new StringBuilder(base.ToString(exchange)); + sb.AppendLine($"Time filter supported: {TimePeriodFilterSupport}"); + return sb.ToString(); + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetWithdrawalsOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetWithdrawalsOptions.cs index 01572378..fa8a2bcd 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetWithdrawalsOptions.cs +++ b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/GetWithdrawalsOptions.cs @@ -1,4 +1,5 @@ using CryptoExchange.Net.Objects; +using System; using System.Text; namespace CryptoExchange.Net.SharedApis @@ -8,24 +9,36 @@ namespace CryptoExchange.Net.SharedApis /// public class GetWithdrawalsOptions : PaginatedEndpointOptions { - /// - /// Whether the start/end time filter is supported - /// - public bool TimeFilterSupported { get; set; } - /// /// ctor /// - public GetWithdrawalsOptions(SharedPaginationSupport paginationType, bool timeFilterSupported, int maxLimit) : base(paginationType, timeFilterSupported, maxLimit, true) + public GetWithdrawalsOptions(bool supportsAscending, bool supportsDescending, bool timeFilterSupported, int maxLimit) + : base(supportsAscending, supportsDescending, timeFilterSupported, maxLimit, true) { - TimeFilterSupported = timeFilterSupported; } /// public override Error? ValidateRequest(string exchange, GetWithdrawalsRequest request, TradingMode? tradingMode, TradingMode[] supportedApiTypes) { - if (!TimeFilterSupported && request.StartTime != null) - return ArgumentError.Invalid(nameof(GetWithdrawalsRequest.StartTime), $"Time filter is not supported"); + if (!SupportsAscending && request.Direction == DataDirection.Ascending) + return ArgumentError.Invalid(nameof(GetWithdrawalsRequest.Direction), $"Ascending direction is not supported"); + + if (!SupportsDescending && request.Direction == DataDirection.Descending) + return ArgumentError.Invalid(nameof(GetWithdrawalsRequest.Direction), $"Descending direction is not supported"); + + if (MaxAge.HasValue && request.StartTime < DateTime.UtcNow.Add(-MaxAge.Value)) + return ArgumentError.Invalid(nameof(GetKlinesRequest.StartTime), $"Only the most recent {MaxAge} period data is available"); + + if (!TimePeriodFilterSupport) + { + // When going descending we can still allow startTime filter to limit the results + var now = DateTime.UtcNow; + if ((request.Direction != DataDirection.Descending && request.StartTime != null) + || (request.EndTime != null && now - request.EndTime > TimeSpan.FromSeconds(5))) + { + return ArgumentError.Invalid(nameof(GetDepositsRequest.StartTime), $"Time filter is not supported"); + } + } return base.ValidateRequest(exchange, request, tradingMode, supportedApiTypes); } @@ -34,7 +47,7 @@ namespace CryptoExchange.Net.SharedApis public override string ToString(string exchange) { var sb = new StringBuilder(base.ToString(exchange)); - sb.AppendLine($"Time filter supported: {TimeFilterSupported}"); + sb.AppendLine($"Time filter supported: {TimePeriodFilterSupport}"); return sb.ToString(); } } diff --git a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/PaginatedEndpointOptions.cs b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/PaginatedEndpointOptions.cs index ed0e4960..e61cf11a 100644 --- a/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/PaginatedEndpointOptions.cs +++ b/CryptoExchange.Net/SharedApis/Models/Options/Endpoints/PaginatedEndpointOptions.cs @@ -1,4 +1,5 @@ -using System.Diagnostics.CodeAnalysis; +using System; +using System.Diagnostics.CodeAnalysis; using System.Text; namespace CryptoExchange.Net.SharedApis @@ -14,9 +15,13 @@ namespace CryptoExchange.Net.SharedApis #endif { /// - /// Type of pagination supported + /// Whether ascending data retrieval and pagination is available /// - public SharedPaginationSupport PaginationSupport { get; } + public bool SupportsAscending { get; set; } + /// + /// Whether ascending data retrieval and pagination is available + /// + public bool SupportsDescending { get; set; } /// /// Whether filtering based on start/end time is supported @@ -28,12 +33,23 @@ namespace CryptoExchange.Net.SharedApis /// public int MaxLimit { get; set; } + /// + /// Max age of data that can be requested + /// + public TimeSpan? MaxAge { get; set; } + /// /// ctor /// - public PaginatedEndpointOptions(SharedPaginationSupport paginationType, bool timePeriodSupport, int maxLimit, bool needsAuthentication) : base(needsAuthentication) + public PaginatedEndpointOptions( + bool supportsAscending, + bool supportsDescending, + bool timePeriodSupport, + int maxLimit, + bool needsAuthentication) : base(needsAuthentication) { - PaginationSupport = paginationType; + SupportsAscending = supportsAscending; + SupportsDescending = supportsDescending; TimePeriodFilterSupport = timePeriodSupport; MaxLimit = maxLimit; } @@ -42,9 +58,11 @@ namespace CryptoExchange.Net.SharedApis public override string ToString(string exchange) { var sb = new StringBuilder(base.ToString(exchange)); - sb.AppendLine($"Pagination type: {PaginationSupport}"); + sb.AppendLine($"Ascending retrieval supported: {SupportsAscending}"); + sb.AppendLine($"Descending retrieval supported: {SupportsDescending}"); sb.AppendLine($"Time period filter support: {TimePeriodFilterSupport}"); sb.AppendLine($"Max limit: {MaxLimit}"); + sb.AppendLine($"Max age: {MaxAge}"); return sb.ToString(); } } diff --git a/CryptoExchange.Net/SharedApis/Models/Pagination/DataDirection.cs b/CryptoExchange.Net/SharedApis/Models/Pagination/DataDirection.cs new file mode 100644 index 00000000..30f541b7 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Models/Pagination/DataDirection.cs @@ -0,0 +1,17 @@ +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Data direction + /// + public enum DataDirection + { + /// + /// Old to new order + /// + Ascending, + /// + /// New to old order + /// + Descending + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Pagination/PageRequest.cs b/CryptoExchange.Net/SharedApis/Models/Pagination/PageRequest.cs new file mode 100644 index 00000000..f77acea2 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Models/Pagination/PageRequest.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Next page request info + /// + public class PageRequest + { + /// + /// Pagination cursor + /// + public string? Cursor { get; set; } + /// + /// Page number + /// + public int? Page { get; set; } + /// + /// Result offset + /// + public int? Offset { get; set; } + /// + /// From id filter + /// + public string? FromId { get; set; } + /// + /// Start time filter + /// + public DateTime? StartTime { get; set; } + /// + /// End time filter + /// + public DateTime? EndTime { get; set; } + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Pagination/Pagination.cs b/CryptoExchange.Net/SharedApis/Models/Pagination/Pagination.cs new file mode 100644 index 00000000..81042ef9 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Models/Pagination/Pagination.cs @@ -0,0 +1,405 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Pagination methods + /// + public static class Pagination + { + /// + /// Get pagination parameters + /// + /// The data direction + /// Result limit + /// User request start time + /// User request end time + /// Provided page request + /// Whether to set start time if direction is descending, or end time if direction is ascending + /// Max period the time filters can span + /// + public static PaginationParameters GetPaginationParameters( + DataDirection direction, + int limit, + DateTime? requestStartTime, + DateTime requestEndTime, + PageRequest? paginationRequest, + bool setOtherTimeLimiter = true, + TimeSpan? maxPeriod = null + ) + { + var startTime = paginationRequest?.StartTime ?? requestStartTime; + var endTime = paginationRequest?.EndTime ?? requestEndTime; + if (maxPeriod != null) + { + if (direction == DataDirection.Ascending) + { + if (startTime == null) + { + startTime = endTime.Add(-maxPeriod.Value); + } + else + { + endTime = startTime.Value.Add(maxPeriod.Value); + if (endTime > DateTime.UtcNow) + endTime = DateTime.UtcNow; + } + } + else + { + startTime = endTime.Add(-maxPeriod.Value); + } + } + + return new PaginationParameters + { + Limit = limit, + StartTime = direction == DataDirection.Ascending || setOtherTimeLimiter ? startTime : null, + EndTime = direction == DataDirection.Descending || setOtherTimeLimiter ? endTime : null, + Direction = direction, + FromId = paginationRequest?.FromId, + Offset = paginationRequest?.Offset, + Page = paginationRequest?.Page, + Cursor = paginationRequest?.Cursor + }; + } + + /// + /// Get the next page request parameters from result kline data + /// + /// Callback for returning the next page request + /// Number of results in data + /// Timestamps of the result data + /// User request start time + /// User request end time + /// The last used pagination data + /// Kline interval + /// + public static PageRequest? GetNextPageRequestKlines( + Func nextPageRequest, + int resultCount, + IEnumerable timestamps, + DateTime? requestStartTime, + DateTime requestEndTime, + PaginationParameters lastPaginationData, + SharedKlineInterval interval + ) + { + if (HasNextPageKlines(resultCount, timestamps, requestStartTime, requestEndTime, lastPaginationData.Limit, lastPaginationData.Direction, interval)) + { + var result = nextPageRequest(); + if (result != null) + { + result.StartTime ??= lastPaginationData.StartTime; + result.EndTime ??= lastPaginationData.EndTime; + return result; + } + } + + return null; + } + + /// + /// Get the next page request parameters from result data + /// + /// Callback for returning the next page request + /// Number of results in data + /// Timestamps of the result data + /// User request start time + /// User request end time + /// The last used pagination data + /// Max period the time filters can span + /// Max age of the data + /// + public static PageRequest? GetNextPageRequest( + Func nextPageRequest, + int resultCount, + IEnumerable timestamps, + DateTime? requestStartTime, + DateTime requestEndTime, + PaginationParameters lastPaginationData, + TimeSpan? maxPeriod = null, + TimeSpan? maxAge = null + ) + { + if (HasNextPage(resultCount, timestamps, requestStartTime, requestEndTime, lastPaginationData.Limit, lastPaginationData.Direction)) + { + var result = nextPageRequest(); + if (result != null) + { + result.StartTime ??= lastPaginationData.StartTime; + result.EndTime ??= lastPaginationData.EndTime; + return result; + } + } + + if (maxPeriod != null) + { + if (HasNextPeriod(requestStartTime, requestEndTime, lastPaginationData.Direction, lastPaginationData, maxPeriod.Value, maxAge)) + { + var (startTime, endTime) = GetNextPeriod(requestStartTime, requestEndTime, lastPaginationData.Direction, lastPaginationData, maxPeriod.Value, maxAge); + return new PageRequest + { + StartTime = startTime, + EndTime = endTime + }; + } + } + + return null; + } + + /// + /// Check whether there is (potentially) another page available + /// + /// Number of result entries + /// Timestamps + /// User request start time + /// User request end time + /// Max number of results requested + /// Data direction + /// Kline interval + /// + public static bool HasNextPageKlines( + int resultCount, + IEnumerable timestamps, + DateTime? requestStartTime, + DateTime requestEndTime, + int limit, + DataDirection direction, + SharedKlineInterval interval + ) + { + if (resultCount < limit) + return false; + + if (direction == DataDirection.Ascending) + { + if (timestamps.Max().AddSeconds((int)interval) >= requestEndTime) + return false; + + return true; + } + else + { + if (timestamps.Min().AddSeconds((int)interval) < requestStartTime) + return false; + + return true; + } + } + + /// + /// Check whether there is (potentially) another page available + /// + /// Number of result entries + /// Timestamps + /// User request start time + /// User request end time + /// Max number of results requested + /// Data direction + /// + public static bool HasNextPage( + int resultCount, + IEnumerable timestamps, + DateTime? requestStartTime, + DateTime requestEndTime, + int limit, + DataDirection direction) + { + if (resultCount < limit) + return false; + + if (!timestamps.Any()) + return false; + + if (direction == DataDirection.Ascending) + { + if (timestamps.Max() >= requestEndTime) + return false; + + return true; + } + else + { + if (timestamps.Min() < requestStartTime) + return false; + + return true; + } + } + + /// + /// Get the next page PageRequest + /// + public static PageRequest NextPageFromPage(PaginationParameters lastPaginationData) + { + return new PageRequest { Page = (lastPaginationData.Page ?? 1) + 1 }; + } + /// + /// Get the next offset PageRequest + /// + public static PageRequest NextPageFromOffset(PaginationParameters lastPaginationData, int resultCount) + { + return new PageRequest { Offset = (lastPaginationData.Offset ?? 0) + resultCount }; + } + /// + /// Get the next page cursor PageRequest + /// + public static PageRequest NextPageFromCursor(string nextCursor) + { + return new PageRequest { Cursor = nextCursor }; + } + /// + /// Get the next id PageRequest + /// + public static PageRequest NextPageFromId(long nextFromId) + { + return new PageRequest { FromId = nextFromId.ToString() }; + } + /// + /// Get the next id PageRequest + /// + public static PageRequest NextPageFromId(string nextFromId) + { + return new PageRequest { FromId = nextFromId }; + } + /// + /// Get the next start/end time PageRequest + /// + public static PageRequest NextPageFromTime(PaginationParameters lastPaginationData, DateTime lastTimestamp, bool setOtherTimeLimiter = true) + { + if (lastPaginationData.Direction == DataDirection.Ascending) + return new PageRequest { StartTime = lastTimestamp.AddMilliseconds(1), EndTime = setOtherTimeLimiter ? lastPaginationData.EndTime : null }; + else + return new PageRequest { EndTime = lastTimestamp.AddMilliseconds(-1), StartTime = setOtherTimeLimiter ? lastPaginationData.StartTime : null }; + } + + /// + /// Get the next start/end time klines PageRequest + /// + public static PageRequest NextPageFromTimeKlines(DataDirection direction, GetKlinesRequest request, DateTime lastTimestamp, int limit) + { + if (direction == DataDirection.Ascending) + { + var nextStartTime = lastTimestamp.AddSeconds((int)request.Interval); + var endTime = nextStartTime.AddSeconds(limit * (int)request.Interval); + var requestEndTime = request.EndTime ?? DateTime.UtcNow; + if (endTime > requestEndTime) + endTime = requestEndTime; + + return new PageRequest { StartTime = nextStartTime, EndTime = endTime }; + } + else + { + var nextEndTime = lastTimestamp.AddSeconds(-(int)request.Interval); + var startTime = nextEndTime.AddSeconds(-(limit * (int)request.Interval)); + var requestStartTime = request.StartTime ?? DateTime.UtcNow; + if (startTime < requestStartTime) + startTime = requestStartTime; + return new PageRequest { StartTime = startTime, EndTime = nextEndTime }; + } + } + + /// + /// Whether another time period is to be requested + /// + /// User request start time + /// User request end time + /// Data direction + /// Pagination parameters used + /// Max time period a request can span + /// Max age of data that can be requested + public static bool HasNextPeriod( + DateTime? requestStartTime, + DateTime requestEndTime, + DataDirection direction, + PaginationParameters lastPaginationParameters, + TimeSpan period, + TimeSpan? maxAge) + { + if (direction == DataDirection.Ascending && lastPaginationParameters.StartTime == null) + throw new InvalidOperationException("Invalid pagination data; no start time for ascending pagination"); + + if (direction == DataDirection.Ascending) + { + return (requestEndTime - lastPaginationParameters.EndTime!.Value).TotalSeconds > 1; + } + else + { + var lastPageStartTime = lastPaginationParameters.StartTime ?? lastPaginationParameters.EndTime!.Value.Add(-period); + if (requestStartTime != null) + { + var nextPeriodDuration = lastPageStartTime - requestStartTime.Value; + return nextPeriodDuration.TotalSeconds > 1; + } + else + { + var nextStartTime = lastPageStartTime - period; + if (maxAge != null) + { + var minStartTime = DateTime.UtcNow - maxAge.Value; + if ((nextStartTime.Add(period) - minStartTime).TotalSeconds < 1) + return false; + } + + var nextPeriodDuration = lastPageStartTime - nextStartTime; + return (nextPeriodDuration).TotalSeconds > 1; + } + } + } + + /// + /// Get the start/end time for the next data period + /// + /// User request start time + /// User request end time + /// Data direction + /// Pagination parameters used + /// Max time period a request can span + /// Max age of data that can be requested + public static (DateTime? startTime, DateTime? endTime) GetNextPeriod( + DateTime? requestStartTime, + DateTime requestEndTime, + DataDirection direction, + PaginationParameters lastPaginationParameters, + TimeSpan period, + TimeSpan? maxAge + ) + { + DateTime? nextStartTime = null; + DateTime? nextEndTime = null; + if (direction == DataDirection.Ascending) + { + if (lastPaginationParameters.StartTime != null) + nextStartTime = lastPaginationParameters.StartTime.Value.Add(period); + if (lastPaginationParameters.EndTime != null) + nextEndTime = lastPaginationParameters.EndTime.Value.Add(period); + } + else + { + if (lastPaginationParameters.StartTime != null) + nextStartTime = lastPaginationParameters.StartTime.Value.Add(-period); + if (lastPaginationParameters.EndTime != null) + nextEndTime = lastPaginationParameters.EndTime.Value.Add(-period); + } + + if (nextStartTime != null && nextStartTime < requestStartTime) + nextStartTime = requestStartTime; + + if (nextStartTime != null && maxAge != null && nextStartTime < DateTime.UtcNow - maxAge) + { + nextStartTime = DateTime.UtcNow.Add(-maxAge.Value); + // Add 30 seconds to max sure the client/server time offset and latency doesn't push the timestamp over the limit + nextStartTime = nextStartTime.Value.Add(TimeSpan.FromSeconds(30)); + } + + if (nextEndTime != null && nextEndTime > requestEndTime) + nextEndTime = requestEndTime; + + return (nextStartTime, nextEndTime); + } + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Pagination/PaginationParameters.cs b/CryptoExchange.Net/SharedApis/Models/Pagination/PaginationParameters.cs new file mode 100644 index 00000000..cf8e35d4 --- /dev/null +++ b/CryptoExchange.Net/SharedApis/Models/Pagination/PaginationParameters.cs @@ -0,0 +1,43 @@ +using System; + +namespace CryptoExchange.Net.SharedApis +{ + /// + /// Pagination parameters + /// + public record PaginationParameters + { + /// + /// Data direction + /// + public DataDirection Direction { get; set; } + /// + /// Start time filter + /// + public DateTime? StartTime { get; set; } + /// + /// End time filter + /// + public DateTime? EndTime { get; set; } + /// + /// Id filter + /// + public string? FromId { get; set; } + /// + /// Result offset + /// + public int? Offset { get; set; } + /// + /// Page number + /// + public int? Page { get; set; } + /// + /// Pagination cursor + /// + public string? Cursor { get; set; } + /// + /// Max number of results + /// + public int Limit { get; set; } + } +} diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetClosedOrdersRequest.cs b/CryptoExchange.Net/SharedApis/Models/Rest/GetClosedOrdersRequest.cs index 4920d8e3..5e1808ea 100644 --- a/CryptoExchange.Net/SharedApis/Models/Rest/GetClosedOrdersRequest.cs +++ b/CryptoExchange.Net/SharedApis/Models/Rest/GetClosedOrdersRequest.cs @@ -19,6 +19,10 @@ namespace CryptoExchange.Net.SharedApis /// Max number of results /// public int? Limit { get; } + /// + /// Data direction + /// + public DataDirection? Direction { get; set; } /// /// ctor @@ -27,12 +31,14 @@ namespace CryptoExchange.Net.SharedApis /// Filter by start time /// Filter by end time /// Max number of results + /// Data direction /// Exchange specific parameters - public GetClosedOrdersRequest(SharedSymbol symbol, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, ExchangeParameters? exchangeParameters = null) : base(symbol, exchangeParameters) + public GetClosedOrdersRequest(SharedSymbol symbol, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, DataDirection? direction = null, ExchangeParameters? exchangeParameters = null) : base(symbol, exchangeParameters) { StartTime = startTime; EndTime = endTime; Limit = limit; + Direction = direction; } } } diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetDepositsRequest.cs b/CryptoExchange.Net/SharedApis/Models/Rest/GetDepositsRequest.cs index 5f53be22..78fc887f 100644 --- a/CryptoExchange.Net/SharedApis/Models/Rest/GetDepositsRequest.cs +++ b/CryptoExchange.Net/SharedApis/Models/Rest/GetDepositsRequest.cs @@ -23,6 +23,10 @@ namespace CryptoExchange.Net.SharedApis /// Max number of results /// public int? Limit { get; } + /// + /// Data direction + /// + public DataDirection? Direction { get; set; } /// /// ctor @@ -31,13 +35,15 @@ namespace CryptoExchange.Net.SharedApis /// Filter by start time /// Filter by end time /// Max number of results + /// Data direction /// Exchange specific parameters - public GetDepositsRequest(string? asset = null, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, ExchangeParameters? exchangeParameters = null) : base(exchangeParameters) + public GetDepositsRequest(string? asset = null, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, DataDirection? direction = null, ExchangeParameters? exchangeParameters = null) : base(exchangeParameters) { Asset = asset; StartTime = startTime; EndTime = endTime; Limit = limit; + Direction = direction; } } } diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetFundingRateHistoryRequest.cs b/CryptoExchange.Net/SharedApis/Models/Rest/GetFundingRateHistoryRequest.cs index 213657fe..6f9ad50f 100644 --- a/CryptoExchange.Net/SharedApis/Models/Rest/GetFundingRateHistoryRequest.cs +++ b/CryptoExchange.Net/SharedApis/Models/Rest/GetFundingRateHistoryRequest.cs @@ -19,6 +19,10 @@ namespace CryptoExchange.Net.SharedApis /// Max number of results /// public int? Limit { get; set; } + /// + /// Data direction + /// + public DataDirection? Direction { get; set; } /// /// ctor @@ -27,12 +31,14 @@ namespace CryptoExchange.Net.SharedApis /// Filter by start time /// Filter by end time /// Max number of results + /// Data direction /// Exchange specific parameters - public GetFundingRateHistoryRequest(SharedSymbol symbol, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, ExchangeParameters? exchangeParameters = null) : base(symbol, exchangeParameters) + public GetFundingRateHistoryRequest(SharedSymbol symbol, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, DataDirection? direction = null, ExchangeParameters? exchangeParameters = null) : base(symbol, exchangeParameters) { StartTime = startTime; EndTime = endTime; Limit = limit; + Direction = direction; } } } diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetKlinesRequest.cs b/CryptoExchange.Net/SharedApis/Models/Rest/GetKlinesRequest.cs index 6eef5a22..54b1013a 100644 --- a/CryptoExchange.Net/SharedApis/Models/Rest/GetKlinesRequest.cs +++ b/CryptoExchange.Net/SharedApis/Models/Rest/GetKlinesRequest.cs @@ -23,6 +23,10 @@ namespace CryptoExchange.Net.SharedApis /// Max number of results /// public int? Limit { get; set; } + /// + /// Data direction + /// + public DataDirection? Direction { get; set; } /// /// ctor @@ -32,13 +36,15 @@ namespace CryptoExchange.Net.SharedApis /// Filter by start time /// Filter by end time /// Max number of results + /// Data direction /// Exchange specific parameters - public GetKlinesRequest(SharedSymbol symbol, SharedKlineInterval interval, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, ExchangeParameters? exchangeParameters = null) : base(symbol, exchangeParameters) + public GetKlinesRequest(SharedSymbol symbol, SharedKlineInterval interval, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, DataDirection? direction = null, ExchangeParameters? exchangeParameters = null) : base(symbol, exchangeParameters) { Interval = interval; StartTime = startTime; EndTime = endTime; Limit = limit; + Direction = direction; } } } diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetPositionHistoryRequest.cs b/CryptoExchange.Net/SharedApis/Models/Rest/GetPositionHistoryRequest.cs index 7bbb209a..db62b2fa 100644 --- a/CryptoExchange.Net/SharedApis/Models/Rest/GetPositionHistoryRequest.cs +++ b/CryptoExchange.Net/SharedApis/Models/Rest/GetPositionHistoryRequest.cs @@ -27,6 +27,10 @@ namespace CryptoExchange.Net.SharedApis /// Max number of results /// public int? Limit { get; set; } + /// + /// Data direction + /// + public DataDirection? Direction { get; set; } /// /// ctor @@ -35,13 +39,15 @@ namespace CryptoExchange.Net.SharedApis /// Filter by start time /// Filter by end time /// Max number of results + /// Data direction /// Exchange specific parameters - public GetPositionHistoryRequest(SharedSymbol symbol, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, ExchangeParameters? exchangeParameters = null) : base(exchangeParameters) + public GetPositionHistoryRequest(SharedSymbol symbol, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, DataDirection? direction = null, ExchangeParameters? exchangeParameters = null) : base(exchangeParameters) { Symbol = symbol; StartTime = startTime; EndTime = endTime; Limit = limit; + Direction = direction; } /// @@ -51,13 +57,15 @@ namespace CryptoExchange.Net.SharedApis /// Filter by start time /// Filter by end time /// Max number of results + /// Data direction /// Exchange specific parameters - public GetPositionHistoryRequest(TradingMode? tradeMode = null, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, ExchangeParameters? exchangeParameters = null) : base(exchangeParameters) + public GetPositionHistoryRequest(TradingMode? tradeMode = null, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, DataDirection? direction = null, ExchangeParameters? exchangeParameters = null) : base(exchangeParameters) { TradingMode = tradeMode; StartTime = startTime; EndTime = endTime; Limit = limit; + Direction = direction; } } } diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetTradeHistoryRequest.cs b/CryptoExchange.Net/SharedApis/Models/Rest/GetTradeHistoryRequest.cs index 8664f747..2eaea31c 100644 --- a/CryptoExchange.Net/SharedApis/Models/Rest/GetTradeHistoryRequest.cs +++ b/CryptoExchange.Net/SharedApis/Models/Rest/GetTradeHistoryRequest.cs @@ -10,15 +10,19 @@ namespace CryptoExchange.Net.SharedApis /// /// Filter by start time /// - public DateTime StartTime { get; } + public DateTime StartTime { get; set; } /// /// Filter by end time /// - public DateTime EndTime { get; } + public DateTime? EndTime { get; set; } /// /// Max number of results /// - public int? Limit { get; } + public int? Limit { get; set; } + /// + /// Data direction + /// + public DataDirection? Direction { get; set; } /// /// ctor @@ -27,12 +31,14 @@ namespace CryptoExchange.Net.SharedApis /// Filter by start time /// Filter by end time /// Max number of results + /// Data direction /// Exchange specific parameters - public GetTradeHistoryRequest(SharedSymbol symbol, DateTime startTime, DateTime endTime, int? limit = null, ExchangeParameters? exchangeParameters = null) : base(symbol, exchangeParameters) + public GetTradeHistoryRequest(SharedSymbol symbol, DateTime startTime, DateTime? endTime = null, int? limit = null, DataDirection? direction = null, ExchangeParameters? exchangeParameters = null) : base(symbol, exchangeParameters) { StartTime = startTime; EndTime = endTime; Limit = limit; + Direction = direction; } } } diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetUserTradesRequest.cs b/CryptoExchange.Net/SharedApis/Models/Rest/GetUserTradesRequest.cs index 8fd84078..0cf88b0f 100644 --- a/CryptoExchange.Net/SharedApis/Models/Rest/GetUserTradesRequest.cs +++ b/CryptoExchange.Net/SharedApis/Models/Rest/GetUserTradesRequest.cs @@ -19,6 +19,10 @@ namespace CryptoExchange.Net.SharedApis /// Max number of results /// public int? Limit { get; } + /// + /// Data direction + /// + public DataDirection? Direction { get; set; } /// /// ctor @@ -27,12 +31,14 @@ namespace CryptoExchange.Net.SharedApis /// Filter by start time /// Filter by end time /// Max number of results + /// Data direction /// Exchange specific parameters - public GetUserTradesRequest(SharedSymbol symbol, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, ExchangeParameters? exchangeParameters = null) : base(symbol, exchangeParameters) + public GetUserTradesRequest(SharedSymbol symbol, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, DataDirection? direction = null, ExchangeParameters? exchangeParameters = null) : base(symbol, exchangeParameters) { StartTime = startTime; EndTime = endTime; Limit = limit; + Direction = direction; } } } diff --git a/CryptoExchange.Net/SharedApis/Models/Rest/GetWithdrawalsRequest.cs b/CryptoExchange.Net/SharedApis/Models/Rest/GetWithdrawalsRequest.cs index 5130079d..0438b701 100644 --- a/CryptoExchange.Net/SharedApis/Models/Rest/GetWithdrawalsRequest.cs +++ b/CryptoExchange.Net/SharedApis/Models/Rest/GetWithdrawalsRequest.cs @@ -23,6 +23,10 @@ namespace CryptoExchange.Net.SharedApis /// Max number of results /// public int? Limit { get; } + /// + /// Data direction + /// + public DataDirection? Direction { get; set; } /// /// ctor @@ -31,13 +35,15 @@ namespace CryptoExchange.Net.SharedApis /// Filter by start time /// Filter by end time /// Max number of results + /// Data direction /// Exchange specific parameters - public GetWithdrawalsRequest(string? asset = null, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, ExchangeParameters? exchangeParameters = null) : base(exchangeParameters) + public GetWithdrawalsRequest(string? asset = null, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, DataDirection? direction = null, ExchangeParameters? exchangeParameters = null) : base(exchangeParameters) { Asset = asset; StartTime = startTime; EndTime = endTime; Limit = limit; + Direction = direction; } } } diff --git a/CryptoExchange.Net/Trackers/Klines/KlineTracker.cs b/CryptoExchange.Net/Trackers/Klines/KlineTracker.cs index 50834749..f6be4867 100644 --- a/CryptoExchange.Net/Trackers/Klines/KlineTracker.cs +++ b/CryptoExchange.Net/Trackers/Klines/KlineTracker.cs @@ -332,7 +332,8 @@ namespace CryptoExchange.Net.Trackers.Klines _data.Add(item.OpenTime, item); } - _firstTimestamp = _data.Min(v => v.Key); + _firstTimestamp = _data.Count == 0 ? null : _data.Min(v => v.Key); + ApplyWindow(false); _logger.KlineTrackerInitialDataSet(SymbolName, _data.Last().Key); } @@ -375,7 +376,7 @@ namespace CryptoExchange.Net.Trackers.Klines } } - _firstTimestamp = _data.Min(x => x.Key); + _firstTimestamp = _data.Count == 0 ? null : _data.Min(x => x.Key); _changed = true; SetSyncStatus(); diff --git a/CryptoExchange.Net/Trackers/Trades/TradeTracker.cs b/CryptoExchange.Net/Trackers/Trades/TradeTracker.cs index 76c17324..6a7e0095 100644 --- a/CryptoExchange.Net/Trackers/Trades/TradeTracker.cs +++ b/CryptoExchange.Net/Trackers/Trades/TradeTracker.cs @@ -259,11 +259,11 @@ namespace CryptoExchange.Net.Trackers.Trades var startTime = Period == null ? DateTime.UtcNow.AddMinutes(-5) : DateTime.UtcNow.Add(-Period.Value); var request = new GetTradeHistoryRequest(Symbol, startTime, DateTime.UtcNow); var data = new List(); - await foreach(var result in ExchangeHelpers.ExecutePages(_historyRestClient.GetTradeHistoryAsync, request).ConfigureAwait(false)) + await foreach (var result in ExchangeHelpers.ExecutePages(_historyRestClient.GetTradeHistoryAsync, request).ConfigureAwait(false)) { if (!result) return result; - + if (Limit != null && data.Count > Limit) break;