diff --git a/BingX.Net/BingX.Net.csproj b/BingX.Net/BingX.Net.csproj index c3c6906..0ac4bbe 100644 --- a/BingX.Net/BingX.Net.csproj +++ b/BingX.Net/BingX.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/BingX.Net/BingX.Net.xml b/BingX.Net/BingX.Net.xml index 12b76b8..1d1b85e 100644 --- a/BingX.Net/BingX.Net.xml +++ b/BingX.Net/BingX.Net.xml @@ -59,6 +59,16 @@ Urls to the API documentation + + + Format a base and quote asset to a BingX recognized symbol + + Base asset + Quote asset + Trading mode + Delivery time for delivery futures + + Rate limiter configuration for the BingX API @@ -74,6 +84,26 @@ Event for when a rate limit is triggered + + + + + + ctor + + + + + ctor + + Service provider for resolving logging and clients + + + + + + + @@ -2666,6 +2696,14 @@ Perpetual Futures order book factory methods + + + Create a SymbolOrderBook for the symbol + + The symbol + Book options + + Create a new futures local order book instance @@ -2682,6 +2720,30 @@ + + + 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 + + Api addresses @@ -6477,6 +6539,9 @@ + + + diff --git a/BingX.Net/BingXExchange.cs b/BingX.Net/BingXExchange.cs index c4a518a..ffa71fd 100644 --- a/BingX.Net/BingXExchange.cs +++ b/BingX.Net/BingXExchange.cs @@ -2,6 +2,7 @@ using CryptoExchange.Net.RateLimiting; using CryptoExchange.Net.RateLimiting.Guards; using CryptoExchange.Net.RateLimiting.Interfaces; +using CryptoExchange.Net.SharedApis; using System; using System.Collections.Generic; @@ -29,6 +30,19 @@ public static class BingXExchange "https://bingx-api.github.io/docs" }; + /// + /// Format a base and quote asset to a BingX 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) + { + return baseAsset.ToUpperInvariant() + "-" + quoteAsset.ToUpperInvariant(); + } + /// /// Rate limiter configuration for the BingX API /// diff --git a/BingX.Net/BingXTrackerFactory.cs b/BingX.Net/BingXTrackerFactory.cs new file mode 100644 index 0000000..4a03bc2 --- /dev/null +++ b/BingX.Net/BingXTrackerFactory.cs @@ -0,0 +1,92 @@ +using BingX.Net.Clients; +using BingX.Net.Interfaces; +using BingX.Net.Interfaces.Clients; +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 BingX.Net +{ + /// + public class BingXTrackerFactory : IBingXTrackerFactory + { + private readonly IServiceProvider? _serviceProvider; + + /// + /// ctor + /// + public BingXTrackerFactory() + { + } + + /// + /// ctor + /// + /// Service provider for resolving logging and clients + public BingXTrackerFactory(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + /// + public IKlineTracker CreateKlineTracker(SharedSymbol symbol, SharedKlineInterval interval, int? limit = null, TimeSpan? period = null) + { + var restClient = _serviceProvider?.GetRequiredService() ?? new BingXRestClient(); + var socketClient = _serviceProvider?.GetRequiredService() ?? new BingXSocketClient(); + + IKlineRestClient sharedRestClient; + IKlineSocketClient sharedSocketClient; + if (symbol.TradingMode == TradingMode.Spot) + { + sharedRestClient = restClient.SpotApi.SharedClient; + sharedSocketClient = socketClient.SpotApi.SharedClient; + } + else + { + sharedRestClient = restClient.PerpetualFuturesApi.SharedClient; + sharedSocketClient = socketClient.PerpetualFuturesApi.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 BingXRestClient(); + var socketClient = _serviceProvider?.GetRequiredService() ?? new BingXSocketClient(); + + IRecentTradeRestClient sharedRestClient; + ITradeSocketClient sharedSocketClient; + if (symbol.TradingMode == TradingMode.Spot) { + sharedRestClient = restClient.SpotApi.SharedClient; + sharedSocketClient = socketClient.SpotApi.SharedClient; + } + else { + sharedRestClient = restClient.PerpetualFuturesApi.SharedClient; + sharedSocketClient = socketClient.PerpetualFuturesApi.SharedClient; + } + + return new TradeTracker( + _serviceProvider?.GetRequiredService().CreateLogger(restClient.Exchange), + sharedRestClient, + null, + sharedSocketClient, + symbol, + limit, + period + ); + } + } +} diff --git a/BingX.Net/Clients/PerpetualFuturesApi/BingXRestClientPerpetualFuturesApi.cs b/BingX.Net/Clients/PerpetualFuturesApi/BingXRestClientPerpetualFuturesApi.cs index b663068..11129f3 100644 --- a/BingX.Net/Clients/PerpetualFuturesApi/BingXRestClientPerpetualFuturesApi.cs +++ b/BingX.Net/Clients/PerpetualFuturesApi/BingXRestClientPerpetualFuturesApi.cs @@ -55,7 +55,9 @@ internal BingXRestClientPerpetualFuturesApi(ILogger logger, HttpClient? httpClie #endregion /// - 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) + => BingXExchange.FormatSymbol(baseAsset, quoteAsset, tradingMode, deliverTime); + public IBingXRestClientPerpetualFuturesApiShared SharedClient => this; /// diff --git a/BingX.Net/Clients/PerpetualFuturesApi/BingXRestClientPerpetualFuturesApiShared.cs b/BingX.Net/Clients/PerpetualFuturesApi/BingXRestClientPerpetualFuturesApiShared.cs index c91f1a3..8ccaa47 100644 --- a/BingX.Net/Clients/PerpetualFuturesApi/BingXRestClientPerpetualFuturesApiShared.cs +++ b/BingX.Net/Clients/PerpetualFuturesApi/BingXRestClientPerpetualFuturesApiShared.cs @@ -181,7 +181,10 @@ async Task>> IRecentTradeRestClient.G if (!result) return result.AsExchangeResult>(Exchange, null, default); - return result.AsExchangeResult>(Exchange, request.Symbol.TradingMode, result.Data.Reverse().Select(x => new SharedTrade(x.Quantity, x.Price, x.Timestamp)).ToArray()); + return result.AsExchangeResult>(Exchange, request.Symbol.TradingMode, result.Data.Reverse().Select(x => new SharedTrade(x.Quantity, x.Price, x.Timestamp) + { + Side = x.BuyerIsMaker ? SharedOrderSide.Buy : SharedOrderSide.Sell + }).ToArray()); } #endregion diff --git a/BingX.Net/Clients/PerpetualFuturesApi/BingXSocketClientPerpetualFuturesApi.cs b/BingX.Net/Clients/PerpetualFuturesApi/BingXSocketClientPerpetualFuturesApi.cs index c1124c5..758c309 100644 --- a/BingX.Net/Clients/PerpetualFuturesApi/BingXSocketClientPerpetualFuturesApi.cs +++ b/BingX.Net/Clients/PerpetualFuturesApi/BingXSocketClientPerpetualFuturesApi.cs @@ -50,7 +50,8 @@ internal BingXSocketClientPerpetualFuturesApi(ILogger logger, BingXSocketOptions #endregion /// - 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) + => BingXExchange.FormatSymbol(baseAsset, quoteAsset, tradingMode, deliverTime); /// protected override AuthenticationProvider CreateAuthenticationProvider(ApiCredentials credentials) diff --git a/BingX.Net/Clients/PerpetualFuturesApi/BingXSocketClientPerpetualFuturesApiShared.cs b/BingX.Net/Clients/PerpetualFuturesApi/BingXSocketClientPerpetualFuturesApiShared.cs index 7c6b5e4..c19b147 100644 --- a/BingX.Net/Clients/PerpetualFuturesApi/BingXSocketClientPerpetualFuturesApiShared.cs +++ b/BingX.Net/Clients/PerpetualFuturesApi/BingXSocketClientPerpetualFuturesApiShared.cs @@ -44,13 +44,42 @@ 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.TradeTime)).ToArray())), ct).ConfigureAwait(false); + var result = await SubscribeToTradeUpdatesAsync(symbol, update => handler(update.AsExchangeEvent>(Exchange, update.Data.Select(x => new SharedTrade(x.Quantity, x.Price, x.TradeTime) + { + Side = x.BuyerIsMaker ? SharedOrderSide.Buy : SharedOrderSide.Sell + }).ToArray())), ct).ConfigureAwait(false); return new ExchangeResult(Exchange, result); } #endregion + #region Kline client + SubscribeKlineOptions IKlineSocketClient.SubscribeKlineOptions { get; } = new SubscribeKlineOptions(false); + async Task> IKlineSocketClient.SubscribeToKlineUpdatesAsync(SubscribeKlineRequest request, Action> handler, CancellationToken ct) + { + var interval = (Enums.KlineInterval)request.Interval; + if (!Enum.IsDefined(typeof(Enums.KlineInterval), interval)) + return new ExchangeResult(Exchange, new ArgumentError("Interval not supported")); + + var validationError = ((IKlineSocketClient)this).SubscribeKlineOptions.ValidateRequest(Exchange, request, request.Symbol.TradingMode, SupportedTradingModes); + if (validationError != null) + return new ExchangeResult(Exchange, validationError); + + var symbol = request.Symbol.GetSymbol(FormatSymbol); + var result = await SubscribeToKlineUpdatesAsync(symbol, interval, update => + { + if (update.UpdateType == SocketUpdateType.Snapshot) + return; + + foreach (var item in update.Data) + handler(update.AsExchangeEvent(Exchange, new SharedKline(item.Timestamp, item.ClosePrice, item.HighPrice, item.LowPrice, item.OpenPrice, item.Volume))); + }, ct).ConfigureAwait(false); + + return new ExchangeResult(Exchange, result); + } + #endregion + #region Book Ticker client EndpointOptions IBookTickerSocketClient.SubscribeBookTickerOptions { get; } = new EndpointOptions(false); diff --git a/BingX.Net/Clients/SpotApi/BingXRestClientSpotApi.cs b/BingX.Net/Clients/SpotApi/BingXRestClientSpotApi.cs index fbce924..76f600b 100644 --- a/BingX.Net/Clients/SpotApi/BingXRestClientSpotApi.cs +++ b/BingX.Net/Clients/SpotApi/BingXRestClientSpotApi.cs @@ -66,7 +66,8 @@ internal BingXRestClientSpotApi(ILogger logger, HttpClient? httpClient, BingXRes #endregion /// - 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) + => BingXExchange.FormatSymbol(baseAsset, quoteAsset, tradingMode, deliverTime); /// protected override IMessageSerializer CreateSerializer() => new SystemTextJsonMessageSerializer(); diff --git a/BingX.Net/Clients/SpotApi/BingXRestClientSpotApiShared.cs b/BingX.Net/Clients/SpotApi/BingXRestClientSpotApiShared.cs index 96baadd..9170062 100644 --- a/BingX.Net/Clients/SpotApi/BingXRestClientSpotApiShared.cs +++ b/BingX.Net/Clients/SpotApi/BingXRestClientSpotApiShared.cs @@ -152,7 +152,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.BuyerIsMaker ? SharedOrderSide.Buy : SharedOrderSide.Sell + }).ToArray()); } #endregion diff --git a/BingX.Net/Clients/SpotApi/BingXSocketClientSpotApi.cs b/BingX.Net/Clients/SpotApi/BingXSocketClientSpotApi.cs index 5278363..44833fc 100644 --- a/BingX.Net/Clients/SpotApi/BingXSocketClientSpotApi.cs +++ b/BingX.Net/Clients/SpotApi/BingXSocketClientSpotApi.cs @@ -54,7 +54,8 @@ protected override AuthenticationProvider CreateAuthenticationProvider(ApiCreden => new BingXAuthenticationProvider(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) + => BingXExchange.FormatSymbol(baseAsset, quoteAsset, tradingMode, deliverTime); public IBingXSocketClientSpotApiShared SharedClient => this; diff --git a/BingX.Net/Clients/SpotApi/BingXSocketClientSpotApiShared.cs b/BingX.Net/Clients/SpotApi/BingXSocketClientSpotApiShared.cs index ae48e49..f831205 100644 --- a/BingX.Net/Clients/SpotApi/BingXSocketClientSpotApiShared.cs +++ b/BingX.Net/Clients/SpotApi/BingXSocketClientSpotApiShared.cs @@ -45,13 +45,35 @@ 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.TradeTime) })), ct).ConfigureAwait(false); + var result = await SubscribeToTradeUpdatesAsync(symbol, update => handler(update.AsExchangeEvent>(Exchange, new[] { new SharedTrade(update.Data.Quantity, update.Data.Price, update.Data.TradeTime) + { + Side = update.Data.BuyerIsMaker ? SharedOrderSide.Buy : SharedOrderSide.Sell + } })), ct).ConfigureAwait(false); return new ExchangeResult(Exchange, result); } #endregion + #region Kline client + SubscribeKlineOptions IKlineSocketClient.SubscribeKlineOptions { get; } = new SubscribeKlineOptions(false); + async Task> IKlineSocketClient.SubscribeToKlineUpdatesAsync(SubscribeKlineRequest request, Action> handler, CancellationToken ct) + { + var interval = (Enums.KlineInterval)request.Interval; + if (!Enum.IsDefined(typeof(Enums.KlineInterval), interval)) + return new ExchangeResult(Exchange, new ArgumentError("Interval not supported")); + + var validationError = ((IKlineSocketClient)this).SubscribeKlineOptions.ValidateRequest(Exchange, request, request.Symbol.TradingMode, SupportedTradingModes); + if (validationError != null) + return new ExchangeResult(Exchange, validationError); + + var symbol = request.Symbol.GetSymbol(FormatSymbol); + var result = await SubscribeToKlineUpdatesAsync(symbol, interval, update => handler(update.AsExchangeEvent(Exchange, new SharedKline(update.Data.Kline.OpenTime, update.Data.Kline.ClosePrice, update.Data.Kline.HighPrice, update.Data.Kline.LowPrice, update.Data.Kline.OpenPrice, update.Data.Kline.Volume))), ct).ConfigureAwait(false); + + return new ExchangeResult(Exchange, result); + } + #endregion + #region Book Ticker client EndpointOptions IBookTickerSocketClient.SubscribeBookTickerOptions { get; } = new EndpointOptions(false); diff --git a/BingX.Net/ExtensionMethods/ServiceCollectionExtensions.cs b/BingX.Net/ExtensionMethods/ServiceCollectionExtensions.cs index 161553c..5c14ae1 100644 --- a/BingX.Net/ExtensionMethods/ServiceCollectionExtensions.cs +++ b/BingX.Net/ExtensionMethods/ServiceCollectionExtensions.cs @@ -9,6 +9,7 @@ using BingX.Net.Objects.Options; using BingX.Net.SymbolOrderBooks; using CryptoExchange.Net; +using BingX.Net; namespace Microsoft.Extensions.DependencyInjection { @@ -62,6 +63,7 @@ public static IServiceCollection AddBingX( services.AddTransient(); services.AddSingleton(); services.AddTransient(); + services.AddTransient(); services.AddTransient(x => x.GetRequiredService().SpotApi.CommonSpotClient); services.RegisterSharedRestInterfaces(x => x.GetRequiredService().SpotApi.SharedClient); diff --git a/BingX.Net/Interfaces/Clients/PerpetualFuturesApi/IBingXSocketClientPerpetualFuturesApiShared.cs b/BingX.Net/Interfaces/Clients/PerpetualFuturesApi/IBingXSocketClientPerpetualFuturesApiShared.cs index 2471092..89928be 100644 --- a/BingX.Net/Interfaces/Clients/PerpetualFuturesApi/IBingXSocketClientPerpetualFuturesApiShared.cs +++ b/BingX.Net/Interfaces/Clients/PerpetualFuturesApi/IBingXSocketClientPerpetualFuturesApiShared.cs @@ -11,7 +11,8 @@ public interface IBingXSocketClientPerpetualFuturesApiShared : IBookTickerSocketClient, IBalanceSocketClient, IPositionSocketClient, - IFuturesOrderSocketClient + IFuturesOrderSocketClient, + IKlineSocketClient { } } diff --git a/BingX.Net/Interfaces/Clients/SpotApi/IBingXSocketClientSpotApiShared.cs b/BingX.Net/Interfaces/Clients/SpotApi/IBingXSocketClientSpotApiShared.cs index c5efcb9..ebfb9a3 100644 --- a/BingX.Net/Interfaces/Clients/SpotApi/IBingXSocketClientSpotApiShared.cs +++ b/BingX.Net/Interfaces/Clients/SpotApi/IBingXSocketClientSpotApiShared.cs @@ -10,7 +10,8 @@ public interface IBingXSocketClientSpotApiShared : ITradeSocketClient, IBookTickerSocketClient, IBalanceSocketClient, - ISpotOrderSocketClient + ISpotOrderSocketClient, + IKlineSocketClient { } } diff --git a/BingX.Net/Interfaces/IBingXOrderBookFactory.cs b/BingX.Net/Interfaces/IBingXOrderBookFactory.cs index cb64e7d..0c8e544 100644 --- a/BingX.Net/Interfaces/IBingXOrderBookFactory.cs +++ b/BingX.Net/Interfaces/IBingXOrderBookFactory.cs @@ -1,6 +1,7 @@ using CryptoExchange.Net.Interfaces; using System; using BingX.Net.Objects.Options; +using CryptoExchange.Net.SharedApis; namespace BingX.Net.Interfaces { @@ -18,6 +19,14 @@ public interface IBingXOrderBookFactory /// public IOrderBookFactory PerpetualFutures { get; } + /// + /// Create a SymbolOrderBook for the symbol + /// + /// The symbol + /// Book options + /// + ISymbolOrderBook Create(SharedSymbol symbol, Action? options = null); + /// /// Create a new futures local order book instance /// diff --git a/BingX.Net/Interfaces/IBingXTrackerFactory.cs b/BingX.Net/Interfaces/IBingXTrackerFactory.cs new file mode 100644 index 0000000..fc481ae --- /dev/null +++ b/BingX.Net/Interfaces/IBingXTrackerFactory.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 BingX.Net.Interfaces +{ + /// + /// Tracker factory + /// + public interface IBingXTrackerFactory + { + /// + /// 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/BingX.Net/SymbolOrderBooks/BingXOrderBookFactory.cs b/BingX.Net/SymbolOrderBooks/BingXOrderBookFactory.cs index e57a474..0bf8d08 100644 --- a/BingX.Net/SymbolOrderBooks/BingXOrderBookFactory.cs +++ b/BingX.Net/SymbolOrderBooks/BingXOrderBookFactory.cs @@ -6,6 +6,7 @@ using BingX.Net.Interfaces.Clients; using BingX.Net.Objects.Options; using CryptoExchange.Net.OrderBook; +using CryptoExchange.Net.SharedApis; namespace BingX.Net.SymbolOrderBooks { @@ -24,8 +25,13 @@ public BingXOrderBookFactory(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider; - Spot = new OrderBookFactory((symbol, options) => CreateSpot(symbol, options), (baseAsset, quoteAsset, options) => CreateSpot(baseAsset + "-" + quoteAsset, options)); - PerpetualFutures = new OrderBookFactory((symbol, options) => CreatePerpetualFutures(symbol, options), (baseAsset, quoteAsset, options) => CreatePerpetualFutures(baseAsset + quoteAsset, options)); + Spot = new OrderBookFactory( + CreateSpot, + (sharedSymbol, options) => CreateSpot(BingXExchange.FormatSymbol(sharedSymbol.BaseAsset, sharedSymbol.QuoteAsset, sharedSymbol.TradingMode, sharedSymbol.DeliverTime), options)); + + PerpetualFutures = new OrderBookFactory( + CreatePerpetualFutures, + (sharedSymbol, options) => CreatePerpetualFutures(BingXExchange.FormatSymbol(sharedSymbol.BaseAsset, sharedSymbol.QuoteAsset, sharedSymbol.TradingMode, sharedSymbol.DeliverTime), options)); } /// @@ -33,6 +39,16 @@ public BingXOrderBookFactory(IServiceProvider serviceProvider) /// public IOrderBookFactory PerpetualFutures { get; } + /// + public ISymbolOrderBook Create(SharedSymbol symbol, Action? options = null) + { + var symbolName = BingXExchange.FormatSymbol(symbol.BaseAsset, symbol.QuoteAsset, symbol.TradingMode, symbol.DeliverTime); + if (symbol.TradingMode == TradingMode.Spot) + return CreateSpot(symbolName, options); + + return CreatePerpetualFutures(symbolName, options); + } + /// public ISymbolOrderBook CreateSpot(string symbol, Action? options = null) => new BingXSpotSymbolOrderBook(symbol, diff --git a/Examples/BingX.Examples.Api/BingX.Examples.Api.csproj b/Examples/BingX.Examples.Api/BingX.Examples.Api.csproj index 093e78f..da64028 100644 --- a/Examples/BingX.Examples.Api/BingX.Examples.Api.csproj +++ b/Examples/BingX.Examples.Api/BingX.Examples.Api.csproj @@ -1,16 +1,19 @@ - net7.0 + net8.0 enable enable true - + + + + diff --git a/Examples/BingX.Examples.Console/BingX.Examples.Console.csproj b/Examples/BingX.Examples.Console/BingX.Examples.Console.csproj index 3351840..bf23bce 100644 --- a/Examples/BingX.Examples.Console/BingX.Examples.Console.csproj +++ b/Examples/BingX.Examples.Console/BingX.Examples.Console.csproj @@ -2,13 +2,13 @@ Exe - net7.0 + net8.0 enable enable - + diff --git a/Examples/BingX.Examples.OrderBook/BingX.Examples.OrderBook.csproj b/Examples/BingX.Examples.OrderBook/BingX.Examples.OrderBook.csproj new file mode 100644 index 0000000..5a74ae1 --- /dev/null +++ b/Examples/BingX.Examples.OrderBook/BingX.Examples.OrderBook.csproj @@ -0,0 +1,18 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + diff --git a/Examples/BingX.Examples.OrderBook/Program.cs b/Examples/BingX.Examples.OrderBook/Program.cs new file mode 100644 index 0000000..8b21c1e --- /dev/null +++ b/Examples/BingX.Examples.OrderBook/Program.cs @@ -0,0 +1,52 @@ +using BingX.Net.Interfaces; +using CryptoExchange.Net; +using CryptoExchange.Net.SharedApis; +using Microsoft.Extensions.DependencyInjection; +using Spectre.Console; + +var collection = new ServiceCollection(); +collection.AddBingX(); +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); + 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/BingX.Examples.Tracker/BingX.Examples.Tracker.csproj b/Examples/BingX.Examples.Tracker/BingX.Examples.Tracker.csproj new file mode 100644 index 0000000..5a74ae1 --- /dev/null +++ b/Examples/BingX.Examples.Tracker/BingX.Examples.Tracker.csproj @@ -0,0 +1,18 @@ + + + + Exe + net8.0 + enable + enable + + + + + + + + + + + diff --git a/Examples/BingX.Examples.Tracker/Program.cs b/Examples/BingX.Examples.Tracker/Program.cs new file mode 100644 index 0000000..5e7495f --- /dev/null +++ b/Examples/BingX.Examples.Tracker/Program.cs @@ -0,0 +1,104 @@ +using BingX.Net.Interfaces; +using CryptoExchange.Net.SharedApis; +using Microsoft.Extensions.DependencyInjection; +using Spectre.Console; +using System.Globalization; + +var collection = new ServiceCollection(); +collection.AddBingX(); +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 590ca1c..084dfbc 100644 --- a/Examples/Examples.sln +++ b/Examples/Examples.sln @@ -7,6 +7,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BingX.Examples.Api", "BingX EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BingX.Examples.Console", "BingX.Examples.Console\BingX.Examples.Console.csproj", "{FD4F95C8-D9B7-4F81-9245-4CE667DFD421}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BingX.Net", "..\BingX.Net\BingX.Net.csproj", "{90651429-48DD-454F-BEAA-14058E43877A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BingX.Examples.OrderBook", "BingX.Examples.OrderBook\BingX.Examples.OrderBook.csproj", "{33D58C29-15EB-48E2-A6E9-FC9FD84B43E8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BingX.Examples.Tracker", "BingX.Examples.Tracker\BingX.Examples.Tracker.csproj", "{0051E490-FA97-4B88-BB14-30AAF17DAAA1}" +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 + {90651429-48DD-454F-BEAA-14058E43877A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90651429-48DD-454F-BEAA-14058E43877A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90651429-48DD-454F-BEAA-14058E43877A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90651429-48DD-454F-BEAA-14058E43877A}.Release|Any CPU.Build.0 = Release|Any CPU + {33D58C29-15EB-48E2-A6E9-FC9FD84B43E8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {33D58C29-15EB-48E2-A6E9-FC9FD84B43E8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {33D58C29-15EB-48E2-A6E9-FC9FD84B43E8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {33D58C29-15EB-48E2-A6E9-FC9FD84B43E8}.Release|Any CPU.Build.0 = Release|Any CPU + {0051E490-FA97-4B88-BB14-30AAF17DAAA1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0051E490-FA97-4B88-BB14-30AAF17DAAA1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0051E490-FA97-4B88-BB14-30AAF17DAAA1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0051E490-FA97-4B88-BB14-30AAF17DAAA1}.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 ba25ea5..bd922af 100644 --- a/Examples/README.md +++ b/Examples/README.md @@ -4,4 +4,10 @@ A minimal API showing how to integrate BingX.Net in a web API project ### BingX.Examples.Console -A simple console client demonstrating basic usage \ No newline at end of file +A simple console client demonstrating basic usage + +### BingX.Examples.OrderBook +Example of using the client side order book implementation + +### BingX.Examples.Tracker +Example of using the trade tracker \ No newline at end of file