From 6d2bc3e20bd2d65c296a729f6c836abdbc180f46 Mon Sep 17 00:00:00 2001 From: Jkorf Date: Fri, 25 Oct 2024 15:50:40 +0200 Subject: [PATCH 1/3] wip --- .../FuturesApi/KucoinRestClientFuturesApi.cs | 10 +-- .../KucoinRestClientFuturesApiShared.cs | 9 ++- .../KucoinSocketClientFuturesApi.cs | 11 +-- .../KucoinSocketClientFuturesApiShared.cs | 4 +- .../SpotApi/KucoinRestClientSpotApi.cs | 3 +- .../SpotApi/KucoinRestClientSpotApiShared.cs | 5 +- .../SpotApi/KucoinSocketClientSpotApi.cs | 5 +- .../KucoinSocketClientSpotApiShared.cs | 4 +- .../ServiceCollectionExtensions.cs | 2 + .../Interfaces/IKucoinOrderBookFactory.cs | 9 +++ .../Interfaces/IKucoinTrackerFactory.cs | 34 ++++++++ Kucoin.Net/Kucoin.Net.csproj | 4 +- Kucoin.Net/Kucoin.Net.xml | 60 ++++++++++++++ Kucoin.Net/KucoinExchange.cs | 26 ++++++- Kucoin.Net/KucoinTrackerFactory.cs | 78 +++++++++++++++++++ .../KucoinOrderBookFactory.cs | 19 ++++- 16 files changed, 252 insertions(+), 31 deletions(-) create mode 100644 Kucoin.Net/Interfaces/IKucoinTrackerFactory.cs create mode 100644 Kucoin.Net/KucoinTrackerFactory.cs diff --git a/Kucoin.Net/Clients/FuturesApi/KucoinRestClientFuturesApi.cs b/Kucoin.Net/Clients/FuturesApi/KucoinRestClientFuturesApi.cs index f29d0c48..018c427a 100644 --- a/Kucoin.Net/Clients/FuturesApi/KucoinRestClientFuturesApi.cs +++ b/Kucoin.Net/Clients/FuturesApi/KucoinRestClientFuturesApi.cs @@ -70,15 +70,7 @@ protected override AuthenticationProvider CreateAuthenticationProvider(ApiCreden /// public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverTime = null) - { - if (baseAsset.Equals("BTC", StringComparison.OrdinalIgnoreCase)) - baseAsset = "XBT"; - - if (!deliverTime.HasValue) - return baseAsset.ToUpperInvariant() + quoteAsset.ToUpperInvariant() + "M"; - - return baseAsset.ToUpperInvariant() + "M" + ExchangeHelpers.GetDeliveryMonthSymbol(deliverTime.Value) + deliverTime.Value.ToString("yy"); - } + => KucoinExchange.FormatSymbol(baseAsset, quoteAsset, tradingMode, deliverTime); internal async Task SendAsync(RequestDefinition definition, ParameterCollection? parameters, CancellationToken cancellationToken, int? weight = null) { diff --git a/Kucoin.Net/Clients/FuturesApi/KucoinRestClientFuturesApiShared.cs b/Kucoin.Net/Clients/FuturesApi/KucoinRestClientFuturesApiShared.cs index dc35032b..83f0febe 100644 --- a/Kucoin.Net/Clients/FuturesApi/KucoinRestClientFuturesApiShared.cs +++ b/Kucoin.Net/Clients/FuturesApi/KucoinRestClientFuturesApiShared.cs @@ -60,7 +60,7 @@ async Task> IFuturesTickerRestClient.GetF request.Symbol.TradingMode, new SharedFuturesTicker( result.Data.Symbol, - result.Data.LastTradePrice ?? 0, + result.Data.LastTradePrice, result.Data.HighPrice, result.Data.LowPrice, result.Data.Volume24H, @@ -95,7 +95,7 @@ async Task>> IFuturesTickerRe return result.AsExchangeResult>(Exchange, request.TradingMode == null ? SupportedTradingModes : new[] { request.TradingMode.Value }, result.Data.Select(x => - new SharedFuturesTicker(x.Symbol, x.LastTradePrice ?? 0, x.HighPrice, x.LowPrice, x.Volume24H, x.PriceChangePercentage * 100) + new SharedFuturesTicker(x.Symbol, x.LastTradePrice, x.HighPrice, x.LowPrice, x.Volume24H, x.PriceChangePercentage * 100) { IndexPrice = x.IndexPrice, MarkPrice = x.MarkPrice, @@ -598,7 +598,10 @@ async Task>> IRecentTradeRestClient.G if (!result) return result.AsExchangeResult>(Exchange, null, default); - return result.AsExchangeResult>(Exchange, request.Symbol.TradingMode, result.Data.Take(request.Limit ?? 100).Select(x => new SharedTrade(x.Quantity, x.Price, x.Timestamp)).ToArray()); + return result.AsExchangeResult>(Exchange, request.Symbol.TradingMode, result.Data.Take(request.Limit ?? 100).Select(x => new SharedTrade(x.Quantity, x.Price, x.Timestamp) + { + Side = x.Side == OrderSide.Buy ? SharedOrderSide.Buy : SharedOrderSide.Sell + }).ToArray()); } #endregion diff --git a/Kucoin.Net/Clients/FuturesApi/KucoinSocketClientFuturesApi.cs b/Kucoin.Net/Clients/FuturesApi/KucoinSocketClientFuturesApi.cs index a9c7c689..e8f8a76a 100644 --- a/Kucoin.Net/Clients/FuturesApi/KucoinSocketClientFuturesApi.cs +++ b/Kucoin.Net/Clients/FuturesApi/KucoinSocketClientFuturesApi.cs @@ -71,17 +71,10 @@ public override string GetListenerIdentifier(IMessageAccessor message) protected override AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials) => new KucoinAuthenticationProvider((KucoinApiCredentials)credentials); + /// public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverTime = null) - { - if (baseAsset.Equals("BTC", StringComparison.OrdinalIgnoreCase)) - baseAsset = "XBT"; - - if (!deliverTime.HasValue) - return baseAsset.ToUpperInvariant() + quoteAsset.ToUpperInvariant() + "M"; - - return baseAsset.ToUpperInvariant() + "M" + ExchangeHelpers.GetDeliveryMonthSymbol(deliverTime.Value) + deliverTime.Value.ToString("yy"); - } + => KucoinExchange.FormatSymbol(baseAsset, quoteAsset, tradingMode, deliverTime); /// protected override Query? GetAuthenticationRequest(SocketConnection connection) => null; diff --git a/Kucoin.Net/Clients/FuturesApi/KucoinSocketClientFuturesApiShared.cs b/Kucoin.Net/Clients/FuturesApi/KucoinSocketClientFuturesApiShared.cs index bfebd551..b09bc91c 100644 --- a/Kucoin.Net/Clients/FuturesApi/KucoinSocketClientFuturesApiShared.cs +++ b/Kucoin.Net/Clients/FuturesApi/KucoinSocketClientFuturesApiShared.cs @@ -44,7 +44,9 @@ async Task> ITradeSocketClient.SubscribeToTra return new ExchangeResult(Exchange, validationError); var symbol = request.Symbol.GetSymbol(FormatSymbol); - var result = await SubscribeToTradeUpdatesAsync(symbol, update => handler(update.AsExchangeEvent>(Exchange, new[] { new SharedTrade(update.Data.Quantity, update.Data.Price, update.Data.Timestamp) })), ct).ConfigureAwait(false); + var result = await SubscribeToTradeUpdatesAsync(symbol, update => handler(update.AsExchangeEvent>(Exchange, new[] { new SharedTrade(update.Data.Quantity, update.Data.Price, update.Data.Timestamp){ + Side = update.Data.Side == OrderSide.Buy ? SharedOrderSide.Buy : SharedOrderSide.Sell + } })), ct).ConfigureAwait(false); return new ExchangeResult(Exchange, result); } diff --git a/Kucoin.Net/Clients/SpotApi/KucoinRestClientSpotApi.cs b/Kucoin.Net/Clients/SpotApi/KucoinRestClientSpotApi.cs index 42b68300..a309396c 100644 --- a/Kucoin.Net/Clients/SpotApi/KucoinRestClientSpotApi.cs +++ b/Kucoin.Net/Clients/SpotApi/KucoinRestClientSpotApi.cs @@ -74,7 +74,8 @@ protected override AuthenticationProvider CreateAuthenticationProvider(ApiCreden => new KucoinAuthenticationProvider((KucoinApiCredentials)credentials); /// - public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverTime = null) => baseAsset.ToUpperInvariant() + "-" + quoteAsset.ToUpperInvariant(); + public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverTime = null) + => KucoinExchange.FormatSymbol(baseAsset, quoteAsset, tradingMode, deliverTime); #region common interface diff --git a/Kucoin.Net/Clients/SpotApi/KucoinRestClientSpotApiShared.cs b/Kucoin.Net/Clients/SpotApi/KucoinRestClientSpotApiShared.cs index d66172a7..df37a9ee 100644 --- a/Kucoin.Net/Clients/SpotApi/KucoinRestClientSpotApiShared.cs +++ b/Kucoin.Net/Clients/SpotApi/KucoinRestClientSpotApiShared.cs @@ -149,7 +149,10 @@ async Task>> IRecentTradeRestClient.G if (!result) return result.AsExchangeResult>(Exchange, null, default); - return result.AsExchangeResult>(Exchange, request.Symbol.TradingMode, result.Data.Select(x => new SharedTrade(x.Quantity, x.Price, x.Timestamp)).ToArray()); + return result.AsExchangeResult>(Exchange, request.Symbol.TradingMode, result.Data.Select(x => new SharedTrade(x.Quantity, x.Price, x.Timestamp) + { + Side = x.Side == OrderSide.Buy ? SharedOrderSide.Buy : SharedOrderSide.Sell + }).ToArray()); } #endregion diff --git a/Kucoin.Net/Clients/SpotApi/KucoinSocketClientSpotApi.cs b/Kucoin.Net/Clients/SpotApi/KucoinSocketClientSpotApi.cs index 253373b8..209b4662 100644 --- a/Kucoin.Net/Clients/SpotApi/KucoinSocketClientSpotApi.cs +++ b/Kucoin.Net/Clients/SpotApi/KucoinSocketClientSpotApi.cs @@ -53,9 +53,10 @@ internal KucoinSocketClientSpotApi(ILogger logger, KucoinSocketClient baseClient /// protected override AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials) => new KucoinAuthenticationProvider((KucoinApiCredentials)credentials); - + /// - public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverTime = null) => baseAsset.ToUpperInvariant() + "-" + quoteAsset.ToUpperInvariant(); + public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverTime = null) + => KucoinExchange.FormatSymbol(baseAsset, quoteAsset, tradingMode, deliverTime); public IKucoinSocketClientSpotApiShared SharedClient => this; diff --git a/Kucoin.Net/Clients/SpotApi/KucoinSocketClientSpotApiShared.cs b/Kucoin.Net/Clients/SpotApi/KucoinSocketClientSpotApiShared.cs index 68d95db2..dadb7187 100644 --- a/Kucoin.Net/Clients/SpotApi/KucoinSocketClientSpotApiShared.cs +++ b/Kucoin.Net/Clients/SpotApi/KucoinSocketClientSpotApiShared.cs @@ -44,7 +44,9 @@ async Task> ITradeSocketClient.SubscribeToTra return new ExchangeResult(Exchange, validationError); var symbol = request.Symbol.GetSymbol(FormatSymbol); - var result = await SubscribeToTradeUpdatesAsync(symbol, update => handler(update.AsExchangeEvent>(Exchange, new[] { new SharedTrade(update.Data.Quantity, update.Data.Price, update.Data.Timestamp) })), ct).ConfigureAwait(false); + var result = await SubscribeToTradeUpdatesAsync(symbol, update => handler(update.AsExchangeEvent>(Exchange, new[] { new SharedTrade(update.Data.Quantity, update.Data.Price, update.Data.Timestamp){ + Side = update.Data.Side == OrderSide.Buy ? SharedOrderSide.Buy : SharedOrderSide.Sell + } })), ct).ConfigureAwait(false); return new ExchangeResult(Exchange, result); } diff --git a/Kucoin.Net/ExtensionMethods/ServiceCollectionExtensions.cs b/Kucoin.Net/ExtensionMethods/ServiceCollectionExtensions.cs index 8d648bb3..158fa64e 100644 --- a/Kucoin.Net/ExtensionMethods/ServiceCollectionExtensions.cs +++ b/Kucoin.Net/ExtensionMethods/ServiceCollectionExtensions.cs @@ -1,6 +1,7 @@ using CryptoExchange.Net; using CryptoExchange.Net.Clients; using CryptoExchange.Net.Interfaces; +using Kucoin.Net; using Kucoin.Net.Clients; using Kucoin.Net.Interfaces; using Kucoin.Net.Interfaces.Clients; @@ -61,6 +62,7 @@ public static IServiceCollection AddKucoin( services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(x => x.GetRequiredService().SpotApi.CommonSpotClient); services.AddTransient(x => x.GetRequiredService().FuturesApi.CommonFuturesClient); diff --git a/Kucoin.Net/Interfaces/IKucoinOrderBookFactory.cs b/Kucoin.Net/Interfaces/IKucoinOrderBookFactory.cs index 08edcfb6..34d50fb5 100644 --- a/Kucoin.Net/Interfaces/IKucoinOrderBookFactory.cs +++ b/Kucoin.Net/Interfaces/IKucoinOrderBookFactory.cs @@ -1,4 +1,5 @@ using CryptoExchange.Net.Interfaces; +using CryptoExchange.Net.SharedApis; using Kucoin.Net.Objects.Options; using System; @@ -19,6 +20,14 @@ public interface IKucoinOrderBookFactory /// public IOrderBookFactory Futures { get; } + /// + /// Create a SymbolOrderBook for the symbol + /// + /// The symbol + /// Book options + /// + ISymbolOrderBook Create(SharedSymbol symbol, Action? options = null); + /// /// Create a futures ISymbolOrderBook instance for the symbol /// diff --git a/Kucoin.Net/Interfaces/IKucoinTrackerFactory.cs b/Kucoin.Net/Interfaces/IKucoinTrackerFactory.cs new file mode 100644 index 00000000..ad6f0e68 --- /dev/null +++ b/Kucoin.Net/Interfaces/IKucoinTrackerFactory.cs @@ -0,0 +1,34 @@ +using CryptoExchange.Net.SharedApis; +using CryptoExchange.Net.Trackers.Klines; +using CryptoExchange.Net.Trackers.Trades; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Kucoin.Net.Interfaces +{ + /// + /// Tracker factory + /// + public interface IKucoinTrackerFactory + { + /// + /// Create a new kline tracker + /// + /// The symbol + /// Kline interval + /// The max amount of klines to retain + /// The max period the data should be retained + /// + IKlineTracker CreateKlineTracker(SharedSymbol symbol, SharedKlineInterval interval, int? limit = null, TimeSpan? period = null); + + /// + /// Create a new trade tracker for a symbol + /// + /// The symbol + /// The max amount of klines to retain + /// The max period the data should be retained + /// + ITradeTracker CreateTradeTracker(SharedSymbol symbol, int? limit = null, TimeSpan? period = null); + } +} diff --git a/Kucoin.Net/Kucoin.Net.csproj b/Kucoin.Net/Kucoin.Net.csproj index ac0c64d5..4fa97c69 100644 --- a/Kucoin.Net/Kucoin.Net.csproj +++ b/Kucoin.Net/Kucoin.Net.csproj @@ -53,6 +53,8 @@ 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 97c7d0e4..e2607f5f 100644 --- a/Kucoin.Net/Kucoin.Net.xml +++ b/Kucoin.Net/Kucoin.Net.xml @@ -5266,6 +5266,14 @@ Futures order book factory methods + + + Create a SymbolOrderBook for the symbol + + The symbol + Book options + + Create a futures ISymbolOrderBook instance for the symbol @@ -5282,6 +5290,30 @@ Option configuration delegate + + + Tracker factory + + + + + Create a new kline tracker + + The symbol + Kline interval + The max amount of klines to retain + The max period the data should be retained + + + + + Create a new trade tracker for a symbol + + The symbol + The max amount of klines to retain + The max period the data should be retained + + Kucoin environments @@ -5336,6 +5368,16 @@ Urls to the API documentation + + + Format a base and quote asset to a Kucoin recognized symbol + + Base asset + Quote asset + Trading mode + Delivery time for delivery futures + + Rate limiter configuration for the Kucoin API @@ -5362,6 +5404,21 @@ + + + + + + ctor + + Service provider for resolving logging and clients + + + + + + + Kucoin API addresses @@ -12502,6 +12559,9 @@ Service provider for resolving logging and clients + + + Create a spot SymbolOrderBook diff --git a/Kucoin.Net/KucoinExchange.cs b/Kucoin.Net/KucoinExchange.cs index 001a3b34..1c006f98 100644 --- a/Kucoin.Net/KucoinExchange.cs +++ b/Kucoin.Net/KucoinExchange.cs @@ -1,8 +1,10 @@ -using CryptoExchange.Net.Objects; +using CryptoExchange.Net; +using CryptoExchange.Net.Objects; using CryptoExchange.Net.RateLimiting; using CryptoExchange.Net.RateLimiting.Filters; using CryptoExchange.Net.RateLimiting.Guards; using CryptoExchange.Net.RateLimiting.Interfaces; +using CryptoExchange.Net.SharedApis; using Kucoin.Net.Enums; using System; using System.Collections.Generic; @@ -32,6 +34,28 @@ public static class KucoinExchange "https://www.kucoin.com/docs/beginners/introduction" }; + /// + /// Format a base and quote asset to a Kucoin recognized symbol + /// + /// Base asset + /// Quote asset + /// Trading mode + /// Delivery time for delivery futures + /// + public static string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverTime = null) + { + if (tradingMode == TradingMode.Spot) + return baseAsset.ToUpperInvariant() + "-" + quoteAsset.ToUpperInvariant(); + + if (baseAsset.Equals("BTC", StringComparison.OrdinalIgnoreCase)) + baseAsset = "XBT"; + + if (!deliverTime.HasValue) + return baseAsset.ToUpperInvariant() + quoteAsset.ToUpperInvariant() + "M"; + + return baseAsset.ToUpperInvariant() + "M" + ExchangeHelpers.GetDeliveryMonthSymbol(deliverTime.Value) + deliverTime.Value.ToString("yy"); + } + /// /// Rate limiter configuration for the Kucoin API /// diff --git a/Kucoin.Net/KucoinTrackerFactory.cs b/Kucoin.Net/KucoinTrackerFactory.cs new file mode 100644 index 00000000..b1f870cb --- /dev/null +++ b/Kucoin.Net/KucoinTrackerFactory.cs @@ -0,0 +1,78 @@ +using CryptoExchange.Net.SharedApis; +using CryptoExchange.Net.Trackers.Klines; +using CryptoExchange.Net.Trackers.Trades; +using Kucoin.Net.Interfaces; +using Kucoin.Net.Interfaces.Clients; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using System; + +namespace Kucoin.Net +{ + /// + public class KucoinTrackerFactory : IKucoinTrackerFactory + { + private readonly IServiceProvider _serviceProvider; + + /// + /// ctor + /// + /// Service provider for resolving logging and clients + public KucoinTrackerFactory(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + /// + public IKlineTracker CreateKlineTracker(SharedSymbol symbol, SharedKlineInterval interval, int? limit = null, TimeSpan? period = null) + { + IKlineRestClient restClient; + IKlineSocketClient socketClient; + if (symbol.TradingMode == TradingMode.Spot) + { + restClient = _serviceProvider.GetRequiredService().SpotApi.SharedClient; + socketClient = _serviceProvider.GetRequiredService().SpotApi.SharedClient; + } + else + { + restClient = _serviceProvider.GetRequiredService().FuturesApi.SharedClient; + socketClient = _serviceProvider.GetRequiredService().FuturesApi.SharedClient; + } + + return new KlineTracker( + _serviceProvider.GetRequiredService().CreateLogger(restClient.Exchange), + restClient, + socketClient, + symbol, + interval, + limit, + period + ); + } + /// + public ITradeTracker CreateTradeTracker(SharedSymbol symbol, int? limit = null, TimeSpan? period = null) + { + IRecentTradeRestClient? restClient = null; + ITradeSocketClient socketClient; + if (symbol.TradingMode == TradingMode.Spot) + { + restClient = _serviceProvider.GetRequiredService().SpotApi.SharedClient; + socketClient = _serviceProvider.GetRequiredService().SpotApi.SharedClient; + } + else + { + restClient = _serviceProvider.GetRequiredService().FuturesApi.SharedClient; + socketClient = _serviceProvider.GetRequiredService().FuturesApi.SharedClient; + } + + return new TradeTracker( + _serviceProvider.GetRequiredService().CreateLogger(restClient.Exchange), + restClient, + socketClient, + symbol, + limit, + period + ); + } + } +} diff --git a/Kucoin.Net/SymbolOrderBooks/KucoinOrderBookFactory.cs b/Kucoin.Net/SymbolOrderBooks/KucoinOrderBookFactory.cs index 3e5538ad..67aabb2b 100644 --- a/Kucoin.Net/SymbolOrderBooks/KucoinOrderBookFactory.cs +++ b/Kucoin.Net/SymbolOrderBooks/KucoinOrderBookFactory.cs @@ -1,5 +1,6 @@ using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.OrderBook; +using CryptoExchange.Net.SharedApis; using Kucoin.Net.Interfaces; using Kucoin.Net.Interfaces.Clients; using Kucoin.Net.Objects.Options; @@ -30,8 +31,22 @@ public KucoinOrderBookFactory(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; - Spot = new OrderBookFactory((symbol, options) => CreateSpot(symbol, options), (baseAsset, quoteAsset, options) => CreateSpot(baseAsset.ToUpperInvariant() + "-" + quoteAsset.ToUpperInvariant(), options)); - Futures = new OrderBookFactory((symbol, options) => CreateFutures(symbol, options), (baseAsset, quoteAsset, options) => CreateFutures(baseAsset.ToUpperInvariant() + quoteAsset.ToUpperInvariant(), options)); + Spot = new OrderBookFactory( + CreateSpot, + (sharedSymbol, options) => CreateSpot(KucoinExchange.FormatSymbol(sharedSymbol.BaseAsset, sharedSymbol.QuoteAsset, sharedSymbol.TradingMode, sharedSymbol.DeliverTime), options)); + Futures = new OrderBookFactory( + CreateFutures, + (sharedSymbol, options) => CreateFutures(KucoinExchange.FormatSymbol(sharedSymbol.BaseAsset, sharedSymbol.QuoteAsset, sharedSymbol.TradingMode, sharedSymbol.DeliverTime), options)); + } + + /// + public ISymbolOrderBook Create(SharedSymbol symbol, Action? options = null) + { + var symbolName = KucoinExchange.FormatSymbol(symbol.BaseAsset, symbol.QuoteAsset, symbol.TradingMode, symbol.DeliverTime); + if (symbol.TradingMode == TradingMode.Spot) + return CreateSpot(symbolName, options); + + return CreateFutures(symbolName, options); } /// From 964ba178747213e467c8a545ac6d220e5327c00e Mon Sep 17 00:00:00 2001 From: JKorf Date: Sun, 27 Oct 2024 19:13:53 +0100 Subject: [PATCH 2/3] wip --- Kucoin.Net/KucoinExchange.cs | 2 +- Kucoin.Net/KucoinTrackerFactory.cs | 53 +++++++++++++++++++----------- 2 files changed, 35 insertions(+), 20 deletions(-) diff --git a/Kucoin.Net/KucoinExchange.cs b/Kucoin.Net/KucoinExchange.cs index 1c006f98..3e239957 100644 --- a/Kucoin.Net/KucoinExchange.cs +++ b/Kucoin.Net/KucoinExchange.cs @@ -159,7 +159,7 @@ private void Initialize() 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)); + .AddGuard(new RateLimitGuard(RateLimitGuard.PerConnection, new LimitItemTypeFilter(RateLimitItemType.Request), 100, TimeSpan.FromSeconds(10), RateLimitWindowType.Fixed)); SpotRest.RateLimitTriggered += (x) => RateLimitTriggered?.Invoke(x); FuturesRest.RateLimitTriggered += (x) => RateLimitTriggered?.Invoke(x); diff --git a/Kucoin.Net/KucoinTrackerFactory.cs b/Kucoin.Net/KucoinTrackerFactory.cs index b1f870cb..43ca778c 100644 --- a/Kucoin.Net/KucoinTrackerFactory.cs +++ b/Kucoin.Net/KucoinTrackerFactory.cs @@ -1,6 +1,7 @@ using CryptoExchange.Net.SharedApis; using CryptoExchange.Net.Trackers.Klines; using CryptoExchange.Net.Trackers.Trades; +using Kucoin.Net.Clients; using Kucoin.Net.Interfaces; using Kucoin.Net.Interfaces.Clients; using Microsoft.Extensions.DependencyInjection; @@ -12,7 +13,14 @@ namespace Kucoin.Net /// public class KucoinTrackerFactory : IKucoinTrackerFactory { - private readonly IServiceProvider _serviceProvider; + private readonly IServiceProvider? _serviceProvider; + + /// + /// ctor + /// + public KucoinTrackerFactory() + { + } /// /// ctor @@ -26,23 +34,26 @@ public KucoinTrackerFactory(IServiceProvider serviceProvider) /// public IKlineTracker CreateKlineTracker(SharedSymbol symbol, SharedKlineInterval interval, int? limit = null, TimeSpan? period = null) { - IKlineRestClient restClient; - IKlineSocketClient socketClient; + var restClient = _serviceProvider?.GetRequiredService() ?? new KucoinRestClient(); + var socketClient = _serviceProvider?.GetRequiredService() ?? new KucoinSocketClient(); + + IKlineRestClient sharedRestClient; + IKlineSocketClient sharedSocketClient; if (symbol.TradingMode == TradingMode.Spot) { - restClient = _serviceProvider.GetRequiredService().SpotApi.SharedClient; - socketClient = _serviceProvider.GetRequiredService().SpotApi.SharedClient; + sharedRestClient = restClient.SpotApi.SharedClient; + sharedSocketClient = socketClient.SpotApi.SharedClient; } else { - restClient = _serviceProvider.GetRequiredService().FuturesApi.SharedClient; - socketClient = _serviceProvider.GetRequiredService().FuturesApi.SharedClient; + sharedRestClient = restClient.FuturesApi.SharedClient; + sharedSocketClient = socketClient.FuturesApi.SharedClient; } return new KlineTracker( - _serviceProvider.GetRequiredService().CreateLogger(restClient.Exchange), - restClient, - socketClient, + _serviceProvider?.GetRequiredService().CreateLogger(restClient.Exchange), + sharedRestClient, + sharedSocketClient, symbol, interval, limit, @@ -52,23 +63,27 @@ public IKlineTracker CreateKlineTracker(SharedSymbol symbol, SharedKlineInterval /// public ITradeTracker CreateTradeTracker(SharedSymbol symbol, int? limit = null, TimeSpan? period = null) { - IRecentTradeRestClient? restClient = null; - ITradeSocketClient socketClient; + var restClient = _serviceProvider?.GetRequiredService() ?? new KucoinRestClient(); + var socketClient = _serviceProvider?.GetRequiredService() ?? new KucoinSocketClient(); + + IRecentTradeRestClient? sharedRestClient; + ITradeSocketClient sharedSocketClient; if (symbol.TradingMode == TradingMode.Spot) { - restClient = _serviceProvider.GetRequiredService().SpotApi.SharedClient; - socketClient = _serviceProvider.GetRequiredService().SpotApi.SharedClient; + sharedRestClient = restClient.SpotApi.SharedClient; + sharedSocketClient = socketClient.SpotApi.SharedClient; } else { - restClient = _serviceProvider.GetRequiredService().FuturesApi.SharedClient; - socketClient = _serviceProvider.GetRequiredService().FuturesApi.SharedClient; + sharedRestClient = restClient.FuturesApi.SharedClient; + sharedSocketClient = socketClient.FuturesApi.SharedClient; } return new TradeTracker( - _serviceProvider.GetRequiredService().CreateLogger(restClient.Exchange), - restClient, - socketClient, + _serviceProvider?.GetRequiredService().CreateLogger(restClient.Exchange), + sharedRestClient, + null, + sharedSocketClient, symbol, limit, period From ba48985e00504c46ff188d352d565a0743df3cbf Mon Sep 17 00:00:00 2001 From: Jkorf Date: Mon, 28 Oct 2024 13:42:18 +0100 Subject: [PATCH 3/3] wip --- Examples/Examples.sln | 18 +++ .../Kucoin.Examples.Api.csproj | 7 +- .../Kucoin.Examples.Console.csproj | 4 +- .../Kucoin.Examples.OrderBook.csproj | 18 +++ Examples/Kucoin.Examples.OrderBook/Program.cs | 58 ++++++++++ .../Kucoin.Examples.Tracker.csproj | 18 +++ Examples/Kucoin.Examples.Tracker/Program.cs | 104 ++++++++++++++++++ Examples/README.md | 8 +- Kucoin.Net/Kucoin.Net.csproj | 4 +- Kucoin.Net/Kucoin.Net.xml | 5 + 10 files changed, 236 insertions(+), 8 deletions(-) create mode 100644 Examples/Kucoin.Examples.OrderBook/Kucoin.Examples.OrderBook.csproj create mode 100644 Examples/Kucoin.Examples.OrderBook/Program.cs create mode 100644 Examples/Kucoin.Examples.Tracker/Kucoin.Examples.Tracker.csproj create mode 100644 Examples/Kucoin.Examples.Tracker/Program.cs diff --git a/Examples/Examples.sln b/Examples/Examples.sln index a44315be..eb121856 100644 --- a/Examples/Examples.sln +++ b/Examples/Examples.sln @@ -7,6 +7,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kucoin.Examples.Api", "Kuco EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kucoin.Examples.Console", "Kucoin.Examples.Console\Kucoin.Examples.Console.csproj", "{FD4F95C8-D9B7-4F81-9245-4CE667DFD421}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kucoin.Net", "..\Kucoin.Net\Kucoin.Net.csproj", "{5A503CA9-F47D-4961-8669-F39CBC358F93}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kucoin.Examples.OrderBook", "Kucoin.Examples.OrderBook\Kucoin.Examples.OrderBook.csproj", "{2FEAA7EE-782F-4C56-AE24-DB734B1B5F5F}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Kucoin.Examples.Tracker", "Kucoin.Examples.Tracker\Kucoin.Examples.Tracker.csproj", "{9C3D888D-CF72-40E9-B0D2-0FD272A716A6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -21,6 +27,18 @@ Global {FD4F95C8-D9B7-4F81-9245-4CE667DFD421}.Debug|Any CPU.Build.0 = Debug|Any CPU {FD4F95C8-D9B7-4F81-9245-4CE667DFD421}.Release|Any CPU.ActiveCfg = Release|Any CPU {FD4F95C8-D9B7-4F81-9245-4CE667DFD421}.Release|Any CPU.Build.0 = Release|Any CPU + {5A503CA9-F47D-4961-8669-F39CBC358F93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5A503CA9-F47D-4961-8669-F39CBC358F93}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5A503CA9-F47D-4961-8669-F39CBC358F93}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5A503CA9-F47D-4961-8669-F39CBC358F93}.Release|Any CPU.Build.0 = Release|Any CPU + {2FEAA7EE-782F-4C56-AE24-DB734B1B5F5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2FEAA7EE-782F-4C56-AE24-DB734B1B5F5F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2FEAA7EE-782F-4C56-AE24-DB734B1B5F5F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2FEAA7EE-782F-4C56-AE24-DB734B1B5F5F}.Release|Any CPU.Build.0 = Release|Any CPU + {9C3D888D-CF72-40E9-B0D2-0FD272A716A6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9C3D888D-CF72-40E9-B0D2-0FD272A716A6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9C3D888D-CF72-40E9-B0D2-0FD272A716A6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9C3D888D-CF72-40E9-B0D2-0FD272A716A6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Examples/Kucoin.Examples.Api/Kucoin.Examples.Api.csproj b/Examples/Kucoin.Examples.Api/Kucoin.Examples.Api.csproj index 2e32e76f..fbc446dc 100644 --- a/Examples/Kucoin.Examples.Api/Kucoin.Examples.Api.csproj +++ b/Examples/Kucoin.Examples.Api/Kucoin.Examples.Api.csproj @@ -1,16 +1,19 @@ - net7.0 + net8.0 enable enable true - + + + + diff --git a/Examples/Kucoin.Examples.Console/Kucoin.Examples.Console.csproj b/Examples/Kucoin.Examples.Console/Kucoin.Examples.Console.csproj index 4a356a1a..aa0ddfbb 100644 --- a/Examples/Kucoin.Examples.Console/Kucoin.Examples.Console.csproj +++ b/Examples/Kucoin.Examples.Console/Kucoin.Examples.Console.csproj @@ -2,13 +2,13 @@ Exe - net7.0 + net8.0 enable enable - + diff --git a/Examples/Kucoin.Examples.OrderBook/Kucoin.Examples.OrderBook.csproj b/Examples/Kucoin.Examples.OrderBook/Kucoin.Examples.OrderBook.csproj new file mode 100644 index 00000000..650b3d30 --- /dev/null +++ b/Examples/Kucoin.Examples.OrderBook/Kucoin.Examples.OrderBook.csproj @@ -0,0 +1,18 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + diff --git a/Examples/Kucoin.Examples.OrderBook/Program.cs b/Examples/Kucoin.Examples.OrderBook/Program.cs new file mode 100644 index 00000000..1a55bec9 --- /dev/null +++ b/Examples/Kucoin.Examples.OrderBook/Program.cs @@ -0,0 +1,58 @@ +using Kucoin.Net.Interfaces; +using CryptoExchange.Net; +using CryptoExchange.Net.SharedApis; +using Microsoft.Extensions.DependencyInjection; +using Spectre.Console; +using Kucoin.Net.Objects; + +var collection = new ServiceCollection(); +collection.AddKucoin(opts => +{ + // NOTE Kucoin requires credentials to subscribe order book data + opts.ApiCredentials = new KucoinApiCredentials("APIKEY", "APISECRET", "APIPASS"); +}); +var provider = collection.BuildServiceProvider(); + +var trackerFactory = provider.GetRequiredService(); + +// Creat and start the order book +var book = trackerFactory.Create(new SharedSymbol(TradingMode.Spot, "ETH", "USDT")); +var result = await book.StartAsync(); + +if (!result.Success) +{ + Console.WriteLine(result); + return; +} + +// Create Spectre table +var table = new Table(); +table.ShowRowSeparators = true; +table.AddColumn("Bid Quantity", x => { x.RightAligned(); }) + .AddColumn("Bid Price", x => { x.RightAligned(); }) + .AddColumn("Ask Price", x => { x.LeftAligned(); }) + .AddColumn("Ask Quantity", x => { x.LeftAligned(); }); + +for(var i = 0; i < 10; i++) + table.AddEmptyRow(); + +await AnsiConsole.Live(table) + .StartAsync(async ctx => + { + while (true) + { + var snapshot = book.Book; + for (var i = 0; i < 10; i++) + { + var bid = snapshot.bids.ElementAt(i); + var ask = snapshot.asks.ElementAt(i); + table.UpdateCell(i, 0, ExchangeHelpers.Normalize(bid.Quantity).ToString()); + table.UpdateCell(i, 1, ExchangeHelpers.Normalize(bid.Price).ToString()); + table.UpdateCell(i, 2, ExchangeHelpers.Normalize(ask.Price).ToString()); + table.UpdateCell(i, 3, ExchangeHelpers.Normalize(ask.Quantity).ToString()); + } + + ctx.Refresh(); + await Task.Delay(500); + } + }); diff --git a/Examples/Kucoin.Examples.Tracker/Kucoin.Examples.Tracker.csproj b/Examples/Kucoin.Examples.Tracker/Kucoin.Examples.Tracker.csproj new file mode 100644 index 00000000..650b3d30 --- /dev/null +++ b/Examples/Kucoin.Examples.Tracker/Kucoin.Examples.Tracker.csproj @@ -0,0 +1,18 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + diff --git a/Examples/Kucoin.Examples.Tracker/Program.cs b/Examples/Kucoin.Examples.Tracker/Program.cs new file mode 100644 index 00000000..34387c21 --- /dev/null +++ b/Examples/Kucoin.Examples.Tracker/Program.cs @@ -0,0 +1,104 @@ +using Kucoin.Net.Interfaces; +using CryptoExchange.Net.SharedApis; +using Microsoft.Extensions.DependencyInjection; +using Spectre.Console; +using System.Globalization; + +var collection = new ServiceCollection(); +collection.AddKucoin(); +var provider = collection.BuildServiceProvider(); + +var trackerFactory = provider.GetRequiredService(); + +// Creat and start the tracker, keep track of the last 10 minutes +var tracker = trackerFactory.CreateTradeTracker(new SharedSymbol(TradingMode.Spot, "ETH", "USDT"), period: TimeSpan.FromMinutes(10)); +var result = await tracker.StartAsync(); +if (!result.Success) +{ + Console.WriteLine(result); + return; +} + +// Create Spectre table +var table = new Table(); +table.ShowRowSeparators = true; +table.AddColumn("5 Min Data").AddColumn("-5 Min", x => { x.RightAligned(); }) + .AddColumn("Now", x => { x.RightAligned(); }) + .AddColumn("Dif", x => { x.RightAligned(); }); + +table.AddRow("Count", "", "", ""); +table.AddRow("Average price", "", "", ""); +table.AddRow("Average weighted price", "", "", ""); +table.AddRow("Buy/Sell Ratio", "", "", ""); +table.AddRow("Volume", "", "", ""); +table.AddRow("Value", "", "", ""); +table.AddRow("Complete", "", "", ""); +table.AddRow("", "", "", ""); +table.AddRow("Status", "", "", ""); +table.AddRow("Synced From", "", "", ""); + +// Set default culture for currency display +CultureInfo ci = new CultureInfo("en-US"); +Thread.CurrentThread.CurrentCulture = ci; +Thread.CurrentThread.CurrentUICulture = ci; + +await AnsiConsole.Live(table) + .StartAsync(async ctx => + { + while (true) + { + // Get the stats from 10 minutes until 5 minutes ago + var secondLastMinute = tracker.GetStats(DateTime.UtcNow.AddMinutes(-10), DateTime.UtcNow.AddMinutes(-5)); + + // Get the stats from 5 minutes ago until now + var lastMinute = tracker.GetStats(DateTime.UtcNow.AddMinutes(-5)); + + // Get the differences between them + var compare = secondLastMinute.CompareTo(lastMinute); + + // Update the columns + UpdateDec(0, 1, secondLastMinute.TradeCount); + UpdateDec(0, 2, lastMinute.TradeCount); + UpdateStr(0, 3, $"[{(compare.TradeCountDif.Difference < 0 ? "red" : "green")}]{compare.TradeCountDif.Difference} / {compare.TradeCountDif.PercentageDifference}%[/]"); + + UpdateStr(1, 1, secondLastMinute.AveragePrice?.ToString("C")); + UpdateStr(1, 2, lastMinute.AveragePrice?.ToString("C")); + UpdateStr(1, 3, $"[{(compare.AveragePriceDif?.Difference < 0 ? "red" : "green")}]{compare.AveragePriceDif?.Difference?.ToString("C")} / {compare.AveragePriceDif?.PercentageDifference}%[/]"); + + UpdateStr(2, 1, secondLastMinute.VolumeWeightedAveragePrice?.ToString("C")); + UpdateStr(2, 2, lastMinute.VolumeWeightedAveragePrice?.ToString("C")); + UpdateStr(2, 3, $"[{(compare.VolumeWeightedAveragePriceDif?.Difference < 0 ? "red" : "green")}]{compare.VolumeWeightedAveragePriceDif?.Difference?.ToString("C")} / {compare.VolumeWeightedAveragePriceDif?.PercentageDifference}%[/]"); + + UpdateDec(3, 1, secondLastMinute.BuySellRatio); + UpdateDec(3, 2, lastMinute.BuySellRatio); + UpdateStr(3, 3, $"[{(compare.BuySellRatioDif?.Difference < 0 ? "red" : "green")}]{compare.BuySellRatioDif?.Difference} / {compare.BuySellRatioDif?.PercentageDifference}%[/]"); + + UpdateDec(4, 1, secondLastMinute.Volume); + UpdateDec(4, 2, lastMinute.Volume); + UpdateStr(4, 3, $"[{(compare.VolumeDif.Difference < 0 ? "red" : "green")}]{compare.VolumeDif.Difference} / {compare.VolumeDif.PercentageDifference}%[/]"); + + UpdateStr(5, 1, secondLastMinute.QuoteVolume.ToString("C")); + UpdateStr(5, 2, lastMinute.QuoteVolume.ToString("C")); + UpdateStr(5, 3, $"[{(compare.QuoteVolumeDif.Difference < 0 ? "red" : "green")}]{compare.QuoteVolumeDif.Difference?.ToString("C")} / {compare.QuoteVolumeDif.PercentageDifference}%[/]"); + + UpdateStr(6, 1, secondLastMinute.Complete.ToString()); + UpdateStr(6, 2, lastMinute.Complete.ToString()); + + UpdateStr(8, 1, tracker.Status.ToString()); + UpdateStr(9, 1, tracker.SyncedFrom?.ToString()); + + ctx.Refresh(); + await Task.Delay(500); + } + }); + + +void UpdateDec(int row, int col, decimal? val) +{ + table.UpdateCell(row, col, val?.ToString() ?? string.Empty); +} + +void UpdateStr(int row, int col, string? val) +{ + table.UpdateCell(row, col, val ?? string.Empty); +} diff --git a/Examples/README.md b/Examples/README.md index c57a10e8..4f221b9f 100644 --- a/Examples/README.md +++ b/Examples/README.md @@ -4,4 +4,10 @@ A minimal API showing how to integrate Kucoin.Net in a web API project ### Kucoin.Examples.Console -A simple console client demonstrating basic usage \ No newline at end of file +A simple console client demonstrating basic usage + +### Kucoin.Examples.OrderBook +Example of using the client side order book implementation + +### Kucoin.Examples.Tracker +Example of using the trade tracker \ No newline at end of file diff --git a/Kucoin.Net/Kucoin.Net.csproj b/Kucoin.Net/Kucoin.Net.csproj index 4fa97c69..f129b33a 100644 --- a/Kucoin.Net/Kucoin.Net.csproj +++ b/Kucoin.Net/Kucoin.Net.csproj @@ -49,12 +49,10 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + 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 e2607f5f..f3a6adc8 100644 --- a/Kucoin.Net/Kucoin.Net.xml +++ b/Kucoin.Net/Kucoin.Net.xml @@ -5407,6 +5407,11 @@ + + + ctor + + ctor