diff --git a/HTX.Net/Clients/HTXUserClientProvider.cs b/HTX.Net/Clients/HTXUserClientProvider.cs index ce108844..372cc7ab 100644 --- a/HTX.Net/Clients/HTXUserClientProvider.cs +++ b/HTX.Net/Clients/HTXUserClientProvider.cs @@ -60,7 +60,7 @@ public void ClearUserClients(string userIdentifier) /// public IHTXRestClient GetRestClient(string userIdentifier, ApiCredentials? credentials = null, HTXEnvironment? 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; @@ -69,7 +69,7 @@ public IHTXRestClient GetRestClient(string userIdentifier, ApiCredentials? crede /// public IHTXSocketClient GetSocketClient(string userIdentifier, ApiCredentials? credentials = null, HTXEnvironment? 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/HTX.Net/Clients/MessageHandlers/HTXSocketUsdtFuturesMessageHandler.cs b/HTX.Net/Clients/MessageHandlers/HTXSocketUsdtFuturesMessageHandler.cs index 2e829f4c..040be771 100644 --- a/HTX.Net/Clients/MessageHandlers/HTXSocketUsdtFuturesMessageHandler.cs +++ b/HTX.Net/Clients/MessageHandlers/HTXSocketUsdtFuturesMessageHandler.cs @@ -26,7 +26,8 @@ internal class HTXSocketUsdtFuturesMessageHandler : JsonSocketMessageHandler { "accounts_cross.", "accounts_cross" }, { "orders_cross.", "orders_cross" }, { "positions_cross.", "positions_cross" }, - { "matchOrders.", "matchOrders" } + { "matchOrders.", "matchOrders" }, + { "matchOrders_cross.", "matchOrders_cross" } }; public override JsonSerializerOptions Options { get; } = SerializerOptions.WithConverters(HTXExchange._serializerContext); diff --git a/HTX.Net/Clients/SpotApi/HTXRestClientSpotApiShared.cs b/HTX.Net/Clients/SpotApi/HTXRestClientSpotApiShared.cs index be3f29a0..3f1d1a2f 100644 --- a/HTX.Net/Clients/SpotApi/HTXRestClientSpotApiShared.cs +++ b/HTX.Net/Clients/SpotApi/HTXRestClientSpotApiShared.cs @@ -109,6 +109,44 @@ async Task> ISpotSymbolRestClient.GetSpotS 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 @@ -706,7 +744,15 @@ async Task> IDepositRestClient.GetDepositsAsy if (deposits.Data.Count() == (request.Limit ?? 100)) nextToken = new FromIdToken(deposits.Data.Min(x => x.Id - 1).ToString()); - return deposits.AsExchangeResult(Exchange, TradingMode.Spot, deposits.Data.Select(x => new SharedDeposit(x.Asset!.ToUpperInvariant(), x.Quantity, x.Status == WithdrawDepositStatus.Safe, x.CreateTime) + return deposits.AsExchangeResult(Exchange, TradingMode.Spot, deposits.Data.Select(x => + new SharedDeposit( + x.Asset!.ToUpperInvariant(), + x.Quantity, + x.Status == WithdrawDepositStatus.Safe, + x.CreateTime, + x.Status == WithdrawDepositStatus.Safe ? SharedTransferStatus.Completed + : x.Status == WithdrawDepositStatus.Repealed || x.Status == WithdrawDepositStatus.ConfirmError || x.Status == WithdrawDepositStatus.WalletReject || x.Status == WithdrawDepositStatus.Reject || x.Status == WithdrawDepositStatus.Canceled || x.Status == WithdrawDepositStatus.Failed ? SharedTransferStatus.Failed + : SharedTransferStatus.Failed) { Id = x.Id.ToString(), Network = x.Network, diff --git a/HTX.Net/Clients/UsdtFutures/HTXRestClientUsdtFuturesApiShared.cs b/HTX.Net/Clients/UsdtFutures/HTXRestClientUsdtFuturesApiShared.cs index 6b938db9..e4f535ee 100644 --- a/HTX.Net/Clients/UsdtFutures/HTXRestClientUsdtFuturesApiShared.cs +++ b/HTX.Net/Clients/UsdtFutures/HTXRestClientUsdtFuturesApiShared.cs @@ -37,7 +37,7 @@ async Task> IBalanceRestClient.GetBalancesAsy if (!result) return result.AsExchangeResult(Exchange, null, default); - return result.AsExchangeResult(Exchange, SupportedTradingModes, result.Data.Select(x => new SharedBalance(x.MarginAsset, x.MarginBalance, x.MarginFrozen + x.MarginBalance)).ToArray()); + return result.AsExchangeResult(Exchange, SupportedTradingModes, result.Data.Select(x => new SharedBalance(x.MarginAsset, x.WithdrawAvailable, x.MarginBalance)).ToArray()); } else { @@ -45,7 +45,7 @@ async Task> IBalanceRestClient.GetBalancesAsy if (!result) return result.AsExchangeResult(Exchange, null, default); - return result.AsExchangeResult(Exchange, SupportedTradingModes, result.Data.Select(x => new SharedBalance(x.MarginAsset, x.MarginBalance, x.MarginFrozen + x.MarginBalance) + return result.AsExchangeResult(Exchange, SupportedTradingModes, result.Data.Select(x => new SharedBalance("USDT", x.WithdrawAvailable, x.MarginBalance) { IsolatedMarginSymbol = x.ContractCode }).ToArray()); @@ -177,7 +177,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 @@ -743,6 +780,7 @@ async Task> IFuturesOrderRestClient.GetPosit UnrealizedPnl = x.UnrealizedPnl, AverageOpenPrice = x.CostOpen, Leverage = x.LeverageRate, + PositionMode = x.PositionMode == PositionMode.SingleSide ? SharedPositionMode.OneWay : SharedPositionMode.HedgeMode, PositionSide = x.Side == OrderSide.Sell ? SharedPositionSide.Short : SharedPositionSide.Long }).ToArray()); } @@ -757,6 +795,7 @@ async Task> IFuturesOrderRestClient.GetPosit UnrealizedPnl = x.UnrealizedPnl, AverageOpenPrice = x.CostOpen, Leverage = x.LeverageRate, + PositionMode = x.PositionMode == PositionMode.SingleSide ? SharedPositionMode.OneWay : SharedPositionMode.HedgeMode, PositionSide = x.Side == OrderSide.Sell ? SharedPositionSide.Short : SharedPositionSide.Long }).ToArray()); } diff --git a/HTX.Net/Clients/UsdtFutures/HTXSocketClientUsdtFuturesApiShared.cs b/HTX.Net/Clients/UsdtFutures/HTXSocketClientUsdtFuturesApiShared.cs index 2b079fe7..45324130 100644 --- a/HTX.Net/Clients/UsdtFutures/HTXSocketClientUsdtFuturesApiShared.cs +++ b/HTX.Net/Clients/UsdtFutures/HTXSocketClientUsdtFuturesApiShared.cs @@ -131,7 +131,7 @@ async Task> IBalanceSocketClient.SubscribeToB if (marginMode == SharedMarginMode.Cross) { var result = await SubscribeToCrossMarginBalanceUpdatesAsync( - update => handler(update.ToType(update.Data.Data.Select(x => new SharedBalance(x.MarginAsset, x.MarginBalance - x.MarginFrozen, x.MarginBalance) ).ToArray())), + update => handler(update.ToType(update.Data.Data.Select(x => new SharedBalance(x.MarginAsset, x.WithdrawAvailable, x.MarginBalance) ).ToArray())), ct: ct).ConfigureAwait(false); return new ExchangeResult(Exchange, result); @@ -139,7 +139,7 @@ async Task> IBalanceSocketClient.SubscribeToB else { var result = await SubscribeToIsolatedMarginBalanceUpdatesAsync( - update => handler(update.ToType(update.Data.Data.Select(x => new SharedBalance(x.Asset, x.MarginBalance - x.MarginFrozen, x.MarginBalance) { IsolatedMarginSymbol = x.MarginAccount }).ToArray())), + update => handler(update.ToType(update.Data.Data.Select(x => new SharedBalance("USDT", x.WithdrawAvailable, x.MarginBalance) { IsolatedMarginSymbol = x.MarginAccount }).ToArray())), ct: ct).ConfigureAwait(false); return new ExchangeResult(Exchange, result); @@ -326,6 +326,7 @@ async Task> IPositionSocketClient.SubscribeTo ExchangeSymbolCache.ParseSymbol(_topicId, x.ContractCode), x.ContractCode, x.Quantity, update.Data.Timestamp) { AverageOpenPrice = x.PositionPrice, + PositionMode = x.PositionMode == PositionMode.SingleSide ? SharedPositionMode.OneWay : SharedPositionMode.HedgeMode, PositionSide = x.OrderSide == Enums.OrderSide.Sell ? SharedPositionSide.Short : SharedPositionSide.Long, Leverage = x.LeverageRate, UnrealizedPnl = x.UnrealizedPnl @@ -339,6 +340,7 @@ async Task> IPositionSocketClient.SubscribeTo update => handler(update.ToType(update.Data.Data.Select(x => new SharedPosition(ExchangeSymbolCache.ParseSymbol(_topicId, x.ContractCode), x.ContractCode, x.Quantity, update.Data.Timestamp) { AverageOpenPrice = x.PositionPrice, + PositionMode = x.PositionMode == PositionMode.SingleSide ? SharedPositionMode.OneWay : SharedPositionMode.HedgeMode, PositionSide = x.OrderSide == Enums.OrderSide.Sell ? SharedPositionSide.Short : SharedPositionSide.Long, Leverage = x.LeverageRate, UnrealizedPnl = x.UnrealizedPnl diff --git a/HTX.Net/Enums/EventTrigger.cs b/HTX.Net/Enums/EventTrigger.cs index 25de077b..d328b63e 100644 --- a/HTX.Net/Enums/EventTrigger.cs +++ b/HTX.Net/Enums/EventTrigger.cs @@ -62,6 +62,11 @@ public enum EventTrigger /// Snapshot /// [Map("snapshot")] - Snapshot + Snapshot, + /// + /// Close order + /// + [Map("order.close")] + Close } } diff --git a/HTX.Net/HTX.Net.csproj b/HTX.Net/HTX.Net.csproj index 25a06fc6..66d562d2 100644 --- a/HTX.Net/HTX.Net.csproj +++ b/HTX.Net/HTX.Net.csproj @@ -1,4 +1,4 @@ - + net8.0;net9.0;net10.0;netstandard2.0;netstandard2.1 enable @@ -52,7 +52,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/HTX.Net/HTX.Net.xml b/HTX.Net/HTX.Net.xml index 51221be0..099dd2fa 100644 --- a/HTX.Net/HTX.Net.xml +++ b/HTX.Net/HTX.Net.xml @@ -6107,6 +6107,11 @@ Snapshot + + + Close order + + Fee deduction status. @@ -8019,6 +8024,37 @@ + + + + + + + + + + + + + + + + + + ctor + + + + + + + + + + + ctor + + Client for accessing the HTX API. @@ -11258,6 +11294,38 @@ 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 + Margin mode + Environment + + + + Create a new futures user data tracker + + Configuration + Margin mode + Api addresses usable for the HTX clients diff --git a/HTX.Net/HTXTrackerFactory.cs b/HTX.Net/HTXTrackerFactory.cs index f1c6c96e..2d8741fb 100644 --- a/HTX.Net/HTXTrackerFactory.cs +++ b/HTX.Net/HTXTrackerFactory.cs @@ -1,10 +1,13 @@ 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 HTX.Net.Clients; using HTX.Net.Interfaces; using HTX.Net.Interfaces.Clients; using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging.Abstractions; namespace HTX.Net { @@ -98,5 +101,68 @@ public ITradeTracker CreateTradeTracker(SharedSymbol symbol, int? limit = null, period ); } + /// + public IUserSpotDataTracker CreateUserSpotDataTracker(SpotUserDataTrackerConfig? config = null) + { + var restClient = _serviceProvider?.GetRequiredService() ?? new HTXRestClient(); + var socketClient = _serviceProvider?.GetRequiredService() ?? new HTXSocketClient(); + return new HTXUserSpotDataTracker( + _serviceProvider?.GetRequiredService>() ?? new NullLogger(), + restClient, + socketClient, + null, + config + ); + } + + /// + public IUserSpotDataTracker CreateUserSpotDataTracker(string userIdentifier, ApiCredentials credentials, SpotUserDataTrackerConfig? config = null, HTXEnvironment? environment = null) + { + var clientProvider = _serviceProvider?.GetRequiredService() ?? new HTXUserClientProvider(); + var restClient = clientProvider.GetRestClient(userIdentifier, credentials, environment); + var socketClient = clientProvider.GetSocketClient(userIdentifier, credentials, environment); + return new HTXUserSpotDataTracker( + _serviceProvider?.GetRequiredService>() ?? new NullLogger(), + restClient, + socketClient, + userIdentifier, + config + ); + } + + /// + public IUserFuturesDataTracker CreateUserFuturesDataTracker(SharedMarginMode marginMode, FuturesUserDataTrackerConfig? config = null) + { + var exchangeParams = new ExchangeParameters(new ExchangeParameter("HTX", "MarginMode", marginMode)); + + var restClient = _serviceProvider?.GetRequiredService() ?? new HTXRestClient(); + var socketClient = _serviceProvider?.GetRequiredService() ?? new HTXSocketClient(); + return new HTXUserFuturesDataTracker( + _serviceProvider?.GetRequiredService>() ?? new NullLogger(), + restClient, + socketClient, + null, + config, + exchangeParams + ); + } + + /// + public IUserFuturesDataTracker CreateUserFuturesDataTracker(string userIdentifier, ApiCredentials credentials, SharedMarginMode marginMode, FuturesUserDataTrackerConfig? config = null, HTXEnvironment? environment = null) + { + var exchangeParams = new ExchangeParameters(new ExchangeParameter("HTX", "MarginMode", marginMode)); + + var clientProvider = _serviceProvider?.GetRequiredService() ?? new HTXUserClientProvider(); + var restClient = clientProvider.GetRestClient(userIdentifier, credentials, environment); + var socketClient = clientProvider.GetSocketClient(userIdentifier, credentials, environment); + return new HTXUserFuturesDataTracker( + _serviceProvider?.GetRequiredService>() ?? new NullLogger(), + restClient, + socketClient, + userIdentifier, + config, + exchangeParams + ); + } } } diff --git a/HTX.Net/HTXUserDataTracker.cs b/HTX.Net/HTXUserDataTracker.cs new file mode 100644 index 00000000..7c1384f6 --- /dev/null +++ b/HTX.Net/HTXUserDataTracker.cs @@ -0,0 +1,65 @@ +using HTX.Net.Interfaces.Clients; +using CryptoExchange.Net.SharedApis; +using CryptoExchange.Net.Trackers.UserData; +using CryptoExchange.Net.Trackers.UserData.Objects; +using Microsoft.Extensions.Logging; + +namespace HTX.Net +{ + /// + public class HTXUserSpotDataTracker : UserSpotDataTracker + { + /// + /// ctor + /// + public HTXUserSpotDataTracker( + ILogger logger, + IHTXRestClient restClient, + IHTXSocketClient socketClient, + string? userIdentifier, + SpotUserDataTrackerConfig? config) : base( + logger, + restClient.SpotApi.SharedClient, + null, + restClient.SpotApi.SharedClient, + socketClient.SpotApi.SharedClient, + restClient.SpotApi.SharedClient, + socketClient.SpotApi.SharedClient, + socketClient.SpotApi.SharedClient, + userIdentifier, + config ?? new SpotUserDataTrackerConfig()) + { + } + } + + /// + public class HTXUserFuturesDataTracker : UserFuturesDataTracker + { + /// + protected override bool WebsocketPositionUpdatesAreFullSnapshots => true; + + /// + /// ctor + /// + public HTXUserFuturesDataTracker( + ILogger logger, + IHTXRestClient restClient, + IHTXSocketClient socketClient, + string? userIdentifier, + FuturesUserDataTrackerConfig? config, + ExchangeParameters? exchangeParameters) : base(logger, + restClient.UsdtFuturesApi.SharedClient, + null, + restClient.UsdtFuturesApi.SharedClient, + socketClient.UsdtFuturesApi.SharedClient, + restClient.UsdtFuturesApi.SharedClient, + socketClient.UsdtFuturesApi.SharedClient, + socketClient.UsdtFuturesApi.SharedClient, + socketClient.UsdtFuturesApi.SharedClient, + userIdentifier, + config ?? new FuturesUserDataTrackerConfig(), + exchangeParameters: exchangeParameters) + { + } + } +} diff --git a/HTX.Net/Interfaces/IHTXTrackerFactory.cs b/HTX.Net/Interfaces/IHTXTrackerFactory.cs index 091bfbd8..4c230173 100644 --- a/HTX.Net/Interfaces/IHTXTrackerFactory.cs +++ b/HTX.Net/Interfaces/IHTXTrackerFactory.cs @@ -1,9 +1,42 @@ -namespace HTX.Net.Interfaces +using CryptoExchange.Net.SharedApis; +using CryptoExchange.Net.Trackers.UserData.Interfaces; +using CryptoExchange.Net.Trackers.UserData.Objects; + +namespace HTX.Net.Interfaces { /// /// Tracker factory /// public interface IHTXTrackerFactory : ITrackerFactory { + /// + /// Create a new Spot user data tracker + /// + /// User identifier + /// Configuration + /// Credentials + /// Environment + IUserSpotDataTracker CreateUserSpotDataTracker(string userIdentifier, ApiCredentials credentials, SpotUserDataTrackerConfig? config = null, HTXEnvironment? 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 + /// Margin mode + /// Environment + IUserFuturesDataTracker CreateUserFuturesDataTracker(string userIdentifier, ApiCredentials credentials, SharedMarginMode marginMode, FuturesUserDataTrackerConfig? config = null, HTXEnvironment? environment = null); + /// + /// Create a new futures user data tracker + /// + /// Configuration + /// Margin mode + IUserFuturesDataTracker CreateUserFuturesDataTracker(SharedMarginMode marginMode, FuturesUserDataTrackerConfig? config = null); } }