diff --git a/CoinEx.Net/Clients/CoinExUserClientProvider.cs b/CoinEx.Net/Clients/CoinExUserClientProvider.cs index 1554c0f..7f2bed5 100644 --- a/CoinEx.Net/Clients/CoinExUserClientProvider.cs +++ b/CoinEx.Net/Clients/CoinExUserClientProvider.cs @@ -66,7 +66,7 @@ public void ClearUserClients(string userIdentifier) /// public ICoinExRestClient GetRestClient(string userIdentifier, ApiCredentials? credentials = null, CoinExEnvironment? environment = null) { - if (!_restClients.TryGetValue(userIdentifier, out var client)) + if (!_restClients.TryGetValue(userIdentifier, out var client) || client.Disposed) client = CreateRestClient(userIdentifier, credentials, environment); return client; @@ -75,7 +75,7 @@ public ICoinExRestClient GetRestClient(string userIdentifier, ApiCredentials? cr /// public ICoinExSocketClient GetSocketClient(string userIdentifier, ApiCredentials? credentials = null, CoinExEnvironment? environment = null) { - if (!_socketClients.TryGetValue(userIdentifier, out var client)) + if (!_socketClients.TryGetValue(userIdentifier, out var client) || client.Disposed) client = CreateSocketClient(userIdentifier, credentials, environment); return client; diff --git a/CoinEx.Net/Clients/FuturesApi/CoinExRestClientFuturesApiShared.cs b/CoinEx.Net/Clients/FuturesApi/CoinExRestClientFuturesApiShared.cs index ff0f63c..832dd2c 100644 --- a/CoinEx.Net/Clients/FuturesApi/CoinExRestClientFuturesApiShared.cs +++ b/CoinEx.Net/Clients/FuturesApi/CoinExRestClientFuturesApiShared.cs @@ -162,7 +162,44 @@ async Task> IFuturesSymbolRestClient.Ge ExchangeSymbolCache.UpdateSymbolInfo(_topicId, response.Data); return response; } + async Task> IFuturesSymbolRestClient.GetFuturesSymbolsForBaseAssetAsync(string baseAsset) + { + if (!ExchangeSymbolCache.HasCached(_topicId)) + { + var symbols = await ((IFuturesSymbolRestClient)this).GetFuturesSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false); + if (!symbols) + return new ExchangeResult(Exchange, symbols.Error!); + } + + return new ExchangeResult(Exchange, ExchangeSymbolCache.GetSymbolsForBaseAsset(_topicId, baseAsset)); + } + async Task> IFuturesSymbolRestClient.SupportsFuturesSymbolAsync(SharedSymbol symbol) + { + if (symbol.TradingMode == TradingMode.Spot) + throw new ArgumentException(nameof(symbol), "Spot symbols not allowed"); + + if (!ExchangeSymbolCache.HasCached(_topicId)) + { + var symbols = await ((IFuturesSymbolRestClient)this).GetFuturesSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false); + if (!symbols) + return new ExchangeResult(Exchange, symbols.Error!); + } + + return new ExchangeResult(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicId, symbol)); + } + + async Task> IFuturesSymbolRestClient.SupportsFuturesSymbolAsync(string symbolName) + { + if (!ExchangeSymbolCache.HasCached(_topicId)) + { + var symbols = await ((IFuturesSymbolRestClient)this).GetFuturesSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false); + if (!symbols) + return new ExchangeResult(Exchange, symbols.Error!); + } + + return new ExchangeResult(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicId, symbolName)); + } #endregion #region Futures Order Client @@ -440,6 +477,7 @@ async Task> IFuturesOrderRestClient.GetPosit Leverage = x.Leverage, StopLossPrice = x.StopLossPrice == 0 ? null : x.StopLossPrice, TakeProfitPrice = x.TakeProfitPrice == 0 ? null : x.TakeProfitPrice, + PositionMode = SharedPositionMode.OneWay, PositionSide = x.Side == PositionSide.Short ? SharedPositionSide.Short : SharedPositionSide.Long }).ToArray()); } diff --git a/CoinEx.Net/Clients/FuturesApi/CoinExSocketClientFuturesApiShared.cs b/CoinEx.Net/Clients/FuturesApi/CoinExSocketClientFuturesApiShared.cs index e4614ad..0603d67 100644 --- a/CoinEx.Net/Clients/FuturesApi/CoinExSocketClientFuturesApiShared.cs +++ b/CoinEx.Net/Clients/FuturesApi/CoinExSocketClientFuturesApiShared.cs @@ -221,6 +221,7 @@ async Task> IPositionSocketClient.SubscribeTo update => handler(update.ToType(new[] { new SharedPosition(ExchangeSymbolCache.ParseSymbol(_topicId, update.Data.Position.Symbol), update.Data.Position.Symbol, update.Data.Position.OpenInterest, update.Data.Position.UpdateTime) { AverageOpenPrice = update.Data.Position.AverageEntryPrice, + PositionMode = SharedPositionMode.OneWay, PositionSide = update.Data.Position.Side == Enums.PositionSide.Short ? SharedPositionSide.Short : SharedPositionSide.Long, LiquidationPrice = update.Data.Position.LiquidationPrice, Leverage = update.Data.Position.Leverage, diff --git a/CoinEx.Net/Clients/SpotApiV2/CoinExRestClientSpotApiShared.cs b/CoinEx.Net/Clients/SpotApiV2/CoinExRestClientSpotApiShared.cs index a7c8441..1466c44 100644 --- a/CoinEx.Net/Clients/SpotApiV2/CoinExRestClientSpotApiShared.cs +++ b/CoinEx.Net/Clients/SpotApiV2/CoinExRestClientSpotApiShared.cs @@ -116,7 +116,44 @@ async Task> ISpotSymbolRestClient.GetSpotS ExchangeSymbolCache.UpdateSymbolInfo(_topicId, response.Data); return response; } + async Task> ISpotSymbolRestClient.GetSpotSymbolsForBaseAssetAsync(string baseAsset) + { + if (!ExchangeSymbolCache.HasCached(_topicId)) + { + var symbols = await ((ISpotSymbolRestClient)this).GetSpotSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false); + if (!symbols) + return new ExchangeResult(Exchange, symbols.Error!); + } + + return new ExchangeResult(Exchange, ExchangeSymbolCache.GetSymbolsForBaseAsset(_topicId, baseAsset)); + } + async Task> ISpotSymbolRestClient.SupportsSpotSymbolAsync(SharedSymbol symbol) + { + if (symbol.TradingMode != TradingMode.Spot) + throw new ArgumentException(nameof(symbol), "Only Spot symbols allowed"); + + if (!ExchangeSymbolCache.HasCached(_topicId)) + { + var symbols = await ((ISpotSymbolRestClient)this).GetSpotSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false); + if (!symbols) + return new ExchangeResult(Exchange, symbols.Error!); + } + + return new ExchangeResult(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicId, symbol)); + } + + async Task> ISpotSymbolRestClient.SupportsSpotSymbolAsync(string symbolName) + { + if (!ExchangeSymbolCache.HasCached(_topicId)) + { + var symbols = await ((ISpotSymbolRestClient)this).GetSpotSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false); + if (!symbols) + return new ExchangeResult(Exchange, symbols.Error!); + } + + return new ExchangeResult(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicId, symbolName)); + } #endregion #region Ticker client @@ -728,7 +765,15 @@ async Task> IDepositRestClient.GetDepositsAsy if (deposits.Data.HasNext == true) nextToken = new PageToken(page + 1, pageSize); - return deposits.AsExchangeResult(Exchange, TradingMode.Spot, deposits.Data.Items.Select(x => new SharedDeposit(x.Asset, x.Quantity, x.Status == DepositStatus.Finished, x.CreateTime) + return deposits.AsExchangeResult(Exchange, TradingMode.Spot, deposits.Data.Items.Select(x => + new SharedDeposit( + x.Asset, + x.Quantity, + x.Status == DepositStatus.Finished, + x.CreateTime, + x.Status == DepositStatus.Finished ? SharedTransferStatus.Completed + : x.Status == DepositStatus.Processing || x.Status == DepositStatus.Confirming ? SharedTransferStatus.InProgress + : SharedTransferStatus.Failed) { Id = x.Id.ToString(), Confirmations = x.Confirmations, diff --git a/CoinEx.Net/CoinEx.Net.csproj b/CoinEx.Net/CoinEx.Net.csproj index c384a38..6ccc382 100644 --- a/CoinEx.Net/CoinEx.Net.csproj +++ b/CoinEx.Net/CoinEx.Net.csproj @@ -54,7 +54,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/CoinEx.Net/CoinEx.Net.xml b/CoinEx.Net/CoinEx.Net.xml index 1d18d24..ef96646 100644 --- a/CoinEx.Net/CoinEx.Net.xml +++ b/CoinEx.Net/CoinEx.Net.xml @@ -796,6 +796,37 @@ + + + + + + + + + + + + + + + + + + ctor + + + + + + + + + + + ctor + + Defines the source generated JSON serialization contract metadata for a given type. @@ -4559,6 +4590,36 @@ Tracker factory + + + Create a new Spot user data tracker + + User identifier + Configuration + Credentials + Environment + + + + Create a new spot user data tracker + + Configuration + + + + Create a new futures user data tracker + + User identifier + Configuration + Credentials + Environment + + + + Create a new futures user data tracker + + Configuration + Api addresses usable for the CoinEx clients diff --git a/CoinEx.Net/CoinExTrackerFactory.cs b/CoinEx.Net/CoinExTrackerFactory.cs index 241a5c6..efd40ed 100644 --- a/CoinEx.Net/CoinExTrackerFactory.cs +++ b/CoinEx.Net/CoinExTrackerFactory.cs @@ -1,11 +1,15 @@ using CoinEx.Net.Clients; using CoinEx.Net.Interfaces; using CoinEx.Net.Interfaces.Clients; +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 Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using System; namespace CoinEx.Net @@ -70,5 +74,63 @@ public ITradeTracker CreateTradeTracker(SharedSymbol symbol, int? limit = null, period ); } + + /// + public IUserSpotDataTracker CreateUserSpotDataTracker(SpotUserDataTrackerConfig? config = null) + { + var restClient = _serviceProvider?.GetRequiredService() ?? new CoinExRestClient(); + var socketClient = _serviceProvider?.GetRequiredService() ?? new CoinExSocketClient(); + return new CoinExUserSpotDataTracker( + _serviceProvider?.GetRequiredService>() ?? new NullLogger(), + restClient, + socketClient, + null, + config + ); + } + + /// + public IUserSpotDataTracker CreateUserSpotDataTracker(string userIdentifier, ApiCredentials credentials, SpotUserDataTrackerConfig? config = null, CoinExEnvironment? environment = null) + { + var clientProvider = _serviceProvider?.GetRequiredService() ?? new CoinExUserClientProvider(); + var restClient = clientProvider.GetRestClient(userIdentifier, credentials, environment); + var socketClient = clientProvider.GetSocketClient(userIdentifier, credentials, environment); + return new CoinExUserSpotDataTracker( + _serviceProvider?.GetRequiredService>() ?? new NullLogger(), + restClient, + socketClient, + userIdentifier, + config + ); + } + + /// + public IUserFuturesDataTracker CreateUserFuturesDataTracker(FuturesUserDataTrackerConfig? config = null) + { + var restClient = _serviceProvider?.GetRequiredService() ?? new CoinExRestClient(); + var socketClient = _serviceProvider?.GetRequiredService() ?? new CoinExSocketClient(); + return new CoinExUserFuturesDataTracker( + _serviceProvider?.GetRequiredService>() ?? new NullLogger(), + restClient, + socketClient, + null, + config + ); + } + + /// + public IUserFuturesDataTracker CreateUserFuturesDataTracker(string userIdentifier, ApiCredentials credentials, FuturesUserDataTrackerConfig? config = null, CoinExEnvironment? environment = null) + { + var clientProvider = _serviceProvider?.GetRequiredService() ?? new CoinExUserClientProvider(); + var restClient = clientProvider.GetRestClient(userIdentifier, credentials, environment); + var socketClient = clientProvider.GetSocketClient(userIdentifier, credentials, environment); + return new CoinExUserFuturesDataTracker( + _serviceProvider?.GetRequiredService>() ?? new NullLogger(), + restClient, + socketClient, + userIdentifier, + config + ); + } } } diff --git a/CoinEx.Net/CoinExUserDataTracker.cs b/CoinEx.Net/CoinExUserDataTracker.cs new file mode 100644 index 0000000..b9f4d92 --- /dev/null +++ b/CoinEx.Net/CoinExUserDataTracker.cs @@ -0,0 +1,63 @@ +using CoinEx.Net.Interfaces.Clients; +using CryptoExchange.Net.SharedApis; +using CryptoExchange.Net.Trackers.UserData; +using CryptoExchange.Net.Trackers.UserData.Objects; +using Microsoft.Extensions.Logging; + +namespace CoinEx.Net +{ + /// + public class CoinExUserSpotDataTracker : UserSpotDataTracker + { + /// + /// ctor + /// + public CoinExUserSpotDataTracker( + ILogger logger, + ICoinExRestClient restClient, + ICoinExSocketClient socketClient, + string? userIdentifier, + SpotUserDataTrackerConfig? config) : base( + logger, + restClient.SpotApiV2.SharedClient, + null, + restClient.SpotApiV2.SharedClient, + socketClient.SpotApiV2.SharedClient, + restClient.SpotApiV2.SharedClient, + socketClient.SpotApiV2.SharedClient, + socketClient.SpotApiV2.SharedClient, + userIdentifier, + config ?? new SpotUserDataTrackerConfig()) + { + } + } + + /// + public class CoinExUserFuturesDataTracker : UserFuturesDataTracker + { + /// + protected override bool WebsocketPositionUpdatesAreFullSnapshots => false; + + /// + /// ctor + /// + public CoinExUserFuturesDataTracker( + ILogger logger, + ICoinExRestClient restClient, + ICoinExSocketClient socketClient, + string? userIdentifier, + FuturesUserDataTrackerConfig? config) : base(logger, + restClient.FuturesApi.SharedClient, + null, + restClient.FuturesApi.SharedClient, + socketClient.FuturesApi.SharedClient, + restClient.FuturesApi.SharedClient, + socketClient.FuturesApi.SharedClient, + socketClient.FuturesApi.SharedClient, + socketClient.FuturesApi.SharedClient, + userIdentifier, + config ?? new FuturesUserDataTrackerConfig()) + { + } + } +} diff --git a/CoinEx.Net/Interfaces/ICoinExTrackerFactory.cs b/CoinEx.Net/Interfaces/ICoinExTrackerFactory.cs index 8bc14f2..88516c3 100644 --- a/CoinEx.Net/Interfaces/ICoinExTrackerFactory.cs +++ b/CoinEx.Net/Interfaces/ICoinExTrackerFactory.cs @@ -1,7 +1,10 @@ -using CryptoExchange.Net.Interfaces; +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 System; using System.Collections.Generic; using System.Text; @@ -13,5 +16,32 @@ namespace CoinEx.Net.Interfaces /// public interface ICoinExTrackerFactory : ITrackerFactory { + /// + /// Create a new Spot user data tracker + /// + /// User identifier + /// Configuration + /// Credentials + /// Environment + IUserSpotDataTracker CreateUserSpotDataTracker(string userIdentifier, ApiCredentials credentials, SpotUserDataTrackerConfig? config = null, CoinExEnvironment? environment = null); + /// + /// Create a new spot user data tracker + /// + /// Configuration + IUserSpotDataTracker CreateUserSpotDataTracker(SpotUserDataTrackerConfig? config = null); + + /// + /// Create a new futures user data tracker + /// + /// User identifier + /// Configuration + /// Credentials + /// Environment + IUserFuturesDataTracker CreateUserFuturesDataTracker(string userIdentifier, ApiCredentials credentials, FuturesUserDataTrackerConfig? config = null, CoinExEnvironment? environment = null); + /// + /// Create a new futures user data tracker + /// + /// Configuration + IUserFuturesDataTracker CreateUserFuturesDataTracker(FuturesUserDataTrackerConfig? config = null); } }