diff --git a/DeepCoin.Net/Clients/DeepCoinUserClientProvider.cs b/DeepCoin.Net/Clients/DeepCoinUserClientProvider.cs index a4b6bd5..4073120 100644 --- a/DeepCoin.Net/Clients/DeepCoinUserClientProvider.cs +++ b/DeepCoin.Net/Clients/DeepCoinUserClientProvider.cs @@ -64,7 +64,7 @@ public void ClearUserClients(string userIdentifier) /// public IDeepCoinRestClient GetRestClient(string userIdentifier, ApiCredentials? credentials = null, DeepCoinEnvironment? 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; @@ -73,7 +73,7 @@ public IDeepCoinRestClient GetRestClient(string userIdentifier, ApiCredentials? /// public IDeepCoinSocketClient GetSocketClient(string userIdentifier, ApiCredentials? credentials = null, DeepCoinEnvironment? 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/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiShared.cs b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiShared.cs index dd31b8d..fb55251 100644 --- a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiShared.cs +++ b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiShared.cs @@ -86,7 +86,15 @@ async Task> IDepositRestClient.GetDepositsAsy if (result.Data.Total > (result.Data.Page * result.Data.PageSize)) nextToken = new PageToken(page++, pageSize); - return result.AsExchangeResult(Exchange, TradingMode.Spot, result.Data.Data.Select(x => new SharedDeposit(x.Asset, x.Quantity, x.DepositStatus == Enums.DepositStatus.Success, x.CreateTime) + return result.AsExchangeResult(Exchange, TradingMode.Spot, result.Data.Data.Select(x => + new SharedDeposit( + x.Asset, + x.Quantity, + x.DepositStatus == Enums.DepositStatus.Success, + x.CreateTime, + x.DepositStatus == DepositStatus.Success ? SharedTransferStatus.Completed + : x.DepositStatus == DepositStatus.Confirming ? SharedTransferStatus.InProgress + : SharedTransferStatus.Failed) { TransactionId = x.TransactionHash, Network = x.NetworkName, @@ -350,6 +358,44 @@ async Task> ISpotSymbolRestClient.GetSpotS return response; } + async Task> ISpotSymbolRestClient.GetSpotSymbolsForBaseAssetAsync(string baseAsset) + { + if (!ExchangeSymbolCache.HasCached(_topicSpotId)) + { + var symbols = await ((ISpotSymbolRestClient)this).GetSpotSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false); + if (!symbols) + return new ExchangeResult(Exchange, symbols.Error!); + } + + return new ExchangeResult(Exchange, ExchangeSymbolCache.GetSymbolsForBaseAsset(_topicSpotId, baseAsset)); + } + + async Task> ISpotSymbolRestClient.SupportsSpotSymbolAsync(SharedSymbol symbol) + { + if (symbol.TradingMode != TradingMode.Spot) + throw new ArgumentException(nameof(symbol), "Only Spot symbols allowed"); + + if (!ExchangeSymbolCache.HasCached(_topicSpotId)) + { + var symbols = await ((ISpotSymbolRestClient)this).GetSpotSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false); + if (!symbols) + return new ExchangeResult(Exchange, symbols.Error!); + } + + return new ExchangeResult(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicSpotId, symbol)); + } + + async Task> ISpotSymbolRestClient.SupportsSpotSymbolAsync(string symbolName) + { + if (!ExchangeSymbolCache.HasCached(_topicSpotId)) + { + var symbols = await ((ISpotSymbolRestClient)this).GetSpotSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false); + if (!symbols) + return new ExchangeResult(Exchange, symbols.Error!); + } + + return new ExchangeResult(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicSpotId, symbolName)); + } #endregion #region Spot Order Client @@ -746,10 +792,51 @@ async Task> IFuturesSymbolRestClient.Ge MaxShortLeverage = s.MaxLeverage }).ToArray()); - ExchangeSymbolCache.UpdateSymbolInfo(_topicFuturesId, response.Data); + // Also register [BaseAsset][QuoteAsset] as they might be returned for websocket updates + var symbolRegistrations = response.Data + .Concat(response.Data.Select(x => new SharedSpotSymbol(x.BaseAsset, x.QuoteAsset, x.BaseAsset + x.QuoteAsset, x.Trading, x.TradingMode))).ToArray(); + + ExchangeSymbolCache.UpdateSymbolInfo(_topicFuturesId, symbolRegistrations); return response; } + async Task> IFuturesSymbolRestClient.GetFuturesSymbolsForBaseAssetAsync(string baseAsset) + { + if (!ExchangeSymbolCache.HasCached(_topicFuturesId)) + { + var symbols = await ((IFuturesSymbolRestClient)this).GetFuturesSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false); + if (!symbols) + return new ExchangeResult(Exchange, symbols.Error!); + } + + return new ExchangeResult(Exchange, ExchangeSymbolCache.GetSymbolsForBaseAsset(_topicFuturesId, baseAsset)); + } + + async Task> IFuturesSymbolRestClient.SupportsFuturesSymbolAsync(SharedSymbol symbol) + { + if (symbol.TradingMode == TradingMode.Spot) + throw new ArgumentException(nameof(symbol), "Spot symbols not allowed"); + + if (!ExchangeSymbolCache.HasCached(_topicFuturesId)) + { + var symbols = await ((IFuturesSymbolRestClient)this).GetFuturesSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false); + if (!symbols) + return new ExchangeResult(Exchange, symbols.Error!); + } + + return new ExchangeResult(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicFuturesId, symbol)); + } + async Task> IFuturesSymbolRestClient.SupportsFuturesSymbolAsync(string symbolName) + { + if (!ExchangeSymbolCache.HasCached(_topicFuturesId)) + { + var symbols = await ((IFuturesSymbolRestClient)this).GetFuturesSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false); + if (!symbols) + return new ExchangeResult(Exchange, symbols.Error!); + } + + return new ExchangeResult(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicFuturesId, symbolName)); + } #endregion #region Futures Order Client @@ -1064,7 +1151,8 @@ async Task> IFuturesOrderRestClient.GetPosit { LiquidationPrice = x.LiquidationPrice == 0 ? null : x.LiquidationPrice, Leverage = x.Leverage, - AverageOpenPrice = x.AveragePrice, + AverageOpenPrice = x.AveragePrice, + PositionMode = SharedPositionMode.HedgeMode, PositionSide = x.PositionSide == PositionSide.Long ? SharedPositionSide.Long : SharedPositionSide.Short }).ToArray()); } diff --git a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApiShared.cs b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApiShared.cs index cb0b3f5..3fc2384 100644 --- a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApiShared.cs +++ b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApiShared.cs @@ -219,9 +219,10 @@ async Task> IPositionSocketClient.SubscribeTo return new ExchangeResult(Exchange, validationError); var result = await SubscribeToUserDataUpdatesAsync(request.ListenKey!, - onPositionMessage: update => handler(update.ToType(update.Data.Select(x => new SharedPosition(ExchangeSymbolCache.ParseSymbol(_topicSpotId, x.Symbol), x.Symbol, x.PositionSize, x.UpdateTime) + onPositionMessage: update => handler(update.ToType(update.Data.Select(x => new SharedPosition(ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol), x.Symbol, x.PositionSize, x.UpdateTime) { AverageOpenPrice = x.OpenPrice, + PositionMode = SharedPositionMode.HedgeMode, PositionSide = x.PositionSide == Enums.PositionSide.Short ? SharedPositionSide.Short : SharedPositionSide.Long, Leverage = x.Leverage }).ToArray())), @@ -251,7 +252,7 @@ async Task> IUserTradeSocketClient.SubscribeT request.ListenKey!, onUserTradeMessage: update => handler(update.ToType(update.Data.Select(x => new SharedUserTrade( - ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol) ?? ExchangeSymbolCache.ParseSymbol(_topicSpotId, x.Symbol), + ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol) ?? ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol), x.Symbol, x.OrderId.ToString(), x.TradeId.ToString(), diff --git a/DeepCoin.Net/DeepCoin.Net.csproj b/DeepCoin.Net/DeepCoin.Net.csproj index cdc02b7..f57786e 100644 --- a/DeepCoin.Net/DeepCoin.Net.csproj +++ b/DeepCoin.Net/DeepCoin.Net.csproj @@ -52,7 +52,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/DeepCoin.Net/DeepCoin.Net.xml b/DeepCoin.Net/DeepCoin.Net.xml index 9307076..2afd10b 100644 --- a/DeepCoin.Net/DeepCoin.Net.xml +++ b/DeepCoin.Net/DeepCoin.Net.xml @@ -1209,6 +1209,37 @@ + + + + + + + + + + + + + + + + + + ctor + + + + + + + + + + + ctor + + Account type @@ -2104,6 +2135,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 diff --git a/DeepCoin.Net/DeepCoinTrackerFactory.cs b/DeepCoin.Net/DeepCoinTrackerFactory.cs index 5a3a623..7e14a10 100644 --- a/DeepCoin.Net/DeepCoinTrackerFactory.cs +++ b/DeepCoin.Net/DeepCoinTrackerFactory.cs @@ -1,12 +1,16 @@ +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.Clients; using DeepCoin.Net.Interfaces; using DeepCoin.Net.Interfaces.Clients; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; using System; -using DeepCoin.Net.Clients; namespace DeepCoin.Net { @@ -73,5 +77,63 @@ public ITradeTracker CreateTradeTracker(SharedSymbol symbol, int? limit = null, period ); } + + /// + public IUserSpotDataTracker CreateUserSpotDataTracker(SpotUserDataTrackerConfig? config = null) + { + var restClient = _serviceProvider?.GetRequiredService() ?? new DeepCoinRestClient(); + var socketClient = _serviceProvider?.GetRequiredService() ?? new DeepCoinSocketClient(); + return new DeepCoinUserSpotDataTracker( + _serviceProvider?.GetRequiredService>() ?? new NullLogger(), + restClient, + socketClient, + null, + config + ); + } + + /// + public IUserSpotDataTracker CreateUserSpotDataTracker(string userIdentifier, ApiCredentials credentials, SpotUserDataTrackerConfig? config = null, DeepCoinEnvironment? environment = null) + { + var clientProvider = _serviceProvider?.GetRequiredService() ?? new DeepCoinUserClientProvider(); + var restClient = clientProvider.GetRestClient(userIdentifier, credentials, environment); + var socketClient = clientProvider.GetSocketClient(userIdentifier, credentials, environment); + return new DeepCoinUserSpotDataTracker( + _serviceProvider?.GetRequiredService>() ?? new NullLogger(), + restClient, + socketClient, + userIdentifier, + config + ); + } + + /// + public IUserFuturesDataTracker CreateUserFuturesDataTracker(FuturesUserDataTrackerConfig? config = null) + { + var restClient = _serviceProvider?.GetRequiredService() ?? new DeepCoinRestClient(); + var socketClient = _serviceProvider?.GetRequiredService() ?? new DeepCoinSocketClient(); + return new DeepCoinUserFuturesDataTracker( + _serviceProvider?.GetRequiredService>() ?? new NullLogger(), + restClient, + socketClient, + null, + config + ); + } + + /// + public IUserFuturesDataTracker CreateUserFuturesDataTracker(string userIdentifier, ApiCredentials credentials, FuturesUserDataTrackerConfig? config = null, DeepCoinEnvironment? environment = null) + { + var clientProvider = _serviceProvider?.GetRequiredService() ?? new DeepCoinUserClientProvider(); + var restClient = clientProvider.GetRestClient(userIdentifier, credentials, environment); + var socketClient = clientProvider.GetSocketClient(userIdentifier, credentials, environment); + return new DeepCoinUserFuturesDataTracker( + _serviceProvider?.GetRequiredService>() ?? new NullLogger(), + restClient, + socketClient, + userIdentifier, + config + ); + } } } diff --git a/DeepCoin.Net/DeepCoinUserDataTracker.cs b/DeepCoin.Net/DeepCoinUserDataTracker.cs new file mode 100644 index 0000000..98c1180 --- /dev/null +++ b/DeepCoin.Net/DeepCoinUserDataTracker.cs @@ -0,0 +1,63 @@ +using DeepCoin.Net.Interfaces.Clients; +using CryptoExchange.Net.SharedApis; +using CryptoExchange.Net.Trackers.UserData; +using CryptoExchange.Net.Trackers.UserData.Objects; +using Microsoft.Extensions.Logging; + +namespace DeepCoin.Net +{ + /// + public class DeepCoinUserSpotDataTracker : UserSpotDataTracker + { + /// + /// ctor + /// + public DeepCoinUserSpotDataTracker( + ILogger logger, + IDeepCoinRestClient restClient, + IDeepCoinSocketClient socketClient, + string? userIdentifier, + SpotUserDataTrackerConfig? config) : base( + logger, + restClient.ExchangeApi.SharedClient, + restClient.ExchangeApi.SharedClient, + restClient.ExchangeApi.SharedClient, + socketClient.ExchangeApi.SharedClient, + restClient.ExchangeApi.SharedClient, + socketClient.ExchangeApi.SharedClient, + socketClient.ExchangeApi.SharedClient, + userIdentifier, + config ?? new SpotUserDataTrackerConfig()) + { + } + } + + /// + public class DeepCoinUserFuturesDataTracker : UserFuturesDataTracker + { + /// + protected override bool WebsocketPositionUpdatesAreFullSnapshots => false; + + /// + /// ctor + /// + public DeepCoinUserFuturesDataTracker( + ILogger logger, + IDeepCoinRestClient restClient, + IDeepCoinSocketClient socketClient, + string? userIdentifier, + FuturesUserDataTrackerConfig? config) : base(logger, + restClient.ExchangeApi.SharedClient, + restClient.ExchangeApi.SharedClient, + restClient.ExchangeApi.SharedClient, + socketClient.ExchangeApi.SharedClient, + restClient.ExchangeApi.SharedClient, + socketClient.ExchangeApi.SharedClient, + socketClient.ExchangeApi.SharedClient, + socketClient.ExchangeApi.SharedClient, + userIdentifier, + config ?? new FuturesUserDataTrackerConfig()) + { + } + } +} diff --git a/DeepCoin.Net/Interfaces/IDeepCoinTrackerFactory.cs b/DeepCoin.Net/Interfaces/IDeepCoinTrackerFactory.cs index 3cc15ef..8688a7c 100644 --- a/DeepCoin.Net/Interfaces/IDeepCoinTrackerFactory.cs +++ b/DeepCoin.Net/Interfaces/IDeepCoinTrackerFactory.cs @@ -1,4 +1,7 @@ +using CryptoExchange.Net.Authentication; using CryptoExchange.Net.Interfaces; +using CryptoExchange.Net.Trackers.UserData.Interfaces; +using CryptoExchange.Net.Trackers.UserData.Objects; namespace DeepCoin.Net.Interfaces { @@ -7,5 +10,32 @@ namespace DeepCoin.Net.Interfaces /// public interface IDeepCoinTrackerFactory : ITrackerFactory { + /// + /// Create a new Spot user data tracker + /// + /// User identifier + /// Configuration + /// Credentials + /// Environment + IUserSpotDataTracker CreateUserSpotDataTracker(string userIdentifier, ApiCredentials credentials, SpotUserDataTrackerConfig? config = null, DeepCoinEnvironment? 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, DeepCoinEnvironment? environment = null); + /// + /// Create a new futures user data tracker + /// + /// Configuration + IUserFuturesDataTracker CreateUserFuturesDataTracker(FuturesUserDataTrackerConfig? config = null); } }