From 5b990086c191031b0505418199b9ef65d69000a5 Mon Sep 17 00:00:00 2001 From: Jan Korf Date: Mon, 28 Oct 2024 14:11:25 +0100 Subject: [PATCH] Trackers (#241) Updated examples Updated CryptoExchange.Net to v8.1.0 Moved FormatSymbol to BybitExchange class Added support Side setting on SharedTrade model Added BybitTrackerFactory Added overload to Create method on BybitOrderBookFactory support SharedSymbol parameter --- ByBit.Net/Bybit.Net.csproj | 2 +- ByBit.Net/BybitExchange.cs | 35 +++++- ByBit.Net/BybitTrackerFactory.cs | 105 ++++++++++++++++++ ByBit.Net/Clients/V5/BybitRestClientApi.cs | 19 +--- .../Clients/V5/BybitRestClientApiShared.cs | 5 +- .../Clients/V5/BybitSocketClientBaseApi.cs | 17 +-- .../V5/BybitSocketClientInverseApiShared.cs | 8 +- .../V5/BybitSocketClientLinearApiShared.cs | 8 +- .../V5/BybitSocketClientSpotApiShared.cs | 8 +- .../ServiceCollectionExtensions.cs | 4 +- .../Interfaces/IBybitOrderBookFactory.cs | 9 ++ ByBit.Net/Interfaces/IBybitTrackerFactory.cs | 34 ++++++ .../SymbolOrderBooks/BybitOrderBookFactory.cs | 23 +++- Examples/Bybit.Api/Bybit.Examples.Api.csproj | 5 +- .../Bybit.Examples.Console.csproj | 3 +- .../Bybit.Examples.OrderBook.csproj | 18 +++ Examples/Bybit.Examples.OrderBook/Program.cs | 52 +++++++++ .../Bybit.Examples.Tracker.csproj | 18 +++ Examples/Bybit.Examples.Tracker/Program.cs | 104 +++++++++++++++++ Examples/Examples.sln | 22 +++- Examples/README.md | 8 +- 21 files changed, 455 insertions(+), 52 deletions(-) create mode 100644 ByBit.Net/BybitTrackerFactory.cs create mode 100644 ByBit.Net/Interfaces/IBybitTrackerFactory.cs create mode 100644 Examples/Bybit.Examples.OrderBook/Bybit.Examples.OrderBook.csproj create mode 100644 Examples/Bybit.Examples.OrderBook/Program.cs create mode 100644 Examples/Bybit.Examples.Tracker/Bybit.Examples.Tracker.csproj create mode 100644 Examples/Bybit.Examples.Tracker/Program.cs diff --git a/ByBit.Net/Bybit.Net.csproj b/ByBit.Net/Bybit.Net.csproj index 86fc6167..5ebf0c2f 100644 --- a/ByBit.Net/Bybit.Net.csproj +++ b/ByBit.Net/Bybit.Net.csproj @@ -48,10 +48,10 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers; buildtransitive - \ No newline at end of file diff --git a/ByBit.Net/BybitExchange.cs b/ByBit.Net/BybitExchange.cs index 3a3c4880..f9498d48 100644 --- a/ByBit.Net/BybitExchange.cs +++ b/ByBit.Net/BybitExchange.cs @@ -1,4 +1,8 @@ -namespace Bybit.Net +using CryptoExchange.Net; +using CryptoExchange.Net.SharedApis; +using System; + +namespace Bybit.Net { /// /// Bybit exchange information and configuration @@ -22,5 +26,34 @@ public static class BybitExchange "https://bybit-exchange.github.io/docs/v3/intro", "https://bybit-exchange.github.io/docs/v5/intro" }; + + /// + /// Format a base and quote asset to a Bybit 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 (tradingMode.IsLinear()) + { + if (tradingMode.IsPerpetual()) + { + if (quoteAsset == "USDC") + return baseAsset + "PERP"; + + return baseAsset.ToUpperInvariant() + quoteAsset.ToUpperInvariant(); + } + + return baseAsset.ToUpperInvariant() + "-" + deliverTime!.Value.ToString("ddMMMyy").ToUpperInvariant(); + } + + return baseAsset.ToUpperInvariant() + quoteAsset.ToUpperInvariant() + (deliverTime == null ? string.Empty : (ExchangeHelpers.GetDeliveryMonthSymbol(deliverTime.Value) + deliverTime.Value.ToString("yy"))); + } } } diff --git a/ByBit.Net/BybitTrackerFactory.cs b/ByBit.Net/BybitTrackerFactory.cs new file mode 100644 index 00000000..7b1fc216 --- /dev/null +++ b/ByBit.Net/BybitTrackerFactory.cs @@ -0,0 +1,105 @@ +using Bybit.Net.Clients; +using Bybit.Net.Interfaces; +using Bybit.Net.Interfaces.Clients; +using CryptoExchange.Net; +using CryptoExchange.Net.SharedApis; +using CryptoExchange.Net.Trackers.Klines; +using CryptoExchange.Net.Trackers.Trades; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using System; + +namespace Bybit.Net +{ + /// + public class BybitTrackerFactory : IBybitTrackerFactory + { + private readonly IServiceProvider? _serviceProvider; + + /// + /// ctor + /// + public BybitTrackerFactory() + { + } + + /// + /// ctor + /// + /// Service provider for resolving logging and clients + public BybitTrackerFactory(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + /// + public IKlineTracker CreateKlineTracker(SharedSymbol symbol, SharedKlineInterval interval, int? limit = null, TimeSpan? period = null) + { + var restClient = _serviceProvider?.GetRequiredService() ?? new BybitRestClient(); + var socketClient = _serviceProvider?.GetRequiredService() ?? new BybitSocketClient(); + + IKlineRestClient sharedRestClient; + IKlineSocketClient sharedSocketClient; + if (symbol.TradingMode == TradingMode.Spot) + { + sharedRestClient = restClient.V5Api.SharedClient; + sharedSocketClient = socketClient.V5SpotApi.SharedClient; + } + else if (symbol.TradingMode.IsLinear()) + { + sharedRestClient = restClient.V5Api.SharedClient; + sharedSocketClient = socketClient.V5LinearApi.SharedClient; + } + else + { + sharedRestClient = restClient.V5Api.SharedClient; + sharedSocketClient = socketClient.V5InverseApi.SharedClient; + } + + return new KlineTracker( + _serviceProvider?.GetRequiredService().CreateLogger(restClient.Exchange), + sharedRestClient, + sharedSocketClient, + symbol, + interval, + limit, + period + ); + } + + /// + public ITradeTracker CreateTradeTracker(SharedSymbol symbol, int? limit = null, TimeSpan? period = null) + { + var restClient = _serviceProvider?.GetRequiredService() ?? new BybitRestClient(); + var socketClient = _serviceProvider?.GetRequiredService() ?? new BybitSocketClient(); + + IRecentTradeRestClient? sharedRestClient = null; + ITradeSocketClient sharedSocketClient; + if (symbol.TradingMode == TradingMode.Spot) + { + sharedRestClient = restClient.V5Api.SharedClient; + sharedSocketClient = socketClient.V5SpotApi.SharedClient; + } + else if (symbol.TradingMode.IsLinear()) + { + sharedRestClient = restClient.V5Api.SharedClient; + sharedSocketClient = socketClient.V5LinearApi.SharedClient; + } + else + { + sharedRestClient = restClient.V5Api.SharedClient; + sharedSocketClient = socketClient.V5InverseApi.SharedClient; + } + + return new TradeTracker( + _serviceProvider?.GetRequiredService().CreateLogger(socketClient.Exchange), + sharedRestClient, + null, + sharedSocketClient, + symbol, + limit, + period + ); + } + } +} diff --git a/ByBit.Net/Clients/V5/BybitRestClientApi.cs b/ByBit.Net/Clients/V5/BybitRestClientApi.cs index f11f5cdd..16a3871c 100644 --- a/ByBit.Net/Clients/V5/BybitRestClientApi.cs +++ b/ByBit.Net/Clients/V5/BybitRestClientApi.cs @@ -80,24 +80,7 @@ protected override AuthenticationProvider CreateAuthenticationProvider(ApiCreden /// public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverTime = null) - { - if (tradingMode == TradingMode.Spot) - return baseAsset.ToUpperInvariant() + quoteAsset.ToUpperInvariant(); - - if (tradingMode.IsLinear()) { - if (tradingMode.IsPerpetual()) - { - if (quoteAsset == "USDC") - return baseAsset + "PERP"; - - return baseAsset.ToUpperInvariant() + quoteAsset.ToUpperInvariant(); - } - - return baseAsset.ToUpperInvariant() + "-" + deliverTime!.Value.ToString("ddMMMyy").ToUpperInvariant(); - } - - return baseAsset.ToUpperInvariant() + quoteAsset.ToUpperInvariant() + (deliverTime == null ? string.Empty : (ExchangeHelpers.GetDeliveryMonthSymbol(deliverTime.Value) + deliverTime.Value.ToString("yy"))); - } + => BybitExchange.FormatSymbol(baseAsset, quoteAsset, tradingMode, deliverTime); /// /// Get url for an endpoint diff --git a/ByBit.Net/Clients/V5/BybitRestClientApiShared.cs b/ByBit.Net/Clients/V5/BybitRestClientApiShared.cs index cf376439..84276657 100644 --- a/ByBit.Net/Clients/V5/BybitRestClientApiShared.cs +++ b/ByBit.Net/Clients/V5/BybitRestClientApiShared.cs @@ -176,7 +176,10 @@ async Task>> IRecentTradeRestClient.G if (!result) return result.AsExchangeResult>(Exchange, null, default); - return result.AsExchangeResult>(Exchange, request.Symbol.TradingMode, result.Data.List.Select(x => new SharedTrade(x.Quantity, x.Price, x.Timestamp)).ToArray()); + return result.AsExchangeResult>(Exchange, request.Symbol.TradingMode, result.Data.List.Select(x => new SharedTrade(x.Quantity, x.Price, x.Timestamp) + { + Side = x.Side == OrderSide.Buy ? SharedOrderSide.Buy : SharedOrderSide.Sell + }).ToArray()); } #endregion diff --git a/ByBit.Net/Clients/V5/BybitSocketClientBaseApi.cs b/ByBit.Net/Clients/V5/BybitSocketClientBaseApi.cs index 6470533d..9a291be9 100644 --- a/ByBit.Net/Clients/V5/BybitSocketClientBaseApi.cs +++ b/ByBit.Net/Clients/V5/BybitSocketClientBaseApi.cs @@ -38,24 +38,9 @@ internal BybitSocketClientBaseApi(ILogger log, BybitSocketOptions options, strin KeepAliveInterval = TimeSpan.Zero; } - /// - /// public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverTime = null) - { - if (tradingMode == TradingMode.Spot) - return baseAsset.ToUpperInvariant() + quoteAsset.ToUpperInvariant(); - - if (tradingMode.IsLinear()) - { - if (tradingMode.IsPerpetual()) - return baseAsset.ToUpperInvariant() + quoteAsset.ToUpperInvariant(); - - return baseAsset.ToUpperInvariant() + "-" + deliverTime!.Value.ToString("ddMMMyy").ToUpperInvariant(); - } - - return baseAsset.ToUpperInvariant() + quoteAsset.ToUpperInvariant() + (deliverTime == null ? string.Empty : (ExchangeHelpers.GetDeliveryMonthSymbol(deliverTime.Value) + deliverTime.Value.ToString("yy"))); - } + => BybitExchange.FormatSymbol(baseAsset, quoteAsset, tradingMode, deliverTime); /// public virtual Task> SubscribeToOrderbookUpdatesAsync(string symbol, int depth, Action> updateHandler, CancellationToken ct = default) diff --git a/ByBit.Net/Clients/V5/BybitSocketClientInverseApiShared.cs b/ByBit.Net/Clients/V5/BybitSocketClientInverseApiShared.cs index c1410e9e..40f90fb3 100644 --- a/ByBit.Net/Clients/V5/BybitSocketClientInverseApiShared.cs +++ b/ByBit.Net/Clients/V5/BybitSocketClientInverseApiShared.cs @@ -1,4 +1,5 @@ -using Bybit.Net.Interfaces.Clients.V5; +using Bybit.Net.Enums; +using Bybit.Net.Interfaces.Clients.V5; using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects.Sockets; using CryptoExchange.Net.SharedApis; @@ -60,7 +61,10 @@ 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, update.Data.Select(x => new SharedTrade(x.Quantity, x.Price, x.Timestamp)))), ct).ConfigureAwait(false); + var result = await SubscribeToTradeUpdatesAsync(symbol, update => handler(update.AsExchangeEvent(Exchange, update.Data.Select(x => new SharedTrade(x.Quantity, x.Price, x.Timestamp) + { + Side = x.Side == OrderSide.Buy ? SharedOrderSide.Buy : SharedOrderSide.Sell + }))), ct).ConfigureAwait(false); return new ExchangeResult(Exchange, result); } diff --git a/ByBit.Net/Clients/V5/BybitSocketClientLinearApiShared.cs b/ByBit.Net/Clients/V5/BybitSocketClientLinearApiShared.cs index 030a6f6e..4266d973 100644 --- a/ByBit.Net/Clients/V5/BybitSocketClientLinearApiShared.cs +++ b/ByBit.Net/Clients/V5/BybitSocketClientLinearApiShared.cs @@ -1,4 +1,5 @@ -using Bybit.Net.Interfaces.Clients.V5; +using Bybit.Net.Enums; +using Bybit.Net.Interfaces.Clients.V5; using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects.Sockets; using CryptoExchange.Net.SharedApis; @@ -59,7 +60,10 @@ 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, update.Data.Select(x => new SharedTrade(x.Quantity, x.Price, x.Timestamp)))), ct).ConfigureAwait(false); + var result = await SubscribeToTradeUpdatesAsync(symbol, update => handler(update.AsExchangeEvent(Exchange, update.Data.Select(x => new SharedTrade(x.Quantity, x.Price, x.Timestamp) + { + Side = x.Side == OrderSide.Buy ? SharedOrderSide.Buy : SharedOrderSide.Sell + }))), ct).ConfigureAwait(false); return new ExchangeResult(Exchange, result); } diff --git a/ByBit.Net/Clients/V5/BybitSocketClientSpotApiShared.cs b/ByBit.Net/Clients/V5/BybitSocketClientSpotApiShared.cs index fe52e30a..332a1612 100644 --- a/ByBit.Net/Clients/V5/BybitSocketClientSpotApiShared.cs +++ b/ByBit.Net/Clients/V5/BybitSocketClientSpotApiShared.cs @@ -1,4 +1,5 @@ -using Bybit.Net.Interfaces.Clients.V5; +using Bybit.Net.Enums; +using Bybit.Net.Interfaces.Clients.V5; using CryptoExchange.Net.Objects; using CryptoExchange.Net.Objects.Sockets; using CryptoExchange.Net.SharedApis; @@ -46,7 +47,10 @@ 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, update.Data.Select(x => new SharedTrade(x.Quantity, x.Price, x.Timestamp)))), ct).ConfigureAwait(false); + var result = await SubscribeToTradeUpdatesAsync(symbol, update => handler(update.AsExchangeEvent(Exchange, update.Data.Select(x => new SharedTrade(x.Quantity, x.Price, x.Timestamp) + { + Side = x.Side == OrderSide.Buy ? SharedOrderSide.Buy : SharedOrderSide.Sell + }))), ct).ConfigureAwait(false); return new ExchangeResult(Exchange, result); } diff --git a/ByBit.Net/ExtensionMethods/ServiceCollectionExtensions.cs b/ByBit.Net/ExtensionMethods/ServiceCollectionExtensions.cs index 631a4de2..0d37d7de 100644 --- a/ByBit.Net/ExtensionMethods/ServiceCollectionExtensions.cs +++ b/ByBit.Net/ExtensionMethods/ServiceCollectionExtensions.cs @@ -1,4 +1,5 @@ -using Bybit.Net.Clients; +using Bybit.Net; +using Bybit.Net.Clients; using Bybit.Net.Interfaces; using Bybit.Net.Interfaces.Clients; using Bybit.Net.Objects.Options; @@ -61,6 +62,7 @@ public static IServiceCollection AddBybit( services.AddTransient(); services.AddTransient(); services.AddTransient(); + services.AddTransient(); services.AddTransient(x => x.GetRequiredService().V5Api.CommonSpotClient); services.RegisterSharedRestInterfaces(x => x.GetRequiredService().V5Api.SharedClient); diff --git a/ByBit.Net/Interfaces/IBybitOrderBookFactory.cs b/ByBit.Net/Interfaces/IBybitOrderBookFactory.cs index 32e54685..da389cb4 100644 --- a/ByBit.Net/Interfaces/IBybitOrderBookFactory.cs +++ b/ByBit.Net/Interfaces/IBybitOrderBookFactory.cs @@ -1,6 +1,7 @@ using Bybit.Net.Enums; using Bybit.Net.Objects.Options; using CryptoExchange.Net.Interfaces; +using CryptoExchange.Net.SharedApis; using System; namespace Bybit.Net.Interfaces @@ -25,6 +26,14 @@ public interface IBybitOrderBookFactory /// public IOrderBookFactory LinearInverse { get; } + /// + /// Create a SymbolOrderBook for the symbol + /// + /// The symbol + /// Book options + /// + ISymbolOrderBook Create(SharedSymbol symbol, Action? options = null); + /// /// Create a SymbolOrderBook specifying the category /// diff --git a/ByBit.Net/Interfaces/IBybitTrackerFactory.cs b/ByBit.Net/Interfaces/IBybitTrackerFactory.cs new file mode 100644 index 00000000..35cae210 --- /dev/null +++ b/ByBit.Net/Interfaces/IBybitTrackerFactory.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 Bybit.Net.Interfaces +{ + /// + /// Tracker factory + /// + public interface IBybitTrackerFactory + { + /// + /// 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/ByBit.Net/SymbolOrderBooks/BybitOrderBookFactory.cs b/ByBit.Net/SymbolOrderBooks/BybitOrderBookFactory.cs index 04480a4a..701ba620 100644 --- a/ByBit.Net/SymbolOrderBooks/BybitOrderBookFactory.cs +++ b/ByBit.Net/SymbolOrderBooks/BybitOrderBookFactory.cs @@ -4,6 +4,7 @@ using Bybit.Net.Objects.Options; using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.OrderBook; +using CryptoExchange.Net.SharedApis; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using System; @@ -32,9 +33,25 @@ public BybitOrderBookFactory(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; - Spot = new OrderBookFactory((symbol, options) => CreateSpot(symbol, options), (baseAsset, quoteAsset, options) => CreateSpot(baseAsset + quoteAsset, options)); - Options = new OrderBookFactory((symbol, options) => CreateOption(symbol, options), (baseAsset, quoteAsset, options) => CreateOption(baseAsset + quoteAsset, options)); - LinearInverse = new OrderBookFactory((symbol, options) => CreateLinearInverse(symbol, options), (baseAsset, quoteAsset, options) => CreateLinearInverse(baseAsset + quoteAsset, options)); + Spot = new OrderBookFactory( + CreateSpot, + (sharedSymbol, options) => CreateSpot(BybitExchange.FormatSymbol(sharedSymbol.BaseAsset, sharedSymbol.QuoteAsset, sharedSymbol.TradingMode, sharedSymbol.DeliverTime), options)); + Options = new OrderBookFactory( + CreateOption, + (sharedSymbol, options) => CreateOption(BybitExchange.FormatSymbol(sharedSymbol.BaseAsset, sharedSymbol.QuoteAsset, sharedSymbol.TradingMode, sharedSymbol.DeliverTime), options)); + LinearInverse = new OrderBookFactory( + CreateLinearInverse, + (sharedSymbol, options) => CreateLinearInverse(BybitExchange.FormatSymbol(sharedSymbol.BaseAsset, sharedSymbol.QuoteAsset, sharedSymbol.TradingMode, sharedSymbol.DeliverTime), options)); + } + + /// + public ISymbolOrderBook Create(SharedSymbol symbol, Action? options = null) + { + var symbolName = BybitExchange.FormatSymbol(symbol.BaseAsset, symbol.QuoteAsset, symbol.TradingMode, symbol.DeliverTime); + if (symbol.TradingMode == TradingMode.Spot) + return CreateSpot(symbolName, options); + + return CreateLinearInverse(symbolName, options); } /// diff --git a/Examples/Bybit.Api/Bybit.Examples.Api.csproj b/Examples/Bybit.Api/Bybit.Examples.Api.csproj index 6a1612f0..2e633228 100644 --- a/Examples/Bybit.Api/Bybit.Examples.Api.csproj +++ b/Examples/Bybit.Api/Bybit.Examples.Api.csproj @@ -8,9 +8,12 @@ - + + + + diff --git a/Examples/Bybit.Examples.Console/Bybit.Examples.Console.csproj b/Examples/Bybit.Examples.Console/Bybit.Examples.Console.csproj index a9c02642..a190dd54 100644 --- a/Examples/Bybit.Examples.Console/Bybit.Examples.Console.csproj +++ b/Examples/Bybit.Examples.Console/Bybit.Examples.Console.csproj @@ -8,7 +8,8 @@ - + + diff --git a/Examples/Bybit.Examples.OrderBook/Bybit.Examples.OrderBook.csproj b/Examples/Bybit.Examples.OrderBook/Bybit.Examples.OrderBook.csproj new file mode 100644 index 00000000..65a29227 --- /dev/null +++ b/Examples/Bybit.Examples.OrderBook/Bybit.Examples.OrderBook.csproj @@ -0,0 +1,18 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + diff --git a/Examples/Bybit.Examples.OrderBook/Program.cs b/Examples/Bybit.Examples.OrderBook/Program.cs new file mode 100644 index 00000000..44db5941 --- /dev/null +++ b/Examples/Bybit.Examples.OrderBook/Program.cs @@ -0,0 +1,52 @@ +using Bybit.Net.Interfaces; +using CryptoExchange.Net; +using CryptoExchange.Net.SharedApis; +using Microsoft.Extensions.DependencyInjection; +using Spectre.Console; + +var collection = new ServiceCollection(); +collection.AddBybit(); +var provider = collection.BuildServiceProvider(); + +var bookFactory = provider.GetRequiredService(); + +// Create and start the order book +var book = bookFactory.Create(new SharedSymbol(TradingMode.Spot, "ETH", "USDT")); +var result = await book.StartAsync(); +if (!result.Success) +{ + Console.WriteLine(result.Error); + 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/Bybit.Examples.Tracker/Bybit.Examples.Tracker.csproj b/Examples/Bybit.Examples.Tracker/Bybit.Examples.Tracker.csproj new file mode 100644 index 00000000..65a29227 --- /dev/null +++ b/Examples/Bybit.Examples.Tracker/Bybit.Examples.Tracker.csproj @@ -0,0 +1,18 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + diff --git a/Examples/Bybit.Examples.Tracker/Program.cs b/Examples/Bybit.Examples.Tracker/Program.cs new file mode 100644 index 00000000..b7285a5c --- /dev/null +++ b/Examples/Bybit.Examples.Tracker/Program.cs @@ -0,0 +1,104 @@ +using Bybit.Net.Interfaces; +using CryptoExchange.Net.SharedApis; +using Microsoft.Extensions.DependencyInjection; +using Spectre.Console; +using System.Globalization; + +var collection = new ServiceCollection(); +collection.AddBybit(); +var provider = collection.BuildServiceProvider(); + +var trackerFactory = provider.GetRequiredService(); + +// Create 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/Examples.sln b/Examples/Examples.sln index 7b08e060..a8588afa 100644 --- a/Examples/Examples.sln +++ b/Examples/Examples.sln @@ -3,9 +3,15 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.8.34330.188 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bybit.Examples.Console", "Bybit.Examples.Console\Bybit.Examples.Console.csproj", "{72F29164-3C3E-4EB1-9C2B-BC9BA5FCD5AA}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bybit.Examples.Console", "Bybit.Examples.Console\Bybit.Examples.Console.csproj", "{72F29164-3C3E-4EB1-9C2B-BC9BA5FCD5AA}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Bybit.Examples.Api", "Bybit.Api\Bybit.Examples.Api.csproj", "{2E6BB5F7-6F04-4122-8CB6-6B89E822040A}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bybit.Examples.Api", "Bybit.Api\Bybit.Examples.Api.csproj", "{2E6BB5F7-6F04-4122-8CB6-6B89E822040A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bybit.Net", "..\ByBit.Net\Bybit.Net.csproj", "{B2977FEC-ECF9-4E12-B2B4-2417107232C7}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bybit.Examples.OrderBook", "Bybit.Examples.OrderBook\Bybit.Examples.OrderBook.csproj", "{CF4C5309-05A1-4B37-82E4-3FDC76A0A162}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Bybit.Examples.Tracker", "Bybit.Examples.Tracker\Bybit.Examples.Tracker.csproj", "{8BF15F78-6C50-4A04-B874-60D790D632C9}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -21,6 +27,18 @@ Global {2E6BB5F7-6F04-4122-8CB6-6B89E822040A}.Debug|Any CPU.Build.0 = Debug|Any CPU {2E6BB5F7-6F04-4122-8CB6-6B89E822040A}.Release|Any CPU.ActiveCfg = Release|Any CPU {2E6BB5F7-6F04-4122-8CB6-6B89E822040A}.Release|Any CPU.Build.0 = Release|Any CPU + {B2977FEC-ECF9-4E12-B2B4-2417107232C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B2977FEC-ECF9-4E12-B2B4-2417107232C7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B2977FEC-ECF9-4E12-B2B4-2417107232C7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B2977FEC-ECF9-4E12-B2B4-2417107232C7}.Release|Any CPU.Build.0 = Release|Any CPU + {CF4C5309-05A1-4B37-82E4-3FDC76A0A162}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CF4C5309-05A1-4B37-82E4-3FDC76A0A162}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CF4C5309-05A1-4B37-82E4-3FDC76A0A162}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CF4C5309-05A1-4B37-82E4-3FDC76A0A162}.Release|Any CPU.Build.0 = Release|Any CPU + {8BF15F78-6C50-4A04-B874-60D790D632C9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8BF15F78-6C50-4A04-B874-60D790D632C9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8BF15F78-6C50-4A04-B874-60D790D632C9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8BF15F78-6C50-4A04-B874-60D790D632C9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Examples/README.md b/Examples/README.md index d6cb5068..445efaf9 100644 --- a/Examples/README.md +++ b/Examples/README.md @@ -4,4 +4,10 @@ A minimal API showing how to integrate Bybit.Net in a web API project ### Bybit.Examples.Console -A simple console client demonstrating basic usage \ No newline at end of file +A simple console client demonstrating basic usage + +### Bybit.Examples.OrderBook +Example of using the client side order book implementation + +### Bybit.Examples.Tracker +Example of using the trade tracker \ No newline at end of file