diff --git a/Kucoin.Net.UnitTests/JsonTests.cs b/Kucoin.Net.UnitTests/JsonTests.cs index 915b1d78..ba8bf3d1 100644 --- a/Kucoin.Net.UnitTests/JsonTests.cs +++ b/Kucoin.Net.UnitTests/JsonTests.cs @@ -14,9 +14,8 @@ public class JsonTests private JsonToObjectComparer _comparerSpot = new JsonToObjectComparer((json) => TestHelpers.CreateResponseClient(json, x => { x.ApiCredentials = new KucoinApiCredentials("1234", "1234", "12"); - x.SpotOptions.RateLimiters = new List(); x.SpotOptions.OutputOriginalData = false; - x.FuturesOptions.RateLimiters = new List(); + x.RateLimiterEnabled = false; x.FuturesOptions.OutputOriginalData = false; })); diff --git a/Kucoin.Net.UnitTests/TestImplementations/TestSocket.cs b/Kucoin.Net.UnitTests/TestImplementations/TestSocket.cs index c8f8e01e..205e441c 100644 --- a/Kucoin.Net.UnitTests/TestImplementations/TestSocket.cs +++ b/Kucoin.Net.UnitTests/TestImplementations/TestSocket.cs @@ -18,6 +18,7 @@ public class TestSocket: IWebsocket #pragma warning disable 0067 public event Func OnReconnected; public event Func OnReconnecting; + public event Func OnRequestRateLimited; #pragma warning restore 0067 public event Func OnRequestSent; public event Action> OnStreamMessage; @@ -50,10 +51,10 @@ public class TestSocket: IWebsocket public TimeSpan KeepAliveInterval { get; set; } public Func> GetReconnectionUrl { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } - public Task ConnectAsync() + public Task ConnectAsync() { Connected = CanConnect; - return Task.FromResult(CanConnect); + return Task.FromResult(CanConnect ? new CallResult(null) : new CallResult(new CantConnectError())); } public void Send(int requestId, string data, int weight) diff --git a/Kucoin.Net/Clients/FuturesApi/KucoinRestClientFuturesApi.cs b/Kucoin.Net/Clients/FuturesApi/KucoinRestClientFuturesApi.cs index f9fcf8d4..fd78c421 100644 --- a/Kucoin.Net/Clients/FuturesApi/KucoinRestClientFuturesApi.cs +++ b/Kucoin.Net/Clients/FuturesApi/KucoinRestClientFuturesApi.cs @@ -68,7 +68,7 @@ protected override AuthenticationProvider CreateAuthenticationProvider(ApiCreden internal async Task Execute(Uri uri, HttpMethod method, CancellationToken ct, Dictionary? parameters = null, bool signed = false, HttpMethodParameterPosition? parameterPosition = null) { - var result = await SendRequestAsync>(uri, method, ct, parameters, signed, parameterPosition: parameterPosition).ConfigureAwait(false); + var result = await SendRequestAsync>(uri, method, ct, parameters, signed, parameterPosition: parameterPosition, requestWeight: 0).ConfigureAwait(false); if (!result) return result.AsDatalessError(result.Error!); @@ -78,9 +78,9 @@ internal async Task Execute(Uri uri, HttpMethod method, Cancellat return result.AsDataless(); } - internal async Task> Execute(Uri uri, HttpMethod method, CancellationToken ct, Dictionary? parameters = null, bool signed = false, int weight = 1, bool ignoreRatelimit = false, HttpMethodParameterPosition? parameterPosition = null) + internal async Task> Execute(Uri uri, HttpMethod method, CancellationToken ct, Dictionary? parameters = null, bool signed = false, HttpMethodParameterPosition? parameterPosition = null) { - var result = await SendRequestAsync>(uri, method, ct, parameters, signed, parameterPosition: parameterPosition, requestWeight: weight, ignoreRatelimit: ignoreRatelimit).ConfigureAwait(false); + var result = await SendRequestAsync>(uri, method, ct, parameters, signed, parameterPosition: parameterPosition, requestWeight: 0).ConfigureAwait(false); if (!result) return result.AsError(result.Error!); diff --git a/Kucoin.Net/Clients/FuturesApi/KucoinRestClientFuturesApiAccount.cs b/Kucoin.Net/Clients/FuturesApi/KucoinRestClientFuturesApiAccount.cs index 70938384..0c79488d 100644 --- a/Kucoin.Net/Clients/FuturesApi/KucoinRestClientFuturesApiAccount.cs +++ b/Kucoin.Net/Clients/FuturesApi/KucoinRestClientFuturesApiAccount.cs @@ -31,7 +31,7 @@ internal KucoinRestClientFuturesApiAccount(KucoinRestClientFuturesApi baseClient /// public async Task> GetAccountOverviewAsync(string? asset = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("currency", asset); return await _baseClient.Execute(_baseClient.GetUri("account-overview"), HttpMethod.Get, ct, parameters, true).ConfigureAwait(false); } @@ -39,7 +39,7 @@ public async Task> GetAccountOverviewAsync( /// public async Task>> GetTransactionHistoryAsync(string? asset = null, TransactionType? type = null, DateTime? startTime = null, DateTime? endTime = null, int? offset = null, int? pageSize = null, bool? forward = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("currency", asset); parameters.AddOptionalParameter("startAt", DateTimeConverter.ConvertToMilliseconds(startTime)); parameters.AddOptionalParameter("endAt", DateTimeConverter.ConvertToMilliseconds(endTime)); @@ -58,7 +58,7 @@ public async Task> TransferToMainAccountAsyn if (receiveAccountType != AccountType.Main && receiveAccountType != AccountType.Trade) throw new ArgumentException("Receiving account type should be Main or Trade"); - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddParameter("currency", asset); parameters.AddParameter("amount", quantity.ToString(CultureInfo.InvariantCulture)); parameters.AddParameter("recAccountType", EnumConverter.GetString(receiveAccountType)); @@ -71,7 +71,7 @@ public async Task TransferToFuturesAccountAsync(string asset, dec if (payAccountType != AccountType.Main && payAccountType != AccountType.Trade) throw new ArgumentException("Receiving account type should be Main or Trade"); - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddParameter("currency", asset); parameters.AddParameter("amount", quantity.ToString(CultureInfo.InvariantCulture)); parameters.AddParameter("payAccountType", EnumConverter.GetString(payAccountType)); @@ -81,7 +81,7 @@ public async Task TransferToFuturesAccountAsync(string asset, dec /// public async Task>> GetTransferToMainAccountHistoryAsync(string? asset = null, DateTime? startTime = null, DateTime? endTime = null, DepositStatus? status = null, int? currentPage = null, int? pageSize = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("currency", asset); parameters.AddOptionalParameter("startAt", DateTimeConverter.ConvertToMilliseconds(startTime)); parameters.AddOptionalParameter("endAt", DateTimeConverter.ConvertToMilliseconds(endTime)); @@ -94,7 +94,7 @@ public async Task>> GetTransferToM /// public async Task CancelTransferToMainAccountAsync(string applyId, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddParameter("applyId", applyId); return await _baseClient.Execute(_baseClient.GetUri("cancel/transfer-out", 1), HttpMethod.Delete, ct, parameters, true).ConfigureAwait(false); } @@ -105,7 +105,7 @@ public async Task CancelTransferToMainAccountAsync(string applyId /// public async Task> GetPositionAsync(string symbol, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddParameter("symbol", symbol); return await _baseClient.Execute(_baseClient.GetUri("position"), HttpMethod.Get, ct, parameters, true).ConfigureAwait(false); } @@ -113,7 +113,7 @@ public async Task> GetPositionAsync(string symbol, /// public async Task>> GetPositionsAsync(string? asset = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("currency", asset); return await _baseClient.Execute>(_baseClient.GetUri("positions"), HttpMethod.Get, ct, parameters, signed: true).ConfigureAwait(false); } @@ -121,7 +121,7 @@ public async Task>> GetPositionsAsync( /// public async Task ToggleAutoDepositMarginAsync(string symbol, bool enabled, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddParameter("symbol", symbol); parameters.AddParameter("status", enabled.ToString()); return await _baseClient.Execute(_baseClient.GetUri("position/margin/auto-deposit-status"), HttpMethod.Post, ct, parameters, true).ConfigureAwait(false); @@ -130,7 +130,7 @@ public async Task ToggleAutoDepositMarginAsync(string symbol, boo /// public async Task AddMarginAsync(string symbol, decimal quantity, string? clientId = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddParameter("symbol", symbol); parameters.AddParameter("bizNo", clientId ?? Convert.ToBase64String(Guid.NewGuid().ToByteArray())); parameters.AddParameter("margin", quantity.ToString(CultureInfo.InvariantCulture)); @@ -144,7 +144,7 @@ public async Task AddMarginAsync(string symbol, decimal quantity, /// public async Task>> GetFundingHistoryAsync(string symbol, DateTime? startTime = null, DateTime? endTime = null, int? offset = null, int? pageSize = null, bool? forward = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("symbol", symbol); parameters.AddOptionalParameter("startAt", DateTimeConverter.ConvertToMilliseconds(startTime)); parameters.AddOptionalParameter("endAt", DateTimeConverter.ConvertToMilliseconds(endTime)); @@ -160,7 +160,7 @@ public async Task>> GetFu /// public async Task> GetOpenOrderValueAsync(string symbol, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddParameter("symbol", symbol); return await _baseClient.Execute(_baseClient.GetUri("openOrderStatistics"), HttpMethod.Get, ct, parameters, true).ConfigureAwait(false); } @@ -171,7 +171,7 @@ public async Task> GetOpenOrderValueAsync(st /// public async Task>> GetRiskLimitLevelAsync(string symbol, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddParameter("symbol", symbol); return await _baseClient.Execute>(_baseClient.GetUri("contracts/risk-limit/" + symbol), HttpMethod.Get, ct, parameters, true).ConfigureAwait(false); } @@ -182,7 +182,7 @@ public async Task> GetOpenOrderValueAsync(st /// public async Task> SetRiskLimitLevelAsync(string symbol, int level, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddParameter("symbol", symbol); parameters.AddParameter("level", level); return await _baseClient.Execute(_baseClient.GetUri("position/risk-limit-level/change"), HttpMethod.Post, ct, parameters, true).ConfigureAwait(false); diff --git a/Kucoin.Net/Clients/FuturesApi/KucoinRestClientFuturesApiExchangeData.cs b/Kucoin.Net/Clients/FuturesApi/KucoinRestClientFuturesApiExchangeData.cs index d1a4480e..310529b8 100644 --- a/Kucoin.Net/Clients/FuturesApi/KucoinRestClientFuturesApiExchangeData.cs +++ b/Kucoin.Net/Clients/FuturesApi/KucoinRestClientFuturesApiExchangeData.cs @@ -47,7 +47,7 @@ public async Task> GetContractAsync(string symbol, /// public async Task> GetTickerAsync(string symbol, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddParameter("symbol", symbol); return await _baseClient.Execute(_baseClient.GetUri("ticker"), HttpMethod.Get, ct, parameters).ConfigureAwait(false); } @@ -59,7 +59,7 @@ public async Task> GetTickerAsync(string symbol /// public async Task> GetAggregatedFullOrderBookAsync(string symbol, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddParameter("symbol", symbol); return await _baseClient.Execute(_baseClient.GetUri("level2/snapshot"), HttpMethod.Get, ct, parameters).ConfigureAwait(false); } @@ -69,7 +69,7 @@ public async Task> GetAggregatedPartialOrderBookA { depth.ValidateIntValues(nameof(depth), 20, 100); - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddParameter("symbol", symbol); return await _baseClient.Execute(_baseClient.GetUri("level2/depth" + depth), HttpMethod.Get, ct, parameters).ConfigureAwait(false); } @@ -81,7 +81,7 @@ public async Task> GetAggregatedPartialOrderBookA /// public async Task>> GetTradeHistoryAsync(string symbol, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddParameter("symbol", symbol); return await _baseClient.Execute>(_baseClient.GetUri("trade/history"), HttpMethod.Get, ct, parameters).ConfigureAwait(false); } @@ -93,7 +93,7 @@ public async Task>> GetTradeHistor /// public async Task>> GetInterestRatesAsync(string symbol, DateTime? startTime = null, DateTime? endTime = null, int? offset = null, int? pageSize = null, bool? forward = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("symbol", symbol); parameters.AddOptionalParameter("startAt", DateTimeConverter.ConvertToMilliseconds(startTime)); parameters.AddOptionalParameter("endAt", DateTimeConverter.ConvertToMilliseconds(endTime)); @@ -106,7 +106,7 @@ public async Task>> G /// public async Task>> GetIndexListAsync(string symbol, DateTime? startTime = null, DateTime? endTime = null, int? offset = null, int? pageSize = null, bool? forward = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("symbol", symbol); parameters.AddOptionalParameter("startAt", DateTimeConverter.ConvertToMilliseconds(startTime)); parameters.AddOptionalParameter("endAt", DateTimeConverter.ConvertToMilliseconds(endTime)); @@ -125,7 +125,7 @@ public async Task> GetCurrentMarkPriceAsync(strin /// public async Task>> GetPremiumIndexAsync(string symbol, DateTime? startTime = null, DateTime? endTime = null, int? offset = null, int? pageSize = null, bool? forward = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("symbol", symbol); parameters.AddOptionalParameter("startAt", DateTimeConverter.ConvertToMilliseconds(startTime)); parameters.AddOptionalParameter("endAt", DateTimeConverter.ConvertToMilliseconds(endTime)); @@ -148,7 +148,7 @@ public async Task> GetCurrentFundingRateAsync(s /// public async Task> GetServerTimeAsync(CancellationToken ct = default) { - var result = await _baseClient.Execute(_baseClient.GetUri("timestamp"), HttpMethod.Get, ct, ignoreRatelimit: true).ConfigureAwait(false); + var result = await _baseClient.Execute(_baseClient.GetUri("timestamp"), HttpMethod.Get, ct).ConfigureAwait(false); return result.As(result ? new DateTime(1970, 1, 1).AddMilliseconds(result.Data) : default); } @@ -169,7 +169,7 @@ public async Task> GetServiceStatusAsy /// public async Task>> GetKlinesAsync(string symbol, FuturesKlineInterval interval, DateTime? startTime = null, DateTime? endTime = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddParameter("symbol", symbol); parameters.AddParameter("granularity", JsonConvert.SerializeObject(interval, new FuturesKlineIntervalConverter(false))); parameters.AddOptionalParameter("from", DateTimeConverter.ConvertToMilliseconds(startTime)); diff --git a/Kucoin.Net/Clients/FuturesApi/KucoinRestClientFuturesApiTrading.cs b/Kucoin.Net/Clients/FuturesApi/KucoinRestClientFuturesApiTrading.cs index 1644c108..e1733597 100644 --- a/Kucoin.Net/Clients/FuturesApi/KucoinRestClientFuturesApiTrading.cs +++ b/Kucoin.Net/Clients/FuturesApi/KucoinRestClientFuturesApiTrading.cs @@ -54,7 +54,7 @@ public async Task> PlaceOrderAsync( string? clientOrderId = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddParameter("symbol", symbol); parameters.AddParameter("side", JsonConvert.SerializeObject(side, new OrderSideConverter(false))); parameters.AddParameter("type", JsonConvert.SerializeObject(type, new NewOrderTypeConverter(false))); @@ -87,7 +87,7 @@ public async Task> CancelOrderAsync(string o /// public async Task> CancelAllOrdersAsync(string? symbol = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("symbol", symbol); return await _baseClient.Execute(_baseClient.GetUri("orders", 1), HttpMethod.Delete, ct, parameters, true).ConfigureAwait(false); } @@ -96,7 +96,7 @@ public async Task> CancelAllOrdersAsync(stri /// public async Task> CancelAllStopOrdersAsync(string? symbol = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("symbol", symbol); return await _baseClient.Execute(_baseClient.GetUri("stopOrders", 1), HttpMethod.Delete, ct, parameters, true).ConfigureAwait(false); } @@ -104,7 +104,7 @@ public async Task> CancelAllStopOrdersAsync( /// public async Task>> GetOrdersAsync(string? symbol = null, OrderStatus? status = null, OrderSide? side = null, OrderType? type = null, DateTime? startTime = null, DateTime? endTime = null, int? currentPage = null, int? pageSize = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("symbol", symbol); parameters.AddOptionalParameter("status", status == null ? null : JsonConvert.SerializeObject(status, new OrderStatusConverter(false))); parameters.AddOptionalParameter("side", side == null ? null : JsonConvert.SerializeObject(side, new OrderSideConverter(false))); @@ -120,7 +120,7 @@ public async Task>> GetOrdersA /// public async Task>> GetUntriggeredStopOrdersAsync(string? symbol = null, OrderSide? side = null, OrderType? type = null, DateTime? startTime = null, DateTime? endTime = null, int? currentPage = null, int? pageSize = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("symbol", symbol); parameters.AddOptionalParameter("side", side == null ? null : JsonConvert.SerializeObject(side, new OrderSideConverter(false))); parameters.AddOptionalParameter("type", type == null ? null : JsonConvert.SerializeObject(type, new OrderTypeConverter(false))); @@ -134,7 +134,7 @@ public async Task>> GetUntrigg /// public async Task>> GetClosedOrdersAsync(string? symbol = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("symbol", symbol); return await _baseClient.Execute>(_baseClient.GetUri("recentDoneOrders"), HttpMethod.Get, ct, parameters, signed: true).ConfigureAwait(false); } @@ -148,7 +148,7 @@ public async Task> GetOrderAsync(string orderI /// public async Task> GetOrderByClientOrderIdAsync(string clientOrderId, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("clientOid", clientOrderId); return await _baseClient.Execute(_baseClient.GetUri("orders/byClientOid"), HttpMethod.Get, ct, parameters, true).ConfigureAwait(false); } @@ -159,7 +159,7 @@ public async Task> GetOrderByClientOrderIdAsyn /// public async Task>> GetUserTradesAsync(string? orderId = null, string? symbol = null, OrderSide? side = null, OrderType? type = null, DateTime? startTime = null, DateTime? endTime = null, int? currentPage = null, int? pageSize = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("symbol", symbol); parameters.AddOptionalParameter("orderId", orderId); parameters.AddOptionalParameter("side", side == null ? null : JsonConvert.SerializeObject(side, new OrderSideConverter(false))); diff --git a/Kucoin.Net/Clients/SpotApi/KucoinRestClientSpotApi.cs b/Kucoin.Net/Clients/SpotApi/KucoinRestClientSpotApi.cs index 4e3a78a4..7f9f35d1 100644 --- a/Kucoin.Net/Clients/SpotApi/KucoinRestClientSpotApi.cs +++ b/Kucoin.Net/Clients/SpotApi/KucoinRestClientSpotApi.cs @@ -344,9 +344,33 @@ internal void InvokeOrderCanceled(OrderId id) OnOrderCanceled?.Invoke(id); } + internal async Task SendAsync(RequestDefinition definition, ParameterCollection? parameters, CancellationToken cancellationToken, int? weight = null) + { + var result = await base.SendAsync(BaseAddress, definition, parameters, cancellationToken, null, weight).ConfigureAwait(false); + if (!result) + return result.AsDatalessError(result.Error!); + + if (result.Data.Code != 200000) + return result.AsDatalessError(new ServerError(result.Data.Code, result.Data.Message ?? "-")); + + return result.AsDataless(); + } + + internal async Task> SendAsync(RequestDefinition definition, ParameterCollection? parameters, CancellationToken cancellationToken, int? weight = null) + { + var result = await base.SendAsync>(BaseAddress, definition, parameters, cancellationToken, null, weight).ConfigureAwait(false); + if (!result) + return result.AsError(result.Error!); + + if (result.Data.Code != 200000) + return result.AsError(new ServerError(result.Data.Code, result.Data.Message ?? "-")); + + return result.As(result.Data.Data); + } + internal async Task Execute(Uri uri, HttpMethod method, CancellationToken ct, Dictionary? parameters = null, bool signed = false, HttpMethodParameterPosition? parameterPosition = null) { - var result = await SendRequestAsync>(uri, method, ct, parameters, signed, parameterPosition: parameterPosition).ConfigureAwait(false); + var result = await SendRequestAsync>(uri, method, ct, parameters, signed, parameterPosition: parameterPosition, requestWeight: 0).ConfigureAwait(false); if (!result) return result.AsDatalessError(result.Error!); @@ -356,9 +380,9 @@ internal async Task Execute(Uri uri, HttpMethod method, Cancellat return result.AsDataless(); } - internal async Task> Execute(Uri uri, HttpMethod method, CancellationToken ct, Dictionary? parameters = null, bool signed = false, int weight = 1, bool ignoreRatelimit = false, HttpMethodParameterPosition? parameterPosition = null) + internal async Task> Execute(Uri uri, HttpMethod method, CancellationToken ct, Dictionary? parameters = null, bool signed = false, HttpMethodParameterPosition? parameterPosition = null) { - var result = await SendRequestAsync>(uri, method, ct, parameters, signed, parameterPosition: parameterPosition, requestWeight: weight, ignoreRatelimit: ignoreRatelimit).ConfigureAwait(false); + var result = await SendRequestAsync>(uri, method, ct, parameters, signed, parameterPosition: parameterPosition, requestWeight: 0).ConfigureAwait(false); if (!result) return result.AsError(result.Error!); @@ -373,6 +397,24 @@ internal Uri GetUri(string path, int apiVersion = 1) return new Uri(BaseAddress.AppendPath("api").AppendPath("v" + apiVersion, path)); } + /// + protected override ServerRateLimitError ParseRateLimitResponse(int httpStatusCode, IEnumerable>> responseHeaders, IMessageAccessor accessor) + { + var retryAfterHeader = responseHeaders.SingleOrDefault(r => r.Key.Equals("gw-ratelimit-reset", StringComparison.InvariantCultureIgnoreCase)); + if (retryAfterHeader.Value?.Any() != true) + return base.ParseRateLimitResponse(httpStatusCode, responseHeaders, accessor); + + var value = retryAfterHeader.Value.First(); + if (!int.TryParse(value, out var milliseconds)) + return base.ParseRateLimitResponse(httpStatusCode, responseHeaders, accessor); + + var msg = accessor.GetValue(MessagePath.Get().Property("msg")); + return new ServerRateLimitError(msg!) + { + RetryAfter = DateTime.UtcNow.AddMilliseconds(milliseconds) + }; + } + /// protected override Error ParseErrorResponse(int httpStatusCode, IEnumerable>> responseHeaders, IMessageAccessor accessor) { diff --git a/Kucoin.Net/Clients/SpotApi/KucoinRestClientSpotApiAccount.cs b/Kucoin.Net/Clients/SpotApi/KucoinRestClientSpotApiAccount.cs index 6077ca99..48c3010f 100644 --- a/Kucoin.Net/Clients/SpotApi/KucoinRestClientSpotApiAccount.cs +++ b/Kucoin.Net/Clients/SpotApi/KucoinRestClientSpotApiAccount.cs @@ -11,6 +11,7 @@ using System; using System.Collections.Generic; using System.Net.Http; +using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; @@ -19,6 +20,7 @@ namespace Kucoin.Net.Clients.SpotApi /// public class KucoinRestClientSpotApiAccount : IKucoinRestClientSpotApiAccount { + private static readonly RequestDefinitionCache _definitions = new(); private readonly KucoinRestClientSpotApi _baseClient; internal KucoinRestClientSpotApiAccount(KucoinRestClientSpotApi baseClient) @@ -29,38 +31,43 @@ internal KucoinRestClientSpotApiAccount(KucoinRestClientSpotApi baseClient) /// public async Task> GetUserInfoAsync(CancellationToken ct = default) { - return await _baseClient.Execute(_baseClient.GetUri("user-info", 2), HttpMethod.Get, ct, signed: true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v2/user-info", KucoinExchange.RateLimiter.ManagementRest, 20, true); + return await _baseClient.SendAsync(request, null, ct).ConfigureAwait(false); } /// public async Task>> GetSubUserInfoAsync(CancellationToken ct = default) { - return await _baseClient.Execute>(_baseClient.GetUri("sub/user"), HttpMethod.Get, ct, signed: true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/sub/user", KucoinExchange.RateLimiter.ManagementRest, 20, true); + return await _baseClient.SendAsync>(request, null, ct).ConfigureAwait(false); } /// public async Task>> GetAccountsAsync(string? asset = null, AccountType? accountType = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("currency", asset); parameters.AddOptionalParameter("type", accountType.HasValue ? JsonConvert.SerializeObject(accountType, new AccountTypeConverter(false)) : null); - return await _baseClient.Execute>(_baseClient.GetUri("accounts"), HttpMethod.Get, ct, parameters, true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/accounts", KucoinExchange.RateLimiter.ManagementRest, 5, true); + return await _baseClient.SendAsync>(request, parameters, ct).ConfigureAwait(false); } /// public async Task> GetAccountAsync(string accountId, CancellationToken ct = default) { accountId.ValidateNotNull(nameof(accountId)); - return await _baseClient.Execute(_baseClient.GetUri("accounts/" + accountId), HttpMethod.Get, ct, signed: true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/accounts/" + accountId, KucoinExchange.RateLimiter.ManagementRest, 5, true); + return await _baseClient.SendAsync(request, null, ct).ConfigureAwait(false); } /// public async Task> GetBasicUserFeeAsync(AssetType? assetType = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("currencyType", EnumConverter.GetString(assetType)); - return await _baseClient.Execute(_baseClient.GetUri("base-fee"), HttpMethod.Get, ct, parameters, signed: true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/base-fee", KucoinExchange.RateLimiter.SpotRest, 3, true); + return await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); } /// @@ -70,11 +77,12 @@ public async Task>> GetSymbolTradingFe /// public async Task>> GetSymbolTradingFeesAsync(IEnumerable symbols, CancellationToken ct = default) { - var parameters = new Dictionary + var parameters = new ParameterCollection { { "symbols", string.Join(",", symbols) } }; - return await _baseClient.Execute>(_baseClient.GetUri("trade-fees"), HttpMethod.Get, ct, parameters, true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/trade-fees", KucoinExchange.RateLimiter.SpotRest, 3, true); + return await _baseClient.SendAsync>(request, parameters, ct).ConfigureAwait(false); } /// @@ -96,7 +104,7 @@ public async Task>> GetAcco directionString = JsonConvert.SerializeObject(direction, new AccountDirectionConverter(false)).ToLowerInvariant(); } - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("currency", asset); parameters.AddOptionalParameter("direction", direction.HasValue ? directionString : null); parameters.AddOptionalParameter("bizType", bizType.HasValue ? bizTypeString : null); @@ -105,18 +113,20 @@ public async Task>> GetAcco parameters.AddOptionalParameter("currentPage", currentPage); parameters.AddOptionalParameter("pageSize", pageSize); - return await _baseClient.Execute>(_baseClient.GetUri("accounts/ledgers"), HttpMethod.Get, ct, parameters, true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/accounts/ledgers", KucoinExchange.RateLimiter.ManagementRest, 2, true); + return await _baseClient.SendAsync>(request, parameters, ct).ConfigureAwait(false); } /// public async Task> GetTransferableAsync(string asset, AccountType accountType, CancellationToken ct = default) { - var parameters = new Dictionary + var parameters = new ParameterCollection { { "currency", asset }, { "type", JsonConvert.SerializeObject(accountType, new AccountTypeConverter(false, true))} }; - return await _baseClient.Execute(_baseClient.GetUri("accounts/transferable"), HttpMethod.Get, ct, parameters, true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/accounts/transferable", KucoinExchange.RateLimiter.ManagementRest, 20, true); + return await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); } /// @@ -133,7 +143,7 @@ public async Task> UniversalTransferAsync( string? clientOrderId = null, CancellationToken ct = default) { - var parameters = new Dictionary + var parameters = new ParameterCollection { { "fromAccountType", EnumConverter.GetString(fromAccountType) }, { "toAccountType", EnumConverter.GetString(toAccountType)}, @@ -147,14 +157,15 @@ public async Task> UniversalTransferAsync( parameters.AddOptionalParameter("toUserId", toUserId); parameters.AddOptionalParameter("toAccountTag", toAccountTag); - return await _baseClient.Execute(_baseClient.GetUri("accounts/universal-transfer", 3), HttpMethod.Post, ct, parameters, true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Post, $"api/v3/universal-transfer", KucoinExchange.RateLimiter.ManagementRest, 4, true); + return await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); } /// public async Task> InnerTransferAsync(string asset, AccountType from, AccountType to, decimal quantity, string? fromTag = null, string? toTag = null, string? clientOrderId = null, CancellationToken ct = default) { asset.ValidateNotNull(nameof(asset)); - var parameters = new Dictionary + var parameters = new ParameterCollection { { "currency", asset }, { "from", JsonConvert.SerializeObject(from, new AccountTypeConverter(false))}, @@ -165,7 +176,8 @@ public async Task> InnerTransferAsync(string parameters.AddOptionalParameter("fromTag", fromTag); parameters.AddOptionalParameter("toTag", toTag); - return await _baseClient.Execute(_baseClient.GetUri("accounts/inner-transfer", 2), HttpMethod.Post, ct, parameters, true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Post, $"api/v2/accounts/inner-transfer", KucoinExchange.RateLimiter.ManagementRest, 10, true); + return await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); } /// @@ -173,14 +185,15 @@ public async Task>> GetDepositsAsyn { pageSize?.ValidateIntBetween(nameof(pageSize), 10, 500); - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("currency", asset); parameters.AddOptionalParameter("startAt", DateTimeConverter.ConvertToMilliseconds(startTime)); parameters.AddOptionalParameter("endAt", DateTimeConverter.ConvertToMilliseconds(endTime)); parameters.AddOptionalParameter("status", status.HasValue ? JsonConvert.SerializeObject(status, new DepositStatusConverter(false)) : null); parameters.AddOptionalParameter("currentPage", currentPage); parameters.AddOptionalParameter("pageSize", pageSize); - return await _baseClient.Execute>(_baseClient.GetUri("deposits"), HttpMethod.Get, ct, parameters, true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/deposits", KucoinExchange.RateLimiter.ManagementRest, 5, true); + return await _baseClient.SendAsync>(request, parameters, ct).ConfigureAwait(false); } /// @@ -188,40 +201,44 @@ public async Task>> GetHi { pageSize?.ValidateIntBetween(nameof(pageSize), 10, 500); - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("currency", asset); parameters.AddOptionalParameter("startAt", DateTimeConverter.ConvertToMilliseconds(startTime)); parameters.AddOptionalParameter("endAt", DateTimeConverter.ConvertToMilliseconds(endTime)); parameters.AddOptionalParameter("status", status.HasValue ? JsonConvert.SerializeObject(status, new DepositStatusConverter(false)) : null); parameters.AddOptionalParameter("currentPage", currentPage); parameters.AddOptionalParameter("pageSize", pageSize); - return await _baseClient.Execute>(_baseClient.GetUri("hist-deposits"), HttpMethod.Get, ct, parameters, true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/hist-deposits", KucoinExchange.RateLimiter.ManagementRest, 5, true); + return await _baseClient.SendAsync>(request, parameters, ct).ConfigureAwait(false); } /// public async Task> GetDepositAddressAsync(string asset, string? network = null, CancellationToken ct = default) { asset.ValidateNotNull(nameof(asset)); - var parameters = new Dictionary { { "currency", asset } }; + var parameters = new ParameterCollection { { "currency", asset } }; parameters.AddOptionalParameter("chain", network); - return await _baseClient.Execute(_baseClient.GetUri("deposit-addresses"), HttpMethod.Get, ct, parameters, true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/deposit-addresses", KucoinExchange.RateLimiter.ManagementRest, 5, true); + return await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); } /// public async Task>> GetDepositAddressesAsync(string asset, CancellationToken ct = default) { asset.ValidateNotNull(nameof(asset)); - var parameters = new Dictionary { { "currency", asset } }; - return await _baseClient.Execute>(_baseClient.GetUri("deposit-addresses", 2), HttpMethod.Get, ct, parameters, true).ConfigureAwait(false); + var parameters = new ParameterCollection { { "currency", asset } }; + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v2/deposit-addresses", KucoinExchange.RateLimiter.ManagementRest, 5, true); + return await _baseClient.SendAsync>(request, parameters, ct).ConfigureAwait(false); } /// public async Task> CreateDepositAddressAsync(string asset, string? network = null, CancellationToken ct = default) { asset.ValidateNotNull(nameof(asset)); - var parameters = new Dictionary { { "currency", asset } }; + var parameters = new ParameterCollection { { "currency", asset } }; parameters.AddOptionalParameter("chain", network); - return await _baseClient.Execute(_baseClient.GetUri("deposit-addresses"), HttpMethod.Post, ct, parameters, true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Post, $"api/v1/deposit-addresses", KucoinExchange.RateLimiter.ManagementRest, 20, true); + return await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); } /// @@ -229,14 +246,15 @@ public async Task>> GetWithdrawa { pageSize?.ValidateIntBetween(nameof(pageSize), 10, 500); - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("currency", asset); parameters.AddOptionalParameter("startAt", DateTimeConverter.ConvertToMilliseconds(startTime)); parameters.AddOptionalParameter("endAt", DateTimeConverter.ConvertToMilliseconds(endTime)); parameters.AddOptionalParameter("status", status.HasValue ? JsonConvert.SerializeObject(status, new WithdrawalStatusConverter(false)) : null); parameters.AddOptionalParameter("currentPage", currentPage); parameters.AddOptionalParameter("pageSize", pageSize); - return await _baseClient.Execute>(_baseClient.GetUri("withdrawals"), HttpMethod.Get, ct, parameters, true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/withdrawals", KucoinExchange.RateLimiter.ManagementRest, 20, true); + return await _baseClient.SendAsync>(request, parameters, ct).ConfigureAwait(false); } /// @@ -244,14 +262,15 @@ public async Task>> Ge { pageSize?.ValidateIntBetween(nameof(pageSize), 10, 500); - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("currency", asset); parameters.AddOptionalParameter("startAt", DateTimeConverter.ConvertToMilliseconds(startTime)); parameters.AddOptionalParameter("endAt", DateTimeConverter.ConvertToMilliseconds(endTime)); parameters.AddOptionalParameter("status", status.HasValue ? JsonConvert.SerializeObject(status, new WithdrawalStatusConverter(false)) : null); parameters.AddOptionalParameter("currentPage", currentPage); parameters.AddOptionalParameter("pageSize", pageSize); - return await _baseClient.Execute>(_baseClient.GetUri("hist-withdrawals"), HttpMethod.Get, ct, parameters, true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/hist-withdrawals", KucoinExchange.RateLimiter.ManagementRest, 20, true); + return await _baseClient.SendAsync>(request, parameters, ct).ConfigureAwait(false); } /// @@ -259,9 +278,10 @@ public async Task> GetWithdrawalQuotasAsync { asset.ValidateNotNull(nameof(asset)); - var parameters = new Dictionary { { "currency", asset } }; + var parameters = new ParameterCollection { { "currency", asset } }; parameters.AddOptionalParameter("chain", network); - return await _baseClient.Execute(_baseClient.GetUri("withdrawals/quotas"), HttpMethod.Get, ct, parameters: parameters, signed: true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/withdrawals/quotas", KucoinExchange.RateLimiter.ManagementRest, 20, true); + return await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); } /// @@ -269,7 +289,7 @@ public async Task> WithdrawAsync(string asset { asset.ValidateNotNull(nameof(asset)); toAddress.ValidateNotNull(nameof(toAddress)); - var parameters = new Dictionary { + var parameters = new ParameterCollection { { "currency", asset }, { "address", toAddress }, { "amount", quantity }, @@ -279,20 +299,23 @@ public async Task> WithdrawAsync(string asset parameters.AddOptionalParameter("remark", remark); parameters.AddOptionalParameter("chain", network); parameters.AddOptionalParameter("feeDeductType", EnumConverter.GetString(deductType)); - return await _baseClient.Execute(_baseClient.GetUri("withdrawals"), HttpMethod.Post, ct, parameters, true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Post, $"api/v1/withdrawals", KucoinExchange.RateLimiter.ManagementRest, 5, true); + return await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); } /// - public async Task> CancelWithdrawalAsync(string withdrawalId, CancellationToken ct = default) + public async Task CancelWithdrawalAsync(string withdrawalId, CancellationToken ct = default) { withdrawalId.ValidateNotNull(nameof(withdrawalId)); - return await _baseClient.Execute(_baseClient.GetUri($"withdrawals/{withdrawalId}"), HttpMethod.Delete, ct, signed: true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Delete, $"api/v1/withdrawals/{withdrawalId}", KucoinExchange.RateLimiter.ManagementRest, 20, true); + return await _baseClient.SendAsync(request, null, ct).ConfigureAwait(false); } /// public async Task> GetMarginAccountAsync(CancellationToken ct = default) { - return await _baseClient.Execute(_baseClient.GetUri($"margin/account"), HttpMethod.Get, ct, signed: true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/margin/account", KucoinExchange.RateLimiter.SpotRest, 40, true); + return await _baseClient.SendAsync(request, null, ct).ConfigureAwait(false); } /// @@ -300,38 +323,34 @@ public async Task> GetCrossMarginAccount { var parameters = new ParameterCollection(); parameters.AddOptional("quoteCurrency", quoteAsset); - return await _baseClient.Execute(_baseClient.GetUri($"margin/accounts", 3), HttpMethod.Get, ct, parameters, signed: true).ConfigureAwait(false); - } - - /// - public async Task>> GetRiskLimitCrossMarginAsync(CancellationToken ct = default) - { - return await _baseClient.Execute>(_baseClient.GetUri($"risk/limit/strategy"), HttpMethod.Get, ct, signed: true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v3/margin/accounts", KucoinExchange.RateLimiter.SpotRest, 15, true); + return await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); } /// - public async Task>> GetRiskLimitIsolatedMarginAsync(CancellationToken ct = default) + public async Task> GetIsolatedMarginAccountsAsync(CancellationToken ct = default) { - var parameters = new Dictionary(); - parameters.AddOptionalParameter("marginModel", "isolated"); - return await _baseClient.Execute>(_baseClient.GetUri($"risk/limit/strategy"), HttpMethod.Get, ct, parameters, signed: true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/isolated/accounts", KucoinExchange.RateLimiter.SpotRest, 50, true); + return await _baseClient.SendAsync(request, null, ct).ConfigureAwait(false); } /// - public async Task> GetIsolatedMarginAccountsAsync(CancellationToken ct = default) + public async Task> GetIsolatedMarginAccountAsync(string symbol, CancellationToken ct = default) { - return await _baseClient.Execute(_baseClient.GetUri($"isolated/accounts"), HttpMethod.Get, ct, signed: true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/isolated/account/{symbol}", KucoinExchange.RateLimiter.SpotRest, 50, true); + return await _baseClient.SendAsync(request, null, ct).ConfigureAwait(false); } - /// - public async Task> GetIsolatedMarginAccountAsync(string symbol, CancellationToken ct = default) + internal async Task> GetWebsocketTokenPublicAsync(CancellationToken ct = default) { - return await _baseClient.Execute(_baseClient.GetUri($"isolated/account/{symbol}"), HttpMethod.Get, ct, signed: true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Post, $"api/v1/bullet-public", KucoinExchange.RateLimiter.PublicRest, 10, false); + return await _baseClient.SendAsync(request, null, ct).ConfigureAwait(false); } - internal async Task> GetWebsocketToken(bool authenticated, CancellationToken ct = default) + internal async Task> GetWebsocketTokenPrivateAsync(CancellationToken ct = default) { - return await _baseClient.Execute(_baseClient.GetUri(authenticated ? "bullet-private" : "bullet-public"), method: HttpMethod.Post, ct, signed: authenticated).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Post, $"api/v1/bullet-private", KucoinExchange.RateLimiter.PublicRest, 10, true); + return await _baseClient.SendAsync(request, null, ct).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/Kucoin.Net/Clients/SpotApi/KucoinRestClientSpotApiExchangeData.cs b/Kucoin.Net/Clients/SpotApi/KucoinRestClientSpotApiExchangeData.cs index 2b96c1d0..28d81a52 100644 --- a/Kucoin.Net/Clients/SpotApi/KucoinRestClientSpotApiExchangeData.cs +++ b/Kucoin.Net/Clients/SpotApi/KucoinRestClientSpotApiExchangeData.cs @@ -15,6 +15,7 @@ using Kucoin.Net.Objects.Models.Futures; using Kucoin.Net.Objects; using Kucoin.Net.ExtensionMethods; +using System.Security.Cryptography; namespace Kucoin.Net.Clients.SpotApi { @@ -22,6 +23,7 @@ namespace Kucoin.Net.Clients.SpotApi public class KucoinRestClientSpotApiExchangeData : IKucoinRestClientSpotApiExchangeData { private readonly KucoinRestClientSpotApi _baseClient; + private static readonly RequestDefinitionCache _definitions = new(); internal KucoinRestClientSpotApiExchangeData(KucoinRestClientSpotApi baseClient) { @@ -31,82 +33,92 @@ internal KucoinRestClientSpotApiExchangeData(KucoinRestClientSpotApi baseClient) /// public async Task> GetServerTimeAsync(CancellationToken ct = default) { - var result = await _baseClient.Execute(_baseClient.GetUri("timestamp"), HttpMethod.Get, ct, ignoreRatelimit: true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/timestamp", KucoinExchange.RateLimiter.PublicRest, 3); + var result = await _baseClient.SendAsync(request, null, ct).ConfigureAwait(false); return result.As(result ? JsonConvert.DeserializeObject(result.Data.ToString(), new DateTimeConverter()) : default); } /// public async Task>> GetSymbolsAsync(string? market = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("market", market); // Testnet doesn't support V2 var apiVersion = _baseClient.BaseAddress == KucoinApiAddresses.TestNet.SpotAddress ? 1 : 2; - return await _baseClient.Execute>(_baseClient.GetUri("symbols", apiVersion), HttpMethod.Get, ct, parameters: parameters).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v{apiVersion}/symbols", KucoinExchange.RateLimiter.PublicRest, 4); + return await _baseClient.SendAsync>(request, parameters, ct).ConfigureAwait(false); } /// public async Task> GetTickerAsync(string symbol, CancellationToken ct = default) { - var parameters = new Dictionary { { "symbol", symbol } }; - return await _baseClient.Execute(_baseClient.GetUri("market/orderbook/level1"), HttpMethod.Get, ct, parameters: parameters).ConfigureAwait(false); + var parameters = new ParameterCollection { { "symbol", symbol } }; + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/market/orderbook/level1", KucoinExchange.RateLimiter.PublicRest, 2); + return await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); } /// public async Task> GetTickersAsync(CancellationToken ct = default) { - return await _baseClient.Execute(_baseClient.GetUri("market/allTickers"), HttpMethod.Get, ct).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/market/allTickers", KucoinExchange.RateLimiter.PublicRest, 15); + return await _baseClient.SendAsync(request, null, ct).ConfigureAwait(false); } /// public async Task> Get24HourStatsAsync(string symbol, CancellationToken ct = default) { - var parameters = new Dictionary { { "symbol", symbol } }; - return await _baseClient.Execute(_baseClient.GetUri("market/stats"), HttpMethod.Get, ct, parameters: parameters).ConfigureAwait(false); + var parameters = new ParameterCollection { { "symbol", symbol } }; + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/market/stats", KucoinExchange.RateLimiter.PublicRest, 15); + return await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); } /// public async Task>> GetMarketsAsync(CancellationToken ct = default) { - return await _baseClient.Execute>(_baseClient.GetUri("markets"), HttpMethod.Get, ct).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/markets", KucoinExchange.RateLimiter.PublicRest, 3); + return await _baseClient.SendAsync>(request, null, ct).ConfigureAwait(false); } /// public async Task> GetAggregatedPartialOrderBookAsync(string symbol, int limit, CancellationToken ct = default) { limit.ValidateIntValues(nameof(limit), 20, 100); - var parameters = new Dictionary + var parameters = new ParameterCollection { { "symbol", symbol } }; - return await _baseClient.Execute(_baseClient.GetUri($"market/orderbook/level2_{limit}"), HttpMethod.Get, ct, parameters).ConfigureAwait(false); + var weight = limit == 20 ? 2 : 4; + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/market/orderbook/level2_{limit}", KucoinExchange.RateLimiter.PublicRest, weight); + return await _baseClient.SendAsync(request, parameters, ct, weight).ConfigureAwait(false); } /// public async Task> GetAggregatedFullOrderBookAsync(string symbol, CancellationToken ct = default) { - var parameters = new Dictionary + var parameters = new ParameterCollection { { "symbol", symbol } }; - return await _baseClient.Execute(_baseClient.GetUri($"market/orderbook/level2", 3), HttpMethod.Get, ct, parameters, true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/market/orderbook/level2", KucoinExchange.RateLimiter.PublicRest, 3); + return await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); } /// public async Task>> GetTradeHistoryAsync(string symbol, CancellationToken ct = default) { - var parameters = new Dictionary + var parameters = new ParameterCollection { { "symbol", symbol } }; - return await _baseClient.Execute>(_baseClient.GetUri($"market/histories"), HttpMethod.Get, ct, parameters).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/market/histories", KucoinExchange.RateLimiter.PublicRest, 3); + return await _baseClient.SendAsync>(request, parameters, ct).ConfigureAwait(false); } /// public async Task>> GetKlinesAsync(string symbol, KlineInterval interval, DateTime? startTime = null, DateTime? endTime = null, CancellationToken ct = default) { - var parameters = new Dictionary + var parameters = new ParameterCollection { { "symbol", symbol }, { "type", JsonConvert.SerializeObject(interval, new KlineIntervalConverter(false)) } @@ -114,69 +126,55 @@ public async Task>> GetKlinesAsync(string parameters.AddOptionalParameter("startAt", DateTimeConverter.ConvertToSeconds(startTime)); parameters.AddOptionalParameter("endAt", DateTimeConverter.ConvertToSeconds(endTime)); - return await _baseClient.Execute>(_baseClient.GetUri("market/candles"), HttpMethod.Get, ct, parameters).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/market/candles", KucoinExchange.RateLimiter.PublicRest, 3); + return await _baseClient.SendAsync>(request, parameters, ct).ConfigureAwait(false); } /// public async Task>> GetAssetsAsync(CancellationToken ct = default) { - return await _baseClient.Execute>(_baseClient.GetUri("currencies", 3), HttpMethod.Get, ct).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v3/currencies", KucoinExchange.RateLimiter.PublicRest, 3); + return await _baseClient.SendAsync>(request, null, ct).ConfigureAwait(false); } /// public async Task> GetAssetAsync(string asset, CancellationToken ct = default) { asset.ValidateNotNull(nameof(asset)); - return await _baseClient.Execute(_baseClient.GetUri($"currencies/{asset}", 3), HttpMethod.Get, ct).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v3/currencies/{asset}", KucoinExchange.RateLimiter.PublicRest, 3); + return await _baseClient.SendAsync(request, null, ct).ConfigureAwait(false); } /// public async Task>> GetFiatPricesAsync(string? fiatBase = null, IEnumerable? assets = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("base", fiatBase); parameters.AddOptionalParameter("currencies", assets?.Any() == true ? string.Join(",", assets) : null); - return await _baseClient.Execute>(_baseClient.GetUri("prices"), HttpMethod.Get, ct, parameters: parameters).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/prices", KucoinExchange.RateLimiter.PublicRest, 3); + return await _baseClient.SendAsync>(request, parameters, ct).ConfigureAwait(false); } /// public async Task> GetMarginMarkPriceAsync(string symbol, CancellationToken ct = default) { - return await _baseClient.Execute(_baseClient.GetUri($"mark-price/{symbol}/current"), HttpMethod.Get, ct).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/mark-price/{symbol}/current", KucoinExchange.RateLimiter.PublicRest, 2); + return await _baseClient.SendAsync(request, null, ct).ConfigureAwait(false); } /// public async Task> GetMarginConfigurationAsync(CancellationToken ct = default) { - return await _baseClient.Execute(_baseClient.GetUri("margin/config"), HttpMethod.Get, ct).ConfigureAwait(false); - } - - /// - public async Task>> GetLendMarketDataAsync(string asset, int? term = null, CancellationToken ct = default) - { - var parameters = new Dictionary - { - { "currency", asset } - }; - parameters.AddOptionalParameter("term", term); - return await _baseClient.Execute>(_baseClient.GetUri("margin/market"), HttpMethod.Get, ct, parameters).ConfigureAwait(false); - } - - /// - public async Task>> GetMarginTradeHistoryAsync(string asset, CancellationToken ct = default) - { - var parameters = new Dictionary - { - { "currency", asset } - }; - return await _baseClient.Execute>(_baseClient.GetUri("margin/trade/last"), HttpMethod.Get, ct, parameters).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/margin/config", KucoinExchange.RateLimiter.SpotRest, 25); + return await _baseClient.SendAsync(request, null, ct).ConfigureAwait(false); } /// public async Task>> GetMarginTradingPairConfigurationAsync(CancellationToken ct = default) { - return await _baseClient.Execute>(_baseClient.GetUri($"isolated/symbols"), HttpMethod.Get, ct, signed: true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/isolated/symbols", KucoinExchange.RateLimiter.SpotRest, 20, true); + return await _baseClient.SendAsync>(request, null, ct).ConfigureAwait(false); } } } diff --git a/Kucoin.Net/Clients/SpotApi/KucoinRestClientSpotApiProAccount.cs b/Kucoin.Net/Clients/SpotApi/KucoinRestClientSpotApiProAccount.cs index e514619f..d43b9673 100644 --- a/Kucoin.Net/Clients/SpotApi/KucoinRestClientSpotApiProAccount.cs +++ b/Kucoin.Net/Clients/SpotApi/KucoinRestClientSpotApiProAccount.cs @@ -18,6 +18,7 @@ namespace Kucoin.Net.Clients.SpotApi /// public class KucoinRestClientSpotApiProAccount : IKucoinRestClientSpotApiProAccount { + private static readonly RequestDefinitionCache _definitions = new(); private readonly KucoinRestClientSpotApi _baseClient; internal KucoinRestClientSpotApiProAccount(KucoinRestClientSpotApi baseClient) @@ -56,7 +57,7 @@ public async Task> PlaceOrderAsync( throw new ArgumentException("Market order cant have both quantity and quoteQuantity specified"); } - var parameters = new Dictionary + var parameters = new ParameterCollection { { "symbol", symbol }, { "side", JsonConvert.SerializeObject(side, new OrderSideConverter(false)) }, @@ -74,7 +75,8 @@ public async Task> PlaceOrderAsync( parameters.AddOptionalParameter("visibleSize", visibleIceBergSize); parameters.AddOptionalParameter("remark", remark); parameters.AddOptionalParameter("stp", selfTradePrevention.HasValue ? JsonConvert.SerializeObject(selfTradePrevention.Value, new SelfTradePreventionConverter(false)) : null); - var result = await _baseClient.Execute(_baseClient.GetUri("hf/orders"), HttpMethod.Post, ct, parameters, true, weight: 4).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Post, $"api/v1/hf/orders", KucoinExchange.RateLimiter.SpotRest, 1, true); + var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); if (result) _baseClient.InvokeOrderPlaced(new OrderId { SourceObject = result.Data, Id = result.Data.Id }); return result; @@ -86,25 +88,27 @@ public async Task> CancelOrderAsync(string o orderId.ValidateNotNull(nameof(orderId)); symbol.ValidateNotNull(nameof(symbol)); - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("symbol", symbol); - var result = await _baseClient.Execute(_baseClient.GetUri($"hf/orders/{orderId}"), HttpMethod.Delete, ct, signed: true, weight: 3, parameters: parameters).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Delete, $"api/v1/hf/orders/{orderId}", KucoinExchange.RateLimiter.SpotRest, 1, true); + var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); if (result) _baseClient.InvokeOrderCanceled(new OrderId { SourceObject = result.Data, Id = orderId }); return result; } /// - public Task> GetOrderAsync(string orderId, string symbol, CancellationToken ct = default) + public async Task> GetOrderAsync(string orderId, string symbol, CancellationToken ct = default) { orderId.ValidateNotNull(nameof(orderId)); symbol.ValidateNotNull(nameof(symbol)); - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("symbol", symbol); - return _baseClient.Execute(_baseClient.GetUri($"hf/orders/{orderId}"), HttpMethod.Get, ct, signed: true, parameters: parameters); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/hf/orders/{orderId}", KucoinExchange.RateLimiter.SpotRest, 2, true); + return await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); } } } \ No newline at end of file diff --git a/Kucoin.Net/Clients/SpotApi/KucoinRestClientSpotApiTrading.cs b/Kucoin.Net/Clients/SpotApi/KucoinRestClientSpotApiTrading.cs index bac3b2a0..dd8cb331 100644 --- a/Kucoin.Net/Clients/SpotApi/KucoinRestClientSpotApiTrading.cs +++ b/Kucoin.Net/Clients/SpotApi/KucoinRestClientSpotApiTrading.cs @@ -23,6 +23,7 @@ namespace Kucoin.Net.Clients.SpotApi public class KucoinRestClientSpotApiTrading : IKucoinRestClientSpotApiTrading { private readonly KucoinRestClientSpotApi _baseClient; + private static readonly RequestDefinitionCache _definitions = new(); internal KucoinRestClientSpotApiTrading(KucoinRestClientSpotApi baseClient) { @@ -60,7 +61,7 @@ public async Task> PlaceOrderAsync( throw new ArgumentException("Market order cant have both quantity and quoteQuantity specified"); } - var parameters = new Dictionary + var parameters = new ParameterCollection { { "symbol", symbol }, { "side", JsonConvert.SerializeObject(side, new OrderSideConverter(false)) }, @@ -78,7 +79,9 @@ public async Task> PlaceOrderAsync( parameters.AddOptionalParameter("visibleSize", visibleIceBergSize); parameters.AddOptionalParameter("remark", remark); parameters.AddOptionalParameter("stp", selfTradePrevention.HasValue ? JsonConvert.SerializeObject(selfTradePrevention.Value, new SelfTradePreventionConverter(false)) : null); - var result = await _baseClient.Execute(_baseClient.GetUri("orders"), HttpMethod.Post, ct, parameters, true, weight: 2).ConfigureAwait(false); + + var request = _definitions.GetOrCreate(HttpMethod.Post, $"api/v1/orders", KucoinExchange.RateLimiter.SpotRest, 2, true); + var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); if (result) _baseClient.InvokeOrderPlaced(new OrderId { SourceObject = result.Data, Id = result.Data.Id }); return result; @@ -115,7 +118,7 @@ public async Task> PlaceTestOrderAsync( throw new ArgumentException("Market order cant have both quantity and quoteQuantity specified"); } - var parameters = new Dictionary + var parameters = new ParameterCollection { { "symbol", symbol }, { "side", JsonConvert.SerializeObject(side, new OrderSideConverter(false)) }, @@ -133,7 +136,8 @@ public async Task> PlaceTestOrderAsync( parameters.AddOptionalParameter("visibleSize", visibleIceBergSize); parameters.AddOptionalParameter("remark", remark); parameters.AddOptionalParameter("stp", selfTradePrevention.HasValue ? JsonConvert.SerializeObject(selfTradePrevention.Value, new SelfTradePreventionConverter(false)) : null); - return await _baseClient.Execute(_baseClient.GetUri("orders/test"), HttpMethod.Post, ct, parameters, true, weight: 2).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Post, $"api/v1/orders/test", KucoinExchange.RateLimiter.SpotRest, 2, true); + return await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); } /// @@ -171,7 +175,7 @@ public async Task> PlaceMarginOrderAsync( if (marginMode.HasValue && marginMode.Value != MarginMode.CrossMode) throw new ArgumentException("Currently, the platform only supports the cross mode"); - var parameters = new Dictionary + var parameters = new ParameterCollection { { "symbol", symbol }, { "side", JsonConvert.SerializeObject(side, new OrderSideConverter(false)) }, @@ -192,7 +196,8 @@ public async Task> PlaceMarginOrderAsync( parameters.AddOptionalParameter("autoBorrow", autoBorrow); parameters.AddOptionalParameter("autoRepay", autoRepay); parameters.AddOptionalParameter("stp", selfTradePrevention.HasValue ? JsonConvert.SerializeObject(selfTradePrevention.Value, new SelfTradePreventionConverter(false)) : null); - return await _baseClient.Execute(_baseClient.GetUri("margin/order"), HttpMethod.Post, ct, parameters, true, weight: 5).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Post, $"api/v1/margin/order", KucoinExchange.RateLimiter.SpotRest, 5, true); + return await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); } /// @@ -230,7 +235,7 @@ public async Task> PlaceTestMarginOrderAsync if (marginMode.HasValue && marginMode.Value != MarginMode.CrossMode) throw new ArgumentException("Currently, the platform only supports the cross mode"); - var parameters = new Dictionary + var parameters = new ParameterCollection { { "symbol", symbol }, { "side", JsonConvert.SerializeObject(side, new OrderSideConverter(false)) }, @@ -251,7 +256,8 @@ public async Task> PlaceTestMarginOrderAsync parameters.AddOptionalParameter("autoBorrow", autoBorrow); parameters.AddOptionalParameter("autoRepay", autoRepay); parameters.AddOptionalParameter("stp", selfTradePrevention.HasValue ? JsonConvert.SerializeObject(selfTradePrevention.Value, new SelfTradePreventionConverter(false)) : null); - return await _baseClient.Execute(_baseClient.GetUri("margin/order/test"), HttpMethod.Post, ct, parameters, true, weight: 5).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Post, $"api/v1/margin/order/test", KucoinExchange.RateLimiter.SpotRest, 5, true); + return await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); } /// @@ -267,13 +273,14 @@ public async Task> PlaceBulkOrderAsync(st if (orderList.Any(o => o.TradeType != null && o.TradeType != TradeType.SpotTrade)) throw new ArgumentException("Only spot orders can be part of a bulk order"); - var parameters = new Dictionary + var parameters = new ParameterCollection { { "symbol", symbol }, { "orderList", orderList } }; - var result = await _baseClient.Execute(_baseClient.GetUri("orders/multi"), HttpMethod.Post, ct, parameters, true, weight: 60, parameterPosition: HttpMethodParameterPosition.InBody).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Post, $"api/v1/orders/multi", KucoinExchange.RateLimiter.SpotRest, 3, true); + var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); if (result) { foreach (var order in result.Data.Orders.Where(o => o.Status == BulkOrderCreationStatus.Success)) @@ -288,7 +295,8 @@ public async Task> PlaceBulkOrderAsync(st public async Task> CancelOrderAsync(string orderId, CancellationToken ct = default) { orderId.ValidateNotNull(nameof(orderId)); - var result = await _baseClient.Execute(_baseClient.GetUri($"orders/{orderId}"), HttpMethod.Delete, ct, signed: true, weight: 3).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Delete, $"api/v1/orders/{orderId}", KucoinExchange.RateLimiter.SpotRest, 3, true); + var result = await _baseClient.SendAsync(request, null, ct).ConfigureAwait(false); if (result) _baseClient.InvokeOrderCanceled(new OrderId { SourceObject = result.Data, Id = orderId }); return result; @@ -298,7 +306,8 @@ public async Task> CancelOrderAsync(string o public async Task> CancelOrderByClientOrderIdAsync(string clientOrderId, CancellationToken ct = default) { clientOrderId.ValidateNotNull(nameof(clientOrderId)); - var result = await _baseClient.Execute(_baseClient.GetUri($"order/client-order/{clientOrderId}"), HttpMethod.Delete, ct, signed: true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Delete, $"api/v1/order/client-order/{clientOrderId}", KucoinExchange.RateLimiter.SpotRest, 5, true); + var result = await _baseClient.SendAsync(request, null, ct).ConfigureAwait(false); if (result) _baseClient.InvokeOrderCanceled(new OrderId { SourceObject = result.Data, Id = clientOrderId }); return result; @@ -307,10 +316,11 @@ public async Task> CancelOrderByClientOrderId /// public async Task> CancelAllOrdersAsync(string? symbol = null, TradeType? type = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("symbol", symbol); parameters.AddOptionalParameter("tradeType", EnumConverter.GetString(type)); - return await _baseClient.Execute(_baseClient.GetUri("orders"), HttpMethod.Delete, ct, parameters, true, weight: 60).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Delete, $"api/v1/orders", KucoinExchange.RateLimiter.SpotRest, 5, true); + return await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); } /// @@ -318,7 +328,7 @@ public async Task>> GetOrdersAsync(st { pageSize?.ValidateIntBetween(nameof(pageSize), 10, 500); - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("symbol", symbol); parameters.AddOptionalParameter("side", side.HasValue ? JsonConvert.SerializeObject(side, new OrderSideConverter(false)) : null); parameters.AddOptionalParameter("type", type.HasValue ? JsonConvert.SerializeObject(type, new OrderTypeConverter(false)) : null); @@ -329,43 +339,31 @@ public async Task>> GetOrdersAsync(st parameters.AddOptionalParameter("currentPage", currentPage); parameters.AddOptionalParameter("pageSize", pageSize); - return await _baseClient.Execute>(_baseClient.GetUri("orders"), HttpMethod.Get, ct, parameters: parameters, signed: true, weight: 6).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/orders", KucoinExchange.RateLimiter.SpotRest, 2, true); + return await _baseClient.SendAsync>(request, parameters, ct).ConfigureAwait(false); } /// public async Task>> GetRecentOrdersAsync(CancellationToken ct = default) { - return await _baseClient.Execute>(_baseClient.GetUri("limit/orders"), HttpMethod.Get, ct, signed: true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/limit/orders", KucoinExchange.RateLimiter.SpotRest, 3, true); + return await _baseClient.SendAsync>(request, null, ct).ConfigureAwait(false); } /// public async Task> GetOrderByClientOrderIdAsync(string clientOrderId, CancellationToken ct = default) { clientOrderId.ValidateNotNull(nameof(clientOrderId)); - return await _baseClient.Execute(_baseClient.GetUri($"order/client-order/{clientOrderId}"), HttpMethod.Get, ct, signed: true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/order/client-order/{clientOrderId}", KucoinExchange.RateLimiter.SpotRest, 3, true); + return await _baseClient.SendAsync(request, null, ct).ConfigureAwait(false); } /// public async Task> GetOrderAsync(string orderId, CancellationToken ct = default) { orderId.ValidateNotNull(nameof(orderId)); - return await _baseClient.Execute(_baseClient.GetUri($"orders/{orderId}"), HttpMethod.Get, ct, signed: true).ConfigureAwait(false); - } - - /// - public async Task>> GetHistoricalOrdersAsync(string? symbol = null, Enums.OrderSide? side = null, DateTime? startTime = null, DateTime? endTime = null, int? currentPage = null, int? pageSize = null, CancellationToken ct = default) - { - pageSize?.ValidateIntBetween(nameof(pageSize), 10, 500); - - var parameters = new Dictionary(); - parameters.AddOptionalParameter("symbol", symbol); - parameters.AddOptionalParameter("side", side.HasValue ? JsonConvert.SerializeObject(side, new OrderSideConverter(false)) : null); - parameters.AddOptionalParameter("startAt", DateTimeConverter.ConvertToMilliseconds(startTime)); - parameters.AddOptionalParameter("endAt", DateTimeConverter.ConvertToMilliseconds(endTime)); - parameters.AddOptionalParameter("currentPage", currentPage); - parameters.AddOptionalParameter("pageSize", pageSize); - - return await _baseClient.Execute>(_baseClient.GetUri("hist-orders"), HttpMethod.Get, ct, signed: true, parameters: parameters).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/orders/{orderId}", KucoinExchange.RateLimiter.SpotRest, 2, true); + return await _baseClient.SendAsync(request, null, ct).ConfigureAwait(false); } /// @@ -376,7 +374,7 @@ public async Task>> GetUserTrades if (endTime.HasValue && startTime.HasValue && (endTime.Value - startTime.Value).TotalDays > 7) throw new ArgumentException("Difference between start and end time can be a maximum of 1 week"); - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("symbol", symbol); parameters.AddOptionalParameter("side", side.HasValue ? JsonConvert.SerializeObject(side, new OrderSideConverter(false)) : null); parameters.AddOptionalParameter("type", type.HasValue ? JsonConvert.SerializeObject(type, new OrderTypeConverter(false)) : null); @@ -387,13 +385,15 @@ public async Task>> GetUserTrades parameters.AddOptionalParameter("currentPage", currentPage); parameters.AddOptionalParameter("pageSize", pageSize); - return await _baseClient.Execute>(_baseClient.GetUri("fills"), HttpMethod.Get, ct, signed: true, parameters: parameters).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/fills", KucoinExchange.RateLimiter.SpotRest, 10, true); + return await _baseClient.SendAsync>(request, parameters, ct).ConfigureAwait(false); } /// public async Task>> GetRecentUserTradesAsync(CancellationToken ct = default) { - return await _baseClient.Execute>(_baseClient.GetUri("limit/fills"), HttpMethod.Get, ct, signed: true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/limit/fills", KucoinExchange.RateLimiter.SpotRest, 20, true); + return await _baseClient.SendAsync>(request, null, ct).ConfigureAwait(false); } /// @@ -432,7 +432,7 @@ public async Task> PlaceStopOrderAsync( if (stopCondition == StopCondition.None) throw new ArgumentException("Invalid stop condition", nameof(stopCondition)); - var parameters = new Dictionary + var parameters = new ParameterCollection { { "symbol", symbol }, { "clientOid", clientOrderId ?? Guid.NewGuid().ToString() }, @@ -457,41 +457,45 @@ public async Task> PlaceStopOrderAsync( parameters.AddOptionalParameter("funds", quoteQuantity); - return await _baseClient.Execute(_baseClient.GetUri("stop-order"), HttpMethod.Post, ct, parameters, true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Post, $"api/v1/stop-order", KucoinExchange.RateLimiter.SpotRest, 2, true); + return await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); } /// public async Task> CancelStopOrderAsync(string orderId, CancellationToken ct = default) { - return await _baseClient.Execute(_baseClient.GetUri("stop-order/" + orderId), HttpMethod.Delete, ct, signed: true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Delete, $"api/v1/stop-order/" + orderId, KucoinExchange.RateLimiter.SpotRest, 3, true); + return await _baseClient.SendAsync(request, null, ct).ConfigureAwait(false); } /// public async Task> CancelStopOrderByClientOrderIdAsync(string clientOrderId, CancellationToken ct = default) { - var parameters = new Dictionary() + var parameters = new ParameterCollection() { { "clientOid", clientOrderId } }; - return await _baseClient.Execute(_baseClient.GetUri("stop-order/cancelOrderByClientOid"), HttpMethod.Delete, ct, parameters, signed: true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Delete, $"api/v1/stop-order/cancelOrderByClientOid", KucoinExchange.RateLimiter.SpotRest, 5, true); + return await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); } /// public async Task> CancelStopOrdersAsync(string? symbol = null, IEnumerable? orderIds = null, TradeType? tradeType = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("symbol", symbol); parameters.AddOptionalParameter("orderIds", orderIds == null ? null : string.Join(",", orderIds)); parameters.AddOptionalParameter("tradeType", tradeType.HasValue ? JsonConvert.SerializeObject(tradeType, new TradeTypeConverter(false)) : null); - return await _baseClient.Execute(_baseClient.GetUri("stop-order/cancel"), HttpMethod.Delete, ct, parameters, signed: true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Delete, $"api/v1/stop-order/cancel", KucoinExchange.RateLimiter.SpotRest, 3, true); + return await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); } /// public async Task>> GetStopOrdersAsync(bool? activeOrders = null, string? symbol = null, Enums.OrderSide? side = null, Enums.OrderType? type = null, TradeType? tradeType = null, DateTime? startTime = null, DateTime? endTime = null, IEnumerable? orderIds = null, int? currentPage = null, int? pageSize = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("status", activeOrders.HasValue ? activeOrders == true ? "active" : "done" : null); parameters.AddOptionalParameter("symbol", symbol); parameters.AddOptionalParameter("side", side.HasValue ? JsonConvert.SerializeObject(side, new OrderSideConverter(false)) : null); @@ -503,23 +507,26 @@ public async Task>> GetStopOrders parameters.AddOptionalParameter("pageSize", pageSize); parameters.AddOptionalParameter("tradeType", tradeType.HasValue ? JsonConvert.SerializeObject(tradeType, new TradeTypeConverter(false)) : null); - return await _baseClient.Execute>(_baseClient.GetUri("stop-order"), HttpMethod.Get, ct, parameters, signed: true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/stop-order", KucoinExchange.RateLimiter.SpotRest, 8, true); + return await _baseClient.SendAsync>(request, parameters, ct).ConfigureAwait(false); } /// public async Task> GetStopOrderAsync(string orderId, CancellationToken ct = default) { - return await _baseClient.Execute(_baseClient.GetUri("stop-order/" + orderId), HttpMethod.Get, ct, signed: true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/stop-order/" + orderId, KucoinExchange.RateLimiter.SpotRest, 3, true); + return await _baseClient.SendAsync(request, null, ct).ConfigureAwait(false); } /// public async Task>> GetStopOrderByClientOrderIdAsync(string clientOrderId, CancellationToken ct = default) { - var parameters = new Dictionary() + var parameters = new ParameterCollection() { { "clientOid", clientOrderId } }; - return await _baseClient.Execute>(_baseClient.GetUri("stop-order/queryOrderByClientOid"), HttpMethod.Get, ct, parameters, signed: true).ConfigureAwait(false); + var request = _definitions.GetOrCreate(HttpMethod.Get, $"api/v1/stop-order/queryOrderByClientOid", KucoinExchange.RateLimiter.SpotRest, 3, true); + return await _baseClient.SendAsync>(request, parameters, ct).ConfigureAwait(false); } /// @@ -531,7 +538,7 @@ public async Task> PlaceBorrowOrderAsync( string? term = null, CancellationToken ct = default) { - var parameters = new Dictionary + var parameters = new ParameterCollection { { "currency", asset }, { "type", JsonConvert.SerializeObject(type, new BorrowOrderTypeConverter(false)) }, @@ -546,7 +553,7 @@ public async Task> PlaceBorrowOrderAsync( public async Task> GetBorrowOrderAsync(string orderId, CancellationToken ct = default) { orderId.ValidateNotNull(nameof(orderId)); - var parameters = new Dictionary + var parameters = new ParameterCollection { { "orderId", orderId } }; @@ -556,7 +563,7 @@ public async Task> GetBorrowOrderAsync(string o /// public async Task RepaySingleBorrowOrderAsync(string asset, string tradeId, decimal quantity, CancellationToken ct = default) { - var parameters = new Dictionary + var parameters = new ParameterCollection { { "currency", asset }, { "tradeId", tradeId }, @@ -569,7 +576,7 @@ public async Task RepaySingleBorrowOrderAsync(string asset, strin /// public async Task>> GetOpenBorrowRecordsAsync(string? asset = null, int? page = null, int? pageSize = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("currency", asset); parameters.AddOptionalParameter("page", page); parameters.AddOptionalParameter("pageSize", pageSize); @@ -579,7 +586,7 @@ public async Task>> GetOpenBorr /// public async Task>> GetClosedBorrowRecordsAsync(string? asset = null, int? page = null, int? pageSize = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("currency", asset); parameters.AddOptionalParameter("page", page); parameters.AddOptionalParameter("pageSize", pageSize); @@ -589,7 +596,7 @@ public async Task>> GetClosedB /// public async Task RepayAllAsync(string asset, RepaymentStrategy strategy, decimal quantity, CancellationToken ct = default) { - var parameters = new Dictionary() + var parameters = new ParameterCollection() { { "currency", asset }, { "sequence", EnumConverter.GetString(strategy) }, @@ -601,7 +608,7 @@ public async Task RepayAllAsync(string asset, RepaymentStrategy s /// public async Task> PlaceLendOrderAsync(string asset, decimal quantity, decimal dailyInterestRate, int term, CancellationToken ct = default) { - var parameters = new Dictionary() + var parameters = new ParameterCollection() { { "currency", asset }, { "dailyIntRate", dailyInterestRate }, @@ -620,7 +627,7 @@ public async Task CancelLendOrderAsync(string orderId, Cancellati /// public async Task SetAutoLendAsync(string asset, bool isEnabled, decimal? retainQuantity = null, decimal? dailyInterestRate = null, int? term = null, CancellationToken ct = default) { - var parameters = new Dictionary() + var parameters = new ParameterCollection() { { "currency", asset }, { "isEnable", isEnabled } @@ -634,7 +641,7 @@ public async Task SetAutoLendAsync(string asset, bool isEnabled, /// public async Task>> GetOpenLendOrdersAsync(string? asset = null, int? page = null, int? pageSize = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("currency", asset); parameters.AddOptionalParameter("page", page); parameters.AddOptionalParameter("pageSize", pageSize); @@ -644,7 +651,7 @@ public async Task>> GetOpenLendOr /// public async Task>> GetClosedLendOrdersAsync(string? asset = null, int? page = null, int? pageSize = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("currency", asset); parameters.AddOptionalParameter("page", page); parameters.AddOptionalParameter("pageSize", pageSize); @@ -654,7 +661,7 @@ public async Task>> GetClosedLend /// public async Task>> GetOpenLendsAsync(string? asset = null, int? page = null, int? pageSize = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("currency", asset); parameters.AddOptionalParameter("page", page); parameters.AddOptionalParameter("pageSize", pageSize); @@ -664,7 +671,7 @@ public async Task>> GetOpenLendsAs /// public async Task>> GetClosedLendsAsync(string? asset = null, int? page = null, int? pageSize = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("currency", asset); parameters.AddOptionalParameter("page", page); parameters.AddOptionalParameter("pageSize", pageSize); @@ -674,7 +681,7 @@ public async Task>> GetClosedLen /// public async Task>> GetLendingStatusAsync(string? asset = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("currency", asset); return await _baseClient.Execute>(_baseClient.GetUri($"margin/lend/assets"), HttpMethod.Get, ct, parameters, true).ConfigureAwait(false); } @@ -690,7 +697,7 @@ public async Task> PlaceIsolatedBorr string? term = null, CancellationToken ct = default) { - var parameters = new Dictionary + var parameters = new ParameterCollection { { "symbol", symbol }, { "currency", asset }, @@ -705,7 +712,7 @@ public async Task> PlaceIsolatedBorr /// public async Task>> GetIsolatedOpenBorrowRecordsAsync(string? symbol = null, string? asset = null, int? page = null, int? pageSize = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("symbol", symbol); parameters.AddOptionalParameter("currency", asset); parameters.AddOptionalParameter("page", page); @@ -716,7 +723,7 @@ public async Task> /// public async Task>> GetIsolatedClosedBorrowRecordsAsync(string? symbol = null, string? asset = null, int? page = null, int? pageSize = null, CancellationToken ct = default) { - var parameters = new Dictionary(); + var parameters = new ParameterCollection(); parameters.AddOptionalParameter("symbol", symbol); parameters.AddOptionalParameter("currency", asset); parameters.AddOptionalParameter("page", page); @@ -727,7 +734,7 @@ public async Task public async Task RepayAllIsolatedAsync(string symbol, string asset, RepaymentStrategy strategy, decimal quantity, CancellationToken ct = default) { - var parameters = new Dictionary() + var parameters = new ParameterCollection() { { "symbol", symbol }, { "currency", asset }, @@ -740,7 +747,7 @@ public async Task RepayAllIsolatedAsync(string symbol, string ass /// public async Task RepaySingleIsolatedBorrowOrderAsync(string symbol, string asset, decimal quantity, string loanId, CancellationToken ct = default) { - var parameters = new Dictionary + var parameters = new ParameterCollection { { "symbol", symbol }, { "currency", asset }, diff --git a/Kucoin.Net/Clients/SpotApi/KucoinSocketClientSpotApi.cs b/Kucoin.Net/Clients/SpotApi/KucoinSocketClientSpotApi.cs index 80614168..2a4ffe57 100644 --- a/Kucoin.Net/Clients/SpotApi/KucoinSocketClientSpotApi.cs +++ b/Kucoin.Net/Clients/SpotApi/KucoinSocketClientSpotApi.cs @@ -234,7 +234,11 @@ public async Task> SubscribeToStopOrderUpdatesAsy options.Environment = ClientOptions.Environment; })) { - WebCallResult tokenResult = await ((KucoinRestClientSpotApiAccount)restClient.SpotApi.Account).GetWebsocketToken(authenticated).ConfigureAwait(false); + WebCallResult tokenResult; + if (authenticated) + tokenResult = await ((KucoinRestClientSpotApiAccount)restClient.SpotApi.Account).GetWebsocketTokenPrivateAsync().ConfigureAwait(false); + else + tokenResult = await ((KucoinRestClientSpotApiAccount)restClient.SpotApi.Account).GetWebsocketTokenPublicAsync().ConfigureAwait(false); if (!tokenResult) return tokenResult.As(null); diff --git a/Kucoin.Net/Enums/VipLevel.cs b/Kucoin.Net/Enums/VipLevel.cs new file mode 100644 index 00000000..e8045933 --- /dev/null +++ b/Kucoin.Net/Enums/VipLevel.cs @@ -0,0 +1,65 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Kucoin.Net.Enums +{ + /// + /// Account VIP level + /// + public enum VipLevel + { + /// + /// Level 0 + /// + Vip0, + /// + /// Level 1 + /// + Vip1, + /// + /// Level 2 + /// + Vip2, + /// + /// Level 3 + /// + Vip3, + /// + /// Level 4 + /// + Vip4, + /// + /// Level 5 + /// + Vip5, + /// + /// Level 6 + /// + Vip6, + /// + /// Level 7 + /// + Vip7, + /// + /// Level 8 + /// + Vip8, + /// + /// Level 9 + /// + Vip9, + /// + /// Level 10 + /// + Vip10, + /// + /// Level 11 + /// + Vip11, + /// + /// Level 12 + /// + Vip12 + } +} diff --git a/Kucoin.Net/Interfaces/Clients/SpotApi/IKucoinRestClientSpotApiAccount.cs b/Kucoin.Net/Interfaces/Clients/SpotApi/IKucoinRestClientSpotApiAccount.cs index 88a5e1cc..bc11ed52 100644 --- a/Kucoin.Net/Interfaces/Clients/SpotApi/IKucoinRestClientSpotApiAccount.cs +++ b/Kucoin.Net/Interfaces/Clients/SpotApi/IKucoinRestClientSpotApiAccount.cs @@ -263,23 +263,7 @@ Task> UniversalTransferAsync( /// The id of the withdrawal to cancel /// Cancellation token /// Null - Task> CancelWithdrawalAsync(string withdrawalId, CancellationToken ct = default); - - /// - /// Get cross margin risk limit - /// - /// - /// Cancellation token - /// - Task>> GetRiskLimitCrossMarginAsync(CancellationToken ct = default); - - /// - /// Get isolated margin risk limit - /// - /// - /// Cancellation token - /// - Task>> GetRiskLimitIsolatedMarginAsync(CancellationToken ct = default); + Task CancelWithdrawalAsync(string withdrawalId, CancellationToken ct = default); /// /// Get margin account info diff --git a/Kucoin.Net/Interfaces/Clients/SpotApi/IKucoinRestClientSpotApiExchangeData.cs b/Kucoin.Net/Interfaces/Clients/SpotApi/IKucoinRestClientSpotApiExchangeData.cs index 228076b9..7da93ac4 100644 --- a/Kucoin.Net/Interfaces/Clients/SpotApi/IKucoinRestClientSpotApiExchangeData.cs +++ b/Kucoin.Net/Interfaces/Clients/SpotApi/IKucoinRestClientSpotApiExchangeData.cs @@ -149,25 +149,6 @@ public interface IKucoinRestClientSpotApiExchangeData /// Task> GetMarginMarkPriceAsync(string symbol, CancellationToken ct = default); - /// - /// Get lending market data - /// - /// - /// Asset - /// Filter by term - /// Cancellation token - /// - Task>> GetLendMarketDataAsync(string asset, int? term = null, CancellationToken ct = default); - - /// - /// Get the last 300 fills for borrow/lending orders - /// - /// - /// The asset - /// Cancellation token - /// - Task>> GetMarginTradeHistoryAsync(string asset, CancellationToken ct = default); - /// /// Get Margin Trading Pair ConfigurationAsync /// diff --git a/Kucoin.Net/Interfaces/Clients/SpotApi/IKucoinRestClientSpotApiTrading.cs b/Kucoin.Net/Interfaces/Clients/SpotApi/IKucoinRestClientSpotApiTrading.cs index f118ba41..ff5426b9 100644 --- a/Kucoin.Net/Interfaces/Clients/SpotApi/IKucoinRestClientSpotApiTrading.cs +++ b/Kucoin.Net/Interfaces/Clients/SpotApi/IKucoinRestClientSpotApiTrading.cs @@ -263,20 +263,6 @@ Task> PlaceTestMarginOrderAsync( /// Order info Task> GetOrderAsync(string orderId, CancellationToken ct = default); - /// - /// Gets a list of historical orders - /// - /// - /// Filter list by symbol - /// Filter list by order side - /// Filter list by start time - /// Filter list by end time - /// The page to retrieve - /// The amount of results per page - /// Cancellation token - /// List of historical orders - Task>> GetHistoricalOrdersAsync(string? symbol = null, OrderSide? side = null, DateTime? startTime = null, DateTime? endTime = null, int? currentPage = null, int? pageSize = null, CancellationToken ct = default); - /// /// Gets a list of fills /// diff --git a/Kucoin.Net/Kucoin.Net.csproj b/Kucoin.Net/Kucoin.Net.csproj index 4a20033a..68d8f385 100644 --- a/Kucoin.Net/Kucoin.Net.csproj +++ b/Kucoin.Net/Kucoin.Net.csproj @@ -53,6 +53,6 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + \ No newline at end of file diff --git a/Kucoin.Net/Kucoin.Net.xml b/Kucoin.Net/Kucoin.Net.xml index 6b41cba2..40be4a02 100644 --- a/Kucoin.Net/Kucoin.Net.xml +++ b/Kucoin.Net/Kucoin.Net.xml @@ -358,6 +358,9 @@ + + + @@ -445,12 +448,6 @@ - - - - - - @@ -505,12 +502,6 @@ - - - - - - @@ -565,9 +556,6 @@ - - - @@ -1859,6 +1847,76 @@ Sub to parent + + + Account VIP level + + + + + Level 0 + + + + + Level 1 + + + + + Level 2 + + + + + Level 3 + + + + + Level 4 + + + + + Level 5 + + + + + Level 6 + + + + + Level 7 + + + + + Level 8 + + + + + Level 9 + + + + + Level 10 + + + + + Level 11 + + + + + Level 12 + + Status of a withdrawal @@ -2857,22 +2915,6 @@ Cancellation token Null - - - Get cross margin risk limit - - - Cancellation token - - - - - Get isolated margin risk limit - - - Cancellation token - - Get margin account info @@ -3047,25 +3089,6 @@ Cancellation token - - - Get lending market data - - - Asset - Filter by term - Cancellation token - - - - - Get the last 300 fills for borrow/lending orders - - - The asset - Cancellation token - - Get Margin Trading Pair ConfigurationAsync @@ -3306,20 +3329,6 @@ Cancellation token Order info - - - Gets a list of historical orders - - - Filter list by symbol - Filter list by order side - Filter list by start time - Filter list by end time - The page to retrieve - The amount of results per page - Cancellation token - List of historical orders - Gets a list of fills @@ -3944,6 +3953,42 @@ + + + Kucoin exchange information and configuration + + + + + Exchange name + + + + + Rate limiter configuration for the Kucoin API + + + + + Rate limiter configuration for the Kucoin API + + + + + The VIP level to use when calculating rate limits + + + + + Event for when a rate limit is triggered + + + + + Configure the rate limit with a different VIP level + + + Kucoin API addresses diff --git a/Kucoin.Net/KucoinExchange.cs b/Kucoin.Net/KucoinExchange.cs new file mode 100644 index 00000000..16fdcf18 --- /dev/null +++ b/Kucoin.Net/KucoinExchange.cs @@ -0,0 +1,135 @@ +using CryptoExchange.Net.Objects; +using CryptoExchange.Net.RateLimiting; +using CryptoExchange.Net.RateLimiting.Filters; +using CryptoExchange.Net.RateLimiting.Guards; +using CryptoExchange.Net.RateLimiting.Interfaces; +using Kucoin.Net.Enums; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Kucoin.Net +{ + /// + /// Kucoin exchange information and configuration + /// + public static class KucoinExchange + { + /// + /// Exchange name + /// + public static string ExchangeName => "Kucoin"; + + /// + /// Rate limiter configuration for the Kucoin API + /// + public static KucoinRateLimiters RateLimiter { get; } = new KucoinRateLimiters(); + } + + /// + /// Rate limiter configuration for the Kucoin API + /// + public class KucoinRateLimiters + { + private static readonly Dictionary _spotLimits = new() + { + { VipLevel.Vip0, 4000 }, + { VipLevel.Vip1, 6000 }, + { VipLevel.Vip2, 8000 }, + { VipLevel.Vip3, 10000 }, + { VipLevel.Vip4, 13000 }, + { VipLevel.Vip5, 16000 }, + { VipLevel.Vip6, 20000 }, + { VipLevel.Vip7, 23000 }, + { VipLevel.Vip8, 26000 }, + { VipLevel.Vip9, 30000 }, + { VipLevel.Vip10, 33000 }, + { VipLevel.Vip11, 36000 }, + { VipLevel.Vip12, 40000 }, + }; + + private static readonly Dictionary _futuresLimits = new() + { + { VipLevel.Vip0, 2000 }, + { VipLevel.Vip1, 2000 }, + { VipLevel.Vip2, 4000 }, + { VipLevel.Vip3, 5000 }, + { VipLevel.Vip4, 6000 }, + { VipLevel.Vip5, 7000 }, + { VipLevel.Vip6, 8000 }, + { VipLevel.Vip7, 10000 }, + { VipLevel.Vip8, 12000 }, + { VipLevel.Vip9, 14000 }, + { VipLevel.Vip10, 16000 }, + { VipLevel.Vip11, 18000 }, + { VipLevel.Vip12, 20000 }, + }; + + private static readonly Dictionary _managementLimits = new() + { + { VipLevel.Vip0, 2000 }, + { VipLevel.Vip1, 2000 }, + { VipLevel.Vip2, 4000 }, + { VipLevel.Vip3, 5000 }, + { VipLevel.Vip4, 6000 }, + { VipLevel.Vip5, 7000 }, + { VipLevel.Vip6, 8000 }, + { VipLevel.Vip7, 10000 }, + { VipLevel.Vip8, 12000 }, + { VipLevel.Vip9, 14000 }, + { VipLevel.Vip10, 16000 }, + { VipLevel.Vip11, 18000 }, + { VipLevel.Vip12, 20000 }, + }; + + internal IRateLimitGate SpotRest { get; private set; } + internal IRateLimitGate FuturesRest { get; private set; } + internal IRateLimitGate ManagementRest { get; private set; } + internal IRateLimitGate PublicRest { get; private set; } + internal IRateLimitGate Socket { get; private set; } + + /// + /// The VIP level to use when calculating rate limits + /// + public VipLevel VipLevel { get; private set; } = VipLevel.Vip0; + + /// + /// Event for when a rate limit is triggered + /// + public event Action RateLimitTriggered; + +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + internal KucoinRateLimiters() +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. + { + Initialize(); + } + + /// + /// Configure the rate limit with a different VIP level + /// + /// + public void Configure(VipLevel level) + { + VipLevel = level; + Initialize(); + } + + private void Initialize() + { + SpotRest = new RateLimitGate("Spot Rest").AddGuard(new RateLimitGuard(RateLimitGuard.PerHost, Array.Empty(), _spotLimits[VipLevel], TimeSpan.FromSeconds(30), RateLimitWindowType.FixedAfterFirst)); // Might be fixed but from the first request timestamp instead of the the whole interval + FuturesRest = new RateLimitGate("Futures Rest").AddGuard(new RateLimitGuard(RateLimitGuard.PerHost, Array.Empty(), _futuresLimits[VipLevel], TimeSpan.FromSeconds(30), RateLimitWindowType.FixedAfterFirst)); // Might be fixed but from the first request timestamp instead of the the whole interval + ManagementRest = new RateLimitGate("Management Rest").AddGuard(new RateLimitGuard(RateLimitGuard.PerHost, Array.Empty(), _managementLimits[VipLevel], TimeSpan.FromSeconds(30), RateLimitWindowType.FixedAfterFirst)); // Might be fixed but from the first request timestamp instead of the the whole interval + PublicRest = new RateLimitGate("Public Rest").AddGuard(new RateLimitGuard(RateLimitGuard.PerHost, Array.Empty(), 2000, TimeSpan.FromSeconds(30), RateLimitWindowType.FixedAfterFirst)); // Might be fixed but from the first request timestamp instead of the the whole interval + Socket = new RateLimitGate("Socket") + .AddGuard(new RateLimitGuard(RateLimitGuard.PerHost, new LimitItemTypeFilter(RateLimitItemType.Connection), 30, TimeSpan.FromMinutes(1), RateLimitWindowType.Fixed)) + .AddGuard(new RateLimitGuard(RateLimitGuard.PerEndpoint, new LimitItemTypeFilter(RateLimitItemType.Request), 100, TimeSpan.FromSeconds(10), RateLimitWindowType.Fixed)); + + SpotRest.RateLimitTriggered += (x) => RateLimitTriggered?.Invoke(x); + FuturesRest.RateLimitTriggered += (x) => RateLimitTriggered?.Invoke(x); + ManagementRest.RateLimitTriggered += (x) => RateLimitTriggered?.Invoke(x); + PublicRest.RateLimitTriggered += (x) => RateLimitTriggered?.Invoke(x); + Socket.RateLimitTriggered += (x) => RateLimitTriggered?.Invoke(x); + } + } +} diff --git a/Kucoin.Net/Objects/Internal/KucoinResult.cs b/Kucoin.Net/Objects/Internal/KucoinResult.cs index c6545614..c56ae211 100644 --- a/Kucoin.Net/Objects/Internal/KucoinResult.cs +++ b/Kucoin.Net/Objects/Internal/KucoinResult.cs @@ -2,13 +2,16 @@ namespace Kucoin.Net.Objects.Internal { - internal class KucoinResult + internal class KucoinResult { [JsonProperty("code")] public int Code { get; set; } [JsonProperty("msg")] public string? Message { get; set; } + } + internal class KucoinResult : KucoinResult + { public T Data { get; set; } = default!; } } diff --git a/Kucoin.Net/Objects/Options/KucoinRestOptions.cs b/Kucoin.Net/Objects/Options/KucoinRestOptions.cs index c5f14cb7..0caeef29 100644 --- a/Kucoin.Net/Objects/Options/KucoinRestOptions.cs +++ b/Kucoin.Net/Objects/Options/KucoinRestOptions.cs @@ -22,16 +22,7 @@ public class KucoinRestOptions : RestExchangeOptions /// Spot API options /// - public KucoinRestApiOptions SpotOptions { get; private set; } = new KucoinRestApiOptions() - { - RateLimiters = new List - { - new RateLimiter() - .AddPartialEndpointLimit("/api/v1/orders", 180, TimeSpan.FromSeconds(3), null, true, true) - .AddApiKeyLimit(200, TimeSpan.FromSeconds(10), true, true) - .AddTotalRateLimit(100, TimeSpan.FromSeconds(10)) - } - }; + public KucoinRestApiOptions SpotOptions { get; private set; } = new KucoinRestApiOptions(); /// /// Futures API options diff --git a/docs/index.html b/docs/index.html index 44e352f3..dc8e90f8 100644 --- a/docs/index.html +++ b/docs/index.html @@ -83,6 +83,7 @@