From 0efdf53e969e0afa70a7631a476049c1f478a3cc Mon Sep 17 00:00:00 2001 From: JKorf Date: Sun, 1 Feb 2026 20:56:25 +0100 Subject: [PATCH 1/5] wip --- .../Rest/ExchangeRestClient.FuturesSymbol.cs | 70 +++++++++++ .../Rest/ExchangeRestClient.SpotSymbol.cs | 63 +++++++++- CryptoClients.Net/CryptoClients.Net.csproj | 56 ++++----- CryptoClients.Net/CryptoClients.Net.xml | 114 ++++++++++++++++++ .../Interfaces/IExchangeRestClient.cs | 78 ++++++++++++ 5 files changed, 353 insertions(+), 28 deletions(-) diff --git a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.FuturesSymbol.cs b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.FuturesSymbol.cs index f9eaa4b..ed26b0d 100644 --- a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.FuturesSymbol.cs +++ b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.FuturesSymbol.cs @@ -18,6 +18,76 @@ public partial class ExchangeRestClient /// public IFuturesSymbolRestClient? GetFuturesSymbolClient(TradingMode api, string exchange) => _sharedClients.OfType().SingleOrDefault(s => s.SupportedTradingModes.Contains(api) && s.Exchange == exchange); + /// + public async Task> GetFuturesSymbolsForBaseAssetAsync(string exchange, string baseAsset) + { + var clients = GetFuturesSymbolClients().Where(x => x.Exchange == exchange).ToArray(); + if (clients.Length == 0) + return new ExchangeResult(exchange, ArgumentError.Invalid(nameof(exchange), "Exchange client not found")); + + var supportsTask = clients.Select(x => x.GetFuturesSymbolsForBaseAssetAsync(baseAsset)).ToArray(); + await Task.WhenAll(supportsTask).ConfigureAwait(false); + var failedResult = supportsTask.FirstOrDefault(x => !x.Result.Success); + if (failedResult != null) + return new ExchangeResult(exchange, failedResult.Result.Error!); + + return new ExchangeResult(exchange, supportsTask.SelectMany(x => x.Result.Data).ToArray()); + } + + /// + public async Task> GetFuturesSymbolsForBaseAssetAsync(string baseAsset) + { + var clients = GetFuturesSymbolClients(); + var supportsTask = clients.Select(x => x.GetFuturesSymbolsForBaseAssetAsync(baseAsset)).ToArray(); + await Task.WhenAll(supportsTask).ConfigureAwait(false); + + return supportsTask + .Where(x => x.Result.Success) + .GroupBy(x => x.Result.Exchange) + .ToDictionary(x => x.Key, x => x.SelectMany(x => x.Result.Data) + .ToArray()); + } + + /// + public async Task GetExchangesSupportingFuturesSymbolAsync(SharedSymbol symbol) + { + var clients = GetFuturesSymbolClients(); + var supportsTask = clients.Select(x => x.SupportsFuturesSymbolAsync(symbol)).ToArray(); + await Task.WhenAll(supportsTask).ConfigureAwait(false); + + return supportsTask.Where(x => x.Result.Data).Select(x => x.Result.Exchange).Distinct().ToArray(); + } + + /// + public async Task GetExchangesSupportingFuturesSymbolAsync(string symbolName) + { + var clients = GetFuturesSymbolClients(); + var supportsTask = clients.Select(x => x.SupportsFuturesSymbolAsync(symbolName)).ToArray(); + await Task.WhenAll(supportsTask).ConfigureAwait(false); + + return supportsTask.Where(x => x.Result.Data).Select(x => x.Result.Exchange).Distinct().ToArray(); + } + + /// + public async Task> SupportsFuturesSymbolAsync(string exchange, SharedSymbol symbol) + { + var client = GetFuturesSymbolClients().SingleOrDefault(x => x.Exchange == exchange); + if (client == null) + return new ExchangeResult(exchange, ArgumentError.Invalid(nameof(exchange), "Exchange client not found")); + + return await client.SupportsFuturesSymbolAsync(symbol).ConfigureAwait(false); + } + + /// + public async Task> SupportsFuturesSymbolAsync(string exchange, string symbolName) + { + var client = GetFuturesSymbolClients().SingleOrDefault(x => x.Exchange == exchange); + if (client == null) + return new ExchangeResult(exchange, ArgumentError.Invalid(nameof(exchange), "Exchange client not found")); + + return await client.SupportsFuturesSymbolAsync(symbolName).ConfigureAwait(false); + + } #region Get Futures Symbols /// diff --git a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.SpotSymbol.cs b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.SpotSymbol.cs index ccbd11c..c5d6430 100644 --- a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.SpotSymbol.cs +++ b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.SpotSymbol.cs @@ -1,4 +1,5 @@ -using CryptoExchange.Net.Objects; +using CryptoExchange.Net; +using CryptoExchange.Net.Objects; using CryptoExchange.Net.SharedApis; using System; using System.Collections.Generic; @@ -16,6 +17,66 @@ public partial class ExchangeRestClient /// public ISpotSymbolRestClient? GetSpotSymbolClient(string exchange) => GetSpotSymbolClients().SingleOrDefault(s => s.Exchange == exchange); + /// + public async Task> GetSpotSymbolsForBaseAssetAsync(string exchange, string baseAsset) + { + var client = GetSpotSymbolClients().SingleOrDefault(x => x.Exchange == exchange); + if (client == null) + return new ExchangeResult(exchange, ArgumentError.Invalid(nameof(exchange), "Exchange client not found")); + + return await client.GetSpotSymbolsForBaseAssetAsync(baseAsset).ConfigureAwait(false); + } + + /// + public async Task> GetSpotSymbolsForBaseAssetAsync(string baseAsset) + { + var clients = GetSpotSymbolClients(); + var supportsTask = clients.Select(x => x.GetSpotSymbolsForBaseAssetAsync(baseAsset)).ToArray(); + await Task.WhenAll(supportsTask).ConfigureAwait(false); + + return supportsTask.Where(x => x.Result.Success).ToDictionary(x => x.Result.Exchange, x => x.Result.Data); + } + + /// + public async Task GetExchangesSupportingSpotSymbolAsync(SharedSymbol symbol) + { + var clients = GetSpotSymbolClients(); + var supportsTask = clients.Select(x => x.SupportsSpotSymbolAsync(symbol)).ToArray(); + await Task.WhenAll(supportsTask).ConfigureAwait(false); + + return supportsTask.Where(x => x.Result.Data).Select(x => x.Result.Exchange).ToArray(); + } + + /// + public async Task GetExchangesSupportingSpotSymbolAsync(string symbolName) + { + var clients = GetSpotSymbolClients(); + var supportsTask = clients.Select(x => x.SupportsSpotSymbolAsync(symbolName)).ToArray(); + await Task.WhenAll(supportsTask).ConfigureAwait(false); + + return supportsTask.Where(x => x.Result.Data).Select(x => x.Result.Exchange).ToArray(); + } + + /// + public async Task> SupportsSpotSymbolAsync(string exchange, SharedSymbol symbol) + { + var client = GetSpotSymbolClients().SingleOrDefault(x => x.Exchange == exchange); + if (client == null) + return new ExchangeResult(exchange, ArgumentError.Invalid(nameof(exchange), "Exchange client not found")); + + return await client.SupportsSpotSymbolAsync(symbol).ConfigureAwait(false); + } + + /// + public async Task> SupportsSpotSymbolAsync(string exchange, string symbolName) + { + var client = GetSpotSymbolClients().SingleOrDefault(x => x.Exchange == exchange); + if (client == null) + return new ExchangeResult(exchange, ArgumentError.Invalid(nameof(exchange), "Exchange client not found")); + + return await client.SupportsSpotSymbolAsync(symbolName).ConfigureAwait(false); + } + #region Get Spot Symbols /// public async Task> GetSpotSymbolsAsync(string exchange, GetSymbolsRequest request, CancellationToken ct = default) diff --git a/CryptoClients.Net/CryptoClients.Net.csproj b/CryptoClients.Net/CryptoClients.Net.csproj index 9a4070d..49d1d64 100644 --- a/CryptoClients.Net/CryptoClients.Net.csproj +++ b/CryptoClients.Net/CryptoClients.Net.csproj @@ -47,37 +47,39 @@ - - - - - - - - all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CryptoClients.Net.xml diff --git a/CryptoClients.Net/CryptoClients.Net.xml b/CryptoClients.Net/CryptoClients.Net.xml index 77ac99c..752f436 100644 --- a/CryptoClients.Net/CryptoClients.Net.xml +++ b/CryptoClients.Net/CryptoClients.Net.xml @@ -375,6 +375,24 @@ + + + + + + + + + + + + + + + + + + @@ -696,6 +714,24 @@ + + + + + + + + + + + + + + + + + + @@ -2463,6 +2499,45 @@ Exchange name + + + Get spot symbols for a specific base asset on all exchanges + + Base asset + + + + Get spot symbols for a specific base asset on a specific exchange + + Exchange + Base asset + + + + Get exchanges supporting a specific symbol + + Name of the symbol + + + + Get exchanges supporting a specific symbol + + The symbol + + + + Check whether an exchange supports a specific spot symbol + + Exchange name + Symbol + + + + Check whether an exchange supports a specific futures symbol + + Exchange name + Symbol name + Get the clients for all exchanges @@ -2546,6 +2621,45 @@ Trading mode Exchange name + + + Get futures symbols for a specific base asset on all exchanges + + Base asset + + + + Get futures symbols for a specific base asset on a specific exchange + + Exchange name + Base asset + + + + Get exchanges supporting a specific symbol + + Name of the symbol + + + + Get exchanges supporting a specific symbol + + Symbol + + + + Check whether an exchange supports a specific futures symbol + + Exchange name + Symbol + + + + Check whether an exchange supports a specific futures symbol + + Exchange name + Symbol name + Get the clients for all exchanges diff --git a/CryptoClients.Net/Interfaces/IExchangeRestClient.cs b/CryptoClients.Net/Interfaces/IExchangeRestClient.cs index aebed86..055966f 100644 --- a/CryptoClients.Net/Interfaces/IExchangeRestClient.cs +++ b/CryptoClients.Net/Interfaces/IExchangeRestClient.cs @@ -355,6 +355,45 @@ public interface IExchangeRestClient /// Exchange name ISpotSymbolRestClient? GetSpotSymbolClient(string exchange); + /// + /// Get spot symbols for a specific base asset on all exchanges + /// + /// Base asset + Task> GetSpotSymbolsForBaseAssetAsync(string baseAsset); + + /// + /// Get spot symbols for a specific base asset on a specific exchange + /// + /// Exchange + /// Base asset + Task> GetSpotSymbolsForBaseAssetAsync(string exchange, string baseAsset); + + /// + /// Get exchanges supporting a specific symbol + /// + /// Name of the symbol + Task GetExchangesSupportingSpotSymbolAsync(string symbolName); + + /// + /// Get exchanges supporting a specific symbol + /// + /// The symbol + Task GetExchangesSupportingSpotSymbolAsync(SharedSymbol symbol); + + /// + /// Check whether an exchange supports a specific spot symbol + /// + /// Exchange name + /// Symbol + Task> SupportsSpotSymbolAsync(string exchange, SharedSymbol symbol); + + /// + /// Check whether an exchange supports a specific futures symbol + /// + /// Exchange name + /// Symbol name + Task> SupportsSpotSymbolAsync(string exchange, string symbolName); + /// /// Get the clients for all exchanges /// @@ -429,6 +468,45 @@ public interface IExchangeRestClient /// Exchange name IFuturesTriggerOrderRestClient? GetFuturesTriggerOrderClient(TradingMode tradingMode, string exchange); + /// + /// Get futures symbols for a specific base asset on all exchanges + /// + /// Base asset + Task> GetFuturesSymbolsForBaseAssetAsync(string baseAsset); + + /// + /// Get futures symbols for a specific base asset on a specific exchange + /// + /// Exchange name + /// Base asset + Task> GetFuturesSymbolsForBaseAssetAsync(string exchange, string baseAsset); + + /// + /// Get exchanges supporting a specific symbol + /// + /// Name of the symbol + Task GetExchangesSupportingFuturesSymbolAsync(string symbolName); + + /// + /// Get exchanges supporting a specific symbol + /// + /// Symbol + Task GetExchangesSupportingFuturesSymbolAsync(SharedSymbol symbol); + + /// + /// Check whether an exchange supports a specific futures symbol + /// + /// Exchange name + /// Symbol + Task> SupportsFuturesSymbolAsync(string exchange, SharedSymbol symbol); + + /// + /// Check whether an exchange supports a specific futures symbol + /// + /// Exchange name + /// Symbol name + Task> SupportsFuturesSymbolAsync(string exchange, string symbolName); + /// /// Get the clients for all exchanges /// From 5c20bdb188429cbf4a923048d5bbc6a8e08b4b2e Mon Sep 17 00:00:00 2001 From: Jkorf Date: Thu, 5 Feb 2026 16:45:58 +0100 Subject: [PATCH 2/5] wip --- CryptoClients.Net/CryptoClients.Net.csproj | 56 ++--- CryptoClients.Net/CryptoClients.Net.xml | 77 ++++++- CryptoClients.Net/ExchangeTrackerFactory.cs | 216 +++++++++++++++++- .../ServiceCollectionExtensions.cs | 1 - .../Interfaces/IExchangeTrackerFactory.cs | 77 +++++++ .../Models/GlobalExchangeOptions.cs | 5 - 6 files changed, 393 insertions(+), 39 deletions(-) diff --git a/CryptoClients.Net/CryptoClients.Net.csproj b/CryptoClients.Net/CryptoClients.Net.csproj index 9a4070d..ab55379 100644 --- a/CryptoClients.Net/CryptoClients.Net.csproj +++ b/CryptoClients.Net/CryptoClients.Net.csproj @@ -47,37 +47,39 @@ - - - - - - - - all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CryptoClients.Net.xml diff --git a/CryptoClients.Net/CryptoClients.Net.xml b/CryptoClients.Net/CryptoClients.Net.xml index 77ac99c..e90ccc0 100644 --- a/CryptoClients.Net/CryptoClients.Net.xml +++ b/CryptoClients.Net/CryptoClients.Net.xml @@ -1728,6 +1728,24 @@ + + + + + + + + + + + + + + + + + + Extension methods @@ -4471,6 +4489,60 @@ The max age of the trades to be tracked, any trade older than this period will be removed Exchange specific parameters + + + Create a new Spot user data tracker for an exchange + + The name of the exchange + Tracker config + + + + Create a new Spot user data tracker for all or specified exchanges + + Tracker config + Filter exchanges to create tracker for + + + + Create a new Spot user data tracker for an exchange + + The name of the exchange + User identifier + Configuration + Credentials + Environment + + + + Create a new Futures user data tracker for an exchange + + The name of the exchange + Configuration + Futures trade mode + Exchange parameters + + + + Create a new Futures user data tracker for all or specified exchanges + + Configuration + Futures trade mode + Exchange parameters + Filter exchanges to create tracker for + + + + Create a new Futures user data tracker for an exchange + + The name of the exchange + Futures trade mode + User identifier + Configuration + Credentials + Environment + Exchange parameters + Provider for clients with credentials for specific users @@ -4706,11 +4778,6 @@ Time to wait between socket reconnect attempts - - - Whether or not to use the updated deserialization logic, default is true - - Information on supported platforms and universal functionality diff --git a/CryptoClients.Net/ExchangeTrackerFactory.cs b/CryptoClients.Net/ExchangeTrackerFactory.cs index ab72ad8..bf82cc9 100644 --- a/CryptoClients.Net/ExchangeTrackerFactory.cs +++ b/CryptoClients.Net/ExchangeTrackerFactory.cs @@ -1,33 +1,64 @@ -using Aster.Net.Interfaces; +using Aster.Net; +using Aster.Net.Interfaces; +using Binance.Net; using Binance.Net.Interfaces; +using BingX.Net; using BingX.Net.Interfaces; +using Bitfinex.Net; using Bitfinex.Net.Interfaces; +using Bitget.Net; +using Bitget.Net.Enums; using Bitget.Net.Interfaces; +using BitMart.Net; using BitMart.Net.Interfaces; +using BitMEX.Net; using BitMEX.Net.Interfaces; +using BloFin.Net; using BloFin.Net.Interfaces; +using Bybit.Net; using Bybit.Net.Interfaces; +using Coinbase.Net; using Coinbase.Net.Interfaces; +using CoinEx.Net; using CoinEx.Net.Interfaces; +using CoinW.Net; using CoinW.Net.Interfaces; +using CryptoClients.Net.Enums; using CryptoClients.Net.Interfaces; +using CryptoCom.Net; using CryptoCom.Net.Interfaces; +using CryptoExchange.Net; +using CryptoExchange.Net.Authentication; using CryptoExchange.Net.Interfaces; using CryptoExchange.Net.SharedApis; using CryptoExchange.Net.Trackers.Klines; using CryptoExchange.Net.Trackers.Trades; +using CryptoExchange.Net.Trackers.UserData.Interfaces; +using CryptoExchange.Net.Trackers.UserData.Objects; +using DeepCoin.Net; using DeepCoin.Net.Interfaces; +using GateIo.Net; using GateIo.Net.Interfaces; +using HTX.Net; using HTX.Net.Interfaces; +using HyperLiquid.Net; using HyperLiquid.Net.Interfaces; +using Kraken.Net; using Kraken.Net.Interfaces; +using Kucoin.Net; using Kucoin.Net.Interfaces; +using Mexc.Net; using Mexc.Net.Interfaces; +using OKX.Net; using OKX.Net.Interfaces; using System; +using System.Collections.Generic; +using Toobit.Net; using Toobit.Net.Interfaces; using Upbit.Net.Interfaces; +using WhiteBit.Net; using WhiteBit.Net.Interfaces; +using XT.Net; using XT.Net.Interfaces; namespace CryptoClients.Net @@ -201,5 +232,188 @@ public ExchangeTrackerFactory( return factory.CreateTradeTracker(symbol); } + + /// + public IUserSpotDataTracker? CreateUserSpotDataTracker(string exchange, SpotUserDataTrackerConfig? config = null) + { + return exchange switch + { + "Aster" => Aster.CreateUserSpotDataTracker(config), + "Binance" => Binance.CreateUserSpotDataTracker(config), + "BingX" => BingX.CreateUserSpotDataTracker(config), + "Bitfinex" => Bitfinex.CreateUserSpotDataTracker(config), + "Bitget" => Bitget.CreateUserSpotDataTracker(config), + "BitMart" => BitMart.CreateUserSpotDataTracker(config), + "BitMEX" => BitMEX.CreateUserSpotDataTracker(config), + "Bybit" => Bybit.CreateUserSpotDataTracker(config), + "Coinbase" => Coinbase.CreateUserSpotDataTracker(config), + "CoinEx" => CoinEx.CreateUserSpotDataTracker(config), + "CoinW" => CoinW.CreateUserSpotDataTracker(config), + "CryptoCom" => CryptoCom.CreateUserSpotDataTracker(config), + "DeepCoin" => DeepCoin.CreateUserSpotDataTracker(config), + "GateIo" => GateIo.CreateUserSpotDataTracker(config), + "HTX" => HTX.CreateUserSpotDataTracker(config), + "HyperLiquid" => HyperLiquid.CreateUserSpotDataTracker(config), + "Kraken" => Kraken.CreateUserSpotDataTracker(config), + "Kucoin" => Kucoin.CreateUserSpotDataTracker(config), + "Mexc" => Mexc.CreateUserSpotDataTracker(config), + "OKX" => OKX.CreateUserSpotDataTracker(config), + "Toobit" => Toobit.CreateUserSpotDataTracker(config), + "WhiteBit" => WhiteBit.CreateUserSpotDataTracker(config), + "XT" => XT.CreateUserSpotDataTracker(config), + _ => null + }; + } + + /// + public IUserSpotDataTracker[] CreateUserSpotDataTrackers(SpotUserDataTrackerConfig? config = null, string[]? exchanges = null) + { + var result = new List(); + foreach (var exchange in exchanges ?? Exchange.All) + { + var tracker = CreateUserSpotDataTracker(exchange, config); + if (tracker == null) + continue; + + result.Add(tracker); + } + + return result.ToArray(); + } + + /// + public IUserSpotDataTracker? CreateUserSpotDataTracker(string exchange, string userIdentifier, ApiCredentials credentials, SpotUserDataTrackerConfig? config = null, string? environment = null) + { + return exchange switch + { + "Aster" => Aster.CreateUserSpotDataTracker(userIdentifier, credentials, config, AsterEnvironment.GetEnvironmentByName(environment)), + "Binance" => Binance.CreateUserSpotDataTracker(userIdentifier, credentials, config, BinanceEnvironment.GetEnvironmentByName(environment)), + "BingX" => BingX.CreateUserSpotDataTracker(userIdentifier, credentials, config, BingXEnvironment.GetEnvironmentByName(environment)), + "Bitfinex" => Bitfinex.CreateUserSpotDataTracker(userIdentifier, credentials, config, BitfinexEnvironment.GetEnvironmentByName(environment)), + "Bitget" => Bitget.CreateUserSpotDataTracker(userIdentifier, credentials, config, BitgetEnvironment.GetEnvironmentByName(environment)), + "BitMart" => BitMart.CreateUserSpotDataTracker(userIdentifier, credentials, config, BitMartEnvironment.GetEnvironmentByName(environment)), + "BitMEX" => BitMEX.CreateUserSpotDataTracker(userIdentifier, credentials, config, BitMEXEnvironment.GetEnvironmentByName(environment)), + "Bybit" => Bybit.CreateUserSpotDataTracker(userIdentifier, credentials, config, BybitEnvironment.GetEnvironmentByName(environment)), + "Coinbase" => Coinbase.CreateUserSpotDataTracker(userIdentifier, credentials, config, CoinbaseEnvironment.GetEnvironmentByName(environment)), + "CoinEx" => CoinEx.CreateUserSpotDataTracker(userIdentifier, credentials, config, CoinExEnvironment.GetEnvironmentByName(environment)), + "CoinW" => CoinW.CreateUserSpotDataTracker(userIdentifier, credentials, config, CoinWEnvironment.GetEnvironmentByName(environment)), + "CryptoCom" => CryptoCom.CreateUserSpotDataTracker(userIdentifier, credentials, config, CryptoComEnvironment.GetEnvironmentByName(environment)), + "DeepCoin" => DeepCoin.CreateUserSpotDataTracker(userIdentifier, credentials, config, DeepCoinEnvironment.GetEnvironmentByName(environment)), + "GateIo" => GateIo.CreateUserSpotDataTracker(userIdentifier, credentials, config, GateIoEnvironment.GetEnvironmentByName(environment)), + "HTX" => HTX.CreateUserSpotDataTracker(userIdentifier, credentials, config, HTXEnvironment.GetEnvironmentByName(environment)), + "HyperLiquid" => HyperLiquid.CreateUserSpotDataTracker(userIdentifier, credentials, config, HyperLiquidEnvironment.GetEnvironmentByName(environment)), + "Kraken" => Kraken.CreateUserSpotDataTracker(userIdentifier, credentials, config, KrakenEnvironment.GetEnvironmentByName(environment)), + "Kucoin" => Kucoin.CreateUserSpotDataTracker(userIdentifier, credentials, config, KucoinEnvironment.GetEnvironmentByName(environment)), + "Mexc" => Mexc.CreateUserSpotDataTracker(userIdentifier, credentials, config, MexcEnvironment.GetEnvironmentByName(environment)), + "OKX" => OKX.CreateUserSpotDataTracker(userIdentifier, credentials, config, OKXEnvironment.GetEnvironmentByName(environment)), + "Toobit" => Toobit.CreateUserSpotDataTracker(userIdentifier, credentials, config, ToobitEnvironment.GetEnvironmentByName(environment)), + "WhiteBit" => WhiteBit.CreateUserSpotDataTracker(userIdentifier, credentials, config, WhiteBitEnvironment.GetEnvironmentByName(environment)), + "XT" => XT.CreateUserSpotDataTracker(userIdentifier, credentials, config, XTEnvironment.GetEnvironmentByName(environment)), + _ => null + }; + } + + /// + public IUserFuturesDataTracker? CreateUserFuturesDataTracker(string exchange, TradingMode tradeMode, FuturesUserDataTrackerConfig? config = null, ExchangeParameters? exchangeParameters = null) + { + return exchange switch + { + "Aster" => Aster.CreateUserFuturesDataTracker(config), + "Binance" => tradeMode.IsLinear() ? Binance.CreateUserUsdFuturesDataTracker(config) : Binance.CreateUserCoinFuturesDataTracker(config), + "BingX" => BingX.BingXUserPerpetualFuturesDataTracker(config), + "Bitget" => Bitget.CreateUserFuturesDataTracker( + ExchangeParameters.GetValue(exchangeParameters, "Bitget", "ProductType") == "UsdtFutures" ? BitgetProductTypeV2.UsdtFutures : BitgetProductTypeV2.UsdcFutures, + config), + "BitMart" => BitMart.CreateUserUsdFuturesDataTracker(config), + "BitMEX" => BitMEX.CreateUserFuturesDataTracker(config), + "BloFin" => BloFin.CreateUserFuturesDataTracker(config), + "Bybit" => Bybit.CreateUserFuturesDataTracker(config), + "Coinbase" => Coinbase.CreateUserFuturesDataTracker(config), + "CoinEx" => CoinEx.CreateUserFuturesDataTracker(config), + "CoinW" => CoinW.CreateUserFuturesDataTracker(config), + "CryptoCom" => CryptoCom.CreateUserFuturesDataTracker(config), + "DeepCoin" => DeepCoin.CreateUserFuturesDataTracker(config), + "GateIo" => GateIo.CreateUserPerpetualFuturesDataTracker( + ExchangeParameters.GetValue(exchangeParameters, "GateIo", "SettleAsset") ?? throw new ArgumentException("SettleAsset exchange parameter should be provided for GateIo", "SettleAsset"), + ExchangeParameters.GetValue(exchangeParameters, "GateIo", "UserId") ?? throw new ArgumentException("UserId exchange parameter should be provided for GateIo", "UserId"), + config), + "HTX" => HTX.CreateUserFuturesDataTracker( + ExchangeParameters.GetValue(exchangeParameters, "HTX", "MarginMode") == SharedMarginMode.Isolated ? SharedMarginMode.Isolated : SharedMarginMode.Cross, + config), + "HyperLiquid" => HyperLiquid.CreateUserFuturesDataTracker(config), + "Kraken" => Kraken.CreateUserFuturesDataTracker(config), + "Kucoin" => Kucoin.CreateUserFuturesDataTracker(config), + "OKX" => OKX.CreateUserFuturesDataTracker(config), + "Toobit" => Toobit.CreateUserUsdtFuturesDataTracker(config), + "WhiteBit" => WhiteBit.CreateUserFuturesDataTracker(config), + "XT" => tradeMode.IsLinear() ? XT.CreateUserUsdtFuturesDataTracker(config) : null, + _ => null + }; + } + + /// + public IUserFuturesDataTracker[] CreateUserFuturesDataTrackers(TradingMode tradeMode, FuturesUserDataTrackerConfig? config = null, ExchangeParameters? exchangeParameters = null, string[]? exchanges = null) + { + var result = new List(); + foreach (var exchange in exchanges ?? Exchange.All) + { + var tracker = CreateUserFuturesDataTracker(exchange, tradeMode, config, exchangeParameters); + if (tracker == null) + continue; + + result.Add(tracker); + } + + return result.ToArray(); + } + + /// + public IUserFuturesDataTracker? CreateUserFuturesDataTracker(string exchange, TradingMode tradeMode, string userIdentifier, ApiCredentials credentials, FuturesUserDataTrackerConfig? config = null, string? environment = null, ExchangeParameters? exchangeParameters = null) + { + return exchange switch + { + "Aster" => Aster.CreateUserFuturesDataTracker(userIdentifier, credentials, config, AsterEnvironment.GetEnvironmentByName(environment)), + "Binance" => tradeMode.IsLinear() + ? Binance.CreateUserUsdFuturesDataTracker(userIdentifier, credentials, config, BinanceEnvironment.GetEnvironmentByName(environment)) + : Binance.CreateUserCoinFuturesDataTracker(userIdentifier, credentials, config, BinanceEnvironment.GetEnvironmentByName(environment)), + "BingX" => BingX.BingXUserPerpetualFuturesDataTracker(userIdentifier, credentials, config, BingXEnvironment.GetEnvironmentByName(environment)), + "Bitget" => Bitget.CreateUserFuturesDataTracker( + userIdentifier, + credentials, + ExchangeParameters.GetValue(exchangeParameters, "Bitget", "ProductType") == "UsdtFutures" ? BitgetProductTypeV2.UsdtFutures : BitgetProductTypeV2.UsdcFutures, + config, + BitgetEnvironment.GetEnvironmentByName(environment)), + "BitMart" => BitMart.CreateUserUsdFuturesDataTracker(userIdentifier, credentials, config, BitMartEnvironment.GetEnvironmentByName(environment)), + "BitMEX" => BitMEX.CreateUserFuturesDataTracker(userIdentifier, credentials, config, BitMEXEnvironment.GetEnvironmentByName(environment)), + "BloFin" => BloFin.CreateUserFuturesDataTracker(userIdentifier, credentials, config, BloFinEnvironment.GetEnvironmentByName(environment)), + "Bybit" => Bybit.CreateUserFuturesDataTracker(userIdentifier, credentials, config, BybitEnvironment.GetEnvironmentByName(environment)), + "Coinbase" => Coinbase.CreateUserFuturesDataTracker(userIdentifier, credentials, config, CoinbaseEnvironment.GetEnvironmentByName(environment)), + "CoinEx" => CoinEx.CreateUserFuturesDataTracker(userIdentifier, credentials, config, CoinExEnvironment.GetEnvironmentByName(environment)), + "CoinW" => CoinW.CreateUserFuturesDataTracker(userIdentifier, credentials, config, CoinWEnvironment.GetEnvironmentByName(environment)), + "CryptoCom" => CryptoCom.CreateUserFuturesDataTracker(userIdentifier, credentials, config, CryptoComEnvironment.GetEnvironmentByName(environment)), + "DeepCoin" => DeepCoin.CreateUserFuturesDataTracker(userIdentifier, credentials, config, DeepCoinEnvironment.GetEnvironmentByName(environment)), + "GateIo" => GateIo.CreateUserPerpetualFuturesDataTracker( + userIdentifier, + credentials, + ExchangeParameters.GetValue(exchangeParameters, "GateIo", "SettleAsset") ?? throw new ArgumentException("SettleAsset exchange parameter should be provided for GateIo", "SettleAsset"), + ExchangeParameters.GetValue(exchangeParameters, "GateIo", "UserId") ?? throw new ArgumentException("UserId exchange parameter should be provided for GateIo", "UserId"), + config, + GateIoEnvironment.GetEnvironmentByName(environment)), + "HTX" => HTX.CreateUserFuturesDataTracker( + userIdentifier, + credentials, + ExchangeParameters.GetValue(exchangeParameters, "HTX", "MarginMode") == SharedMarginMode.Isolated ? SharedMarginMode.Isolated : SharedMarginMode.Cross, + config, + HTXEnvironment.GetEnvironmentByName(environment)), + "HyperLiquid" => HyperLiquid.CreateUserFuturesDataTracker(userIdentifier, credentials, config, HyperLiquidEnvironment.GetEnvironmentByName(environment)), + "Kraken" => Kraken.CreateUserFuturesDataTracker(userIdentifier, credentials, config, KrakenEnvironment.GetEnvironmentByName(environment)), + "Kucoin" => Kucoin.CreateUserFuturesDataTracker(userIdentifier, credentials, config, KucoinEnvironment.GetEnvironmentByName(environment)), + "OKX" => OKX.CreateUserFuturesDataTracker(userIdentifier, credentials, config, OKXEnvironment.GetEnvironmentByName(environment)), + "Toobit" => Toobit.CreateUserUsdtFuturesDataTracker(userIdentifier, credentials, config, ToobitEnvironment.GetEnvironmentByName(environment)), + "WhiteBit" => WhiteBit.CreateUserFuturesDataTracker(userIdentifier, credentials, config, WhiteBitEnvironment.GetEnvironmentByName(environment)), + "XT" => tradeMode.IsLinear() ? XT.CreateUserUsdtFuturesDataTracker(userIdentifier, credentials, config, XTEnvironment.GetEnvironmentByName(environment)) : null, + _ => null + }; + } } } diff --git a/CryptoClients.Net/ExtensionMethods/ServiceCollectionExtensions.cs b/CryptoClients.Net/ExtensionMethods/ServiceCollectionExtensions.cs index 65d0224..605c3c5 100644 --- a/CryptoClients.Net/ExtensionMethods/ServiceCollectionExtensions.cs +++ b/CryptoClients.Net/ExtensionMethods/ServiceCollectionExtensions.cs @@ -399,7 +399,6 @@ void UpdateExchangeOptions(string exchange, GlobalExchangeOptions globalOptions) UpdateIfNotSpecified($"{exchange}:Socket:ReconnectPolicy", globalOptions.ReconnectPolicy?.ToString()); UpdateIfNotSpecified($"{exchange}:Socket:ReconnectInterval", globalOptions.ReconnectInterval?.ToString()); UpdateIfNotSpecified($"{exchange}:SocketClientLifeTime", socketClientLifetime?.ToString()); - UpdateIfNotSpecified($"{exchange}:UseUpdatedDeserialization", globalOptions.UseUpdatedDeserialization?.ToString()); } UpdateExchangeOptions("Aster", globalOptions); diff --git a/CryptoClients.Net/Interfaces/IExchangeTrackerFactory.cs b/CryptoClients.Net/Interfaces/IExchangeTrackerFactory.cs index ae0c358..f76a6ba 100644 --- a/CryptoClients.Net/Interfaces/IExchangeTrackerFactory.cs +++ b/CryptoClients.Net/Interfaces/IExchangeTrackerFactory.cs @@ -27,6 +27,9 @@ using CoinW.Net.Interfaces; using BloFin.Net.Interfaces; using Upbit.Net.Interfaces; +using CryptoExchange.Net.Authentication; +using CryptoExchange.Net.Trackers.UserData.Objects; +using CryptoExchange.Net.Trackers.UserData.Interfaces; namespace CryptoClients.Net.Interfaces { @@ -156,5 +159,79 @@ public interface IExchangeTrackerFactory /// The max age of the trades to be tracked, any trade older than this period will be removed /// Exchange specific parameters ITradeTracker? CreateTradeTracker(string exchange, SharedSymbol symbol, int? limit = null, TimeSpan? period = null, ExchangeParameters? exchangeParameters = null); + + /// + /// Create a new Spot user data tracker for an exchange + /// + /// The name of the exchange + /// Tracker config + IUserSpotDataTracker? CreateUserSpotDataTracker(string exchange, SpotUserDataTrackerConfig? config = null); + + /// + /// Create a new Spot user data tracker for all or specified exchanges + /// + /// Tracker config + /// Filter exchanges to create tracker for + IUserSpotDataTracker[] CreateUserSpotDataTrackers(SpotUserDataTrackerConfig? config = null, string[]? exchanges = null); + + /// + /// Create a new Spot user data tracker for an exchange + /// + /// The name of the exchange + /// User identifier + /// Configuration + /// Credentials + /// Environment + IUserSpotDataTracker? CreateUserSpotDataTracker( + string exchange, + string userIdentifier, + ApiCredentials credentials, + SpotUserDataTrackerConfig? config = null, + string? environment = null); + + /// + /// Create a new Futures user data tracker for an exchange + /// + /// The name of the exchange + /// Configuration + /// Futures trade mode + /// Exchange parameters + IUserFuturesDataTracker? CreateUserFuturesDataTracker( + string exchange, + TradingMode tradeMode, + FuturesUserDataTrackerConfig? config = null, + ExchangeParameters? exchangeParameters = null); + + /// + /// Create a new Futures user data tracker for all or specified exchanges + /// + /// Configuration + /// Futures trade mode + /// Exchange parameters + /// Filter exchanges to create tracker for + IUserFuturesDataTracker[] CreateUserFuturesDataTrackers( + TradingMode tradeMode, + FuturesUserDataTrackerConfig? config = null, + ExchangeParameters? exchangeParameters = null, + string[]? exchanges = null); + + /// + /// Create a new Futures user data tracker for an exchange + /// + /// The name of the exchange + /// Futures trade mode + /// User identifier + /// Configuration + /// Credentials + /// Environment + /// Exchange parameters + IUserFuturesDataTracker? CreateUserFuturesDataTracker( + string exchange, + TradingMode tradeMode, + string userIdentifier, + ApiCredentials credentials, + FuturesUserDataTrackerConfig? config = null, + string? environment = null, + ExchangeParameters? exchangeParameters = null); } } \ No newline at end of file diff --git a/CryptoClients.Net/Models/GlobalExchangeOptions.cs b/CryptoClients.Net/Models/GlobalExchangeOptions.cs index 2c8ac9f..89a02ec 100644 --- a/CryptoClients.Net/Models/GlobalExchangeOptions.cs +++ b/CryptoClients.Net/Models/GlobalExchangeOptions.cs @@ -61,10 +61,5 @@ public record GlobalExchangeOptions /// Time to wait between socket reconnect attempts /// public TimeSpan? ReconnectInterval { get; set; } - - /// - /// Whether or not to use the updated deserialization logic, default is true - /// - public bool? UseUpdatedDeserialization { get; set; } } } From b87b666c25746b02570697e9d1bc4dd51479aa5e Mon Sep 17 00:00:00 2001 From: JKorf Date: Sun, 8 Feb 2026 21:27:10 +0100 Subject: [PATCH 3/5] wip --- CryptoClients.Net/CryptoClients.Net.csproj | 70 +++++++++---------- CryptoClients.Net/CryptoClients.Net.xml | 35 +++++++++- CryptoClients.Net/ExchangeTrackerFactory.cs | 56 +++++++++++++++ .../Interfaces/IExchangeTrackerFactory.cs | 50 ++++++++++--- .../Models/ExchangeCredentials.cs | 39 +++++++++++ 5 files changed, 205 insertions(+), 45 deletions(-) diff --git a/CryptoClients.Net/CryptoClients.Net.csproj b/CryptoClients.Net/CryptoClients.Net.csproj index ab55379..fd72730 100644 --- a/CryptoClients.Net/CryptoClients.Net.csproj +++ b/CryptoClients.Net/CryptoClients.Net.csproj @@ -9,9 +9,9 @@ CryptoClients.Net JKorf - 4.2.0 - 4.2.0 - 4.2.0 + 4.2.2 + 4.2.2 + 4.2.2 CryptoClients.Net offers full easy access to 25 different cryptocurrency exchange API's, such as Binance, Bybit, HyperLiquid and many more. It offers a unified way to access the API's and tools to dynamically call endpoints on different exchanges. false CryptoClients;CryptoClients.Net;OKX;OKX.Net;Mexc;Mexc.Net;Kucoin;Kucoin.Net;Kraken;Kraken.Net;Huobi;Huobi.Net;HTX;HTX.Net;DeepCoin.Net;DeepCoin;CoinEx;CoinEx.Net;Bybit;Bybit.Net;Bitget;Bitget.Net;Bitfinex;Bitfinex.Net;BingX;BingX.Net;Binance;Binance.Net;GateIo.Net;Gate.io;BitMart;BitMart.Net;WhiteBit.Net;WhiteBit;XT;XT.Net;HyperLiquid;HyperLiquid.Net;DeepCoin;DeepCoin.Net;CoinW;CoinW.Net;BloFin;BloFin.Net;Upbit;Upbit.Net;Aster;Aster.Net;CryptoExchange.Net;CryptoCurrency;CryptoCurrency Exchange @@ -47,39 +47,37 @@ - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + + + + + + + + + CryptoClients.Net.xml diff --git a/CryptoClients.Net/CryptoClients.Net.xml b/CryptoClients.Net/CryptoClients.Net.xml index 96aa8fd..88e34ac 100644 --- a/CryptoClients.Net/CryptoClients.Net.xml +++ b/CryptoClients.Net/CryptoClients.Net.xml @@ -1773,6 +1773,9 @@ + + + @@ -1782,6 +1785,9 @@ + + + Extension methods @@ -4612,11 +4618,21 @@ - Create a new Spot user data tracker for all or specified exchanges + Create new Spot user data trackers for all or specified exchanges Tracker config Filter exchanges to create tracker for + + + Create new Spot user data trackers for all or specified exchanges + + User identifier + Credentials + Configuration + Exchange environments + Filter exchanges to create tracker for + Create a new Spot user data tracker for an exchange @@ -4657,6 +4673,17 @@ Environment Exchange parameters + + + Create new Futures user data trackers for all or specified exchanges + + User identifier + Trading mode + Credentials + Configuration + Exchange environments + Filter exchanges to create tracker for + Provider for clients with credentials for specific users @@ -4837,6 +4864,12 @@ XT API credentials + + + Get credentials for an exchange + + Exchange name + Options to apply to any exchange client (if not overridden) diff --git a/CryptoClients.Net/ExchangeTrackerFactory.cs b/CryptoClients.Net/ExchangeTrackerFactory.cs index bf82cc9..ff8b9b4 100644 --- a/CryptoClients.Net/ExchangeTrackerFactory.cs +++ b/CryptoClients.Net/ExchangeTrackerFactory.cs @@ -25,6 +25,7 @@ using CoinW.Net.Interfaces; using CryptoClients.Net.Enums; using CryptoClients.Net.Interfaces; +using CryptoClients.Net.Models; using CryptoCom.Net; using CryptoCom.Net.Interfaces; using CryptoExchange.Net; @@ -313,6 +314,32 @@ public IUserSpotDataTracker[] CreateUserSpotDataTrackers(SpotUserDataTrackerConf }; } + /// + public IUserSpotDataTracker[] CreateUserSpotDataTracker( + string userIdentifier, + ExchangeCredentials credentials, + SpotUserDataTrackerConfig? config = null, + Dictionary? environments = null, + string[]? exchanges = null) + { + var result = new List(); + foreach (var exchange in exchanges ?? Exchange.All) + { + var tracker = CreateUserSpotDataTracker( + exchange, + userIdentifier, + credentials.GetCredentials(exchange) ?? throw new ArgumentNullException("No credentials provided for " + exchange), + config, + environments?.TryGetValue(exchange, out var env) == true ? env : null); + if (tracker == null) + continue; + + result.Add(tracker); + } + + return result.ToArray(); + } + /// public IUserFuturesDataTracker? CreateUserFuturesDataTracker(string exchange, TradingMode tradeMode, FuturesUserDataTrackerConfig? config = null, ExchangeParameters? exchangeParameters = null) { @@ -415,5 +442,34 @@ public IUserFuturesDataTracker[] CreateUserFuturesDataTrackers(TradingMode trade _ => null }; } + + /// + public IUserFuturesDataTracker[] CreateUserFuturesDataTracker( + string userIdentifier, + TradingMode tradingMode, + ExchangeCredentials credentials, + FuturesUserDataTrackerConfig? config = null, + Dictionary? environments = null, + string[]? exchanges = null) + { + var result = new List(); + foreach (var exchange in exchanges ?? Exchange.All) + { + var tracker = CreateUserFuturesDataTracker( + exchange, + tradingMode, + userIdentifier, + credentials.GetCredentials(exchange) ?? throw new ArgumentNullException("No credentials provided for " + exchange), + config, + environments?.TryGetValue(exchange, out var env) == true ? env : null); + if (tracker == null) + continue; + + result.Add(tracker); + } + + return result.ToArray(); + } + } } diff --git a/CryptoClients.Net/Interfaces/IExchangeTrackerFactory.cs b/CryptoClients.Net/Interfaces/IExchangeTrackerFactory.cs index f76a6ba..29efbf2 100644 --- a/CryptoClients.Net/Interfaces/IExchangeTrackerFactory.cs +++ b/CryptoClients.Net/Interfaces/IExchangeTrackerFactory.cs @@ -5,13 +5,19 @@ using Bitget.Net.Interfaces; using BitMart.Net.Interfaces; using BitMEX.Net.Interfaces; +using BloFin.Net.Interfaces; using Bybit.Net.Interfaces; using Coinbase.Net.Interfaces; using CoinEx.Net.Interfaces; +using CoinW.Net.Interfaces; +using CryptoClients.Net.Models; using CryptoCom.Net.Interfaces; +using CryptoExchange.Net.Authentication; using CryptoExchange.Net.SharedApis; using CryptoExchange.Net.Trackers.Klines; using CryptoExchange.Net.Trackers.Trades; +using CryptoExchange.Net.Trackers.UserData.Interfaces; +using CryptoExchange.Net.Trackers.UserData.Objects; using DeepCoin.Net.Interfaces; using GateIo.Net.Interfaces; using HTX.Net.Interfaces; @@ -20,16 +26,12 @@ using Kucoin.Net.Interfaces; using Mexc.Net.Interfaces; using OKX.Net.Interfaces; -using Toobit.Net.Interfaces; using System; +using System.Collections.Generic; +using Toobit.Net.Interfaces; +using Upbit.Net.Interfaces; using WhiteBit.Net.Interfaces; using XT.Net.Interfaces; -using CoinW.Net.Interfaces; -using BloFin.Net.Interfaces; -using Upbit.Net.Interfaces; -using CryptoExchange.Net.Authentication; -using CryptoExchange.Net.Trackers.UserData.Objects; -using CryptoExchange.Net.Trackers.UserData.Interfaces; namespace CryptoClients.Net.Interfaces { @@ -168,12 +170,27 @@ public interface IExchangeTrackerFactory IUserSpotDataTracker? CreateUserSpotDataTracker(string exchange, SpotUserDataTrackerConfig? config = null); /// - /// Create a new Spot user data tracker for all or specified exchanges + /// Create new Spot user data trackers for all or specified exchanges /// /// Tracker config /// Filter exchanges to create tracker for IUserSpotDataTracker[] CreateUserSpotDataTrackers(SpotUserDataTrackerConfig? config = null, string[]? exchanges = null); + /// + /// Create new Spot user data trackers for all or specified exchanges + /// + /// User identifier + /// Credentials + /// Configuration + /// Exchange environments + /// Filter exchanges to create tracker for + IUserSpotDataTracker[] CreateUserSpotDataTracker( + string userIdentifier, + ExchangeCredentials credentials, + SpotUserDataTrackerConfig? config = null, + Dictionary? environments = null, + string[]? exchanges = null); + /// /// Create a new Spot user data tracker for an exchange /// @@ -233,5 +250,22 @@ IUserFuturesDataTracker[] CreateUserFuturesDataTrackers( FuturesUserDataTrackerConfig? config = null, string? environment = null, ExchangeParameters? exchangeParameters = null); + + /// + /// Create new Futures user data trackers for all or specified exchanges + /// + /// User identifier + /// Trading mode + /// Credentials + /// Configuration + /// Exchange environments + /// Filter exchanges to create tracker for + IUserFuturesDataTracker[] CreateUserFuturesDataTracker( + string userIdentifier, + TradingMode tradingMode, + ExchangeCredentials credentials, + FuturesUserDataTrackerConfig? config = null, + Dictionary? environments = null, + string[]? exchanges = null); } } \ No newline at end of file diff --git a/CryptoClients.Net/Models/ExchangeCredentials.cs b/CryptoClients.Net/Models/ExchangeCredentials.cs index 0b7ae71..3a1f7fb 100644 --- a/CryptoClients.Net/Models/ExchangeCredentials.cs +++ b/CryptoClients.Net/Models/ExchangeCredentials.cs @@ -190,5 +190,44 @@ public ExchangeCredentials(Dictionary exchangeCredential /// XT API credentials /// public ApiCredentials? XT { get; set; } + + /// + /// Get credentials for an exchange + /// + /// Exchange name + public ApiCredentials? GetCredentials(string exchange) + { + switch (exchange) + { + case "Aster": return Aster; + case "Binance": return Binance; + case "BingX": return BingX; + case "Bitfinex": return Bitfinex; + case "Bitget": return Bitget; + case "BitMart": return BitMart; + case "BitMEX": return BitMEX; + case "BloFin": return BloFin; + case "Bybit": return Bybit; + case "Coinbase": return Coinbase; + case "CoinEx": return CoinEx; + case "CoinW": return CoinW; + case "CryptoCom": return CryptoCom; + case "DeepCoin": return DeepCoin; + case "GateIo": return GateIo; + case "HTX": return HTX; + case "HyperLiquid": return HyperLiquid; + case "Kraken": return Kraken; + case "Kucoin": return Kucoin; + case "Mexc": return Mexc; + case "OKX": return OKX; + case "Polymarket": return Polymarket; + case "Toobit": return Toobit; + case "Upbit": return Upbit; + case "WhiteBit": return WhiteBit; + case "XT": return XT; + default: + throw new System.ArgumentException("Unknown exchange name: " + exchange); + } + } } } From 69e3f27b7079d627d35df07de68169f867ed1363 Mon Sep 17 00:00:00 2001 From: Jkorf Date: Tue, 10 Feb 2026 14:03:46 +0100 Subject: [PATCH 4/5] Updated client library versions --- CryptoClients.Net/CryptoClients.Net.csproj | 54 +++++++++++----------- 1 file changed, 27 insertions(+), 27 deletions(-) diff --git a/CryptoClients.Net/CryptoClients.Net.csproj b/CryptoClients.Net/CryptoClients.Net.csproj index fd72730..584c638 100644 --- a/CryptoClients.Net/CryptoClients.Net.csproj +++ b/CryptoClients.Net/CryptoClients.Net.csproj @@ -47,37 +47,37 @@ - - - - - - - - + + + + + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + CryptoClients.Net.xml From cfed31ba1c387c16e087fc6cd716d57dfe2e6976 Mon Sep 17 00:00:00 2001 From: Jkorf Date: Tue, 10 Feb 2026 14:05:43 +0100 Subject: [PATCH 5/5] Added checks to rest client exchange requests to prevent exception when more than one trading mode specific requests are available for an exchange --- .../Clients/Rest/ExchangeRestClient.Balance.cs | 3 +++ .../Clients/Rest/ExchangeRestClient.BookTicker.cs | 3 +++ .../Clients/Rest/ExchangeRestClient.FundingRate.cs | 3 +++ .../Clients/Rest/ExchangeRestClient.FuturesOrder.cs | 12 ++++++++++++ .../Clients/Rest/ExchangeRestClient.FuturesSymbol.cs | 4 ++++ .../Clients/Rest/ExchangeRestClient.FuturesTicker.cs | 6 ++++++ .../Rest/ExchangeRestClient.IndexPriceKline.cs | 3 +++ .../Clients/Rest/ExchangeRestClient.Kline.cs | 3 +++ .../Clients/Rest/ExchangeRestClient.ListenKey.cs | 3 +++ .../Rest/ExchangeRestClient.MarkPriceKline.cs | 3 +++ .../Clients/Rest/ExchangeRestClient.OpenInterest.cs | 3 +++ .../Clients/Rest/ExchangeRestClient.OrderBook.cs | 3 +++ .../Rest/ExchangeRestClient.PositionHistory.cs | 3 +++ .../Clients/Rest/ExchangeRestClient.RecentTrade.cs | 3 +++ .../Clients/Rest/ExchangeRestClient.TradeHistory.cs | 3 +++ .../Clients/Rest/ExchangeRestClient.Withdrawal.cs | 3 +++ 16 files changed, 61 insertions(+) diff --git a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.Balance.cs b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.Balance.cs index 83a6ae3..08bb4ee 100644 --- a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.Balance.cs +++ b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.Balance.cs @@ -29,6 +29,9 @@ public partial class ExchangeRestClient public async Task> GetBalancesAsync(string exchange, GetBalancesRequest request, CancellationToken ct = default) { var result = await Task.WhenAll(GetBalancesIntAsync(request, new[] { exchange }, ct)).ConfigureAwait(false); + if (result.Length > 1) + return new ExchangeWebResult(exchange, new InvalidOperationError($"Multiple API's available for {exchange}, specify the `TradingMode` parameter on the request to choose one")); + return result.SingleOrDefault() ?? new ExchangeWebResult(exchange, new InvalidOperationError($"Request not supported for {exchange}")); } diff --git a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.BookTicker.cs b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.BookTicker.cs index 38a287d..4331ae9 100644 --- a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.BookTicker.cs +++ b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.BookTicker.cs @@ -25,6 +25,9 @@ public partial class ExchangeRestClient public async Task> GetBookTickerAsync(string exchange, GetBookTickerRequest request, CancellationToken ct = default) { var result = await Task.WhenAll(GetBookTickersInt(request, new[] { exchange }, ct)).ConfigureAwait(false); + if (result.Length > 1) + return new ExchangeWebResult(exchange, new InvalidOperationError($"Multiple API's available for {exchange}, specify the `TradingMode` parameter on the request to choose one")); + return result.SingleOrDefault() ?? new ExchangeWebResult(exchange, new InvalidOperationError($"Request not supported for {exchange}")); } diff --git a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.FundingRate.cs b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.FundingRate.cs index d02f50b..1f0c661 100644 --- a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.FundingRate.cs +++ b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.FundingRate.cs @@ -24,6 +24,9 @@ public partial class ExchangeRestClient public async Task> GetFundingRateHistoryAsync(string exchange, GetFundingRateHistoryRequest request, CancellationToken ct = default) { var result = await Task.WhenAll(GetFundingRateHistoryInt(request, new[] { exchange }, ct)).ConfigureAwait(false); + if (result.Length > 1) + return new ExchangeWebResult(exchange, new InvalidOperationError($"Multiple API's available for {exchange}, specify the `TradingMode` parameter on the request to choose one")); + return result.SingleOrDefault() ?? new ExchangeWebResult(exchange, new InvalidOperationError($"Request not supported for {exchange}")); } diff --git a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.FuturesOrder.cs b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.FuturesOrder.cs index ae5fc05..bbd84ff 100644 --- a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.FuturesOrder.cs +++ b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.FuturesOrder.cs @@ -37,6 +37,9 @@ public async Task> PlaceFuturesOrderAsync(string exc public async Task> GetFuturesOpenOrdersAsync(string exchange, GetOpenOrdersRequest request, CancellationToken ct = default) { var result = await Task.WhenAll(GetFuturesOpenOrdersInt(request, new[] { exchange }, ct)).ConfigureAwait(false); + if (result.Length > 1) + return new ExchangeWebResult(exchange, new InvalidOperationError($"Multiple API's available for {exchange}, specify the `TradingMode` parameter on the request to choose one")); + return result.SingleOrDefault() ?? new ExchangeWebResult(exchange, new InvalidOperationError($"Request not supported for {exchange}")); } @@ -70,6 +73,9 @@ private IEnumerable>> GetFuturesOpe public async Task> GetFuturesClosedOrdersAsync(string exchange, GetClosedOrdersRequest request, CancellationToken ct = default) { var result = await Task.WhenAll(GetFuturesClosedOrdersInt(request, new[] { exchange }, ct)).ConfigureAwait(false); + if (result.Length > 1) + return new ExchangeWebResult(exchange, new InvalidOperationError($"Multiple API's available for {exchange}, specify the `TradingMode` parameter on the request to choose one")); + return result.SingleOrDefault() ?? new ExchangeWebResult(exchange, new InvalidOperationError($"Request not supported for {exchange}")); } @@ -103,6 +109,9 @@ private IEnumerable>> GetFuturesClo public async Task> GetFuturesUserTradesAsync(string exchange, GetUserTradesRequest request, CancellationToken ct = default) { var result = await Task.WhenAll(GetFuturesUserTradesInt(request, new[] { exchange }, ct)).ConfigureAwait(false); + if (result.Length > 1) + return new ExchangeWebResult(exchange, new InvalidOperationError($"Multiple API's available for {exchange}, specify the `TradingMode` parameter on the request to choose one")); + return result.SingleOrDefault() ?? new ExchangeWebResult(exchange, new InvalidOperationError($"Request not supported for {exchange}")); } @@ -192,6 +201,9 @@ public async Task> ClosePositionAsync(string exchang public async Task> GetPositionsAsync(string exchange, GetPositionsRequest request, CancellationToken ct = default) { var result = await Task.WhenAll(GetPositionsInt(request, new[] { exchange }, ct)).ConfigureAwait(false); + if (result.Length > 1) + return new ExchangeWebResult(exchange, new InvalidOperationError($"Multiple API's available for {exchange}, specify the `TradingMode` parameter on the request to choose one")); + return result.SingleOrDefault() ?? new ExchangeWebResult(exchange, new InvalidOperationError($"Request not supported for {exchange}")); } diff --git a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.FuturesSymbol.cs b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.FuturesSymbol.cs index ed26b0d..d976ed8 100644 --- a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.FuturesSymbol.cs +++ b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.FuturesSymbol.cs @@ -88,12 +88,16 @@ public async Task> SupportsFuturesSymbolAsync(string exchan return await client.SupportsFuturesSymbolAsync(symbolName).ConfigureAwait(false); } + #region Get Futures Symbols /// public async Task> GetFuturesSymbolsAsync(string exchange, GetSymbolsRequest request, CancellationToken ct = default) { var result = await Task.WhenAll(GetFuturesSymbolsInt(request, new[] { exchange }, ct)).ConfigureAwait(false); + if (result.Length > 1) + return new ExchangeWebResult(exchange, new InvalidOperationError($"Multiple API's available for {exchange}, specify the `TradingMode` parameter on the request to choose one")); + return result.SingleOrDefault() ?? new ExchangeWebResult(exchange, new InvalidOperationError($"Request not supported for {exchange}")); } diff --git a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.FuturesTicker.cs b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.FuturesTicker.cs index d296d73..ad42425 100644 --- a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.FuturesTicker.cs +++ b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.FuturesTicker.cs @@ -24,6 +24,9 @@ public partial class ExchangeRestClient public async Task> GetFuturesTickersAsync(string exchange, GetTickersRequest request, CancellationToken ct = default) { var result = await Task.WhenAll(GetFuturesTickersInt(request, new[] { exchange }, ct)).ConfigureAwait(false); + if (result.Length > 1) + return new ExchangeWebResult(exchange, new InvalidOperationError($"Multiple API's available for {exchange}, specify the `TradingMode` parameter on the request to choose one")); + return result.SingleOrDefault() ?? new ExchangeWebResult(exchange, new InvalidOperationError($"Request not supported for {exchange}")); } @@ -57,6 +60,9 @@ private IEnumerable>> GetFuturesTi public async Task> GetFuturesTickerAsync(string exchange, GetTickerRequest request, CancellationToken ct = default) { var result = await Task.WhenAll(GetFuturesTickerInt(request, new[] { exchange }, ct)).ConfigureAwait(false); + if (result.Length > 1) + return new ExchangeWebResult(exchange, new InvalidOperationError($"Multiple API's available for {exchange}, specify the `TradingMode` parameter on the request to choose one")); + return result.SingleOrDefault() ?? new ExchangeWebResult(exchange, new InvalidOperationError($"Request not supported for {exchange}")); } diff --git a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.IndexPriceKline.cs b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.IndexPriceKline.cs index b5d5010..16ab4c6 100644 --- a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.IndexPriceKline.cs +++ b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.IndexPriceKline.cs @@ -25,6 +25,9 @@ public partial class ExchangeRestClient public async Task> GetIndexPriceKlinesAsync(string exchange, GetKlinesRequest request, CancellationToken ct = default) { var result = await Task.WhenAll(GetIndexPriceKlinesIntAsync(request, new[] { exchange }, ct)).ConfigureAwait(false); + if (result.Length > 1) + return new ExchangeWebResult(exchange, new InvalidOperationError($"Multiple API's available for {exchange}, specify the `TradingMode` parameter on the request to choose one")); + return result.SingleOrDefault() ?? new ExchangeWebResult(exchange, new InvalidOperationError($"Request not supported for {exchange}")); } diff --git a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.Kline.cs b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.Kline.cs index 5d23b8a..955f180 100644 --- a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.Kline.cs +++ b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.Kline.cs @@ -24,6 +24,9 @@ public partial class ExchangeRestClient public async Task> GetKlinesAsync(string exchange, GetKlinesRequest request, CancellationToken ct = default) { var result = await Task.WhenAll(GetKlinesIntAsync(request, new[] { exchange }, ct)).ConfigureAwait(false); + if (result.Length > 1) + return new ExchangeWebResult(exchange, new InvalidOperationError($"Multiple API's available for {exchange}, specify the `TradingMode` parameter on the request to choose one")); + return result.SingleOrDefault() ?? new ExchangeWebResult(exchange, new InvalidOperationError($"Request not supported for {exchange}")); } diff --git a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.ListenKey.cs b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.ListenKey.cs index ed67856..72de926 100644 --- a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.ListenKey.cs +++ b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.ListenKey.cs @@ -24,6 +24,9 @@ public partial class ExchangeRestClient public async Task> StartListenKeyAsync(string exchange, StartListenKeyRequest request, CancellationToken ct = default) { var result = await Task.WhenAll(StartListenKeysInt(request, new[] { exchange }, ct)).ConfigureAwait(false); + if (result.Length > 1) + return new ExchangeWebResult(exchange, new InvalidOperationError($"Multiple API's available for {exchange}, specify the `TradingMode` parameter on the request to choose one")); + return result.SingleOrDefault() ?? new ExchangeWebResult(exchange, new InvalidOperationError($"Request not supported for {exchange}")); } diff --git a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.MarkPriceKline.cs b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.MarkPriceKline.cs index bf98e0a..e21291a 100644 --- a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.MarkPriceKline.cs +++ b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.MarkPriceKline.cs @@ -25,6 +25,9 @@ public partial class ExchangeRestClient public async Task> GetMarkPriceKlinesAsync(string exchange, GetKlinesRequest request, CancellationToken ct = default) { var result = await Task.WhenAll(GetMarkPriceKlinesIntAsync(request, new[] { exchange }, ct)).ConfigureAwait(false); + if (result.Length > 1) + return new ExchangeWebResult(exchange, new InvalidOperationError($"Multiple API's available for {exchange}, specify the `TradingMode` parameter on the request to choose one")); + return result.SingleOrDefault() ?? new ExchangeWebResult(exchange, new InvalidOperationError($"Request not supported for {exchange}")); } diff --git a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.OpenInterest.cs b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.OpenInterest.cs index 3752b3a..f4d66a2 100644 --- a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.OpenInterest.cs +++ b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.OpenInterest.cs @@ -25,6 +25,9 @@ public partial class ExchangeRestClient public async Task> GetOpenInterestAsync(string exchange, GetOpenInterestRequest request, CancellationToken ct = default) { var result = await Task.WhenAll(GetOpenInterestInt(request, new[] { exchange }, ct)).ConfigureAwait(false); + if (result.Length > 1) + return new ExchangeWebResult(exchange, new InvalidOperationError($"Multiple API's available for {exchange}, specify the `TradingMode` parameter on the request to choose one")); + return result.SingleOrDefault() ?? new ExchangeWebResult(exchange, new InvalidOperationError($"Request not supported for {exchange}")); } diff --git a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.OrderBook.cs b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.OrderBook.cs index 11d7fb2..f783a5d 100644 --- a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.OrderBook.cs +++ b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.OrderBook.cs @@ -24,6 +24,9 @@ public partial class ExchangeRestClient public async Task> GetOrderBookAsync(string exchange, GetOrderBookRequest request, CancellationToken ct = default) { var result = await Task.WhenAll(GetOrderBookInt(request, new[] { exchange }, ct)).ConfigureAwait(false); + if (result.Length > 1) + return new ExchangeWebResult(exchange, new InvalidOperationError($"Multiple API's available for {exchange}, specify the `TradingMode` parameter on the request to choose one")); + return result.SingleOrDefault() ?? new ExchangeWebResult(exchange, new InvalidOperationError($"Request not supported for {exchange}")); } diff --git a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.PositionHistory.cs b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.PositionHistory.cs index d36d9cc..3624172 100644 --- a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.PositionHistory.cs +++ b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.PositionHistory.cs @@ -24,6 +24,9 @@ public partial class ExchangeRestClient public async Task> GetPositionHistoryAsync(string exchange, GetPositionHistoryRequest request, CancellationToken ct = default) { var result = await Task.WhenAll(GetPositionHistoryInt(request, new[] { exchange }, ct)).ConfigureAwait(false); + if (result.Length > 1) + return new ExchangeWebResult(exchange, new InvalidOperationError($"Multiple API's available for {exchange}, specify the `TradingMode` parameter on the request to choose one")); + return result.SingleOrDefault() ?? new ExchangeWebResult(exchange, new InvalidOperationError($"Request not supported for {exchange}")); } diff --git a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.RecentTrade.cs b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.RecentTrade.cs index 5cd40b5..c877ce2 100644 --- a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.RecentTrade.cs +++ b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.RecentTrade.cs @@ -24,6 +24,9 @@ public partial class ExchangeRestClient public async Task> GetRecentTradesAsync(string exchange, GetRecentTradesRequest request, CancellationToken ct = default) { var result = await Task.WhenAll(GetRecentTradesIntAsync(request, new[] { exchange }, ct)).ConfigureAwait(false); + if (result.Length > 1) + return new ExchangeWebResult(exchange, new InvalidOperationError($"Multiple API's available for {exchange}, specify the `TradingMode` parameter on the request to choose one")); + return result.SingleOrDefault() ?? new ExchangeWebResult(exchange, new InvalidOperationError($"Request not supported for {exchange}")); } diff --git a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.TradeHistory.cs b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.TradeHistory.cs index 812bfae..c98dd52 100644 --- a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.TradeHistory.cs +++ b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.TradeHistory.cs @@ -24,6 +24,9 @@ public partial class ExchangeRestClient public async Task> GetTradeHistoryAsync(string exchange, GetTradeHistoryRequest request, CancellationToken ct = default) { var result = await Task.WhenAll(GetTradeHistoryInt(request, new[] { exchange }, ct)).ConfigureAwait(false); + if (result.Length > 1) + return new ExchangeWebResult(exchange, new InvalidOperationError($"Multiple API's available for {exchange}, specify the `TradingMode` parameter on the request to choose one")); + return result.SingleOrDefault() ?? new ExchangeWebResult(exchange, new InvalidOperationError($"Request not supported for {exchange}")); } diff --git a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.Withdrawal.cs b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.Withdrawal.cs index 9773a2f..0db93d2 100644 --- a/CryptoClients.Net/Clients/Rest/ExchangeRestClient.Withdrawal.cs +++ b/CryptoClients.Net/Clients/Rest/ExchangeRestClient.Withdrawal.cs @@ -22,6 +22,9 @@ public partial class ExchangeRestClient public async Task> GetWithdrawalsAsync(string exchange, GetWithdrawalsRequest request, CancellationToken ct = default) { var result = await Task.WhenAll(GetWithdrawalsInt(request, new[] { exchange }, ct)).ConfigureAwait(false); + if (result.Length > 1) + return new ExchangeWebResult(exchange, new InvalidOperationError($"Multiple API's available for {exchange}, specify the `TradingMode` parameter on the request to choose one")); + return result.SingleOrDefault() ?? new ExchangeWebResult(exchange, new InvalidOperationError($"Request not supported for {exchange}")); }