From ee8a07d510ffde837730eee41a11031b24319a80 Mon Sep 17 00:00:00 2001 From: Jkorf Date: Fri, 12 Jun 2026 10:38:29 +0200 Subject: [PATCH 01/12] wip --- .../DeepCoinRestClientTests.cs | 4 +- DeepCoin.Net.UnitTests/RestRequestTests.cs | 2 +- .../DeepCoinRestClientExchangeApi.cs | 30 +- .../DeepCoinRestClientExchangeApiAccount.cs | 137 ++--- ...epCoinRestClientExchangeApiExchangeData.cs | 50 +- .../DeepCoinRestClientExchangeApiShared.cs | 568 +++++++++--------- .../DeepCoinRestClientExchangeApiTrading.cs | 176 +++--- .../DeepCoinSocketClientExchangeApi.cs | 12 +- .../DeepCoinSocketClientExchangeApiShared.cs | 85 ++- DeepCoin.Net/DeepCoin.Net.csproj | 4 +- .../DeepCoinAuthenticationProvider.cs | 4 +- DeepCoin.Net/DeepCoinExchange.cs | 5 + .../IDeepCoinRestClientExchangeApiAccount.cs | 20 +- ...epCoinRestClientExchangeApiExchangeData.cs | 10 +- .../IDeepCoinRestClientExchangeApiTrading.cs | 24 +- .../IDeepCoinSocketClientExchangeApi.cs | 10 +- .../Objects/Sockets/DeepCoinPingQuery.cs | 2 +- DeepCoin.Net/Objects/Sockets/DeepCoinQuery.cs | 6 +- .../Subscriptions/DeepCoinBookSubscription.cs | 6 +- .../Subscriptions/DeepCoinSubscription.cs | 4 +- .../Subscriptions/DeepCoinUserSubscription.cs | 24 +- .../DeepCoinSymbolOrderBook.cs | 13 +- 22 files changed, 584 insertions(+), 612 deletions(-) diff --git a/DeepCoin.Net.UnitTests/DeepCoinRestClientTests.cs b/DeepCoin.Net.UnitTests/DeepCoinRestClientTests.cs index 843ce19..223857e 100644 --- a/DeepCoin.Net.UnitTests/DeepCoinRestClientTests.cs +++ b/DeepCoin.Net.UnitTests/DeepCoinRestClientTests.cs @@ -5,6 +5,7 @@ using System.Net.Http; using DeepCoin.Net.Clients; using CryptoExchange.Net.Converters.SystemTextJson; +using CryptoExchange.Net.Objects; namespace DeepCoin.Net.UnitTests { @@ -27,12 +28,11 @@ public void CheckSignatureExample1() return headers["DC-ACCESS-SIGN"].ToString(); }, "Vf4Agvnq70YtbqrjZVCcJUmbqgK8L6ONwb5ldafaptQ=", - new Dictionary + new Parameters(DeepCoinExchange._parameterSerializationSettings) { { "symbol", "LTCBTC" }, }, DateTimeConverter.ParseFromDouble(1499827320559), - true, false); } diff --git a/DeepCoin.Net.UnitTests/RestRequestTests.cs b/DeepCoin.Net.UnitTests/RestRequestTests.cs index 91d5dde..083099c 100644 --- a/DeepCoin.Net.UnitTests/RestRequestTests.cs +++ b/DeepCoin.Net.UnitTests/RestRequestTests.cs @@ -74,7 +74,7 @@ public async Task ValidateTradingCalls() await tester.ValidateAsync(client => client.ExchangeApi.Trading.SetTpSlAsync("123"), "SetTpSl"); } - private bool IsAuthenticated(WebCallResult result) + private bool IsAuthenticated(IHttpResult result) { return result.RequestHeaders.Any(x => x.Key == "DC-ACCESS-SIGN"); } diff --git a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApi.cs b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApi.cs index d388ea3..7b27f5c 100644 --- a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApi.cs +++ b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApi.cs @@ -43,7 +43,7 @@ internal partial class DeepCoinRestClientExchangeApi : RestApiClient new DeepCoinAuthenticationProvider(credentials); - internal Task SendAsync(RequestDefinition definition, ParameterCollection? parameters, CancellationToken cancellationToken, int? weight = null) - => SendToAddressAsync(BaseAddress, definition, parameters, cancellationToken, weight); - - internal async Task SendToAddressAsync(string baseAddress, RequestDefinition definition, ParameterCollection? parameters, CancellationToken cancellationToken, int? weight = null) + internal async Task SendAsync(RequestDefinition definition, Parameters? parameters, CancellationToken cancellationToken, int? weight = null) { - var result = await base.SendAsync(baseAddress, definition, parameters, cancellationToken, null, weight).ConfigureAwait(false); - - // Optional response checking - - return result; + return await base.SendAsync(definition, parameters, cancellationToken, null, weight).ConfigureAwait(false); } - internal Task> SendAsync(RequestDefinition definition, ParameterCollection? parameters, CancellationToken cancellationToken, int? weight = null) where T : class - => SendToAddressAsync(BaseAddress, definition, parameters, cancellationToken, weight); - - internal async Task> SendToAddressAsync(string baseAddress, RequestDefinition definition, ParameterCollection? parameters, CancellationToken cancellationToken, int? weight = null) where T : class + internal async Task> SendAsync(RequestDefinition definition, Parameters? parameters, CancellationToken cancellationToken, int? weight = null) { - var result = await base.SendAsync>(baseAddress, definition, parameters, cancellationToken, null, weight).ConfigureAwait(false); - if (!result) - return result.As(default); + var result = await base.SendAsync>(definition, parameters, cancellationToken, null, weight).ConfigureAwait(false); + if (!result.Success) + return HttpResult.Fail(result); if (result.Data.Code != 0) - return result.AsError(new ServerError(result.Data.Code, GetErrorInfo(result.Data.Code, result.Data.Message!))); + return HttpResult.Fail(result, new ServerError(result.Data.Code, GetErrorInfo(result.Data.Code, result.Data.Message!))); - return result.As(result.Data.Data); + return HttpResult.Ok(result, result.Data.Data!); } /// - protected override Task> GetServerTimestampAsync() => throw new NotImplementedException(); + protected override Task> GetServerTimestampAsync() => throw new NotImplementedException(); /// diff --git a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiAccount.cs b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiAccount.cs index 9582982..4715897 100644 --- a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiAccount.cs +++ b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiAccount.cs @@ -24,12 +24,12 @@ internal DeepCoinRestClientExchangeApiAccount(DeepCoinRestClientExchangeApi base #region Get Balances /// - public async Task> GetBalancesAsync(SymbolType accountType, string? asset = null, CancellationToken ct = default) + public async Task> GetBalancesAsync(SymbolType accountType, string? asset = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddEnum("instType", accountType); - parameters.AddOptional("ccy", asset); - var request = _definitions.GetOrCreate(HttpMethod.Get, "deepcoin/account/balances", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); + var parameters = new Parameters(DeepCoinExchange._parameterSerializationSettings); + parameters.Add("instType", accountType); + parameters.Add("ccy", asset); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "deepcoin/account/balances", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -39,16 +39,16 @@ public async Task> GetBalancesAsync(SymbolType #region Get Bills /// - public async Task> GetBillsAsync(SymbolType symbolType, string? asset = null, BillType? billType = null, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, CancellationToken ct = default) + public async Task> GetBillsAsync(SymbolType symbolType, string? asset = null, BillType? billType = null, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddEnum("instType", symbolType); - parameters.AddOptional("ccy", asset); - parameters.AddOptionalEnum("type", billType); - parameters.AddOptionalMillisecondsString("after", startTime); - parameters.AddOptionalMillisecondsString("before", endTime); - parameters.AddOptional("limit", limit); - var request = _definitions.GetOrCreate(HttpMethod.Get, "/deepcoin/account/bills", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); + var parameters = new Parameters(DeepCoinExchange._parameterSerializationSettings); + parameters.Add("instType", symbolType); + parameters.Add("ccy", asset); + parameters.Add("type", billType); + parameters.Add("after", startTime); + parameters.Add("before", endTime); + parameters.Add("limit", limit); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "/deepcoin/account/bills", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -58,17 +58,20 @@ public async Task> GetBillsAsync(SymbolType symbol #region Set Leverage /// - public async Task> SetLeverageAsync(string symbol, decimal leverage, TradeMode tradeMode, PositionType positionType, CancellationToken ct = default) + public async Task> SetLeverageAsync(string symbol, decimal leverage, TradeMode tradeMode, PositionType positionType, CancellationToken ct = default) { - var parameters = new ParameterCollection(); + var parameters = new Parameters(DeepCoinExchange._parameterSerializationSettings); parameters.Add("instId", symbol); parameters.Add("lever", leverage); - parameters.AddEnum("mgnMode", tradeMode); - parameters.AddEnum("mrgPosition", positionType); - var request = _definitions.GetOrCreate(HttpMethod.Post, "/deepcoin/account/set-leverage", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); + parameters.Add("mgnMode", tradeMode); + parameters.Add("mrgPosition", positionType); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, "/deepcoin/account/set-leverage", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); + if (!result.Success) + return HttpResult.Fail(result); + if (result.Data.ResultCode != 0) - return result.AsError(new ServerError(result.Data.ResultCode, _baseClient.GetErrorInfo(result.Data.ResultCode, result.Data.ResultMessage!))); + return HttpResult.Fail(result, new ServerError(result.Data.ResultCode, _baseClient.GetErrorInfo(result.Data.ResultCode, result.Data.ResultMessage!))); return result; } @@ -79,10 +82,10 @@ public async Task> SetLeverageAsync(string symbo //#region Get Transferable Assets ///// - //public async Task> GetTransferableAssetsAsync(CancellationToken ct = default) + //public async Task> GetTransferableAssetsAsync(CancellationToken ct = default) //{ - // var parameters = new ParameterCollection(); - // var request = _definitions.GetOrCreate(HttpMethod.Get, "/deepcoin/internal-transfer/support", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); + // var parameters = new Parameters(DeepCoinExchange._parameterSerializationSettings); + // var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "/deepcoin/internal-transfer/support", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); // var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); // return result; //} @@ -92,15 +95,15 @@ public async Task> SetLeverageAsync(string symbo //#region Transfer ///// - //public async Task> TransferAsync(string asset, decimal quantity, string toAccount, AccountType toAccountType, string? clientOrderId = null, CancellationToken ct = default) + //public async Task> TransferAsync(string asset, decimal quantity, string toAccount, AccountType toAccountType, string? clientOrderId = null, CancellationToken ct = default) //{ - // var parameters = new ParameterCollection(); + // var parameters = new Parameters(DeepCoinExchange._parameterSerializationSettings); // parameters.Add("coin", asset); - // parameters.AddString("amount", quantity); + // parameters.Add("amount", quantity); // parameters.Add("receiverAccount", toAccount); - // parameters.AddEnum("accountType", toAccountType); - // parameters.AddOptional("receiverUID", clientOrderId); - // var request = _definitions.GetOrCreate(HttpMethod.Post, "/deepcoin/internal-transfer", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); + // parameters.Add("accountType", toAccountType); + // parameters.Add("receiverUID", clientOrderId); + // var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, "/deepcoin/internal-transfer", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); // var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); // return result; //} @@ -110,19 +113,19 @@ public async Task> SetLeverageAsync(string symbo //#region Get Transfer History ///// - //public async Task> GetTransferHistoryAsync(string? toAccount = null, string? asset = null, TransferStatus? status = null, string? receiverId = null, string? orderId = null, DateTime? startTime = null, DateTime? endTime = null, int? page = null, int? pageSize = null, CancellationToken ct = default) + //public async Task> GetTransferHistoryAsync(string? toAccount = null, string? asset = null, TransferStatus? status = null, string? receiverId = null, string? orderId = null, DateTime? startTime = null, DateTime? endTime = null, int? page = null, int? pageSize = null, CancellationToken ct = default) //{ - // var parameters = new ParameterCollection(); - // parameters.AddOptional("account", toAccount); - // parameters.AddOptional("coin", asset); - // parameters.AddOptionalEnum("status", status); - // parameters.AddOptional("receiverUID", receiverId); - // parameters.AddOptional("orderId", orderId); - // parameters.AddOptionalMillisecondsString("startTime", startTime); - // parameters.AddOptionalMillisecondsString("endTime", endTime); - // parameters.AddOptional("page", page); - // parameters.AddOptional("size", pageSize); - // var request = _definitions.GetOrCreate(HttpMethod.Get, "/deepcoin/internal-transfer/history-order", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); + // var parameters = new Parameters(DeepCoinExchange._parameterSerializationSettings); + // parameters.Add("account", toAccount); + // parameters.Add("coin", asset); + // parameters.Add("status", status); + // parameters.Add("receiverUID", receiverId); + // parameters.Add("orderId", orderId); + // parameters.Add("startTime", startTime); + // parameters.Add("endTime", endTime); + // parameters.Add("page", page); + // parameters.Add("size", pageSize); + // var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "/deepcoin/internal-transfer/history-order", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); // var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); // return result; //} @@ -132,18 +135,18 @@ public async Task> SetLeverageAsync(string symbo #region Get Deposit History /// - public async Task> GetDepositHistoryAsync(string? asset = null, string? transactionHash = null, DateTime? startTime = null, DateTime? endTime = null, int? page = null, int? pageSize = null, CancellationToken ct = default) + public async Task> GetDepositHistoryAsync(string? asset = null, string? transactionHash = null, DateTime? startTime = null, DateTime? endTime = null, int? page = null, int? pageSize = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddOptional("coin", asset); - parameters.AddOptional("txHash", transactionHash); - parameters.AddOptionalSecondsString("startTime", startTime); - parameters.AddOptionalSecondsString("endTime", endTime); - parameters.AddOptional("page", page); - parameters.AddOptional("size", pageSize); - var request = _definitions.GetOrCreate(HttpMethod.Get, "/deepcoin/asset/deposit-list", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); + var parameters = new Parameters(DeepCoinExchange._parameterSerializationSettings); + parameters.Add("coin", asset); + parameters.Add("txHash", transactionHash); + parameters.Add("startTime", startTime, DateTimeSerialization.SecondsString); + parameters.Add("endTime", endTime, DateTimeSerialization.SecondsString); + parameters.Add("page", page); + parameters.Add("size", pageSize); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "/deepcoin/asset/deposit-list", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); - if (!result) + if (!result.Success) return result; if (result.Data.Data == null) @@ -157,18 +160,18 @@ public async Task> GetDepositHistoryAsync(str #region Get Withdraw History /// - public async Task> GetWithdrawHistoryAsync(string? asset = null, string? transactionHash = null, DateTime? startTime = null, DateTime? endTime = null, int? page = null, int? pageSize = null, CancellationToken ct = default) + public async Task> GetWithdrawHistoryAsync(string? asset = null, string? transactionHash = null, DateTime? startTime = null, DateTime? endTime = null, int? page = null, int? pageSize = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddOptional("coin", asset); - parameters.AddOptional("txHash", transactionHash); - parameters.AddOptionalSecondsString("startTime", startTime); - parameters.AddOptionalSecondsString("endTime", endTime); - parameters.AddOptional("page", page); - parameters.AddOptional("size", pageSize); - var request = _definitions.GetOrCreate(HttpMethod.Get, "/deepcoin/asset/withdraw-list", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); + var parameters = new Parameters(DeepCoinExchange._parameterSerializationSettings); + parameters.Add("coin", asset); + parameters.Add("txHash", transactionHash); + parameters.Add("startTime", startTime, DateTimeSerialization.SecondsString); + parameters.Add("endTime", endTime, DateTimeSerialization.SecondsString); + parameters.Add("page", page); + parameters.Add("size", pageSize); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "/deepcoin/asset/withdraw-list", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); - if (!result) + if (!result.Success) return result; if (result.Data.Data == null) @@ -182,10 +185,10 @@ public async Task> GetWithdrawHistoryAsync(s #region Start User Stream /// - public async Task> StartUserStreamAsync(CancellationToken ct = default) + public async Task> StartUserStreamAsync(CancellationToken ct = default) { - var parameters = new ParameterCollection(); - var request = _definitions.GetOrCreate(HttpMethod.Get, "/deepcoin/listenkey/acquire", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); + var parameters = new Parameters(DeepCoinExchange._parameterSerializationSettings); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "/deepcoin/listenkey/acquire", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -195,11 +198,11 @@ public async Task> StartUserStreamAsync(Cancell #region Keep Alive User Stream /// - public async Task> KeepAliveUserStreamAsync(string listenKey, CancellationToken ct = default) + public async Task> KeepAliveUserStreamAsync(string listenKey, CancellationToken ct = default) { - var parameters = new ParameterCollection(); + var parameters = new Parameters(DeepCoinExchange._parameterSerializationSettings); parameters.Add("listenkey", listenKey); - var request = _definitions.GetOrCreate(HttpMethod.Get, "/deepcoin/listenkey/extend", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "/deepcoin/listenkey/extend", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } diff --git a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiExchangeData.cs b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiExchangeData.cs index bb833c5..fba47fe 100644 --- a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiExchangeData.cs +++ b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiExchangeData.cs @@ -24,13 +24,13 @@ internal DeepCoinRestClientExchangeApiExchangeData(ILogger logger, DeepCoinRestC #region Get Tickers /// - public async Task> GetTickersAsync(SymbolType symbolType, string? underlying = null, CancellationToken ct = default) + public async Task> GetTickersAsync(SymbolType symbolType, string? underlying = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddEnum("instType", symbolType); - parameters.AddOptional("uly", underlying); + var parameters = new Parameters(DeepCoinExchange._parameterSerializationSettings); + parameters.Add("instType", symbolType); + parameters.Add("uly", underlying); - var request = _definitions.GetOrCreate(HttpMethod.Get, "deepcoin/market/tickers", DeepCoinExchange.RateLimiter.DeepCoin, 1, false); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "deepcoin/market/tickers", DeepCoinExchange.RateLimiter.DeepCoin, 1, false); return await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); } #endregion @@ -38,13 +38,13 @@ public async Task> GetTickersAsync(SymbolType sy #region Get Symbols /// - public async Task> GetSymbolsAsync(SymbolType type, string? underlying = null, string? symbol = null, CancellationToken ct = default) + public async Task> GetSymbolsAsync(SymbolType type, string? underlying = null, string? symbol = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddEnum("instType", type); - parameters.AddOptional("uly", underlying); - parameters.AddOptional("instId", symbol); - var request = _definitions.GetOrCreate(HttpMethod.Get, "/deepcoin/market/instruments", DeepCoinExchange.RateLimiter.DeepCoin, 1, false, limitGuard: new SingleLimitGuard(5, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); + var parameters = new Parameters(DeepCoinExchange._parameterSerializationSettings); + parameters.Add("instType", type); + parameters.Add("uly", underlying); + parameters.Add("instId", symbol); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "/deepcoin/market/instruments", DeepCoinExchange.RateLimiter.DeepCoin, 1, false, limitGuard: new SingleLimitGuard(5, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -54,14 +54,14 @@ public async Task> GetSymbolsAsync(SymbolType ty #region Get Klines /// - public async Task> GetKlinesAsync(string symbol, KlineInterval interval, DateTime? endTime = null, int? limit = null, CancellationToken ct = default) + public async Task> GetKlinesAsync(string symbol, KlineInterval interval, DateTime? endTime = null, int? limit = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); + var parameters = new Parameters(DeepCoinExchange._parameterSerializationSettings); parameters.Add("instId", symbol); - parameters.AddEnum("bar", interval); - parameters.AddOptionalMilliseconds("after", endTime); - parameters.AddOptional("limit", limit); - var request = _definitions.GetOrCreate(HttpMethod.Get, "deepcoin/market/candles", DeepCoinExchange.RateLimiter.DeepCoin, 1, false, limitGuard: new SingleLimitGuard(5, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); + parameters.Add("bar", interval); + parameters.Add("after", endTime); + parameters.Add("limit", limit); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "deepcoin/market/candles", DeepCoinExchange.RateLimiter.DeepCoin, 1, false, limitGuard: new SingleLimitGuard(5, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -71,12 +71,12 @@ public async Task> GetKlinesAsync(string symbol, #region Get Order Book /// - public async Task> GetOrderBookAsync(string symbol, int? depth = null, CancellationToken ct = default) + public async Task> GetOrderBookAsync(string symbol, int? depth = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); + var parameters = new Parameters(DeepCoinExchange._parameterSerializationSettings); parameters.Add("instId", symbol); parameters.Add("sz", depth ?? 20); - var request = _definitions.GetOrCreate(HttpMethod.Get, "deepcoin/market/books", DeepCoinExchange.RateLimiter.DeepCoin, 1, false, limitGuard: new SingleLimitGuard(5, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "deepcoin/market/books", DeepCoinExchange.RateLimiter.DeepCoin, 1, false, limitGuard: new SingleLimitGuard(5, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -86,12 +86,12 @@ public async Task> GetOrderBookAsync(string sym #region Get Funding Rate /// - public async Task> GetFundingRateAsync(ProductGroup contractType, string? symbol = null, CancellationToken ct = default) + public async Task> GetFundingRateAsync(ProductGroup contractType, string? symbol = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddEnum("instType", contractType); - parameters.AddOptional("instId", symbol); - var request = _definitions.GetOrCreate(HttpMethod.Get, "/deepcoin/trade/funding-rate", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); + var parameters = new Parameters(DeepCoinExchange._parameterSerializationSettings); + parameters.Add("instType", contractType); + parameters.Add("instId", symbol); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "/deepcoin/trade/funding-rate", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } diff --git a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiShared.cs b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiShared.cs index bdfb2b5..808a86f 100644 --- a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiShared.cs +++ b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiShared.cs @@ -17,50 +17,51 @@ internal partial class DeepCoinRestClientExchangeApi : IDeepCoinRestClientExchan { private const string _topicSpotId = "DeepCoinSpot"; private const string _topicFuturesId = "DeepCoinFutures"; - public string Exchange => "DeepCoin"; + private const string _exchangeName = "DeepCoin"; public TradingMode[] SupportedTradingModes => new[] { TradingMode.Spot, TradingMode.PerpetualLinear, TradingMode.PerpetualInverse }; public void SetDefaultExchangeParameter(string key, object value) => ExchangeParameters.SetStaticParameter(Exchange, key, value); public void ResetDefaultExchangeParameters() => ExchangeParameters.ResetStaticParameters(); + public SharedClientInfo Discover() => SharedUtils.GetClientInfo(this); #region Balance client - GetBalancesOptions IBalanceRestClient.GetBalancesOptions { get; } = new GetBalancesOptions(AccountTypeFilter.Spot, AccountTypeFilter.Futures); + GetBalancesOptions IBalanceRestClient.GetBalancesOptions { get; } = new GetBalancesOptions(_exchangeName, AccountTypeFilter.Spot, AccountTypeFilter.Futures); - async Task> IBalanceRestClient.GetBalancesAsync(GetBalancesRequest request, CancellationToken ct) + async Task> IBalanceRestClient.GetBalancesAsync(GetBalancesRequest request, CancellationToken ct) { - var validationError = ((IBalanceRestClient)this).GetBalancesOptions.ValidateRequest(Exchange, request, SupportedTradingModes); + var validationError = SharedClient.GetBalancesOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var type = (request.AccountType == null || request.AccountType == SharedAccountType.Spot) ? Enums.SymbolType.Spot : Enums.SymbolType.Swap; var result = await Account.GetBalancesAsync(type, ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); - return result.AsExchangeResult(Exchange, type == SymbolType.Spot ? [TradingMode.Spot] : SupportedTradingModes.Where(x => x != TradingMode.Spot).ToArray(), result.Data.Select(x => new SharedBalance(x.Asset, x.AvailableBalance, x.Balance)).ToArray()); + return HttpResult.Ok(result, result.Data.Select(x => new SharedBalance(x.Asset, x.AvailableBalance, x.Balance)).ToArray()); } #endregion #region Deposit client - EndpointOptions IDepositRestClient.GetDepositAddressesOptions { get; } = new EndpointOptions(true) + GetDepositAddressesOptions IDepositRestClient.GetDepositAddressesOptions { get; } = new GetDepositAddressesOptions(_exchangeName, true) { RequestNotes = "Deposit addresses request not available in API" }; - Task> IDepositRestClient.GetDepositAddressesAsync(GetDepositAddressesRequest request, CancellationToken ct) + Task> IDepositRestClient.GetDepositAddressesAsync(GetDepositAddressesRequest request, CancellationToken ct) { - return Task.FromResult(new ExchangeWebResult(Exchange, new InvalidOperationError("Deposit addresses request not available in API"))); + return Task.FromResult(HttpResult.Fail(Exchange, new InvalidOperationError("Deposit addresses request not available in API"))); } - GetDepositsOptions IDepositRestClient.GetDepositsOptions { get; } = new GetDepositsOptions(false, true, true, 50); - async Task> IDepositRestClient.GetDepositsAsync(GetDepositsRequest request, PageRequest? pageRequest, CancellationToken ct) + GetDepositsOptions IDepositRestClient.GetDepositsOptions { get; } = new GetDepositsOptions(_exchangeName, false, true, true, 50); + async Task> IDepositRestClient.GetDepositsAsync(GetDepositsRequest request, PageRequest? pageRequest, CancellationToken ct) { - var validationError = ((IDepositRestClient)this).GetDepositsOptions.ValidateRequest(Exchange, request, TradingMode.Spot, SupportedTradingModes); + var validationError = SharedClient.GetDepositsOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); int limit = request.Limit ?? 50; var direction = DataDirection.Descending; @@ -75,8 +76,8 @@ async Task> IDepositRestClient.GetDepositsAsy pageSize: pageParams.Limit, page: pageParams.Page, ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); var nextPageRequest = Pagination.GetNextPageRequest( () => Pagination.NextPageFromPage(pageParams), @@ -86,9 +87,7 @@ async Task> IDepositRestClient.GetDepositsAsy request.EndTime ?? DateTime.UtcNow, pageParams); - return result.AsExchangeResult( - Exchange, - TradingMode.Spot, + return HttpResult.Ok(result, ExchangeHelpers.ApplyFilter(result.Data.Data, x => x.CreateTime, request.StartTime, request.EndTime, direction) .Select(x => new SharedDeposit( @@ -118,7 +117,7 @@ private SharedTransferStatus ParseTransferStatus(DepositStatus depositStatus) #region Kline client - GetKlinesOptions IKlineRestClient.GetKlinesOptions { get; } = new GetKlinesOptions(false, true, true, 300, false, + GetKlinesOptions IKlineRestClient.GetKlinesOptions { get; } = new GetKlinesOptions(_exchangeName, false, true, true, 300, false, SharedKlineInterval.OneMinute, SharedKlineInterval.FiveMinutes, SharedKlineInterval.FifteenMinutes, @@ -131,15 +130,12 @@ private SharedTransferStatus ParseTransferStatus(DepositStatus depositStatus) SharedKlineInterval.OneMonth ); - async Task> IKlineRestClient.GetKlinesAsync(GetKlinesRequest request, PageRequest? pageRequest, CancellationToken ct) + async Task> IKlineRestClient.GetKlinesAsync(GetKlinesRequest request, PageRequest? pageRequest, CancellationToken ct) { - var interval = (Enums.KlineInterval)request.Interval; - if (!Enum.IsDefined(typeof(Enums.KlineInterval), interval)) - return new ExchangeWebResult(Exchange, ArgumentError.Invalid(nameof(GetKlinesRequest.Interval), "Interval not supported")); - var validationError = ((IKlineRestClient)this).GetKlinesOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetKlinesOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); int limit = request.Limit ?? 300; var direction = DataDirection.Descending; @@ -150,13 +146,13 @@ async Task> IKlineRestClient.GetKlinesAsync(Get var symbol = request.Symbol!.GetSymbol(FormatSymbol); var result = await ExchangeData.GetKlinesAsync( symbol, - interval, + (Enums.KlineInterval)request.Interval, endTime, pageParams.Limit, ct: ct ).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); var nextPageRequest = Pagination.GetNextPageRequest( () => Pagination.NextPageFromTime(pageParams, result.Data.Min(x => x.OpenTime)), @@ -166,9 +162,7 @@ async Task> IKlineRestClient.GetKlinesAsync(Get request.EndTime ?? DateTime.UtcNow, pageParams); - return result.AsExchangeResult( - Exchange, - TradingMode.Spot, + return HttpResult.Ok(result, ExchangeHelpers.ApplyFilter(result.Data, x => x.OpenTime, request.StartTime, request.EndTime, direction) .Select(x => new SharedKline(request.Symbol, symbol, x.OpenTime, x.ClosePrice, x.HighPrice, x.LowPrice, x.OpenPrice, x.Volume)) @@ -179,70 +173,74 @@ async Task> IKlineRestClient.GetKlinesAsync(Get #region Listen Key client - EndpointOptions IListenKeyRestClient.StartOptions { get; } = new EndpointOptions(true); - async Task> IListenKeyRestClient.StartListenKeyAsync(StartListenKeyRequest request, CancellationToken ct) + StartListenKeyOptions IListenKeyRestClient.StartOptions { get; } = new StartListenKeyOptions(_exchangeName, true); + async Task> IListenKeyRestClient.StartListenKeyAsync(StartListenKeyRequest request, CancellationToken ct) { - var validationError = ((IListenKeyRestClient)this).StartOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.StartOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); // Get data var result = await Account.StartUserStreamAsync(ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); - return result.AsExchangeResult(Exchange, SupportedTradingModes, result.Data.ListenKey); + return HttpResult.Ok(result, result.Data.ListenKey); } - EndpointOptions IListenKeyRestClient.KeepAliveOptions { get; } = new EndpointOptions(true); - async Task> IListenKeyRestClient.KeepAliveListenKeyAsync(KeepAliveListenKeyRequest request, CancellationToken ct) + KeepAliveListenKeyOptions IListenKeyRestClient.KeepAliveOptions { get; } = new KeepAliveListenKeyOptions(_exchangeName, true); + async Task> IListenKeyRestClient.KeepAliveListenKeyAsync(KeepAliveListenKeyRequest request, CancellationToken ct) { - var validationError = ((IListenKeyRestClient)this).KeepAliveOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.KeepAliveOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); // Get data var result = await Account.KeepAliveUserStreamAsync(request.ListenKey, ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); - return result.AsExchangeResult(Exchange, SupportedTradingModes, request.ListenKey); + return HttpResult.Ok(result, request.ListenKey); } - EndpointOptions IListenKeyRestClient.StopOptions { get; } = new EndpointOptions(true); - Task> IListenKeyRestClient.StopListenKeyAsync(StopListenKeyRequest request, CancellationToken ct) - { - return Task.FromResult(new ExchangeWebResult(Exchange, SupportedTradingModes, null, null, null, null, null, null, null, null, null, null, null, ResultDataSource.Server, request.ListenKey, null)); + StopListenKeyOptions IListenKeyRestClient.StopOptions { get; } = new StopListenKeyOptions(_exchangeName, true); + Task> IListenKeyRestClient.StopListenKeyAsync(StopListenKeyRequest request, CancellationToken ct) + { + var validationError = SharedClient.StopOptions.ValidateRequest(request, this); + if (validationError != null) + return Task.FromResult(HttpResult.Fail(Exchange, validationError)); + + return Task.FromResult(new HttpResult(Exchange, request.ListenKey, null)); } #endregion #region Order Book client - GetOrderBookOptions IOrderBookRestClient.GetOrderBookOptions { get; } = new GetOrderBookOptions(1, 400, false); - async Task> IOrderBookRestClient.GetOrderBookAsync(GetOrderBookRequest request, CancellationToken ct) + GetOrderBookOptions IOrderBookRestClient.GetOrderBookOptions { get; } = new GetOrderBookOptions(_exchangeName, 1, 400, false); + async Task> IOrderBookRestClient.GetOrderBookAsync(GetOrderBookRequest request, CancellationToken ct) { - var validationError = ((IOrderBookRestClient)this).GetOrderBookOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetOrderBookOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var result = await ExchangeData.GetOrderBookAsync( request.Symbol!.GetSymbol(FormatSymbol), depth: request.Limit, ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); - return result.AsExchangeResult(Exchange, request.Symbol.TradingMode, new SharedOrderBook(result.Data.Asks, result.Data.Bids)); + return HttpResult.Ok(result, new SharedOrderBook(result.Data.Asks, result.Data.Bids)); } #endregion #region Withdrawal client - GetWithdrawalsOptions IWithdrawalRestClient.GetWithdrawalsOptions { get; } = new GetWithdrawalsOptions(false, true, true, 50); - async Task> IWithdrawalRestClient.GetWithdrawalsAsync(GetWithdrawalsRequest request, PageRequest? pageRequest, CancellationToken ct) + GetWithdrawalsOptions IWithdrawalRestClient.GetWithdrawalsOptions { get; } = new GetWithdrawalsOptions(_exchangeName, false, true, true, 50); + async Task> IWithdrawalRestClient.GetWithdrawalsAsync(GetWithdrawalsRequest request, PageRequest? pageRequest, CancellationToken ct) { - var validationError = ((IWithdrawalRestClient)this).GetWithdrawalsOptions.ValidateRequest(Exchange, request, TradingMode.Spot, SupportedTradingModes); + var validationError = SharedClient.GetWithdrawalsOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); int limit = request.Limit ?? 50; var direction = DataDirection.Descending; @@ -256,8 +254,8 @@ async Task> IWithdrawalRestClient.GetWithd pageSize: pageParams.Limit, page: pageParams.Page, ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); var nextPageRequest = Pagination.GetNextPageRequest( () => Pagination.NextPageFromPage(pageParams), @@ -267,9 +265,7 @@ async Task> IWithdrawalRestClient.GetWithd request.EndTime ?? DateTime.UtcNow, pageParams); - return result.AsExchangeResult( - Exchange, - TradingMode.Spot, + return HttpResult.Ok(result, ExchangeHelpers.ApplyFilter(result.Data.Data, x => x.CreateTime, request.StartTime, request.EndTime, direction) .Select(x => new SharedWithdrawal(x.Asset, x.Address, x.Quantity, x.DepositStatus == Enums.WithdrawStatus.Success, x.CreateTime) @@ -283,40 +279,40 @@ async Task> IWithdrawalRestClient.GetWithd #region Spot Ticker client - GetTickerOptions ISpotTickerRestClient.GetSpotTickerOptions { get; } = new GetTickerOptions(); - async Task> ISpotTickerRestClient.GetSpotTickerAsync(GetTickerRequest request, CancellationToken ct) + GetSpotTickerOptions ISpotTickerRestClient.GetSpotTickerOptions { get; } = new GetSpotTickerOptions(_exchangeName); + async Task> ISpotTickerRestClient.GetSpotTickerAsync(GetTickerRequest request, CancellationToken ct) { - var validationError = ((ISpotTickerRestClient)this).GetSpotTickerOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetSpotTickerOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var result = await ExchangeData.GetTickersAsync(Enums.SymbolType.Spot, ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); var symbol = result.Data.SingleOrDefault(x => x.Symbol == request.Symbol!.GetSymbol(FormatSymbol)); if (symbol == null) - return result.AsExchangeError(Exchange, new ServerError(new ErrorInfo(ErrorType.UnknownSymbol, "Symbol not found"))); + return HttpResult.Fail(result, new ServerError(new ErrorInfo(ErrorType.UnknownSymbol, "Symbol not found"))); - return result.AsExchangeResult(Exchange, TradingMode.Spot, new SharedSpotTicker(ExchangeSymbolCache.ParseSymbol(_topicSpotId, symbol.Symbol), symbol.Symbol, symbol.LastPrice, symbol.HighPrice, symbol.LowPrice, symbol.Volume, symbol.OpenPrice == null ? null : Math.Round((symbol.LastPrice ?? 0) / symbol.OpenPrice.Value * 100 - 100, 3)) + return HttpResult.Ok(result, new SharedSpotTicker(ExchangeSymbolCache.ParseSymbol(_topicSpotId, symbol.Symbol), symbol.Symbol, symbol.LastPrice, symbol.HighPrice, symbol.LowPrice, symbol.Volume, symbol.OpenPrice == null ? null : Math.Round((symbol.LastPrice ?? 0) / symbol.OpenPrice.Value * 100 - 100, 3)) { // Value is incorrect from the API // QuoteVolume = symbol.QuoteVolume }); } - GetTickersOptions ISpotTickerRestClient.GetSpotTickersOptions { get; } = new GetTickersOptions(); - async Task> ISpotTickerRestClient.GetSpotTickersAsync(GetTickersRequest request, CancellationToken ct) + GetSpotTickersOptions ISpotTickerRestClient.GetSpotTickersOptions { get; } = new GetSpotTickersOptions(_exchangeName); + async Task> ISpotTickerRestClient.GetSpotTickersAsync(GetTickersRequest request, CancellationToken ct) { - var validationError = ((ISpotTickerRestClient)this).GetSpotTickersOptions.ValidateRequest(Exchange, request, TradingMode.Spot, SupportedTradingModes); + var validationError = SharedClient.GetSpotTickersOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var result = await ExchangeData.GetTickersAsync(Enums.SymbolType.Spot, ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); - return result.AsExchangeResult(Exchange, TradingMode.Spot, result.Data.Select(x => new SharedSpotTicker(ExchangeSymbolCache.ParseSymbol(_topicSpotId, x.Symbol), x.Symbol, x.LastPrice, x.HighPrice, x.LowPrice, x.Volume, x.OpenPrice == null ? null : Math.Round((x.LastPrice ?? 0) / x.OpenPrice.Value * 100 - 100, 3)) + return HttpResult.Ok(result, result.Data.Select(x => new SharedSpotTicker(ExchangeSymbolCache.ParseSymbol(_topicSpotId, x.Symbol), x.Symbol, x.LastPrice, x.HighPrice, x.LowPrice, x.Volume, x.OpenPrice == null ? null : Math.Round((x.LastPrice ?? 0) / x.OpenPrice.Value * 100 - 100, 3)) { // Value is incorrect from the API // QuoteVolume = symbol.QuoteVolume @@ -327,19 +323,19 @@ async Task> ISpotTickerRestClient.GetSpotT #region Book Ticker client - EndpointOptions IBookTickerRestClient.GetBookTickerOptions { get; } = new EndpointOptions(false); - async Task> IBookTickerRestClient.GetBookTickerAsync(GetBookTickerRequest request, CancellationToken ct) + GetBookTickerOptions IBookTickerRestClient.GetBookTickerOptions { get; } = new GetBookTickerOptions(_exchangeName, false); + async Task> IBookTickerRestClient.GetBookTickerAsync(GetBookTickerRequest request, CancellationToken ct) { - var validationError = ((IBookTickerRestClient)this).GetBookTickerOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetBookTickerOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var symbol = request.Symbol!.GetSymbol(FormatSymbol); var resultTicker = await ExchangeData.GetOrderBookAsync(symbol, 1, ct: ct).ConfigureAwait(false); - if (!resultTicker) - return resultTicker.AsExchangeResult(Exchange, null, default); + if (!resultTicker.Success) + return HttpResult.Fail(resultTicker); - return resultTicker.AsExchangeResult(Exchange, request.Symbol.TradingMode, new SharedBookTicker( + return HttpResult.Ok(resultTicker, new SharedBookTicker( ExchangeSymbolCache.ParseSymbol(request.Symbol.TradingMode == TradingMode.Spot ? _topicSpotId : _topicFuturesId, symbol), symbol, resultTicker.Data.Asks[0].Price, @@ -351,19 +347,19 @@ async Task> IBookTickerRestClient.GetBookTic #endregion #region Spot Symbol client - EndpointOptions ISpotSymbolRestClient.GetSpotSymbolsOptions { get; } = new EndpointOptions(false); + GetSpotSymbolsOptions ISpotSymbolRestClient.GetSpotSymbolsOptions { get; } = new GetSpotSymbolsOptions(_exchangeName, false); - async Task> ISpotSymbolRestClient.GetSpotSymbolsAsync(GetSymbolsRequest request, CancellationToken ct) + async Task> ISpotSymbolRestClient.GetSpotSymbolsAsync(GetSymbolsRequest request, CancellationToken ct) { - var validationError = ((ISpotSymbolRestClient)this).GetSpotSymbolsOptions.ValidateRequest(Exchange, request, TradingMode.Spot, SupportedTradingModes); + var validationError = SharedClient.GetSpotSymbolsOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var result = await ExchangeData.GetSymbolsAsync(Enums.SymbolType.Spot, ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); - var response = result.AsExchangeResult(Exchange, TradingMode.Spot, result.Data.Select(s => new SharedSpotSymbol(s.BaseAsset, s.QuoteAsset, s.Symbol, s.Status == Enums.SymbolStatus.Live) + var response = HttpResult.Ok(result, result.Data.Select(s => new SharedSpotSymbol(s.BaseAsset, s.QuoteAsset, s.Symbol, s.Status == Enums.SymbolStatus.Live) { MaxTradeQuantity = Math.Min(s.MaxLimitQuantity, s.MaxMarketQuantity), MinTradeQuantity = s.MinQuantity, @@ -371,24 +367,24 @@ async Task> ISpotSymbolRestClient.GetSpotS QuantityStep = s.LotSize }).ToArray()); - var symbolInfo = response.Data.Concat(response.Data.Select(x => new SharedSpotSymbol(x.BaseAsset, x.QuoteAsset, x.BaseAsset + "/" + x.QuoteAsset, x.Trading))); + var symbolInfo = response.Data!.Concat(response.Data!.Select(x => new SharedSpotSymbol(x.BaseAsset, x.QuoteAsset, x.BaseAsset + "/" + x.QuoteAsset, x.Trading))); ExchangeSymbolCache.UpdateSymbolInfo(_topicSpotId, symbolInfo.ToArray()); return response; } - async Task> ISpotSymbolRestClient.GetSpotSymbolsForBaseAssetAsync(string baseAsset) + 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!); + if (!symbols.Success) + return ExchangeCallResult.Fail(Exchange, symbols.Error!); } - return new ExchangeResult(Exchange, ExchangeSymbolCache.GetSymbolsForBaseAsset(_topicSpotId, baseAsset)); + return ExchangeCallResult.Ok(Exchange, ExchangeSymbolCache.GetSymbolsForBaseAsset(_topicSpotId, baseAsset)); } - async Task> ISpotSymbolRestClient.SupportsSpotSymbolAsync(SharedSymbol symbol) + async Task> ISpotSymbolRestClient.SupportsSpotSymbolAsync(SharedSymbol symbol) { if (symbol.TradingMode != TradingMode.Spot) throw new ArgumentException(nameof(symbol), "Only Spot symbols allowed"); @@ -396,23 +392,23 @@ async Task> ISpotSymbolRestClient.SupportsSpotSymbolAsync(S if (!ExchangeSymbolCache.HasCached(_topicSpotId)) { var symbols = await ((ISpotSymbolRestClient)this).GetSpotSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false); - if (!symbols) - return new ExchangeResult(Exchange, symbols.Error!); + if (!symbols.Success) + return ExchangeCallResult.Fail(Exchange, symbols.Error!); } - return new ExchangeResult(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicSpotId, symbol)); + return ExchangeCallResult.Ok(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicSpotId, symbol)); } - async Task> ISpotSymbolRestClient.SupportsSpotSymbolAsync(string symbolName) + 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!); + if (!symbols.Success) + return ExchangeCallResult.Fail(Exchange, symbols.Error!); } - return new ExchangeResult(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicSpotId, symbolName)); + return ExchangeCallResult.Ok(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicSpotId, symbolName)); } #endregion @@ -430,19 +426,12 @@ async Task> ISpotSymbolRestClient.SupportsSpotSymbolAsync(s string ISpotOrderRestClient.GenerateClientOrderId() => ExchangeHelpers.RandomString(32); - PlaceSpotOrderOptions ISpotOrderRestClient.PlaceSpotOrderOptions { get; } = new PlaceSpotOrderOptions(); - async Task> ISpotOrderRestClient.PlaceSpotOrderAsync(PlaceSpotOrderRequest request, CancellationToken ct) + PlaceSpotOrderOptions ISpotOrderRestClient.PlaceSpotOrderOptions { get; } = new PlaceSpotOrderOptions(_exchangeName); + async Task> ISpotOrderRestClient.PlaceSpotOrderAsync(PlaceSpotOrderRequest request, CancellationToken ct) { - var validationError = ((ISpotOrderRestClient)this).PlaceSpotOrderOptions.ValidateRequest( - Exchange, - request, - request.TradingMode, - SupportedTradingModes, - ((ISpotOrderRestClient)this).SpotSupportedOrderTypes, - ((ISpotOrderRestClient)this).SpotSupportedTimeInForce, - ((ISpotOrderRestClient)this).SpotSupportedOrderQuantity); + var validationError = SharedClient.PlaceSpotOrderOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var result = await Trading.PlaceOrderAsync( request.Symbol!.GetSymbol(FormatSymbol), @@ -454,30 +443,30 @@ async Task> ISpotOrderRestClient.PlaceSpotOrderAsync clientOrderId: request.ClientOrderId, ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); - return result.AsExchangeResult(Exchange, TradingMode.Spot, new SharedId(result.Data.OrderId.ToString())); + return HttpResult.Ok(result, new SharedId(result.Data.OrderId.ToString())); } - EndpointOptions ISpotOrderRestClient.GetSpotOrderOptions { get; } = new EndpointOptions(true); - async Task> ISpotOrderRestClient.GetSpotOrderAsync(GetOrderRequest request, CancellationToken ct) + GetSpotOrderOptions ISpotOrderRestClient.GetSpotOrderOptions { get; } = new GetSpotOrderOptions(_exchangeName, true); + async Task> ISpotOrderRestClient.GetSpotOrderAsync(GetOrderRequest request, CancellationToken ct) { - var validationError = ((ISpotOrderRestClient)this).GetSpotOrderOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetSpotOrderOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var symbol = request.Symbol!.GetSymbol(FormatSymbol); var order = await Trading.GetOpenOrderAsync(symbol, request.OrderId, ct: ct).ConfigureAwait(false); - if (!order) + if (!order.Success) { order = await Trading.GetClosedOrderAsync(symbol, request.OrderId, ct: ct).ConfigureAwait(false); - if (!order) - return order.AsExchangeResult(Exchange, null, default); + if (!order.Success) + return HttpResult.Fail(order); } - return order.AsExchangeResult(Exchange, TradingMode.Spot, new SharedSpotOrder( + return HttpResult.Ok(order, new SharedSpotOrder( ExchangeSymbolCache.ParseSymbol(_topicSpotId, order.Data.Symbol), order.Data.Symbol, order.Data.OrderId, @@ -498,7 +487,7 @@ async Task> ISpotOrderRestClient.GetSpotOrder }); } - EndpointOptions ISpotOrderRestClient.GetOpenSpotOrdersOptions { get; } = new EndpointOptions(true) + GetOpenSpotOrdersOptions ISpotOrderRestClient.GetOpenSpotOrdersOptions { get; } = new GetOpenSpotOrdersOptions(_exchangeName, true) { RequiredOptionalParameters = new List { @@ -506,18 +495,18 @@ async Task> ISpotOrderRestClient.GetSpotOrder } }; - async Task> ISpotOrderRestClient.GetOpenSpotOrdersAsync(GetOpenOrdersRequest request, CancellationToken ct) + async Task> ISpotOrderRestClient.GetOpenSpotOrdersAsync(GetOpenOrdersRequest request, CancellationToken ct) { - var validationError = ((ISpotOrderRestClient)this).GetOpenSpotOrdersOptions.ValidateRequest(Exchange, request, request.Symbol?.TradingMode ?? request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetOpenSpotOrdersOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var symbol = request.Symbol?.GetSymbol(FormatSymbol); var orders = await Trading.GetOpenOrdersAsync(symbol!, ct: ct).ConfigureAwait(false); - if (!orders) - return orders.AsExchangeResult(Exchange, null, default); + if (!orders.Success) + return HttpResult.Fail(orders); - return orders.AsExchangeResult(Exchange, TradingMode.Spot, orders.Data.Select(x => new SharedSpotOrder( + return HttpResult.Ok(orders, orders.Data.Select(x => new SharedSpotOrder( ExchangeSymbolCache.ParseSymbol(_topicSpotId, x.Symbol), x.Symbol, x.OrderId, @@ -538,12 +527,12 @@ async Task> ISpotOrderRestClient.GetOpenSpo }).ToArray()); } - GetClosedOrdersOptions ISpotOrderRestClient.GetClosedSpotOrdersOptions { get; } = new GetClosedOrdersOptions(false, true, false, 100); - async Task> ISpotOrderRestClient.GetClosedSpotOrdersAsync(GetClosedOrdersRequest request, PageRequest? pageRequest, CancellationToken ct) + GetSpotClosedOrdersOptions ISpotOrderRestClient.GetClosedSpotOrdersOptions { get; } = new GetSpotClosedOrdersOptions(_exchangeName, false, true, false, 100); + async Task> ISpotOrderRestClient.GetClosedSpotOrdersAsync(GetClosedOrdersRequest request, PageRequest? pageRequest, CancellationToken ct) { - var validationError = ((ISpotOrderRestClient)this).GetClosedSpotOrdersOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetClosedSpotOrdersOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); int limit = request.Limit ?? 100; var direction = DataDirection.Descending; @@ -555,8 +544,8 @@ async Task> ISpotOrderRestClient.GetClosedS limit: pageParams.Limit, afterId: pageParams.FromId, // Correct? ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); var nextPageRequest = Pagination.GetNextPageRequest( () => Pagination.NextPageFromId(result.Data.OrderBy(x => x.CreateTime).First().OrderId), @@ -566,9 +555,7 @@ async Task> ISpotOrderRestClient.GetClosedS request.EndTime ?? DateTime.UtcNow, pageParams); - return result.AsExchangeResult( - Exchange, - TradingMode.Spot, + return HttpResult.Ok(result, ExchangeHelpers.ApplyFilter(result.Data, x => x.CreateTime, request.StartTime, request.EndTime, direction) .Select(x => new SharedSpotOrder( @@ -592,22 +579,22 @@ async Task> ISpotOrderRestClient.GetClosedS }).ToArray(), nextPageRequest); } - EndpointOptions ISpotOrderRestClient.GetSpotOrderTradesOptions { get; } = new EndpointOptions(true); - async Task> ISpotOrderRestClient.GetSpotOrderTradesAsync(GetOrderTradesRequest request, CancellationToken ct) + GetSpotOrderTradesOptions ISpotOrderRestClient.GetSpotOrderTradesOptions { get; } = new GetSpotOrderTradesOptions(_exchangeName, true); + async Task> ISpotOrderRestClient.GetSpotOrderTradesAsync(GetOrderTradesRequest request, CancellationToken ct) { - var validationError = ((ISpotOrderRestClient)this).GetSpotOrderTradesOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetSpotOrderTradesOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var orders = await Trading.GetUserTradesAsync( SymbolType.Spot, request.Symbol!.GetSymbol(FormatSymbol), orderId: request.OrderId, ct: ct).ConfigureAwait(false); - if (!orders) - return orders.AsExchangeResult(Exchange, null, default); + if (!orders.Success) + return HttpResult.Fail(orders); - return orders.AsExchangeResult(Exchange, TradingMode.Spot, orders.Data.Select(x => new SharedUserTrade( + return HttpResult.Ok(orders, orders.Data.Select(x => new SharedUserTrade( ExchangeSymbolCache.ParseSymbol(_topicSpotId, x.Symbol), x.Symbol, x.OrderId.ToString(), @@ -624,12 +611,12 @@ async Task> ISpotOrderRestClient.GetSpotOrd }).ToArray()); } - GetUserTradesOptions ISpotOrderRestClient.GetSpotUserTradesOptions { get; } = new GetUserTradesOptions(false, true, true, 100); - async Task> ISpotOrderRestClient.GetSpotUserTradesAsync(GetUserTradesRequest request, PageRequest? pageRequest, CancellationToken ct) + GetSpotUserTradesOptions ISpotOrderRestClient.GetSpotUserTradesOptions { get; } = new GetSpotUserTradesOptions(_exchangeName, false, true, true, 100); + async Task> ISpotOrderRestClient.GetSpotUserTradesAsync(GetUserTradesRequest request, PageRequest? pageRequest, CancellationToken ct) { - var validationError = ((ISpotOrderRestClient)this).GetSpotUserTradesOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetSpotUserTradesOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); int limit = request.Limit ?? 100; var direction = DataDirection.Descending; @@ -645,8 +632,8 @@ async Task> ISpotOrderRestClient.GetSpotUse afterId: pageParams.FromId, ct: ct ).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); var nextPageRequest = Pagination.GetNextPageRequest( () => Pagination.NextPageFromId(result.Data.OrderBy(x => x.Timestamp).First().BillId), @@ -656,9 +643,7 @@ async Task> ISpotOrderRestClient.GetSpotUse request.EndTime ?? DateTime.UtcNow, pageParams); - return result.AsExchangeResult( - Exchange, - TradingMode.Spot, + return HttpResult.Ok(result, ExchangeHelpers.ApplyFilter(result.Data, x => x.Timestamp, request.StartTime, request.EndTime, direction) .Select(x => new SharedUserTrade( @@ -678,18 +663,18 @@ async Task> ISpotOrderRestClient.GetSpotUse }).ToArray(), nextPageRequest); } - EndpointOptions ISpotOrderRestClient.CancelSpotOrderOptions { get; } = new EndpointOptions(true); - async Task> ISpotOrderRestClient.CancelSpotOrderAsync(CancelOrderRequest request, CancellationToken ct) + CancelSpotOrderOptions ISpotOrderRestClient.CancelSpotOrderOptions { get; } = new CancelSpotOrderOptions(_exchangeName, true); + async Task> ISpotOrderRestClient.CancelSpotOrderAsync(CancelOrderRequest request, CancellationToken ct) { - var validationError = ((ISpotOrderRestClient)this).CancelSpotOrderOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.CancelSpotOrderOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var order = await Trading.CancelOrderAsync(request.Symbol!.GetSymbol(FormatSymbol), request.OrderId, ct: ct).ConfigureAwait(false); - if (!order) - return order.AsExchangeResult(Exchange, null, default); + if (!order.Success) + return HttpResult.Fail(order); - return order.AsExchangeResult(Exchange, TradingMode.Spot, new SharedId(order.Data.OrderId.ToString())); + return HttpResult.Ok(order, new SharedId(order.Data.OrderId.ToString())); } private SharedOrderStatus ParseOrderStatus(OrderStatus status) @@ -712,27 +697,27 @@ private SharedOrderType ParseOrderType(OrderType type) #region Leverage client SharedLeverageSettingMode ILeverageRestClient.LeverageSettingType => SharedLeverageSettingMode.PerSymbol; - EndpointOptions ILeverageRestClient.GetLeverageOptions { get; } = new EndpointOptions(true); - async Task> ILeverageRestClient.GetLeverageAsync(GetLeverageRequest request, CancellationToken ct) + GetLeverageOptions ILeverageRestClient.GetLeverageOptions { get; } = new GetLeverageOptions(_exchangeName, true); + async Task> ILeverageRestClient.GetLeverageAsync(GetLeverageRequest request, CancellationToken ct) { - var validationError = ((ILeverageRestClient)this).GetLeverageOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetLeverageOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var result = await Trading.GetPositionsAsync(SymbolType.Swap, symbol: request.Symbol!.GetSymbol(FormatSymbol), ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); if (!result.Data.Any()) - return result.AsExchangeError(Exchange, new ServerError(new ErrorInfo(ErrorType.Unknown, "Not found"))); + return HttpResult.Fail(result, new ServerError(new ErrorInfo(ErrorType.Unknown, "Not found"))); - return result.AsExchangeResult(Exchange, request.Symbol.TradingMode, new SharedLeverage(result.Data.First().Leverage) + return HttpResult.Ok(result, new SharedLeverage(result.Data.First().Leverage) { Side = request.PositionSide }); } - SetLeverageOptions ILeverageRestClient.SetLeverageOptions { get; } = new SetLeverageOptions() + SetLeverageOptions ILeverageRestClient.SetLeverageOptions { get; } = new SetLeverageOptions(_exchangeName) { RequiredOptionalParameters = new List { @@ -744,11 +729,11 @@ async Task> ILeverageRestClient.GetLeverageAsy } }; - async Task> ILeverageRestClient.SetLeverageAsync(SetLeverageRequest request, CancellationToken ct) + async Task> ILeverageRestClient.SetLeverageAsync(SetLeverageRequest request, CancellationToken ct) { - var validationError = ((ILeverageRestClient)this).SetLeverageOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.SetLeverageOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var positionType = ExchangeParameters.GetValue(request.ExchangeParameters, Exchange, "PositionType"); @@ -758,63 +743,63 @@ async Task> ILeverageRestClient.SetLeverageAsy tradeMode: request.MarginMode == SharedMarginMode.Cross ? TradeMode.Cross : TradeMode.Isolated, positionType ?? PositionType.Merge, ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); - return result.AsExchangeResult(Exchange, request.Symbol.TradingMode, new SharedLeverage(result.Data.Leverage)); + return HttpResult.Ok(result, new SharedLeverage(result.Data.Leverage)); } #endregion #region Futures Ticker client - GetTickerOptions IFuturesTickerRestClient.GetFuturesTickerOptions { get; } = new GetTickerOptions(); - async Task> IFuturesTickerRestClient.GetFuturesTickerAsync(GetTickerRequest request, CancellationToken ct) + GetFuturesTickerOptions IFuturesTickerRestClient.GetFuturesTickerOptions { get; } = new GetFuturesTickerOptions(_exchangeName); + async Task> IFuturesTickerRestClient.GetFuturesTickerAsync(GetTickerRequest request, CancellationToken ct) { - var validationError = ((IFuturesTickerRestClient)this).GetFuturesTickerOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetFuturesTickerOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var resultTicker = await ExchangeData.GetTickersAsync(SymbolType.Swap, ct: ct).ConfigureAwait(false); - if (!resultTicker) - return resultTicker.AsExchangeResult(Exchange, null, default); + if (!resultTicker.Success) + return HttpResult.Fail(resultTicker); var symbol = resultTicker.Data.FirstOrDefault(x => x.Symbol == request.Symbol!.GetSymbol(FormatSymbol)); if (symbol == null) - return resultTicker.AsExchangeError(Exchange, new ServerError(new ErrorInfo(ErrorType.UnknownSymbol, "Symbol not found"))); + return HttpResult.Fail(resultTicker, new ServerError(new ErrorInfo(ErrorType.UnknownSymbol, "Symbol not found"))); - return resultTicker.AsExchangeResult(Exchange, request.TradingMode, new SharedFuturesTicker(ExchangeSymbolCache.ParseSymbol(_topicFuturesId, symbol.Symbol), symbol.Symbol, symbol.LastPrice, symbol.HighPrice, symbol.LowPrice, symbol.Volume, symbol.OpenPrice == null ? null : Math.Round((symbol.LastPrice ?? 0) / symbol.OpenPrice.Value * 100 - 100, 3))); + return HttpResult.Ok(resultTicker, new SharedFuturesTicker(ExchangeSymbolCache.ParseSymbol(_topicFuturesId, symbol.Symbol), symbol.Symbol, symbol.LastPrice, symbol.HighPrice, symbol.LowPrice, symbol.Volume, symbol.OpenPrice == null ? null : Math.Round((symbol.LastPrice ?? 0) / symbol.OpenPrice.Value * 100 - 100, 3))); } - GetTickersOptions IFuturesTickerRestClient.GetFuturesTickersOptions { get; } = new GetTickersOptions(); - async Task> IFuturesTickerRestClient.GetFuturesTickersAsync(GetTickersRequest request, CancellationToken ct) + GetFuturesTickersOptions IFuturesTickerRestClient.GetFuturesTickersOptions { get; } = new GetFuturesTickersOptions(_exchangeName); + async Task> IFuturesTickerRestClient.GetFuturesTickersAsync(GetTickersRequest request, CancellationToken ct) { - var validationError = ((IFuturesTickerRestClient)this).GetFuturesTickersOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetFuturesTickersOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var result = await ExchangeData.GetTickersAsync(Enums.SymbolType.Swap, ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); - return result.AsExchangeResult(Exchange, SupportedTradingModes.Where(x => x != TradingMode.Spot).ToArray(), result.Data.Select(x => new SharedFuturesTicker(ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol), x.Symbol, x.LastPrice, x.HighPrice, x.LowPrice, x.Volume, x.OpenPrice == null ? null : Math.Round((x.LastPrice ?? 0) / x.OpenPrice.Value * 100 - 100, 3))).ToArray()); + return HttpResult.Ok(result, result.Data.Select(x => new SharedFuturesTicker(ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol), x.Symbol, x.LastPrice, x.HighPrice, x.LowPrice, x.Volume, x.OpenPrice == null ? null : Math.Round((x.LastPrice ?? 0) / x.OpenPrice.Value * 100 - 100, 3))).ToArray()); } #endregion #region Futures Symbol client - EndpointOptions IFuturesSymbolRestClient.GetFuturesSymbolsOptions { get; } = new EndpointOptions(false); - async Task> IFuturesSymbolRestClient.GetFuturesSymbolsAsync(GetSymbolsRequest request, CancellationToken ct) + GetFuturesSymbolsOptions IFuturesSymbolRestClient.GetFuturesSymbolsOptions { get; } = new GetFuturesSymbolsOptions(_exchangeName, false); + async Task> IFuturesSymbolRestClient.GetFuturesSymbolsAsync(GetSymbolsRequest request, CancellationToken ct) { - var validationError = ((IFuturesSymbolRestClient)this).GetFuturesSymbolsOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetFuturesSymbolsOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var result = await ExchangeData.GetSymbolsAsync(Enums.SymbolType.Swap, ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); - var response = result.AsExchangeResult(Exchange, SupportedTradingModes.Where(x => x != TradingMode.Spot).ToArray(), result.Data.Select(s => + var response = HttpResult.Ok(result, result.Data.Select(s => new SharedFuturesSymbol(s.QuoteAsset.Equals("USD") ? TradingMode.PerpetualInverse : TradingMode.PerpetualLinear, s.BaseAsset, s.QuoteAsset, s.Symbol, s.Status == Enums.SymbolStatus.Live) { MaxTradeQuantity = Math.Min(s.MaxLimitQuantity, s.MaxMarketQuantity), @@ -827,25 +812,25 @@ async Task> IFuturesSymbolRestClient.Ge }).ToArray()); // 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(); + 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) + 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!); + if (!symbols.Success) + return ExchangeCallResult.Fail(Exchange, symbols.Error!); } - return new ExchangeResult(Exchange, ExchangeSymbolCache.GetSymbolsForBaseAsset(_topicFuturesId, baseAsset)); + return ExchangeCallResult.Ok(Exchange, ExchangeSymbolCache.GetSymbolsForBaseAsset(_topicFuturesId, baseAsset)); } - async Task> IFuturesSymbolRestClient.SupportsFuturesSymbolAsync(SharedSymbol symbol) + async Task> IFuturesSymbolRestClient.SupportsFuturesSymbolAsync(SharedSymbol symbol) { if (symbol.TradingMode == TradingMode.Spot) throw new ArgumentException(nameof(symbol), "Spot symbols not allowed"); @@ -853,23 +838,23 @@ async Task> IFuturesSymbolRestClient.SupportsFuturesSymbolA if (!ExchangeSymbolCache.HasCached(_topicFuturesId)) { var symbols = await ((IFuturesSymbolRestClient)this).GetFuturesSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false); - if (!symbols) - return new ExchangeResult(Exchange, symbols.Error!); + if (!symbols.Success) + return ExchangeCallResult.Fail(Exchange, symbols.Error!); } - return new ExchangeResult(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicFuturesId, symbol)); + return ExchangeCallResult.Ok(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicFuturesId, symbol)); } - async Task> IFuturesSymbolRestClient.SupportsFuturesSymbolAsync(string symbolName) + 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!); + if (!symbols.Success) + return ExchangeCallResult.Fail(Exchange, symbols.Error!); } - return new ExchangeResult(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicFuturesId, symbolName)); + return ExchangeCallResult.Ok(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicFuturesId, symbolName)); } #endregion @@ -887,7 +872,7 @@ async Task> IFuturesSymbolRestClient.SupportsFuturesSymbolA string IFuturesOrderRestClient.GenerateClientOrderId() => ExchangeHelpers.RandomString(32); - PlaceFuturesOrderOptions IFuturesOrderRestClient.PlaceFuturesOrderOptions { get; } = new PlaceFuturesOrderOptions(true) + PlaceFuturesOrderOptions IFuturesOrderRestClient.PlaceFuturesOrderOptions { get; } = new PlaceFuturesOrderOptions(_exchangeName, true) { RequiredOptionalParameters = new List { @@ -898,18 +883,11 @@ async Task> IFuturesSymbolRestClient.SupportsFuturesSymbolA new ParameterDescription("PositionType", typeof(PositionType), "Merge or split position mode", PositionType.Merge) } }; - async Task> IFuturesOrderRestClient.PlaceFuturesOrderAsync(PlaceFuturesOrderRequest request, CancellationToken ct) + async Task> IFuturesOrderRestClient.PlaceFuturesOrderAsync(PlaceFuturesOrderRequest request, CancellationToken ct) { - var validationError = ((IFuturesOrderRestClient)this).PlaceFuturesOrderOptions.ValidateRequest( - Exchange, - request, - request.TradingMode, - SupportedTradingModes, - ((IFuturesOrderRestClient)this).FuturesSupportedOrderTypes, - ((IFuturesOrderRestClient)this).FuturesSupportedTimeInForce, - ((IFuturesOrderRestClient)this).FuturesSupportedOrderQuantity); + var validationError = SharedClient.PlaceFuturesOrderOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var positionType = ExchangeParameters.GetValue(request.ExchangeParameters, Exchange, "PositionType"); @@ -927,36 +905,36 @@ async Task> IFuturesOrderRestClient.PlaceFuturesOrde slTriggerPrice: request.StopLossPrice, ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); - return result.AsExchangeResult(Exchange, request.Symbol.TradingMode, new SharedId(result.Data.OrderId.ToString())); + return HttpResult.Ok(result, new SharedId(result.Data.OrderId.ToString())); } - EndpointOptions IFuturesOrderRestClient.GetFuturesOrderOptions { get; } = new EndpointOptions(true); - async Task> IFuturesOrderRestClient.GetFuturesOrderAsync(GetOrderRequest request, CancellationToken ct) + GetFuturesOrderOptions IFuturesOrderRestClient.GetFuturesOrderOptions { get; } = new GetFuturesOrderOptions(_exchangeName, true); + async Task> IFuturesOrderRestClient.GetFuturesOrderAsync(GetOrderRequest request, CancellationToken ct) { - var validationError = ((IFuturesOrderRestClient)this).GetFuturesOrderOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetFuturesOrderOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var symbol = request.Symbol!.GetSymbol(FormatSymbol); var order = await Trading.GetOpenOrderAsync(symbol, request.OrderId, ct: ct).ConfigureAwait(false); - if (!order) + if (!order.Success) { order = await Trading.GetClosedOrderAsync(symbol, request.OrderId, ct: ct).ConfigureAwait(false); - if (!order) + if (!order.Success) { // NOTE: Market orders seem to return the incorrect order id when placing the order // Place order endpoint returns order id X while the actually order id which can be retrieved is X + 1 order = await Trading.GetClosedOrderAsync(symbol, (long.Parse(request.OrderId) + 1).ToString(), ct: ct).ConfigureAwait(false); - if (!order) - return order.AsExchangeResult(Exchange, null, default); + if (!order.Success) + return HttpResult.Fail(order); } } - return order.AsExchangeResult(Exchange, request.Symbol.TradingMode, new SharedFuturesOrder( + return HttpResult.Ok(order, new SharedFuturesOrder( ExchangeSymbolCache.ParseSymbol(_topicFuturesId, order.Data.Symbol), order.Data.Symbol, order.Data.OrderId, @@ -981,25 +959,25 @@ async Task> IFuturesOrderRestClient.GetFut }); } - EndpointOptions IFuturesOrderRestClient.GetOpenFuturesOrdersOptions { get; } = new EndpointOptions(true) + GetOpenFuturesOrdersOptions IFuturesOrderRestClient.GetOpenFuturesOrdersOptions { get; } = new GetOpenFuturesOrdersOptions(_exchangeName, true) { RequiredOptionalParameters = new List { new ParameterDescription(nameof(GetOpenOrdersRequest.Symbol), typeof(SharedSymbol), "Symbol to get open orders for", "ETH-USDT-SWAP") } }; - async Task> IFuturesOrderRestClient.GetOpenFuturesOrdersAsync(GetOpenOrdersRequest request, CancellationToken ct) + async Task> IFuturesOrderRestClient.GetOpenFuturesOrdersAsync(GetOpenOrdersRequest request, CancellationToken ct) { - var validationError = ((IFuturesOrderRestClient)this).GetOpenFuturesOrdersOptions.ValidateRequest(Exchange, request, request.Symbol?.TradingMode ?? request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetOpenFuturesOrdersOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var symbol = request.Symbol!.GetSymbol(FormatSymbol); var orders = await Trading.GetOpenOrdersAsync(symbol, ct: ct).ConfigureAwait(false); - if (!orders) - return orders.AsExchangeResult(Exchange, null, default); + if (!orders.Success) + return HttpResult.Fail(orders); - return orders.AsExchangeResult(Exchange, request.Symbol == null ? SupportedTradingModes : new[] { request.Symbol.TradingMode }, orders.Data.Select(x => new SharedFuturesOrder( + return HttpResult.Ok(orders, orders.Data.Select(x => new SharedFuturesOrder( ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol), x.Symbol, x.OrderId, @@ -1024,12 +1002,12 @@ async Task> IFuturesOrderRestClient.GetO }).ToArray()); } - GetClosedOrdersOptions IFuturesOrderRestClient.GetClosedFuturesOrdersOptions { get; } = new GetClosedOrdersOptions(false, true, true, 100); - async Task> IFuturesOrderRestClient.GetClosedFuturesOrdersAsync(GetClosedOrdersRequest request, PageRequest? pageRequest, CancellationToken ct) + GetFuturesClosedOrdersOptions IFuturesOrderRestClient.GetClosedFuturesOrdersOptions { get; } = new GetFuturesClosedOrdersOptions(_exchangeName, false, true, true, 100); + async Task> IFuturesOrderRestClient.GetClosedFuturesOrdersAsync(GetClosedOrdersRequest request, PageRequest? pageRequest, CancellationToken ct) { - var validationError = ((IFuturesOrderRestClient)this).GetClosedFuturesOrdersOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetClosedFuturesOrdersOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); int limit = request.Limit ?? 100; var direction = DataDirection.Descending; @@ -1041,8 +1019,8 @@ async Task> IFuturesOrderRestClient.GetC limit: pageParams.Limit, afterId: pageParams.FromId, // Correct? ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); var nextPageRequest = Pagination.GetNextPageRequest( () => Pagination.NextPageFromId(result.Data.OrderBy(x => x.CreateTime).First().OrderId), @@ -1052,9 +1030,7 @@ async Task> IFuturesOrderRestClient.GetC request.EndTime ?? DateTime.UtcNow, pageParams); - return result.AsExchangeResult( - Exchange, - request.Symbol.TradingMode, + return HttpResult.Ok(result, ExchangeHelpers.ApplyFilter(result.Data, x => x.CreateTime, request.StartTime, request.EndTime, direction).Select(x => new SharedFuturesOrder( ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol), @@ -1082,22 +1058,22 @@ async Task> IFuturesOrderRestClient.GetC .ToArray(), nextPageRequest); } - EndpointOptions IFuturesOrderRestClient.GetFuturesOrderTradesOptions { get; } = new EndpointOptions(true); - async Task> IFuturesOrderRestClient.GetFuturesOrderTradesAsync(GetOrderTradesRequest request, CancellationToken ct) + GetFuturesOrderTradesOptions IFuturesOrderRestClient.GetFuturesOrderTradesOptions { get; } = new GetFuturesOrderTradesOptions(_exchangeName, true); + async Task> IFuturesOrderRestClient.GetFuturesOrderTradesAsync(GetOrderTradesRequest request, CancellationToken ct) { - var validationError = ((IFuturesOrderRestClient)this).GetFuturesOrderTradesOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetFuturesOrderTradesOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var orders = await Trading.GetUserTradesAsync( SymbolType.Swap, request.Symbol!.GetSymbol(FormatSymbol), orderId: request.OrderId, ct: ct).ConfigureAwait(false); - if (!orders) - return orders.AsExchangeResult(Exchange, null, default); + if (!orders.Success) + return HttpResult.Fail(orders); - return orders.AsExchangeResult(Exchange, request.Symbol.TradingMode, orders.Data.Select(x => new SharedUserTrade( + return HttpResult.Ok(orders, orders.Data.Select(x => new SharedUserTrade( ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol), x.Symbol, x.OrderId.ToString(), @@ -1113,12 +1089,12 @@ async Task> IFuturesOrderRestClient.GetFutu }).ToArray()); } - GetUserTradesOptions IFuturesOrderRestClient.GetFuturesUserTradesOptions { get; } = new GetUserTradesOptions(false, true, true, 100); - async Task> IFuturesOrderRestClient.GetFuturesUserTradesAsync(GetUserTradesRequest request, PageRequest? pageRequest, CancellationToken ct) + GetFuturesUserTradesOptions IFuturesOrderRestClient.GetFuturesUserTradesOptions { get; } = new GetFuturesUserTradesOptions(_exchangeName, false, true, true, 100); + async Task> IFuturesOrderRestClient.GetFuturesUserTradesAsync(GetUserTradesRequest request, PageRequest? pageRequest, CancellationToken ct) { - var validationError = ((IFuturesOrderRestClient)this).GetFuturesUserTradesOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetFuturesUserTradesOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); int limit = request.Limit ?? 100; var direction = DataDirection.Descending; @@ -1134,8 +1110,8 @@ async Task> IFuturesOrderRestClient.GetFutu afterId: pageParams.FromId, ct: ct ).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); var nextPageRequest = Pagination.GetNextPageRequest( () => Pagination.NextPageFromId(result.Data.OrderBy(x => x.Timestamp).First().BillId), @@ -1145,9 +1121,7 @@ async Task> IFuturesOrderRestClient.GetFutu request.EndTime ?? DateTime.UtcNow, pageParams); - return result.AsExchangeResult( - Exchange, - TradingMode.Spot, + return HttpResult.Ok(result, ExchangeHelpers.ApplyFilter(result.Data, x => x.Timestamp, request.StartTime, request.EndTime, direction) .Select(x => new SharedUserTrade( @@ -1167,37 +1141,37 @@ async Task> IFuturesOrderRestClient.GetFutu .ToArray(), nextPageRequest); } - EndpointOptions IFuturesOrderRestClient.CancelFuturesOrderOptions { get; } = new EndpointOptions(true); - async Task> IFuturesOrderRestClient.CancelFuturesOrderAsync(CancelOrderRequest request, CancellationToken ct) + CancelFuturesOrderOptions IFuturesOrderRestClient.CancelFuturesOrderOptions { get; } = new CancelFuturesOrderOptions(_exchangeName, true); + async Task> IFuturesOrderRestClient.CancelFuturesOrderAsync(CancelOrderRequest request, CancellationToken ct) { - var validationError = ((IFuturesOrderRestClient)this).CancelFuturesOrderOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.CancelFuturesOrderOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var order = await Trading.CancelOrderAsync(request.Symbol!.GetSymbol(FormatSymbol), request.OrderId, ct: ct).ConfigureAwait(false); - if (!order) - return order.AsExchangeResult(Exchange, null, default); + if (!order.Success) + return HttpResult.Fail(order); - return order.AsExchangeResult(Exchange, request.Symbol.TradingMode, new SharedId(order.Data.OrderId.ToString())); + return HttpResult.Ok(order, new SharedId(order.Data.OrderId.ToString())); } - EndpointOptions IFuturesOrderRestClient.GetPositionsOptions { get; } = new EndpointOptions(true); - async Task> IFuturesOrderRestClient.GetPositionsAsync(GetPositionsRequest request, CancellationToken ct) + GetPositionsOptions IFuturesOrderRestClient.GetPositionsOptions { get; } = new GetPositionsOptions(_exchangeName, true); + async Task> IFuturesOrderRestClient.GetPositionsAsync(GetPositionsRequest request, CancellationToken ct) { - var validationError = ((IFuturesOrderRestClient)this).GetPositionsOptions.ValidateRequest(Exchange, request, request.Symbol?.TradingMode ?? request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetPositionsOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var result = await Trading.GetPositionsAsync(SymbolType.Swap, symbol: request.Symbol?.GetSymbol(FormatSymbol), ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); IEnumerable data = result.Data; if (request.TradingMode.HasValue) data = data.Where(x => request.TradingMode == TradingMode.PerpetualInverse ? x.Symbol.Contains("_USD_") : !x.Symbol.Contains("_USD_")); var resultTypes = request.Symbol == null && request.TradingMode == null ? SupportedTradingModes : request.Symbol != null ? new[] { request.Symbol.TradingMode } : new[] { request.TradingMode!.Value }; - return result.AsExchangeResult(Exchange, resultTypes, data.Select(x => new SharedPosition(ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol), x.Symbol, Math.Abs(x.Size), x.UpdateTime) + return HttpResult.Ok(result, data.Select(x => new SharedPosition(ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol), x.Symbol, Math.Abs(x.Size), x.UpdateTime) { LiquidationPrice = x.LiquidationPrice == 0 ? null : x.LiquidationPrice, Leverage = x.Leverage, @@ -1207,7 +1181,7 @@ async Task> IFuturesOrderRestClient.GetPosit }).ToArray()); } - EndpointOptions IFuturesOrderRestClient.ClosePositionOptions { get; } = new EndpointOptions(true) + ClosePositionOptions IFuturesOrderRestClient.ClosePositionOptions { get; } = new ClosePositionOptions(_exchangeName, true) { RequiredOptionalParameters = new List { @@ -1215,11 +1189,11 @@ async Task> IFuturesOrderRestClient.GetPosit new ParameterDescription(nameof(ClosePositionRequest.Quantity), typeof(decimal), "Quantity of the position is required", 0.1m) } }; - async Task> IFuturesOrderRestClient.ClosePositionAsync(ClosePositionRequest request, CancellationToken ct) + async Task> IFuturesOrderRestClient.ClosePositionAsync(ClosePositionRequest request, CancellationToken ct) { - var validationError = ((IFuturesOrderRestClient)this).ClosePositionOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.ClosePositionOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var symbol = request.Symbol!.GetSymbol(FormatSymbol); var result = await Trading.PlaceOrderAsync( @@ -1230,10 +1204,10 @@ async Task> IFuturesOrderRestClient.ClosePositionAsy positionSide: request.PositionSide == SharedPositionSide.Short ? PositionSide.Short : PositionSide.Long, reduceOnly: true, ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); - return result.AsExchangeResult(Exchange, request.Symbol.TradingMode, new SharedId(result.Data.OrderId.ToString())); + return HttpResult.Ok(result, new SharedId(result.Data.OrderId.ToString())); } #endregion } diff --git a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiTrading.cs b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiTrading.cs index 41e150e..5f6f7ae 100644 --- a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiTrading.cs +++ b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiTrading.cs @@ -30,12 +30,12 @@ internal DeepCoinRestClientExchangeApiTrading(ILogger logger, DeepCoinRestClient #region Get Positions /// - public async Task> GetPositionsAsync(SymbolType symbolType, string? symbol = null, CancellationToken ct = default) + public async Task> GetPositionsAsync(SymbolType symbolType, string? symbol = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddEnum("instType", symbolType); - parameters.AddOptional("instId", symbol); - var request = _definitions.GetOrCreate(HttpMethod.Get, "/deepcoin/account/positions", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); + var parameters = new Parameters(DeepCoinExchange._parameterSerializationSettings); + parameters.Add("instType", symbolType); + parameters.Add("instId", symbol); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "/deepcoin/account/positions", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -45,31 +45,31 @@ public async Task> GetPositionsAsync(SymbolTyp #region Place Order /// - public async Task> PlaceOrderAsync(string symbol, OrderSide side, OrderType orderType, decimal quantity, decimal? price = null, TradeMode? tradeMode = null, string? asset = null, string? clientOrderId = null, QuantityType? quantityType = null, PositionSide? positionSide = null, PositionType? positionType = null, string? closePosId = null, bool? reduceOnly = null, decimal? tpTriggerPrice = null, decimal? slTriggerPrice = null, CancellationToken ct = default) + public async Task> PlaceOrderAsync(string symbol, OrderSide side, OrderType orderType, decimal quantity, decimal? price = null, TradeMode? tradeMode = null, string? asset = null, string? clientOrderId = null, QuantityType? quantityType = null, PositionSide? positionSide = null, PositionType? positionType = null, string? closePosId = null, bool? reduceOnly = null, decimal? tpTriggerPrice = null, decimal? slTriggerPrice = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); + var parameters = new Parameters(DeepCoinExchange._parameterSerializationSettings); parameters.Add("instId", symbol); - parameters.AddEnum("side", side); + parameters.Add("side", side); parameters.Add("ordType", orderType); - parameters.AddString("size", quantity); - parameters.AddEnum("tdMode", tradeMode ?? TradeMode.Cross); - parameters.AddOptionalString("px", price); - parameters.AddOptional("ccy", asset); - parameters.AddOptional("clOrdId", clientOrderId); - parameters.AddOptionalEnum("tgtCcy", quantityType); - parameters.AddOptionalEnum("posSide", positionSide); - parameters.AddOptionalEnum("mrgPosition", positionType); - parameters.AddOptional("closePosId", closePosId); - parameters.AddOptional("reduceOnly", reduceOnly); - parameters.AddOptionalString("tpTriggerPx", tpTriggerPrice); - parameters.AddOptionalString("slTriggerPx", slTriggerPrice); - var request = _definitions.GetOrCreate(HttpMethod.Post, "/deepcoin/trade/order", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); + parameters.Add("size", quantity); + parameters.Add("tdMode", tradeMode ?? TradeMode.Cross); + parameters.Add("px", price); + parameters.Add("ccy", asset); + parameters.Add("clOrdId", clientOrderId); + parameters.Add("tgtCcy", quantityType); + parameters.Add("posSide", positionSide); + parameters.Add("mrgPosition", positionType); + parameters.Add("closePosId", closePosId); + parameters.Add("reduceOnly", reduceOnly); + parameters.Add("tpTriggerPx", tpTriggerPrice); + parameters.Add("slTriggerPx", slTriggerPrice); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, "/deepcoin/trade/order", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); - if (!result) + if (!result.Success) return result; if (result.Data.ResultCode != 0) - return result.AsError(new ServerError(result.Data.ResultCode, _baseClient.GetErrorInfo(result.Data.ResultCode, result.Data.ResultMessage!))); + return HttpResult.Fail(result, new ServerError(result.Data.ResultCode, _baseClient.GetErrorInfo(result.Data.ResultCode, result.Data.ResultMessage!))); return result; } @@ -79,19 +79,19 @@ public async Task> PlaceOrderAsync(string sym #region Edit Order /// - public async Task> EditOrderAsync(string orderId, decimal? price = null, decimal? quantity = null, CancellationToken ct = default) + public async Task> EditOrderAsync(string orderId, decimal? price = null, decimal? quantity = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); + var parameters = new Parameters(DeepCoinExchange._parameterSerializationSettings); parameters.Add("OrderSysID", $"{orderId}"); - parameters.AddOptional("price", price); - parameters.AddOptional("volume", quantity); - var request = _definitions.GetOrCreate(HttpMethod.Post, "/deepcoin/trade/replace-order", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); + parameters.Add("price", price); + parameters.Add("volume", quantity); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, "/deepcoin/trade/replace-order", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); - if (!result) + if (!result.Success) return result; if (result.Data.ResultCode != 0) - return result.AsError(new ServerError(result.Data.ResultCode, _baseClient.GetErrorInfo(result.Data.ResultCode, result.Data.ResultMessage!))); + return HttpResult.Fail(result, new ServerError(result.Data.ResultCode, _baseClient.GetErrorInfo(result.Data.ResultCode, result.Data.ResultMessage!))); return result; } @@ -101,18 +101,18 @@ public async Task> EditOrderAsync(string orde #region Cancel Order /// - public async Task> CancelOrderAsync(string symbol, string orderId, CancellationToken ct = default) + public async Task> CancelOrderAsync(string symbol, string orderId, CancellationToken ct = default) { - var parameters = new ParameterCollection(); + var parameters = new Parameters(DeepCoinExchange._parameterSerializationSettings); parameters.Add("instId", symbol); parameters.Add("ordId", orderId); - var request = _definitions.GetOrCreate(HttpMethod.Post, "/deepcoin/trade/cancel-order", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, "/deepcoin/trade/cancel-order", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); - if (!result) + if (!result.Success) return result; if (result.Data.ResultCode != 0) - return result.AsError(new ServerError(result.Data.ResultCode, _baseClient.GetErrorInfo(result.Data.ResultCode, result.Data.ResultMessage!))); + return HttpResult.Fail(result, new ServerError(result.Data.ResultCode, _baseClient.GetErrorInfo(result.Data.ResultCode, result.Data.ResultMessage!))); return result; } @@ -122,11 +122,11 @@ public async Task> CancelOrderAsync(string sy #region Cancel Orders /// - public async Task> CancelOrdersAsync(IEnumerable orderIds, CancellationToken ct = default) + public async Task> CancelOrdersAsync(IEnumerable orderIds, CancellationToken ct = default) { - var parameters = new ParameterCollection(); + var parameters = new Parameters(DeepCoinExchange._parameterSerializationSettings); parameters.Add("OrderSysIDs", orderIds.ToArray()); - var request = _definitions.GetOrCreate(HttpMethod.Post, "/deepcoin/trade/batch-cancel-order", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, "/deepcoin/trade/batch-cancel-order", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -136,14 +136,14 @@ public async Task> CancelOrdersAsync(I #region Cancel All Orders /// - public async Task> CancelAllOrdersAsync(string symbol, ProductGroup productGroup, TradeMode tradeMode, PositionType positionType, CancellationToken ct = default) + public async Task> CancelAllOrdersAsync(string symbol, ProductGroup productGroup, TradeMode tradeMode, PositionType positionType, CancellationToken ct = default) { - var parameters = new ParameterCollection(); + var parameters = new Parameters(DeepCoinExchange._parameterSerializationSettings); parameters.Add("instrumentID", symbol); - parameters.AddEnum("ProductGroup", productGroup); + parameters.Add("ProductGroup", productGroup); parameters.Add("IsCrossMargin", tradeMode == TradeMode.Cross ? "1" : "0"); parameters.Add("IsMergeMode", positionType == PositionType.Merge ? "1" : "0"); - var request = _definitions.GetOrCreate(HttpMethod.Post, "/deepcoin/trade/swap/cancel-all", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, "/deepcoin/trade/swap/cancel-all", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -153,18 +153,18 @@ public async Task> CancelAllOrdersAsyn #region Get User Trades /// - public async Task> GetUserTradesAsync(SymbolType symbolType, string? symbol = null, string? orderId = null, string? afterId = null, string? beforeId = null, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, CancellationToken ct = default) + public async Task> GetUserTradesAsync(SymbolType symbolType, string? symbol = null, string? orderId = null, string? afterId = null, string? beforeId = null, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddEnum("instType", symbolType); - parameters.AddOptional("instId", symbol); - parameters.AddOptional("ordId", orderId); - parameters.AddOptional("after", afterId); - parameters.AddOptional("before", beforeId); - parameters.AddOptionalMilliseconds("begin", startTime); - parameters.AddOptionalMilliseconds("end", endTime); - parameters.AddOptional("limit", limit); - var request = _definitions.GetOrCreate(HttpMethod.Get, "/deepcoin/trade/fills", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); + var parameters = new Parameters(DeepCoinExchange._parameterSerializationSettings); + parameters.Add("instType", symbolType); + parameters.Add("instId", symbol); + parameters.Add("ordId", orderId); + parameters.Add("after", afterId); + parameters.Add("before", beforeId); + parameters.Add("begin", startTime); + parameters.Add("end", endTime); + parameters.Add("limit", limit); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "/deepcoin/trade/fills", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -174,21 +174,21 @@ public async Task> GetUserTradesAsync(SymbolT #region Get Open Order /// - public async Task> GetOpenOrderAsync(string symbol, string orderId, CancellationToken ct = default) + public async Task> GetOpenOrderAsync(string symbol, string orderId, CancellationToken ct = default) { - var parameters = new ParameterCollection(); + var parameters = new Parameters(DeepCoinExchange._parameterSerializationSettings); parameters.Add("instId", symbol); parameters.Add("ordId", orderId); - var request = _definitions.GetOrCreate(HttpMethod.Get, "/deepcoin/trade/orderByID", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "/deepcoin/trade/orderByID", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); - if (!result) - return result.As(default); + if (!result.Success) + return HttpResult.Fail(result); var order = result.Data.SingleOrDefault(); if (order == null) - return result.AsError(new ServerError(new ErrorInfo(ErrorType.UnknownOrder, "Order not found"))); + return HttpResult.Fail(result, new ServerError(new ErrorInfo(ErrorType.UnknownOrder, "Order not found"))); - return result.As(order); + return HttpResult.Ok(result, order); } #endregion @@ -196,21 +196,21 @@ public async Task> GetOpenOrderAsync(string symbol, #region Get Closed Order /// - public async Task> GetClosedOrderAsync(string symbol, string orderId, CancellationToken ct = default) + public async Task> GetClosedOrderAsync(string symbol, string orderId, CancellationToken ct = default) { - var parameters = new ParameterCollection(); + var parameters = new Parameters(DeepCoinExchange._parameterSerializationSettings); parameters.Add("instId", symbol); parameters.Add("ordId", orderId); - var request = _definitions.GetOrCreate(HttpMethod.Get, "/deepcoin/trade/finishOrderByID", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "/deepcoin/trade/finishOrderByID", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); - if (!result) - return result.As(default); + if (!result.Success) + return HttpResult.Fail(result); var order = result.Data.SingleOrDefault(); if (order == null) - return result.AsError(new ServerError(new ErrorInfo(ErrorType.UnknownOrder, "Order not found"))); + return HttpResult.Fail(result, new ServerError(new ErrorInfo(ErrorType.UnknownOrder, "Order not found"))); - return result.As(order); + return HttpResult.Ok(result, order); } #endregion @@ -218,18 +218,18 @@ public async Task> GetClosedOrderAsync(string symbo #region Get Closed Orders /// - public async Task> GetClosedOrdersAsync(SymbolType symbolType, string? symbol = null, string? orderId = null, OrderType? orderType = null, OrderStatus? status = null, string? afterId = null, string? beforeId = null, int? limit = null, CancellationToken ct = default) + public async Task> GetClosedOrdersAsync(SymbolType symbolType, string? symbol = null, string? orderId = null, OrderType? orderType = null, OrderStatus? status = null, string? afterId = null, string? beforeId = null, int? limit = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddEnum("instType", symbolType); - parameters.AddOptional("instId", symbol); - parameters.AddOptional("ordId", orderId); - parameters.AddOptionalEnum("ordType", orderType); - parameters.AddOptionalEnum("state", status); - parameters.AddOptional("after", afterId); - parameters.AddOptional("before", beforeId); - parameters.AddOptional("limit", limit); - var request = _definitions.GetOrCreate(HttpMethod.Get, "/deepcoin/trade/orders-history", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); + var parameters = new Parameters(DeepCoinExchange._parameterSerializationSettings); + parameters.Add("instType", symbolType); + parameters.Add("instId", symbol); + parameters.Add("ordId", orderId); + parameters.Add("ordType", orderType); + parameters.Add("state", status); + parameters.Add("after", afterId); + parameters.Add("before", beforeId); + parameters.Add("limit", limit); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "/deepcoin/trade/orders-history", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -239,14 +239,14 @@ public async Task> GetClosedOrdersAsync(SymbolTyp #region Get Open Orders /// - public async Task> GetOpenOrdersAsync(string symbol, int? page = null, int? pageSize = null, string? orderId = null, CancellationToken ct = default) + public async Task> GetOpenOrdersAsync(string symbol, int? page = null, int? pageSize = null, string? orderId = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); + var parameters = new Parameters(DeepCoinExchange._parameterSerializationSettings); parameters.Add("instId", symbol); parameters.Add("index", page ?? 1); - parameters.AddOptional("limit", pageSize); - parameters.AddOptional("ordId", orderId); - var request = _definitions.GetOrCreate(HttpMethod.Get, "/deepcoin/trade/v2/orders-pending", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); + parameters.Add("limit", pageSize); + parameters.Add("ordId", orderId); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "/deepcoin/trade/v2/orders-pending", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -256,13 +256,13 @@ public async Task> GetOpenOrdersAsync(string symb #region Set Tp Sl /// - public async Task SetTpSlAsync(string orderId, decimal? takeProfitTriggerPrice = null, decimal? stopLossTriggerPrice = null, CancellationToken ct = default) + public async Task SetTpSlAsync(string orderId, decimal? takeProfitTriggerPrice = null, decimal? stopLossTriggerPrice = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); + var parameters = new Parameters(DeepCoinExchange._parameterSerializationSettings); parameters.Add("orderSysID", orderId); - parameters.AddOptionalString("tpTriggerPx", takeProfitTriggerPrice); - parameters.AddOptionalString("slTriggerPx", stopLossTriggerPrice); - var request = _definitions.GetOrCreate(HttpMethod.Post, "/deepcoin/trade/replace-order-sltp", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); + parameters.Add("tpTriggerPx", takeProfitTriggerPrice); + parameters.Add("slTriggerPx", stopLossTriggerPrice); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, "/deepcoin/trade/replace-order-sltp", DeepCoinExchange.RateLimiter.DeepCoin, 1, true, limitGuard: new SingleLimitGuard(1, TimeSpan.FromSeconds(1), RateLimitWindowType.Sliding)); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } diff --git a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApi.cs b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApi.cs index 2ba7c89..f018e5e 100644 --- a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApi.cs +++ b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApi.cs @@ -38,7 +38,7 @@ internal partial class DeepCoinSocketClientExchangeApi : SocketApiClient internal DeepCoinSocketClientExchangeApi(ILogger logger, DeepCoinSocketOptions options) : - base(logger, options.Environment.SocketClientAddress!, options, options.ExchangeOptions) + base(logger, DeepCoinExchange.Metadata.Id, options.Environment.SocketClientAddress!, options, options.ExchangeOptions) { KeepAliveInterval = TimeSpan.Zero; @@ -71,7 +71,7 @@ protected override DeepCoinAuthenticationProvider CreateAuthenticationProvider(D => new DeepCoinAuthenticationProvider(credentials); /// - public async Task> SubscribeToSymbolUpdatesAsync(string symbol, Action> onMessage, CancellationToken ct = default) + public async Task> SubscribeToSymbolUpdatesAsync(string symbol, Action> onMessage, CancellationToken ct = default) { string path; if (symbol.EndsWith("-SWAP", StringComparison.InvariantCultureIgnoreCase)) @@ -102,7 +102,7 @@ public async Task> SubscribeToSymbolUpdatesAsync( } /// - public async Task> SubscribeToTradeUpdatesAsync(string symbol, Action> onMessage, CancellationToken ct = default) + public async Task> SubscribeToTradeUpdatesAsync(string symbol, Action> onMessage, CancellationToken ct = default) { string path; if (symbol.EndsWith("-SWAP", StringComparison.InvariantCultureIgnoreCase)) @@ -134,7 +134,7 @@ public async Task> SubscribeToTradeUpdatesAsync(s } /// - public async Task> SubscribeToKlineUpdatesAsync(string symbol, Action> onMessage, CancellationToken ct = default) + public async Task> SubscribeToKlineUpdatesAsync(string symbol, Action> onMessage, CancellationToken ct = default) { string path; if (symbol.EndsWith("-SWAP", StringComparison.InvariantCultureIgnoreCase)) @@ -166,7 +166,7 @@ public async Task> SubscribeToKlineUpdatesAsync(s } /// - public async Task> SubscribeToOrderBookUpdatesAsync(string symbol, Action> onMessage, CancellationToken ct = default) + public async Task> SubscribeToOrderBookUpdatesAsync(string symbol, Action> onMessage, CancellationToken ct = default) { string path; if (symbol.EndsWith("-SWAP", StringComparison.InvariantCultureIgnoreCase)) @@ -185,7 +185,7 @@ public async Task> SubscribeToOrderBookUpdatesAsy } /// - public async Task> SubscribeToUserDataUpdatesAsync( + public async Task> SubscribeToUserDataUpdatesAsync( string listenKey, Action>? onOrderMessage = null, Action>? onBalanceMessage = null, diff --git a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApiShared.cs b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApiShared.cs index e5c15cc..9852142 100644 --- a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApiShared.cs +++ b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApiShared.cs @@ -16,40 +16,39 @@ internal partial class DeepCoinSocketClientExchangeApi : IDeepCoinSocketClientEx { private const string _topicSpotId = "DeepCoinSpot"; private const string _topicFuturesId = "DeepCoinFutures"; - public string Exchange => "DeepCoin"; + private const string _exchangeName = "DeepCoin"; public TradingMode[] SupportedTradingModes => new[] { TradingMode.Spot, TradingMode.PerpetualLinear, TradingMode.PerpetualInverse }; public void SetDefaultExchangeParameter(string key, object value) => ExchangeParameters.SetStaticParameter(Exchange, key, value); public void ResetDefaultExchangeParameters() => ExchangeParameters.ResetStaticParameters(); + public SharedClientInfo Discover() => SharedUtils.GetClientInfo(this); #region Kline client - SubscribeKlineOptions IKlineSocketClient.SubscribeKlineOptions { get; } = new SubscribeKlineOptions(false, SharedKlineInterval.OneMinute); - async Task> IKlineSocketClient.SubscribeToKlineUpdatesAsync(SubscribeKlineRequest request, Action> handler, CancellationToken ct) + SubscribeKlineOptions IKlineSocketClient.SubscribeKlineOptions { get; } = new SubscribeKlineOptions(_exchangeName, false, SharedKlineInterval.OneMinute); + async Task> IKlineSocketClient.SubscribeToKlineUpdatesAsync(SubscribeKlineRequest request, Action> handler, CancellationToken ct) { - if(request.Interval != SharedKlineInterval.OneMinute) - return new ExchangeResult(Exchange, ArgumentError.Invalid(nameof(GetKlinesRequest.Interval), "Interval not supported")); - var validationError = ((IKlineSocketClient)this).SubscribeKlineOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.SubscribeKlineOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeResult(Exchange, validationError); + return WebSocketResult.Fail(_exchangeName, validationError); var symbol = request.Symbol!.GetSymbol(DeepCoinExchange.FormatSymbol); var result = await SubscribeToKlineUpdatesAsync(symbol, update => handler(update.ToType( new SharedKline(request.Symbol, symbol, update.Data.OpenTime, update.Data.ClosePrice, update.Data.HighPrice, update.Data.LowPrice, update.Data.OpenPrice, update.Data.Volume))), ct).ConfigureAwait(false); - return new ExchangeResult(Exchange, result); + return result; } #endregion #region Ticker client - SubscribeTickerOptions ITickerSocketClient.SubscribeTickerOptions { get; } = new SubscribeTickerOptions(); - async Task> ITickerSocketClient.SubscribeToTickerUpdatesAsync(SubscribeTickerRequest request, Action> handler, CancellationToken ct) + SubscribeTickerOptions ITickerSocketClient.SubscribeTickerOptions { get; } = new SubscribeTickerOptions(_exchangeName); + async Task> ITickerSocketClient.SubscribeToTickerUpdatesAsync(SubscribeTickerRequest request, Action> handler, CancellationToken ct) { - var validationError = ((ITickerSocketClient)this).SubscribeTickerOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.SubscribeTickerOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeResult(Exchange, validationError); + return WebSocketResult.Fail(_exchangeName, validationError); var symbol = request.Symbol!.GetSymbol(DeepCoinExchange.FormatSymbol); var result = await SubscribeToSymbolUpdatesAsync(symbol, update => handler(update.ToType(new SharedSpotTicker(ExchangeSymbolCache.ParseSymbol(_topicSpotId, symbol) ?? ExchangeSymbolCache.ParseSymbol(_topicFuturesId, symbol), symbol, update.Data.LastPrice, update.Data.HighPrice, update.Data.LowPrice, update.Data.Volume, update.Data.OpenPrice == null ? null : Math.Round((update.Data.LastPrice ?? 0) / update.Data.OpenPrice.Value * 100 - 100, 3)) @@ -58,19 +57,19 @@ async Task> ITickerSocketClient.SubscribeToTi QuoteVolume = request.Symbol.TradingMode == TradingMode.Spot ? null : update.Data.Turnover })), ct: ct).ConfigureAwait(false); - return new ExchangeResult(Exchange, result); + return result; } #endregion #region Trade client - EndpointOptions ITradeSocketClient.SubscribeTradeOptions { get; } = new EndpointOptions(false); - async Task> ITradeSocketClient.SubscribeToTradeUpdatesAsync(SubscribeTradeRequest request, Action> handler, CancellationToken ct) + SubscribeTradeOptions ITradeSocketClient.SubscribeTradeOptions { get; } = new SubscribeTradeOptions(_exchangeName, false); + async Task> ITradeSocketClient.SubscribeToTradeUpdatesAsync(SubscribeTradeRequest request, Action> handler, CancellationToken ct) { - var validationError = ((ITradeSocketClient)this).SubscribeTradeOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.SubscribeTradeOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeResult(Exchange, validationError); + return WebSocketResult.Fail(_exchangeName, validationError); var symbol = request.Symbol!.GetSymbol(DeepCoinExchange.FormatSymbol); var result = await SubscribeToTradeUpdatesAsync(symbol, update => handler(update.ToType(new[] { @@ -79,48 +78,48 @@ async Task> ITradeSocketClient.SubscribeToTra Side = update.Data.Side == Enums.OrderSide.Sell ? SharedOrderSide.Sell : SharedOrderSide.Buy } })), ct: ct).ConfigureAwait(false); - return new ExchangeResult(Exchange, result); + return result; } #endregion #region Balance client - EndpointOptions IBalanceSocketClient.SubscribeBalanceOptions { get; } = new EndpointOptions(false) + SubscribeBalanceOptions IBalanceSocketClient.SubscribeBalanceOptions { get; } = new SubscribeBalanceOptions(_exchangeName, false) { RequiredOptionalParameters = new List { new ParameterDescription(nameof(SubscribeBalancesRequest.ListenKey), typeof(string), "The listenkey for starting the user stream", "123123123") } }; - async Task> IBalanceSocketClient.SubscribeToBalanceUpdatesAsync(SubscribeBalancesRequest request, Action> handler, CancellationToken ct) + async Task> IBalanceSocketClient.SubscribeToBalanceUpdatesAsync(SubscribeBalancesRequest request, Action> handler, CancellationToken ct) { - var validationError = ((IBalanceSocketClient)this).SubscribeBalanceOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.SubscribeBalanceOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeResult(Exchange, validationError); + return WebSocketResult.Fail(_exchangeName, validationError); var result = await SubscribeToUserDataUpdatesAsync(request.ListenKey!, onBalanceMessage: update => handler(update.ToType(update.Data.Select(x => new SharedBalance(x.Asset, x.Available, x.Balance)).ToArray())), ct: ct).ConfigureAwait(false); - return new ExchangeResult(Exchange, result); + return result; } #endregion #region Futures Order client - EndpointOptions IFuturesOrderSocketClient.SubscribeFuturesOrderOptions { get; } = new EndpointOptions(false) + SubscribeFuturesOrderOptions IFuturesOrderSocketClient.SubscribeFuturesOrderOptions { get; } = new SubscribeFuturesOrderOptions(_exchangeName, false) { RequiredOptionalParameters = new List { new ParameterDescription(nameof(SubscribeFuturesOrderRequest.ListenKey), typeof(string), "The listenkey for starting the user stream", "123123123") } }; - async Task> IFuturesOrderSocketClient.SubscribeToFuturesOrderUpdatesAsync(SubscribeFuturesOrderRequest request, Action> handler, CancellationToken ct) + async Task> IFuturesOrderSocketClient.SubscribeToFuturesOrderUpdatesAsync(SubscribeFuturesOrderRequest request, Action> handler, CancellationToken ct) { - var validationError = ((IFuturesOrderSocketClient)this).SubscribeFuturesOrderOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.SubscribeFuturesOrderOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeResult(Exchange, validationError); + return WebSocketResult.Fail(_exchangeName, validationError); var result = await SubscribeToUserDataUpdatesAsync(request.ListenKey!, onOrderMessage: update => @@ -152,24 +151,24 @@ async Task> IFuturesOrderSocketClient.Subscri }, ct: ct).ConfigureAwait(false); - return new ExchangeResult(Exchange, result); + return result; } #endregion #region Spot Order client - EndpointOptions ISpotOrderSocketClient.SubscribeSpotOrderOptions { get; } = new EndpointOptions(false) + SubscribeSpotOrderOptions ISpotOrderSocketClient.SubscribeSpotOrderOptions { get; } = new SubscribeSpotOrderOptions(_exchangeName, false) { RequiredOptionalParameters = new List { new ParameterDescription(nameof(SubscribeSpotOrderRequest.ListenKey), typeof(string), "Listenkey for the user stream", "123123123") } }; - async Task> ISpotOrderSocketClient.SubscribeToSpotOrderUpdatesAsync(SubscribeSpotOrderRequest request, Action> handler, CancellationToken ct) + async Task> ISpotOrderSocketClient.SubscribeToSpotOrderUpdatesAsync(SubscribeSpotOrderRequest request, Action> handler, CancellationToken ct) { - var validationError = ((ISpotOrderSocketClient)this).SubscribeSpotOrderOptions.ValidateRequest(Exchange, request, TradingMode.Spot, SupportedTradingModes); + var validationError = SharedClient.SubscribeSpotOrderOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeResult(Exchange, validationError); + return WebSocketResult.Fail(_exchangeName, validationError); var result = await SubscribeToUserDataUpdatesAsync(request.ListenKey!, onOrderMessage: update => @@ -200,23 +199,23 @@ async Task> ISpotOrderSocketClient.SubscribeT }, ct: ct).ConfigureAwait(false); - return new ExchangeResult(Exchange, result); + return result; } #endregion #region Position client - EndpointOptions IPositionSocketClient.SubscribePositionOptions { get; } = new EndpointOptions(false) + SubscribePositionOptions IPositionSocketClient.SubscribePositionOptions { get; } = new SubscribePositionOptions(_exchangeName, false) { RequiredOptionalParameters = new List { new ParameterDescription(nameof(SubscribePositionRequest.ListenKey), typeof(string), "The listenkey for starting the user stream", "123123123") } }; - async Task> IPositionSocketClient.SubscribeToPositionUpdatesAsync(SubscribePositionRequest request, Action> handler, CancellationToken ct) + async Task> IPositionSocketClient.SubscribeToPositionUpdatesAsync(SubscribePositionRequest request, Action> handler, CancellationToken ct) { - var validationError = ((IPositionSocketClient)this).SubscribePositionOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.SubscribePositionOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeResult(Exchange, validationError); + return WebSocketResult.Fail(_exchangeName, validationError); var result = await SubscribeToUserDataUpdatesAsync(request.ListenKey!, onPositionMessage: update => handler(update.ToType(update.Data.Select(x => new SharedPosition(ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol), x.Symbol, x.PositionSize, x.UpdateTime) @@ -228,25 +227,25 @@ async Task> IPositionSocketClient.SubscribeTo }).ToArray())), ct: ct).ConfigureAwait(false); - return new ExchangeResult(Exchange, result); + return result; } #endregion #region User Trade client - EndpointOptions IUserTradeSocketClient.SubscribeUserTradeOptions { get; } = new EndpointOptions(true) + SubscribeUserTradeOptions IUserTradeSocketClient.SubscribeUserTradeOptions { get; } = new SubscribeUserTradeOptions(_exchangeName, true) { RequiredOptionalParameters = new List { new ParameterDescription(nameof(SubscribePositionRequest.ListenKey), typeof(string), "The listenkey for starting the user stream", "123123123") } }; - async Task> IUserTradeSocketClient.SubscribeToUserTradeUpdatesAsync(SubscribeUserTradeRequest request, Action> handler, CancellationToken ct) + async Task> IUserTradeSocketClient.SubscribeToUserTradeUpdatesAsync(SubscribeUserTradeRequest request, Action> handler, CancellationToken ct) { - var validationError = ((IUserTradeSocketClient)this).SubscribeUserTradeOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.SubscribeUserTradeOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeResult(Exchange, validationError); + return WebSocketResult.Fail(_exchangeName, validationError); var result = await SubscribeToUserDataUpdatesAsync( request.ListenKey!, @@ -268,7 +267,7 @@ async Task> IUserTradeSocketClient.SubscribeT ).ToArray())), ct: ct).ConfigureAwait(false); - return new ExchangeResult(Exchange, result); + return result; } #endregion diff --git a/DeepCoin.Net/DeepCoin.Net.csproj b/DeepCoin.Net/DeepCoin.Net.csproj index e16e7b0..e4ed427 100644 --- a/DeepCoin.Net/DeepCoin.Net.csproj +++ b/DeepCoin.Net/DeepCoin.Net.csproj @@ -53,10 +53,12 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - all runtime; build; native; contentfiles; analyzers; buildtransitive + + + \ No newline at end of file diff --git a/DeepCoin.Net/DeepCoinAuthenticationProvider.cs b/DeepCoin.Net/DeepCoinAuthenticationProvider.cs index b804c83..b006088 100644 --- a/DeepCoin.Net/DeepCoinAuthenticationProvider.cs +++ b/DeepCoin.Net/DeepCoinAuthenticationProvider.cs @@ -18,13 +18,13 @@ public DeepCoinAuthenticationProvider(DeepCoinCredentials credentials) : base(cr public override void ProcessRequest(RestApiClient apiClient, RestRequestConfiguration request) { - if (!request.Authenticated) + if (!request.RequestDefinition.Authenticated) return; var timestamp = GetTimestamp(apiClient).ToString("yyyy-MM-ddTHH:mm:ss.fffZ"); var queryParams = request.QueryParameters?.Count > 0 ? $"?{request.GetQueryString(false)}" : ""; var bodyParams = request.BodyParameters?.Count > 0 ? GetSerializedBody(_serializer, request.BodyParameters) : ""; - var signStr = $"{timestamp}{request.Method}{request.Path}{queryParams}{bodyParams}"; + var signStr = $"{timestamp}{request.RequestDefinition.Method}{request.RequestDefinition.Path}{queryParams}{bodyParams}"; var signature = SignHMACSHA256(signStr, SignOutputType.Base64); request.Headers ??= new Dictionary(); diff --git a/DeepCoin.Net/DeepCoinExchange.cs b/DeepCoin.Net/DeepCoinExchange.cs index 03613e9..311486b 100644 --- a/DeepCoin.Net/DeepCoinExchange.cs +++ b/DeepCoin.Net/DeepCoinExchange.cs @@ -60,6 +60,11 @@ public static class DeepCoinExchange public static ExchangeType Type { get; } = ExchangeType.CEX; internal static JsonSerializerContext _serializerContext = JsonSerializerContextCache.GetOrCreate(); + internal static ParameterSerializationSettings _parameterSerializationSettings = new ParameterSerializationSettings + { + Decimal = DecimalSerialization.String, + DateTimes = DateTimeSerialization.MillisecondsNumber + }; /// /// Aliases for DeepCoin assets diff --git a/DeepCoin.Net/Interfaces/Clients/ExchangeApi/IDeepCoinRestClientExchangeApiAccount.cs b/DeepCoin.Net/Interfaces/Clients/ExchangeApi/IDeepCoinRestClientExchangeApiAccount.cs index d32c13e..96b79df 100644 --- a/DeepCoin.Net/Interfaces/Clients/ExchangeApi/IDeepCoinRestClientExchangeApiAccount.cs +++ b/DeepCoin.Net/Interfaces/Clients/ExchangeApi/IDeepCoinRestClientExchangeApiAccount.cs @@ -24,7 +24,7 @@ public interface IDeepCoinRestClientExchangeApiAccount /// ["instType"] Account type /// ["ccy"] Filter by asset, for example `ETH` /// Cancellation token - Task> GetBalancesAsync(SymbolType accountType, string? asset = null, CancellationToken ct = default); + Task> GetBalancesAsync(SymbolType accountType, string? asset = null, CancellationToken ct = default); /// /// @@ -42,7 +42,7 @@ public interface IDeepCoinRestClientExchangeApiAccount /// ["before"] Filter by end time /// ["limit"] Max number of results, max 100 /// Cancellation token - Task> GetBillsAsync(SymbolType accountType, string? asset = null, BillType? billType = null, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, CancellationToken ct = default); + Task> GetBillsAsync(SymbolType accountType, string? asset = null, BillType? billType = null, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, CancellationToken ct = default); /// /// Set leverage for a symbol @@ -58,7 +58,7 @@ public interface IDeepCoinRestClientExchangeApiAccount /// ["mgnMode"] Margin mode /// ["mrgPosition"] Position type /// Cancellation token - Task> SetLeverageAsync(string symbol, decimal leverage, TradeMode tradeMode, PositionType positionType, CancellationToken ct = default); + Task> SetLeverageAsync(string symbol, decimal leverage, TradeMode tradeMode, PositionType positionType, CancellationToken ct = default); // Transfer endpoints currently not useable ///// @@ -66,7 +66,7 @@ public interface IDeepCoinRestClientExchangeApiAccount ///// ///// ///// Cancellation token - //Task> GetTransferableAssetsAsync(CancellationToken ct = default); + //Task> GetTransferableAssetsAsync(CancellationToken ct = default); ///// ///// Transfer an asset to another account @@ -78,7 +78,7 @@ public interface IDeepCoinRestClientExchangeApiAccount ///// Account type ///// Client order id ///// Cancellation token - //Task> TransferAsync(string asset, decimal quantity, string toAccount, AccountType toAccountType, string? clientOrderId = null, CancellationToken ct = default); + //Task> TransferAsync(string asset, decimal quantity, string toAccount, AccountType toAccountType, string? clientOrderId = null, CancellationToken ct = default); ///// ///// Get transfer history @@ -94,7 +94,7 @@ public interface IDeepCoinRestClientExchangeApiAccount ///// Page number ///// Max number of results ///// Cancellation token - //Task> GetTransferHistoryAsync(string? toAccount = null, string? asset = null, TransferStatus? status = null, string? receiverId = null, string? orderId = null, DateTime? startTime = null, DateTime? endTime = null, int? page = null, int? pageSize = null, CancellationToken ct = default); + //Task> GetTransferHistoryAsync(string? toAccount = null, string? asset = null, TransferStatus? status = null, string? receiverId = null, string? orderId = null, DateTime? startTime = null, DateTime? endTime = null, int? page = null, int? pageSize = null, CancellationToken ct = default); /// /// Get deposit history @@ -112,7 +112,7 @@ public interface IDeepCoinRestClientExchangeApiAccount /// ["page"] Page /// ["size"] Page size /// Cancellation token - Task> GetDepositHistoryAsync(string? asset = null, string? transactionHash = null, DateTime? startTime = null, DateTime? endTime = null, int? page = null, int? pageSize = null, CancellationToken ct = default); + Task> GetDepositHistoryAsync(string? asset = null, string? transactionHash = null, DateTime? startTime = null, DateTime? endTime = null, int? page = null, int? pageSize = null, CancellationToken ct = default); /// /// Get withdrawal history @@ -130,7 +130,7 @@ public interface IDeepCoinRestClientExchangeApiAccount /// ["page"] Page /// ["size"] Page size /// Cancellation token - Task> GetWithdrawHistoryAsync(string? asset = null, string? transactionHash = null, DateTime? startTime = null, DateTime? endTime = null, int? page = null, int? pageSize = null, CancellationToken ct = default); + Task> GetWithdrawHistoryAsync(string? asset = null, string? transactionHash = null, DateTime? startTime = null, DateTime? endTime = null, int? page = null, int? pageSize = null, CancellationToken ct = default); /// /// Start the user stream and return the listen key which can be used to subscribe to updates in the socket client @@ -142,7 +142,7 @@ public interface IDeepCoinRestClientExchangeApiAccount /// /// /// Cancellation token - Task> StartUserStreamAsync(CancellationToken ct = default); + Task> StartUserStreamAsync(CancellationToken ct = default); /// /// Extend the lifetime of a listen key @@ -155,6 +155,6 @@ public interface IDeepCoinRestClientExchangeApiAccount /// /// ["listenkey"] Listen key to extend /// Cancellation token - Task> KeepAliveUserStreamAsync(string listenKey, CancellationToken ct = default); + Task> KeepAliveUserStreamAsync(string listenKey, CancellationToken ct = default); } } diff --git a/DeepCoin.Net/Interfaces/Clients/ExchangeApi/IDeepCoinRestClientExchangeApiExchangeData.cs b/DeepCoin.Net/Interfaces/Clients/ExchangeApi/IDeepCoinRestClientExchangeApiExchangeData.cs index 3412dcd..e654da2 100644 --- a/DeepCoin.Net/Interfaces/Clients/ExchangeApi/IDeepCoinRestClientExchangeApiExchangeData.cs +++ b/DeepCoin.Net/Interfaces/Clients/ExchangeApi/IDeepCoinRestClientExchangeApiExchangeData.cs @@ -25,7 +25,7 @@ public interface IDeepCoinRestClientExchangeApiExchangeData /// ["uly"] Filter by underlying (swap only) /// Cancellation token /// - Task> GetTickersAsync(SymbolType symbolType, string? underlying = null, CancellationToken ct = default); + Task> GetTickersAsync(SymbolType symbolType, string? underlying = null, CancellationToken ct = default); /// /// Get symbols list @@ -40,7 +40,7 @@ public interface IDeepCoinRestClientExchangeApiExchangeData /// ["uly"] Filter by underlying /// ["instId"] Filter by symbol name /// Cancellation token - Task> GetSymbolsAsync(SymbolType type, string? underlying = null, string? symbol = null, CancellationToken ct = default); + Task> GetSymbolsAsync(SymbolType type, string? underlying = null, string? symbol = null, CancellationToken ct = default); /// /// Get kline/candlestick data @@ -56,7 +56,7 @@ public interface IDeepCoinRestClientExchangeApiExchangeData /// ["after"] Filter by end time /// ["limit"] Max number of results, max 300 /// Cancellation token - Task> GetKlinesAsync(string symbol, KlineInterval interval, DateTime? endTime = null, int? limit = null, CancellationToken ct = default); + Task> GetKlinesAsync(string symbol, KlineInterval interval, DateTime? endTime = null, int? limit = null, CancellationToken ct = default); /// /// Get order book @@ -70,7 +70,7 @@ public interface IDeepCoinRestClientExchangeApiExchangeData /// ["instId"] The symbol, for example `ETH-USDT` /// ["sz"] Number of order book rows, max 400 /// Cancellation token - Task> GetOrderBookAsync(string symbol, int? depth = null, CancellationToken ct = default); + Task> GetOrderBookAsync(string symbol, int? depth = null, CancellationToken ct = default); /// /// Get funding rate @@ -84,7 +84,7 @@ public interface IDeepCoinRestClientExchangeApiExchangeData /// ["instType"] Contract type /// ["instId"] Filter by symbol, for example `ETH-USDT-SWAP` /// Cancellation token - Task> GetFundingRateAsync(ProductGroup type, string? symbol = null, CancellationToken ct = default); + Task> GetFundingRateAsync(ProductGroup type, string? symbol = null, CancellationToken ct = default); } } diff --git a/DeepCoin.Net/Interfaces/Clients/ExchangeApi/IDeepCoinRestClientExchangeApiTrading.cs b/DeepCoin.Net/Interfaces/Clients/ExchangeApi/IDeepCoinRestClientExchangeApiTrading.cs index 14e52a4..821fd8a 100644 --- a/DeepCoin.Net/Interfaces/Clients/ExchangeApi/IDeepCoinRestClientExchangeApiTrading.cs +++ b/DeepCoin.Net/Interfaces/Clients/ExchangeApi/IDeepCoinRestClientExchangeApiTrading.cs @@ -25,7 +25,7 @@ public interface IDeepCoinRestClientExchangeApiTrading /// ["instType"] Symbol type /// ["instId"] The symbol, for example `ETH-USDT` /// Cancellation token - Task> GetPositionsAsync(SymbolType symbolType, string? symbol = null, CancellationToken ct = default); + Task> GetPositionsAsync(SymbolType symbolType, string? symbol = null, CancellationToken ct = default); /// /// Place a new order @@ -52,7 +52,7 @@ public interface IDeepCoinRestClientExchangeApiTrading /// ["tpTriggerPx"] Take profit trigger price /// ["slTriggerPx"] Stop loss trigger price /// Cancellation token - Task> PlaceOrderAsync(string symbol, OrderSide side, OrderType orderType, decimal quantity, decimal? price = null, TradeMode? tradeMode = null, string? asset = null, string? clientOrderId = null, QuantityType? quantityType = null, PositionSide? positionSide = null, PositionType? positionType = null, string? closePosId = null, bool? reduceOnly = null, decimal? tpTriggerPrice = null, decimal? slTriggerPrice = null, CancellationToken ct = default); + Task> PlaceOrderAsync(string symbol, OrderSide side, OrderType orderType, decimal quantity, decimal? price = null, TradeMode? tradeMode = null, string? asset = null, string? clientOrderId = null, QuantityType? quantityType = null, PositionSide? positionSide = null, PositionType? positionType = null, string? closePosId = null, bool? reduceOnly = null, decimal? tpTriggerPrice = null, decimal? slTriggerPrice = null, CancellationToken ct = default); /// /// Edit an existing order. Spot not supported. @@ -67,7 +67,7 @@ public interface IDeepCoinRestClientExchangeApiTrading /// ["price"] New price /// ["volume"] New quantity /// Cancellation token - Task> EditOrderAsync(string orderId, decimal? price = null, decimal? quantity = null, CancellationToken ct = default); + Task> EditOrderAsync(string orderId, decimal? price = null, decimal? quantity = null, CancellationToken ct = default); /// /// Cancel an open order @@ -81,7 +81,7 @@ public interface IDeepCoinRestClientExchangeApiTrading /// ["instId"] The symbol, for example `ETH-USDT` /// ["ordId"] Order id /// Cancellation token - Task> CancelOrderAsync(string symbol, string orderId, CancellationToken ct = default); + Task> CancelOrderAsync(string symbol, string orderId, CancellationToken ct = default); /// /// Cancel multiple orders. Make sure to check the result data of the call to see if orders actually successfully canceled @@ -94,7 +94,7 @@ public interface IDeepCoinRestClientExchangeApiTrading /// /// ["OrderSysIDs"] Ids of orders to cancel /// Cancellation token - Task> CancelOrdersAsync(IEnumerable orderIds, CancellationToken ct = default); + Task> CancelOrdersAsync(IEnumerable orderIds, CancellationToken ct = default); /// /// Cancel all orders matching the parameters @@ -110,7 +110,7 @@ public interface IDeepCoinRestClientExchangeApiTrading /// Margin mode /// Position type /// Cancellation token - Task> CancelAllOrdersAsync(string symbol, ProductGroup productGroup, TradeMode marginMode, PositionType positionType, CancellationToken ct = default); + Task> CancelAllOrdersAsync(string symbol, ProductGroup productGroup, TradeMode marginMode, PositionType positionType, CancellationToken ct = default); /// /// Get user trades @@ -130,7 +130,7 @@ public interface IDeepCoinRestClientExchangeApiTrading /// ["end"] Filter by end time /// ["limit"] Max number of results /// Cancellation token - Task> GetUserTradesAsync(SymbolType symbolType, string? symbol = null, string? orderId = null, string? afterId = null, string? beforeId = null, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, CancellationToken ct = default); + Task> GetUserTradesAsync(SymbolType symbolType, string? symbol = null, string? orderId = null, string? afterId = null, string? beforeId = null, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, CancellationToken ct = default); /// /// Get a open order by id @@ -144,7 +144,7 @@ public interface IDeepCoinRestClientExchangeApiTrading /// ["instId"] The symbol, for example `ETH-USDT` /// ["ordId"] Order id /// Cancellation token - Task> GetOpenOrderAsync(string symbol, string orderId, CancellationToken ct = default); + Task> GetOpenOrderAsync(string symbol, string orderId, CancellationToken ct = default); /// /// Get closed order by id @@ -158,7 +158,7 @@ public interface IDeepCoinRestClientExchangeApiTrading /// ["instId"] The symbol, for example `ETH-USDT` /// ["ordId"] Order id /// Cancellation token - Task> GetClosedOrderAsync(string symbol, string orderId, CancellationToken ct = default); + Task> GetClosedOrderAsync(string symbol, string orderId, CancellationToken ct = default); /// /// Get closed order history @@ -178,7 +178,7 @@ public interface IDeepCoinRestClientExchangeApiTrading /// ["before"] Return results before this id /// ["limit"] Max number of results, max 100 /// Cancellation token - Task> GetClosedOrdersAsync(SymbolType symbolType, string? symbol = null, string? orderId = null, OrderType? orderType = null, OrderStatus? status = null, string? afterId = null, string? beforeId = null, int? limit = null, CancellationToken ct = default); + Task> GetClosedOrdersAsync(SymbolType symbolType, string? symbol = null, string? orderId = null, OrderType? orderType = null, OrderStatus? status = null, string? afterId = null, string? beforeId = null, int? limit = null, CancellationToken ct = default); /// /// Get open orders @@ -194,7 +194,7 @@ public interface IDeepCoinRestClientExchangeApiTrading /// ["limit"] Page size /// ["ordId"] Filter by order id /// Cancellation token - Task> GetOpenOrdersAsync(string symbol, int? page = null, int? pageSize = null, string? orderId = null, CancellationToken ct = default); + Task> GetOpenOrdersAsync(string symbol, int? page = null, int? pageSize = null, string? orderId = null, CancellationToken ct = default); /// /// Set take profit / stop loss trigger price @@ -209,7 +209,7 @@ public interface IDeepCoinRestClientExchangeApiTrading /// ["tpTriggerPx"] Take profit trigger price /// ["slTriggerPx"] Stop loss trigger price /// Cancellation token - Task SetTpSlAsync(string orderId, decimal? takeProfitTriggerPrice = null, decimal? stopLossTriggerPrice = null, CancellationToken ct = default); + Task SetTpSlAsync(string orderId, decimal? takeProfitTriggerPrice = null, decimal? stopLossTriggerPrice = null, CancellationToken ct = default); } } diff --git a/DeepCoin.Net/Interfaces/Clients/ExchangeApi/IDeepCoinSocketClientExchangeApi.cs b/DeepCoin.Net/Interfaces/Clients/ExchangeApi/IDeepCoinSocketClientExchangeApi.cs index 211112b..4a41ad4 100644 --- a/DeepCoin.Net/Interfaces/Clients/ExchangeApi/IDeepCoinSocketClientExchangeApi.cs +++ b/DeepCoin.Net/Interfaces/Clients/ExchangeApi/IDeepCoinSocketClientExchangeApi.cs @@ -27,7 +27,7 @@ public interface IDeepCoinSocketClientExchangeApi : ISocketApiClientThe event handler for the received data /// Cancellation token for closing this subscription /// A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected - Task> SubscribeToSymbolUpdatesAsync(string symbol, Action> onMessage, CancellationToken ct = default); + Task> SubscribeToSymbolUpdatesAsync(string symbol, Action> onMessage, CancellationToken ct = default); /// /// Subscribe to live trade updates for a symbol @@ -42,7 +42,7 @@ public interface IDeepCoinSocketClientExchangeApi : ISocketApiClientThe event handler for the received data /// Cancellation token for closing this subscription /// A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected - Task> SubscribeToTradeUpdatesAsync(string symbol, Action> onMessage, CancellationToken ct = default); + Task> SubscribeToTradeUpdatesAsync(string symbol, Action> onMessage, CancellationToken ct = default); /// /// Subscribe to kline/candlestick updates for a symbol. Only 1 minute klines supported. @@ -57,7 +57,7 @@ public interface IDeepCoinSocketClientExchangeApi : ISocketApiClientThe event handler for the received data /// Cancellation token for closing this subscription /// A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected - Task> SubscribeToKlineUpdatesAsync(string symbol, Action> onMessage, CancellationToken ct = default); + Task> SubscribeToKlineUpdatesAsync(string symbol, Action> onMessage, CancellationToken ct = default); /// /// Subscribe to 25-level incremental order book updates @@ -72,7 +72,7 @@ public interface IDeepCoinSocketClientExchangeApi : ISocketApiClientThe event handler for the received data /// Cancellation token for closing this subscription /// A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected - Task> SubscribeToOrderBookUpdatesAsync(string symbol, Action> onMessage, CancellationToken ct = default); + Task> SubscribeToOrderBookUpdatesAsync(string symbol, Action> onMessage, CancellationToken ct = default); /// /// Subscribe to user data updates @@ -86,7 +86,7 @@ public interface IDeepCoinSocketClientExchangeApi : ISocketApiClientThe event handler for trigger order updates /// Cancellation token for closing this subscription /// A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected - Task> SubscribeToUserDataUpdatesAsync( + Task> SubscribeToUserDataUpdatesAsync( string listenKey, Action>? onOrderMessage = null, Action>? onBalanceMessage = null, diff --git a/DeepCoin.Net/Objects/Sockets/DeepCoinPingQuery.cs b/DeepCoin.Net/Objects/Sockets/DeepCoinPingQuery.cs index 5f0a996..80600e3 100644 --- a/DeepCoin.Net/Objects/Sockets/DeepCoinPingQuery.cs +++ b/DeepCoin.Net/Objects/Sockets/DeepCoinPingQuery.cs @@ -9,7 +9,7 @@ internal class DeepCoinPingQuery : Query public DeepCoinPingQuery() : base("ping", false, 1) { RequestTimeout = TimeSpan.FromSeconds(5); - MessageRouter = MessageRouter.CreateWithoutHandler("pong"); + MessageRouter = MessageRouter.CreateVoid("pong"); } } } diff --git a/DeepCoin.Net/Objects/Sockets/DeepCoinQuery.cs b/DeepCoin.Net/Objects/Sockets/DeepCoinQuery.cs index d27639a..e938045 100644 --- a/DeepCoin.Net/Objects/Sockets/DeepCoinQuery.cs +++ b/DeepCoin.Net/Objects/Sockets/DeepCoinQuery.cs @@ -19,15 +19,15 @@ public DeepCoinQuery(SocketApiClient client, SocketRequest request, bool authent }, authenticated, weight) { _client = client; - MessageRouter = MessageRouter.CreateWithoutTopicFilter(request.RequestId.ToString(), HandleMessage); + MessageRouter = MessageRouter.CreateForQuery(request.RequestId.ToString(), HandleMessage); } public CallResult HandleMessage(SocketConnection connection, DateTime receiveTime, string? originalData, SocketResponse message) { if (message.ErrorCode != 0) - return new CallResult(new ServerError(message.ErrorCode, _client.GetErrorInfo(message.ErrorCode, message.ErrorMessage))); + return CallResult.Fail(new ServerError(message.ErrorCode, _client.GetErrorInfo(message.ErrorCode, message.ErrorMessage)), originalData); - return new CallResult(message, originalData, null); + return CallResult.Ok(message, originalData); } } } diff --git a/DeepCoin.Net/Objects/Sockets/Subscriptions/DeepCoinBookSubscription.cs b/DeepCoin.Net/Objects/Sockets/Subscriptions/DeepCoinBookSubscription.cs index 7781ef3..97a71fd 100644 --- a/DeepCoin.Net/Objects/Sockets/Subscriptions/DeepCoinBookSubscription.cs +++ b/DeepCoin.Net/Objects/Sockets/Subscriptions/DeepCoinBookSubscription.cs @@ -35,7 +35,7 @@ public DeepCoinBookSubscription(ILogger logger, SocketApiClient client, string p _topic = topic; _table = table; - MessageRouter = MessageRouter.CreateWithTopicFilter>(pushAction, filter, DoHandleMessage); + MessageRouter = MessageRouter.CreateForEvent>(pushAction, filter, DoHandleMessage); } /// @@ -83,7 +83,7 @@ public CallResult DoHandleMessage(SocketConnection connection, DateTime receiveT { // Cache this incomplete update, we need the next message to complete it _incompleteUpdate = update; - return CallResult.SuccessResult; + return CallResult.Ok(); } if (_incompleteUpdate != null) @@ -127,7 +127,7 @@ public CallResult DoHandleMessage(SocketConnection connection, DateTime receiveT .WithUpdateType(message.BusinessNumber == 0 ? SocketUpdateType.Snapshot : SocketUpdateType.Update) .WithDataTimestamp(timestamp, _client.GetTimeOffset()) .WithSequenceNumber(message.BusinessNumber)); - return CallResult.SuccessResult; + return CallResult.Ok(); } } } diff --git a/DeepCoin.Net/Objects/Sockets/Subscriptions/DeepCoinSubscription.cs b/DeepCoin.Net/Objects/Sockets/Subscriptions/DeepCoinSubscription.cs index 135c8ed..72e00e2 100644 --- a/DeepCoin.Net/Objects/Sockets/Subscriptions/DeepCoinSubscription.cs +++ b/DeepCoin.Net/Objects/Sockets/Subscriptions/DeepCoinSubscription.cs @@ -31,7 +31,7 @@ public DeepCoinSubscription(ILogger logger, SocketApiClient client, string pushA _filter = "DeepCoin_" + filter; _topic = topic; - MessageRouter = MessageRouter.CreateWithTopicFilter>( + MessageRouter = MessageRouter.CreateForEvent>( pushAction, filter, DoHandleMessage); } @@ -66,7 +66,7 @@ public DeepCoinSubscription(ILogger logger, SocketApiClient client, string pushA public CallResult DoHandleMessage(SocketConnection connection, DateTime receiveTime, string? originalData, SocketUpdate message) { _handler.Invoke(receiveTime, originalData, message); - return CallResult.SuccessResult; + return CallResult.Ok(); } } } diff --git a/DeepCoin.Net/Objects/Sockets/Subscriptions/DeepCoinUserSubscription.cs b/DeepCoin.Net/Objects/Sockets/Subscriptions/DeepCoinUserSubscription.cs index 9cc2f10..ce00395 100644 --- a/DeepCoin.Net/Objects/Sockets/Subscriptions/DeepCoinUserSubscription.cs +++ b/DeepCoin.Net/Objects/Sockets/Subscriptions/DeepCoinUserSubscription.cs @@ -49,12 +49,12 @@ public DeepCoinUserSubscription( _triggerUpdateHandler = triggerUpdateHandler; MessageRouter = MessageRouter.Create([ - MessageRoute>.CreateWithoutTopicFilter("PushOrder", DoHandleMessage), - MessageRoute>.CreateWithoutTopicFilter("PushAccount", DoHandleMessage), - MessageRoute>.CreateWithoutTopicFilter("PushPosition", DoHandleMessage), - MessageRoute>.CreateWithoutTopicFilter("PushTrade", DoHandleMessage), - MessageRoute>.CreateWithoutTopicFilter("PushAccountDetail", DoHandleMessage), - MessageRoute>.CreateWithoutTopicFilter("PushTriggerOrder", DoHandleMessage) + MessageRoute.CreateForEvent>("PushOrder", DoHandleMessage), + MessageRoute.CreateForEvent>("PushAccount", DoHandleMessage), + MessageRoute.CreateForEvent>("PushPosition", DoHandleMessage), + MessageRoute.CreateForEvent>("PushTrade", DoHandleMessage), + MessageRoute.CreateForEvent>("PushAccountDetail", DoHandleMessage), + MessageRoute.CreateForEvent>("PushTriggerOrder", DoHandleMessage) ]); } @@ -80,7 +80,7 @@ public CallResult DoHandleMessage(SocketConnection connection, DateTime receiveT .WithSymbol(message.Result.First().Data.Symbol) .WithDataTimestamp(timestamp, _client.GetTimeOffset()) ); - return CallResult.SuccessResult; + return CallResult.Ok(); } public CallResult DoHandleMessage(SocketConnection connection, DateTime receiveTime, string? originalData, SocketUpdate message) @@ -88,7 +88,7 @@ public CallResult DoHandleMessage(SocketConnection connection, DateTime receiveT _balanceUpdateHandler?.Invoke( new DataEvent(DeepCoinExchange.ExchangeName, message.Result.Select(x => x.Data).ToArray(), receiveTime, originalData) ); - return CallResult.SuccessResult; + return CallResult.Ok(); } public CallResult DoHandleMessage(SocketConnection connection, DateTime receiveTime, string? originalData, SocketUpdate message) { @@ -100,7 +100,7 @@ public CallResult DoHandleMessage(SocketConnection connection, DateTime receiveT .WithSymbol(message.Result.First().Data.Symbol) .WithDataTimestamp(timestamp, _client.GetTimeOffset()) ); - return CallResult.SuccessResult; + return CallResult.Ok(); } public CallResult DoHandleMessage(SocketConnection connection, DateTime receiveTime, string? originalData, SocketUpdate message) { @@ -112,7 +112,7 @@ public CallResult DoHandleMessage(SocketConnection connection, DateTime receiveT .WithSymbol(message.Result.First().Data.Symbol) .WithDataTimestamp(timestamp, _client.GetTimeOffset()) ); - return CallResult.SuccessResult; + return CallResult.Ok(); } public CallResult DoHandleMessage(SocketConnection connection, DateTime receiveTime, string? originalData, SocketUpdate message) { @@ -124,7 +124,7 @@ public CallResult DoHandleMessage(SocketConnection connection, DateTime receiveT .WithSymbol(message.Result.First().Data.Symbol) .WithDataTimestamp(timestamp, _client.GetTimeOffset()) ); - return CallResult.SuccessResult; + return CallResult.Ok(); } public CallResult DoHandleMessage(SocketConnection connection, DateTime receiveTime, string? originalData, SocketUpdate message) { @@ -136,7 +136,7 @@ public CallResult DoHandleMessage(SocketConnection connection, DateTime receiveT .WithSymbol(message.Result.First().Data.Symbol) .WithDataTimestamp(timestamp, _client.GetTimeOffset()) ); - return CallResult.SuccessResult; + return CallResult.Ok(); } } } diff --git a/DeepCoin.Net/SymbolOrderBooks/DeepCoinSymbolOrderBook.cs b/DeepCoin.Net/SymbolOrderBooks/DeepCoinSymbolOrderBook.cs index 9ebf218..a5af7b9 100644 --- a/DeepCoin.Net/SymbolOrderBooks/DeepCoinSymbolOrderBook.cs +++ b/DeepCoin.Net/SymbolOrderBooks/DeepCoinSymbolOrderBook.cs @@ -63,22 +63,21 @@ public DeepCoinSymbolOrderBook( protected override async Task> DoStartAsync(CancellationToken ct) { var subResult = await _socketClient.ExchangeApi.SubscribeToOrderBookUpdatesAsync(Symbol, HandleUpdate).ConfigureAwait(false); - - if (!subResult) - return new CallResult(subResult.Error!); + if (!subResult.Success) + return CallResult.Fail(subResult.Error!); if (ct.IsCancellationRequested) { await subResult.Data.CloseAsync().ConfigureAwait(false); - return subResult.AsError(new CancellationRequestedError()); + return CallResult.Fail(new CancellationRequestedError()); } Status = OrderBookStatus.Syncing; var setResult = await WaitForSetOrderBookAsync(_initialDataTimeout, ct).ConfigureAwait(false); - if (!setResult) + if (!setResult.Success) await subResult.Data.CloseAsync().ConfigureAwait(false); - return setResult ? subResult : new CallResult(setResult.Error!); + return setResult.Success ? CallResult.Ok(subResult.Data) : CallResult.Fail(setResult.Error!); } private void HandleUpdate(DataEvent data) @@ -95,7 +94,7 @@ protected override void DoReset() } /// - protected override async Task> DoResyncAsync(CancellationToken ct) + protected override async Task DoResyncAsync(CancellationToken ct) { return await WaitForSetOrderBookAsync(_initialDataTimeout, ct).ConfigureAwait(false); } From 3a3376b7d39451b3696e249c4defdc3b3b81bc84 Mon Sep 17 00:00:00 2001 From: Jkorf Date: Mon, 15 Jun 2026 16:39:34 +0200 Subject: [PATCH 02/12] wip --- DeepCoin.Net/Clients/DeepCoinRestClient.cs | 2 +- DeepCoin.Net/Clients/DeepCoinSocketClient.cs | 2 +- .../Clients/DeepCoinUserClientProvider.cs | 113 +++--------------- .../DeepCoinRestClientExchangeApi.cs | 8 +- .../DeepCoinSocketClientExchangeApi.cs | 4 +- 5 files changed, 24 insertions(+), 105 deletions(-) diff --git a/DeepCoin.Net/Clients/DeepCoinRestClient.cs b/DeepCoin.Net/Clients/DeepCoinRestClient.cs index 3b15766..8b3dbd9 100644 --- a/DeepCoin.Net/Clients/DeepCoinRestClient.cs +++ b/DeepCoin.Net/Clients/DeepCoinRestClient.cs @@ -43,7 +43,7 @@ public DeepCoinRestClient(HttpClient? httpClient, ILoggerFactory? loggerFactory, { Initialize(options.Value); - ExchangeApi = AddApiClient(new DeepCoinRestClientExchangeApi(_logger, httpClient, options.Value)); + ExchangeApi = AddApiClient(new DeepCoinRestClientExchangeApi(loggerFactory, httpClient, options.Value)); } #endregion diff --git a/DeepCoin.Net/Clients/DeepCoinSocketClient.cs b/DeepCoin.Net/Clients/DeepCoinSocketClient.cs index 2d9fb23..6157f66 100644 --- a/DeepCoin.Net/Clients/DeepCoinSocketClient.cs +++ b/DeepCoin.Net/Clients/DeepCoinSocketClient.cs @@ -44,7 +44,7 @@ public DeepCoinSocketClient(IOptions options, ILoggerFact { Initialize(options.Value); - ExchangeApi = AddApiClient(new DeepCoinSocketClientExchangeApi(_logger, options.Value)); + ExchangeApi = AddApiClient(new DeepCoinSocketClientExchangeApi(loggerFactory, options.Value)); } #endregion diff --git a/DeepCoin.Net/Clients/DeepCoinUserClientProvider.cs b/DeepCoin.Net/Clients/DeepCoinUserClientProvider.cs index e0fd47a..3fbf530 100644 --- a/DeepCoin.Net/Clients/DeepCoinUserClientProvider.cs +++ b/DeepCoin.Net/Clients/DeepCoinUserClientProvider.cs @@ -6,22 +6,23 @@ using System; using System.Collections.Concurrent; using System.Net.Http; +using CryptoExchange.Net.Clients; namespace DeepCoin.Net.Clients { /// - public class DeepCoinUserClientProvider : IDeepCoinUserClientProvider + public class DeepCoinUserClientProvider : UserClientProvider< + IDeepCoinRestClient, + IDeepCoinSocketClient, + DeepCoinRestOptions, + DeepCoinSocketOptions, + DeepCoinCredentials, + DeepCoinEnvironment + >, IDeepCoinUserClientProvider { - private ConcurrentDictionary _restClients = new ConcurrentDictionary(); - private ConcurrentDictionary _socketClients = new ConcurrentDictionary(); - - private readonly IOptions _restOptions; - private readonly IOptions _socketOptions; - private readonly HttpClient _httpClient; - private readonly ILoggerFactory? _loggerFactory; - + /// - public string ExchangeName => DeepCoinExchange.ExchangeName; + public override string ExchangeName => DeepCoinExchange.ExchangeName; /// /// ctor @@ -40,97 +41,15 @@ public DeepCoinUserClientProvider( ILoggerFactory? loggerFactory, IOptions restOptions, IOptions socketOptions) + : base(httpClient, loggerFactory, restOptions, socketOptions) { - _httpClient = httpClient ?? new HttpClient(); - _httpClient.Timeout = restOptions.Value.RequestTimeout; - _loggerFactory = loggerFactory; - _restOptions = restOptions; - _socketOptions = socketOptions; } /// - public void InitializeUserClient(string userIdentifier, DeepCoinCredentials credentials, DeepCoinEnvironment? environment = null) - { - CreateRestClient(userIdentifier, credentials, environment); - CreateSocketClient(userIdentifier, credentials, environment); - } - - /// - public void ClearUserClients(string userIdentifier) - { - _restClients.TryRemove(userIdentifier, out _); - _socketClients.TryRemove(userIdentifier, out _); - } - + protected override IDeepCoinRestClient ConstructRestClient(HttpClient client, ILoggerFactory? loggerFactory, IOptions options) + => new DeepCoinRestClient(client, loggerFactory, options); /// - public IDeepCoinRestClient GetRestClient(string userIdentifier, DeepCoinCredentials? credentials = null, DeepCoinEnvironment? environment = null) - { - if (!_restClients.TryGetValue(userIdentifier, out var client) || client.Disposed) - client = CreateRestClient(userIdentifier, credentials, environment); - - return client; - } - - /// - public IDeepCoinSocketClient GetSocketClient(string userIdentifier, DeepCoinCredentials? credentials = null, DeepCoinEnvironment? environment = null) - { - if (!_socketClients.TryGetValue(userIdentifier, out var client) || client.Disposed) - client = CreateSocketClient(userIdentifier, credentials, environment); - - return client; - } - - private IDeepCoinRestClient CreateRestClient(string userIdentifier, DeepCoinCredentials? credentials, DeepCoinEnvironment? environment) - { - var clientRestOptions = SetRestEnvironment(environment); - var client = new DeepCoinRestClient(_httpClient, _loggerFactory, clientRestOptions); - if (credentials != null) - { - client.SetApiCredentials(credentials); - _restClients[userIdentifier] = client; - } - return client; - } - - private IDeepCoinSocketClient CreateSocketClient(string userIdentifier, DeepCoinCredentials? credentials, DeepCoinEnvironment? environment) - { - var clientSocketOptions = SetSocketEnvironment(environment); - var client = new DeepCoinSocketClient(clientSocketOptions!, _loggerFactory); - if (credentials != null) - { - client.SetApiCredentials(credentials); - _socketClients[userIdentifier] = client; - } - return client; - } - - private IOptions SetRestEnvironment(DeepCoinEnvironment? environment) - { - if (environment == null) - return _restOptions; - - var newRestClientOptions = new DeepCoinRestOptions(); - var restOptions = _restOptions.Value.Set(newRestClientOptions); - newRestClientOptions.Environment = environment; - return Options.Create(newRestClientOptions); - } - - private IOptions SetSocketEnvironment(DeepCoinEnvironment? environment) - { - if (environment == null) - return _socketOptions; - - var newSocketClientOptions = new DeepCoinSocketOptions(); - var restOptions = _socketOptions.Value.Set(newSocketClientOptions); - newSocketClientOptions.Environment = environment; - return Options.Create(newSocketClientOptions); - } - - private static T ApplyOptionsDelegate(Action? del) where T : new() - { - var opts = new T(); - del?.Invoke(opts); - return opts; - } + protected override IDeepCoinSocketClient ConstructSocketClient(ILoggerFactory? loggerFactory, IOptions options) + => new DeepCoinSocketClient(options, loggerFactory); } } diff --git a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApi.cs b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApi.cs index 7b27f5c..a3bd83a 100644 --- a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApi.cs +++ b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApi.cs @@ -42,12 +42,12 @@ internal partial class DeepCoinRestClientExchangeApi : RestApiClient /// ctor /// - internal DeepCoinSocketClientExchangeApi(ILogger logger, DeepCoinSocketOptions options) : - base(logger, DeepCoinExchange.Metadata.Id, options.Environment.SocketClientAddress!, options, options.ExchangeOptions) + internal DeepCoinSocketClientExchangeApi(ILoggerFactory? loggerFactory, DeepCoinSocketOptions options) : + base(loggerFactory, DeepCoinExchange.Metadata.Id, options.Environment.SocketClientAddress!, options, options.ExchangeOptions) { KeepAliveInterval = TimeSpan.Zero; From 3c2d72cff4498771a5485e754fabce3f6a564592 Mon Sep 17 00:00:00 2001 From: Jkorf Date: Tue, 16 Jun 2026 14:54:27 +0200 Subject: [PATCH 03/12] wip --- AGENTS.md | 2 +- DeepCoin.Net.UnitTests/SocketSubscriptionTests.cs | 2 +- .../ExchangeApi/DeepCoinRestClientExchangeApiShared.cs | 10 ++++------ .../Interfaces/Clients/IDeepCoinUserClientProvider.cs | 5 +++++ Examples/ai-friendly/05-error-handling.cs | 10 +++++----- Examples/ai-friendly/README.md | 2 +- llms-full.txt | 10 +++++----- llms.txt | 4 ++-- 8 files changed, 24 insertions(+), 21 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 55e8817..68c57fa 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -39,7 +39,7 @@ var publicClient = new DeepCoinRestClient(); ## Core Pattern: Result Handling -Every method returns `WebCallResult` (REST) or `CallResult` (WebSocket). Always check `.Success` before accessing `.Data`. +Every method returns `HttpResult` (REST) or `WebSocketResult` (WebSocket). Always check `.Success` before accessing `.Data`. ```csharp using DeepCoin.Net.Clients; diff --git a/DeepCoin.Net.UnitTests/SocketSubscriptionTests.cs b/DeepCoin.Net.UnitTests/SocketSubscriptionTests.cs index 8cec794..4d325ce 100644 --- a/DeepCoin.Net.UnitTests/SocketSubscriptionTests.cs +++ b/DeepCoin.Net.UnitTests/SocketSubscriptionTests.cs @@ -27,7 +27,7 @@ public async Task ValidateConcurrentSpotSubscriptions() OutputOriginalData = true }), logger); - var tester = new SocketSubscriptionValidator(client, "Subscriptions/Exchange", "wss://stream.crypto.com"); + var tester = new SocketSubscriptionValidator(client, "Subscriptions/Exchange", "wss://stream.deepcoin.com/public/spotws"); await tester.ValidateConcurrentAsync( (client, handler) => client.ExchangeApi.SubscribeToKlineUpdatesAsync("ETHUSDT", handler), (client, handler) => client.ExchangeApi.SubscribeToKlineUpdatesAsync("BTCUSDT", handler), diff --git a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiShared.cs b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiShared.cs index 808a86f..33c086b 100644 --- a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiShared.cs +++ b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiShared.cs @@ -725,7 +725,7 @@ async Task> ILeverageRestClient.GetLeverageAsync(GetL }, OptionalExchangeParameters = new List { - new ParameterDescription("PositionType", typeof(PositionType), "Merge or split position mode", PositionType.Merge) + new ParameterDescription(["PositionType", "mrgPosition"], typeof(PositionType), "Merge or split position mode", PositionType.Merge) } }; @@ -735,8 +735,7 @@ async Task> ILeverageRestClient.SetLeverageAsync(SetL if (validationError != null) return HttpResult.Fail(Exchange, validationError); - var positionType = ExchangeParameters.GetValue(request.ExchangeParameters, Exchange, "PositionType"); - + var positionType = request.GetParamValue(Exchange, "PositionType", "mrgPosition"); var result = await Account.SetLeverageAsync( symbol: request.Symbol!.GetSymbol(FormatSymbol), request.Leverage, @@ -880,7 +879,7 @@ async Task> IFuturesSymbolRestClient.SupportsFuturesSym }, OptionalExchangeParameters = new List { - new ParameterDescription("PositionType", typeof(PositionType), "Merge or split position mode", PositionType.Merge) + new ParameterDescription(["PositionType", "mrgPosition"], typeof(PositionType), "Merge or split position mode", PositionType.Merge) } }; async Task> IFuturesOrderRestClient.PlaceFuturesOrderAsync(PlaceFuturesOrderRequest request, CancellationToken ct) @@ -889,8 +888,7 @@ async Task> IFuturesOrderRestClient.PlaceFuturesOrderAsync( if (validationError != null) return HttpResult.Fail(Exchange, validationError); - var positionType = ExchangeParameters.GetValue(request.ExchangeParameters, Exchange, "PositionType"); - + var positionType = request.GetParamValue(Exchange, "PositionType", "mrgPosition"); var result = await Trading.PlaceOrderAsync( request.Symbol!.GetSymbol(FormatSymbol), request.Side == SharedOrderSide.Buy ? Enums.OrderSide.Buy : Enums.OrderSide.Sell, diff --git a/DeepCoin.Net/Interfaces/Clients/IDeepCoinUserClientProvider.cs b/DeepCoin.Net/Interfaces/Clients/IDeepCoinUserClientProvider.cs index 7f4056b..9ebac17 100644 --- a/DeepCoin.Net/Interfaces/Clients/IDeepCoinUserClientProvider.cs +++ b/DeepCoin.Net/Interfaces/Clients/IDeepCoinUserClientProvider.cs @@ -21,6 +21,11 @@ public interface IDeepCoinUserClientProvider : IExchangeService /// public void ClearUserClients(string userIdentifier); + /// + /// Clear all client from the cache + /// + void Clear(); + /// /// Get the Rest client for a specific user. In case the client does not exist yet it will be created and the should be provided, unless has been called prior for this user. /// diff --git a/Examples/ai-friendly/05-error-handling.cs b/Examples/ai-friendly/05-error-handling.cs index 71270a2..28c5029 100644 --- a/Examples/ai-friendly/05-error-handling.cs +++ b/Examples/ai-friendly/05-error-handling.cs @@ -1,6 +1,6 @@ // 05-error-handling.cs // -// Demonstrates: WebCallResult patterns, retry logic, common error scenarios. +// Demonstrates: HttpResult patterns, retry logic, common error scenarios. // // Setup: dotnet add package DeepCoin.Net @@ -15,7 +15,7 @@ }); // ---- 1. THE BASIC PATTERN ---- -// Every method returns WebCallResult (REST) or CallResult (WebSocket). +// Every method returns HttpResult (REST) or WebSocketResult (WebSocket). // .Success is true/false. .Data is the payload, only valid when .Success is true. // .Error contains structured error info when .Success is false. // .Error.IsTransient hints if a retry might succeed. @@ -39,11 +39,11 @@ // Retry only on transient errors such as network issues, rate limits, or server overload. // Do not retry validation errors, bad credentials, or insufficient balance. -async Task> WithRetry( - Func>> call, +async Task> WithRetry( + Func>> call, int maxAttempts = 3) { - WebCallResult last = default!; + HttpResult last = default!; for (var attempt = 1; attempt <= maxAttempts; attempt++) { last = await call(); diff --git a/Examples/ai-friendly/README.md b/Examples/ai-friendly/README.md index 3d34c10..b6abdd1 100644 --- a/Examples/ai-friendly/README.md +++ b/Examples/ai-friendly/README.md @@ -15,7 +15,7 @@ These examples are optimized for AI coding assistants and quick onboarding. Each | `02-futures.cs` | Swap/futures: set leverage, place market order, get position, close position | | `03-websocket.cs` | Subscribe to ticker, trade, kline, order book, and user data streams with proper teardown | | `04-multi-exchange.cs` | `CryptoExchange.Net.SharedApis` pattern for exchange-agnostic code | -| `05-error-handling.cs` | `WebCallResult` patterns, retry, common error scenarios | +| `05-error-handling.cs` | `HttpResult` patterns, retry, common error scenarios | ## Running diff --git a/llms-full.txt b/llms-full.txt index 1e90165..211bd98 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -23,8 +23,8 @@ Primary documentation: - Use `DeepCoinCredentials`, not generic `ApiCredentials`, for API key setup. DeepCoin credentials need key, secret, and passphrase. - Include `using DeepCoin.Net;` when using `DeepCoinCredentials` or `DeepCoinEnvironment`. - Always check `.Success` before reading `.Data`. -- REST methods return `WebCallResult` or `WebCallResult`. -- WebSocket subscriptions return `CallResult`. +- REST methods return `HttpResult` or `HttpResult`. +- WebSocket subscriptions return `WebSocketResult`. - Reuse clients. Do not instantiate a REST or socket client per request in production code. - Prefer dependency injection for long-running services. - Store WebSocket subscriptions and unsubscribe during shutdown. @@ -166,9 +166,9 @@ Console.WriteLine(ticker?.LastPrice); For retry logic, only retry transient errors: ```csharp -async Task> WithRetry(Func>> call, int maxAttempts = 3) +async Task> WithRetry(Func>> call, int maxAttempts = 3) { - WebCallResult last = default!; + HttpResult last = default!; for (var attempt = 1; attempt <= maxAttempts; attempt++) { @@ -457,7 +457,7 @@ Examples/ai-friendly/04-multi-exchange.cs CryptoExchange.Net SharedApis REST and socket patterns. Examples/ai-friendly/05-error-handling.cs - WebCallResult pattern, transient retry, DeepCoin error categories, symbol validation. + HttpResult pattern, transient retry, DeepCoin error categories, symbol validation. ``` These files are intended to be copied into a console `Program.cs` after `dotnet add package DeepCoin.Net`. diff --git a/llms.txt b/llms.txt index bc1e652..b8362a8 100644 --- a/llms.txt +++ b/llms.txt @@ -2,7 +2,7 @@ > Strongly typed .NET client library for the DeepCoin REST and WebSocket APIs. Supports DeepCoin spot and swap/futures market data, account endpoints, trading endpoints, private user streams, local order book support, and CryptoExchange.Net SharedApis integration. -DeepCoin.Net is part of the CryptoExchange.Net ecosystem. It uses the `WebCallResult` / `CallResult` result pattern, strongly typed enums and models, automatic WebSocket reconnect handling, client-side rate limiting, dependency injection integration, and native AOT support. Current version in this repository: 3.9.2. Targets netstandard2.0, netstandard2.1, net8.0, net9.0, net10.0. +DeepCoin.Net is part of the CryptoExchange.Net ecosystem. It uses the `HttpResult` / `WebSocketResult` result pattern, strongly typed enums and models, automatic WebSocket reconnect handling, client-side rate limiting, dependency injection integration, and native AOT support. Current version in this repository: 3.9.2. Targets netstandard2.0, netstandard2.1, net8.0, net9.0, net10.0. ## Documentation @@ -19,7 +19,7 @@ DeepCoin.Net is part of the CryptoExchange.Net ecosystem. It uses the `WebCallRe - [Swap/Futures Trading](https://github.com/JKorf/DeepCoin.Net/blob/main/Examples/ai-friendly/02-futures.cs): Set leverage, place swap order, retrieve position, reduce-only close - [WebSocket Streams](https://github.com/JKorf/DeepCoin.Net/blob/main/Examples/ai-friendly/03-websocket.cs): Public ticker/trade/kline/order book streams and authenticated user stream - [Multi-Exchange Pattern](https://github.com/JKorf/DeepCoin.Net/blob/main/Examples/ai-friendly/04-multi-exchange.cs): Using CryptoExchange.Net SharedApis for exchange-agnostic code -- [Error Handling](https://github.com/JKorf/DeepCoin.Net/blob/main/Examples/ai-friendly/05-error-handling.cs): WebCallResult pattern, retry logic, common errors and their handling +- [Error Handling](https://github.com/JKorf/DeepCoin.Net/blob/main/Examples/ai-friendly/05-error-handling.cs): HttpResult pattern, retry logic, common errors and their handling - [Full Examples Repository](https://github.com/JKorf/DeepCoin.Net/tree/main/Examples): Console, API, order book, and tracker examples ## Critical Rules From 9ccbad8ecc018030119432a9b3a8bca4409ef823 Mon Sep 17 00:00:00 2001 From: JKorf Date: Tue, 16 Jun 2026 21:58:21 +0200 Subject: [PATCH 04/12] wip --- AGENTS.md | 8 +++++--- Examples/ai-friendly/03-websocket.cs | 1 + Examples/ai-friendly/04-multi-exchange.cs | 5 ++++- Examples/ai-friendly/05-error-handling.cs | 7 +++++-- Examples/ai-friendly/README.md | 4 ++-- docs/ai-api-map.md | 7 ++++++- llms-full.txt | 12 ++++++++---- llms.txt | 8 ++++---- 8 files changed, 35 insertions(+), 17 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 68c57fa..cc920d3 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -9,7 +9,7 @@ description: Use DeepCoin.Net when generating C#/.NET code that interacts with t If the user asks for DeepCoin API access in C#/.NET, use **DeepCoin.Net**. Do not write raw `HttpClient` calls to DeepCoin endpoints because that bypasses signing, rate limiting, result handling, and WebSocket lifecycle support. -For multi-exchange code, use `CryptoExchange.Net.SharedApis` interfaces from `.ExchangeApi.SharedClient`. +For multi-exchange code, use `CryptoExchange.Net.SharedApis` interfaces from `.ExchangeApi.SharedClient`. Call `.ExchangeApi.SharedClient.Discover()` to inspect supported shared features. ## Installation @@ -39,7 +39,7 @@ var publicClient = new DeepCoinRestClient(); ## Core Pattern: Result Handling -Every method returns `HttpResult` (REST) or `WebSocketResult` (WebSocket). Always check `.Success` before accessing `.Data`. +REST methods return `HttpResult` or `HttpResult`; WebSocket subscriptions return `WebSocketResult`; shared symbol/cache helpers can return `ExchangeCallResult`. Always check `.Success` before accessing `.Data`. ```csharp using DeepCoin.Net.Clients; @@ -188,7 +188,9 @@ await socketClient.ExchangeApi.SubscribeToUserDataUpdatesAsync( using DeepCoin.Net.Clients; using CryptoExchange.Net.SharedApis; -ISpotTickerRestClient tickerClient = new DeepCoinRestClient().ExchangeApi.SharedClient; +var restClient = new DeepCoinRestClient(); +ISpotTickerRestClient tickerClient = restClient.ExchangeApi.SharedClient; +var info = restClient.ExchangeApi.SharedClient.Discover(); var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT"); var ticker = await tickerClient.GetSpotTickerAsync(new GetTickerRequest(symbol)); diff --git a/Examples/ai-friendly/03-websocket.cs b/Examples/ai-friendly/03-websocket.cs index 8dc2f3c..b79724a 100644 --- a/Examples/ai-friendly/03-websocket.cs +++ b/Examples/ai-friendly/03-websocket.cs @@ -12,6 +12,7 @@ var socketClient = new DeepCoinSocketClient(); const string symbol = "ETH-USDT"; +// Subscription methods return WebSocketResult. // ---- 1. PUBLIC TICKER STREAM ---- var tickerSub = await socketClient.ExchangeApi.SubscribeToSymbolUpdatesAsync( symbol, diff --git a/Examples/ai-friendly/04-multi-exchange.cs b/Examples/ai-friendly/04-multi-exchange.cs index 4bf1543..fe007f1 100644 --- a/Examples/ai-friendly/04-multi-exchange.cs +++ b/Examples/ai-friendly/04-multi-exchange.cs @@ -17,7 +17,10 @@ // DeepCoin has one ExchangeApi root. SharedClient implements interfaces like // ISpotTickerRestClient, IFuturesOrderRestClient, IBalanceRestClient, etc. -ISpotTickerRestClient deepCoinShared = new DeepCoinRestClient().ExchangeApi.SharedClient; +var restClient = new DeepCoinRestClient(); +ISpotTickerRestClient deepCoinShared = restClient.ExchangeApi.SharedClient; +var capabilities = restClient.ExchangeApi.SharedClient.Discover(); +Console.WriteLine($"Shared REST features: {capabilities.Features.Count(x => x.Supported)}"); // To add Binance or OKX, install the package and: // ISpotTickerRestClient binanceShared = new BinanceRestClient().SpotApi.SharedClient; diff --git a/Examples/ai-friendly/05-error-handling.cs b/Examples/ai-friendly/05-error-handling.cs index 28c5029..0415075 100644 --- a/Examples/ai-friendly/05-error-handling.cs +++ b/Examples/ai-friendly/05-error-handling.cs @@ -1,6 +1,7 @@ // 05-error-handling.cs // -// Demonstrates: HttpResult patterns, retry logic, common error scenarios. +// Demonstrates: HttpResult, WebSocketResult, and ExchangeCallResult patterns, +// retry logic, common error scenarios. // // Setup: dotnet add package DeepCoin.Net @@ -15,7 +16,9 @@ }); // ---- 1. THE BASIC PATTERN ---- -// Every method returns HttpResult (REST) or WebSocketResult (WebSocket). +// REST methods return HttpResult or HttpResult. +// WebSocket subscriptions return WebSocketResult. +// Some SharedApis symbol helper methods return ExchangeCallResult. // .Success is true/false. .Data is the payload, only valid when .Success is true. // .Error contains structured error info when .Success is false. // .Error.IsTransient hints if a retry might succeed. diff --git a/Examples/ai-friendly/README.md b/Examples/ai-friendly/README.md index b6abdd1..aa951bc 100644 --- a/Examples/ai-friendly/README.md +++ b/Examples/ai-friendly/README.md @@ -14,8 +14,8 @@ These examples are optimized for AI coding assistants and quick onboarding. Each | `01-spot-quickstart.cs` | Client setup, public ticker lookup, authenticated account balance, place limit order, query/cancel order | | `02-futures.cs` | Swap/futures: set leverage, place market order, get position, close position | | `03-websocket.cs` | Subscribe to ticker, trade, kline, order book, and user data streams with proper teardown | -| `04-multi-exchange.cs` | `CryptoExchange.Net.SharedApis` pattern for exchange-agnostic code | -| `05-error-handling.cs` | `HttpResult` patterns, retry, common error scenarios | +| `04-multi-exchange.cs` | `CryptoExchange.Net.SharedApis` pattern and shared capability discovery | +| `05-error-handling.cs` | `HttpResult`, `WebSocketResult`, and `ExchangeCallResult` handling patterns | ## Running diff --git a/docs/ai-api-map.md b/docs/ai-api-map.md index 8911589..62e9d17 100644 --- a/docs/ai-api-map.md +++ b/docs/ai-api-map.md @@ -16,6 +16,7 @@ Use this file to route common user intents to the correct DeepCoin.Net client me | WebSocket API root | `socketClient.ExchangeApi` | | Shared REST client | `client.ExchangeApi.SharedClient` | | Shared socket client | `socketClient.ExchangeApi.SharedClient` | +| Discover shared capabilities | `client.ExchangeApi.SharedClient.Discover()` | ## Exchange Data REST @@ -100,6 +101,7 @@ Use this file to route common user intents to the correct DeepCoin.Net client me |---|---| | Shared REST client | `new DeepCoinRestClient().ExchangeApi.SharedClient` | | Shared socket client | `new DeepCoinSocketClient().ExchangeApi.SharedClient` | +| Discover shared capabilities | `client.ExchangeApi.SharedClient.Discover()` | | Shared spot ticker REST | `ISpotTickerRestClient.GetSpotTickerAsync(new GetTickerRequest(symbol))` | | Shared futures ticker REST | `IFuturesTickerRestClient.GetFuturesTickerAsync(new GetTickerRequest(symbol))` | | Shared spot symbols REST | `ISpotSymbolRestClient.GetSpotSymbolsAsync(...)` | @@ -123,6 +125,8 @@ Use this file to route common user intents to the correct DeepCoin.Net client me | Shared user trade socket | `IUserTradeSocketClient.SubscribeToUserTradeUpdatesAsync(...)` | | Shared position socket | `IPositionSocketClient.SubscribeToPositionUpdatesAsync(...)` | +Shared REST methods return `HttpResult` / `HttpResult`; shared socket subscriptions return `WebSocketResult`; shared symbol/cache helpers such as `SupportsSpotSymbolAsync` and `SupportsFuturesSymbolAsync` can return `ExchangeCallResult`. + For shared socket subscriptions, keep the concrete socket client and unsubscribe with `await socketClient.UnsubscribeAsync(subscription.Data)`. ## Result Handling @@ -130,8 +134,9 @@ For shared socket subscriptions, keep the concrete socket client and unsubscribe | Situation | Pattern | |---|---| | REST success check | `if (!result.Success) { Console.WriteLine(result.Error); return; }` | -| Socket subscription success check | `if (!sub.Success) { Console.WriteLine(sub.Error); return; }` | +| Socket subscription success check | `if (!sub.Success) { Console.WriteLine(sub.Error); return; }` where `sub` is `WebSocketResult` | | Read REST data | Read `result.Data` only after `result.Success` | +| Read shared helper data | Read `ExchangeCallResult.Data` only after `.Success` | | Retry decision | Retry only when `result.Error?.IsTransient == true` | | Cancellation | Pass `ct: cancellationToken` | diff --git a/llms-full.txt b/llms-full.txt index 211bd98..d819f94 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -25,6 +25,7 @@ Primary documentation: - Always check `.Success` before reading `.Data`. - REST methods return `HttpResult` or `HttpResult`. - WebSocket subscriptions return `WebSocketResult`. +- Shared non-I/O symbol/cache helper methods can return `ExchangeCallResult`. - Reuse clients. Do not instantiate a REST or socket client per request in production code. - Prefer dependency injection for long-running services. - Store WebSocket subscriptions and unsubscribe during shutdown. @@ -32,7 +33,7 @@ Primary documentation: - Do not invent endpoint names. If unsure, inspect `DeepCoin.Net/Interfaces/Clients/**`. - Do not invent Binance-style branches. DeepCoin has one `ExchangeApi` root. - Native DeepCoin methods use hyphenated symbols such as `ETH-USDT` and `ETH-USDT-SWAP`. -- For multi-exchange code, use `CryptoExchange.Net.SharedApis` interfaces from `.ExchangeApi.SharedClient`. +- For multi-exchange code, use `CryptoExchange.Net.SharedApis` interfaces from `.ExchangeApi.SharedClient` and call `.Discover()` to inspect supported features. ## Installation @@ -372,8 +373,11 @@ var userSub = await socketClient.ExchangeApi.SubscribeToUserDataUpdatesAsync( using DeepCoin.Net.Clients; using CryptoExchange.Net.SharedApis; -ISpotTickerRestClient tickerClient = new DeepCoinRestClient().ExchangeApi.SharedClient; +var restClient = new DeepCoinRestClient(); +ISpotTickerRestClient tickerClient = restClient.ExchangeApi.SharedClient; var symbol = new SharedSymbol(TradingMode.Spot, "ETH", "USDT"); +var info = restClient.ExchangeApi.SharedClient.Discover(); +var supportedFeatures = info.Features.Where(x => x.Supported).Select(x => x.EndpointName); var ticker = await tickerClient.GetSpotTickerAsync(new GetTickerRequest(symbol)); if (!ticker.Success) @@ -454,10 +458,10 @@ Examples/ai-friendly/03-websocket.cs Public ticker, trade, kline, order book, authenticated user data stream, success checks, teardown. Examples/ai-friendly/04-multi-exchange.cs - CryptoExchange.Net SharedApis REST and socket patterns. + CryptoExchange.Net SharedApis REST/socket patterns and capability discovery. Examples/ai-friendly/05-error-handling.cs - HttpResult pattern, transient retry, DeepCoin error categories, symbol validation. + HttpResult, WebSocketResult, ExchangeCallResult patterns; transient retry; DeepCoin error categories; symbol validation. ``` These files are intended to be copied into a console `Program.cs` after `dotnet add package DeepCoin.Net`. diff --git a/llms.txt b/llms.txt index b8362a8..6336a9d 100644 --- a/llms.txt +++ b/llms.txt @@ -2,7 +2,7 @@ > Strongly typed .NET client library for the DeepCoin REST and WebSocket APIs. Supports DeepCoin spot and swap/futures market data, account endpoints, trading endpoints, private user streams, local order book support, and CryptoExchange.Net SharedApis integration. -DeepCoin.Net is part of the CryptoExchange.Net ecosystem. It uses the `HttpResult` / `WebSocketResult` result pattern, strongly typed enums and models, automatic WebSocket reconnect handling, client-side rate limiting, dependency injection integration, and native AOT support. Current version in this repository: 3.9.2. Targets netstandard2.0, netstandard2.1, net8.0, net9.0, net10.0. +DeepCoin.Net is part of the CryptoExchange.Net ecosystem. It uses the standard CryptoExchange.Net result model, strongly typed enums and models, automatic WebSocket reconnect handling, client-side rate limiting, dependency injection integration, and native AOT support. REST methods return `HttpResult` / `HttpResult`, WebSocket subscriptions return `WebSocketResult`, and selected shared helper methods return `ExchangeCallResult`. Current version in this repository: 3.9.2. Targets netstandard2.0, netstandard2.1, net8.0, net9.0, net10.0. ## Documentation @@ -18,8 +18,8 @@ DeepCoin.Net is part of the CryptoExchange.Net ecosystem. It uses the `HttpResul - [Spot Quickstart](https://github.com/JKorf/DeepCoin.Net/blob/main/Examples/ai-friendly/01-spot-quickstart.cs): Client setup, ticker lookup, balances, limit spot order, order status and cancellation - [Swap/Futures Trading](https://github.com/JKorf/DeepCoin.Net/blob/main/Examples/ai-friendly/02-futures.cs): Set leverage, place swap order, retrieve position, reduce-only close - [WebSocket Streams](https://github.com/JKorf/DeepCoin.Net/blob/main/Examples/ai-friendly/03-websocket.cs): Public ticker/trade/kline/order book streams and authenticated user stream -- [Multi-Exchange Pattern](https://github.com/JKorf/DeepCoin.Net/blob/main/Examples/ai-friendly/04-multi-exchange.cs): Using CryptoExchange.Net SharedApis for exchange-agnostic code -- [Error Handling](https://github.com/JKorf/DeepCoin.Net/blob/main/Examples/ai-friendly/05-error-handling.cs): HttpResult pattern, retry logic, common errors and their handling +- [Multi-Exchange Pattern](https://github.com/JKorf/DeepCoin.Net/blob/main/Examples/ai-friendly/04-multi-exchange.cs): Using CryptoExchange.Net SharedApis and shared capability discovery for exchange-agnostic code +- [Error Handling](https://github.com/JKorf/DeepCoin.Net/blob/main/Examples/ai-friendly/05-error-handling.cs): HttpResult, WebSocketResult, and ExchangeCallResult patterns, retry logic, common errors and their handling - [Full Examples Repository](https://github.com/JKorf/DeepCoin.Net/tree/main/Examples): Console, API, order book, and tracker examples ## Critical Rules @@ -31,7 +31,7 @@ DeepCoin.Net is part of the CryptoExchange.Net ecosystem. It uses the `HttpResul - Always check `.Success` before reading `.Data`. - Use `socketClient.UnsubscribeAsync(subscription.Data)` for teardown. - Private WebSocket streams require `restClient.ExchangeApi.Account.StartUserStreamAsync()`. -- For cross-exchange code, use `CryptoExchange.Net.SharedApis` via `.ExchangeApi.SharedClient`. +- For cross-exchange code, use `CryptoExchange.Net.SharedApis` via `.ExchangeApi.SharedClient` and call `.Discover()` to inspect supported shared features. ## Reference From ee3a21b4645b22d77f62585f70e4947caaa9f24b Mon Sep 17 00:00:00 2001 From: Jkorf Date: Wed, 17 Jun 2026 12:43:12 +0200 Subject: [PATCH 05/12] wip --- .../DeepCoinSocketClientExchangeApi.cs | 108 +++++++++++++++++- .../IDeepCoinSocketClientExchangeApi.cs | 20 ++++ 2 files changed, 125 insertions(+), 3 deletions(-) diff --git a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApi.cs b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApi.cs index 1df824c..63b5db1 100644 --- a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApi.cs +++ b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApi.cs @@ -24,6 +24,8 @@ using CryptoExchange.Net.Converters.MessageParsing.DynamicConverters; using DeepCoin.Net.Clients.MessageHandlers; using CryptoExchange.Net.Sockets.Default; +using Microsoft.Extensions.Options; +using CryptoExchange.Net.TokenManagement; namespace DeepCoin.Net.Clients.ExchangeApi { @@ -32,6 +34,28 @@ namespace DeepCoin.Net.Clients.ExchangeApi /// internal partial class DeepCoinSocketClientExchangeApi : SocketApiClient, IDeepCoinSocketClientExchangeApi { + private readonly ILoggerFactory? _loggerFactory; + private DeepCoinRestClient? _tokenClient; + internal TokenManager TokenManager { get; } + private DeepCoinRestClient TokenClient + { + get + { + if (_tokenClient == null) + { + _tokenClient = new DeepCoinRestClient(null, _loggerFactory, Options.Create(new DeepCoinRestOptions + { + ApiCredentials = ApiCredentials, + Environment = ClientOptions.Environment, + Proxy = ClientOptions.Proxy, + OutputOriginalData = ClientOptions.OutputOriginalData + })); + } + + return _tokenClient; + } + } + #region constructor/destructor /// @@ -40,8 +64,18 @@ internal partial class DeepCoinSocketClientExchangeApi : SocketApiClient new DeepCoinPingQuery(), @@ -186,7 +220,18 @@ public async Task> SubscribeToOrderBookUpdat /// public async Task> SubscribeToUserDataUpdatesAsync( - string listenKey, + Action>? onOrderMessage = null, + Action>? onBalanceMessage = null, + Action>? onPositionMessage = null, + Action>? onUserTradeMessage = null, + Action>? onAccountMessage = null, + Action>? onTriggerOrderMessage = null, + CancellationToken ct = default) + => await SubscribeToUserDataUpdatesAsync(null, onOrderMessage, onBalanceMessage, onPositionMessage, onUserTradeMessage, onAccountMessage, onTriggerOrderMessage, ct).ConfigureAwait(false); + + /// + public async Task> SubscribeToUserDataUpdatesAsync( + string? listenKey, Action>? onOrderMessage = null, Action>? onBalanceMessage = null, Action>? onPositionMessage = null, @@ -195,8 +240,33 @@ public async Task> SubscribeToUserDataUpdate Action>? onTriggerOrderMessage = null, CancellationToken ct = default) { - var subscription = new DeepCoinUserSubscription(_logger, this, onOrderMessage, onBalanceMessage, onPositionMessage, onUserTradeMessage, onAccountMessage, onTriggerOrderMessage); - return await SubscribeAsync(BaseAddress.AppendPath("v1/private?listenKey=" + listenKey), subscription, ct).ConfigureAwait(false); + if (listenKey == null && !Authenticated) + return WebSocketResult.Fail(Exchange, new NoApiCredentialsError()); + + TokenLease? lease = null; + if (listenKey == null) + { + var leaseResult = await TokenManager.AcquireAsync(new TokenScope( + DeepCoinExchange.Metadata.Id, + EnvironmentName, + "Exchange", + ApiCredentials!.Key), ct).ConfigureAwait(false); + if (!leaseResult.Success) + return WebSocketResult.Fail(Exchange, leaseResult.Error); + + lease = leaseResult.Data; + } + + var lk = listenKey ?? lease!.Token.Token; + var subscription = new DeepCoinUserSubscription(_logger, this, onOrderMessage, onBalanceMessage, onPositionMessage, onUserTradeMessage, onAccountMessage, onTriggerOrderMessage) + { + TokenLease = lease + }; + var result = await SubscribeAsync(BaseAddress.AppendPath("v1/private?listenKey=" + lk), subscription, ct).ConfigureAwait(false); + if (!result.Success && lease != null) + await lease.ReleaseAsync().ConfigureAwait(false); + + return result; } /// @@ -205,5 +275,37 @@ public async Task> SubscribeToUserDataUpdate /// public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverDate = null) => DeepCoinExchange.FormatWebsocketSymbol(baseAsset, quoteAsset, tradingMode, deliverDate); + + protected override async Task RevitalizeRequestAsync(Subscription subscription) + { + if (subscription.TokenLease == null) + return CallResult.Ok(); // Not an authenticated subscription, no need to revitalize + + var scope = new TokenScope( + DeepCoinExchange.Metadata.Id, + EnvironmentName, + "Exchange", + ApiCredentials!.Key); + + return await TokenManager.AcquireAndReplaceAsync(subscription, scope).ConfigureAwait(false); + } + + private async Task> StartListenKeyAsync(TokenScope tokenScope, CancellationToken ct) + { + var result = await TokenClient.ExchangeApi.Account.StartUserStreamAsync(ct).ConfigureAwait(false); + if (!result.Success) + return CallResult.Fail(result.Error); + + return CallResult.Ok(result.Data.ListenKey); + } + + private async Task KeepAliveListenKeyAsync(TokenInfo token, CancellationToken ct) + { + var result = await TokenClient.ExchangeApi.Account.KeepAliveUserStreamAsync(token.Token, ct).ConfigureAwait(false); + if (!result.Success) + return CallResult.Fail(result.Error); + + return CallResult.Ok(); + } } } diff --git a/DeepCoin.Net/Interfaces/Clients/ExchangeApi/IDeepCoinSocketClientExchangeApi.cs b/DeepCoin.Net/Interfaces/Clients/ExchangeApi/IDeepCoinSocketClientExchangeApi.cs index 4a41ad4..e491a0c 100644 --- a/DeepCoin.Net/Interfaces/Clients/ExchangeApi/IDeepCoinSocketClientExchangeApi.cs +++ b/DeepCoin.Net/Interfaces/Clients/ExchangeApi/IDeepCoinSocketClientExchangeApi.cs @@ -74,6 +74,26 @@ public interface IDeepCoinSocketClientExchangeApi : ISocketApiClientA stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected Task> SubscribeToOrderBookUpdatesAsync(string symbol, Action> onMessage, CancellationToken ct = default); + /// + /// Subscribe to user data updates. Listen key is automatically obtained by the client and will be renewed as needed + /// + /// The event handler for order updates + /// The event handler for balance updates + /// The event handler for position updates + /// The event handler for user trade updates + /// The event handler for account updates + /// The event handler for trigger order updates + /// Cancellation token for closing this subscription + /// A stream subscription. This stream subscription can be used to be notified when the socket is disconnected/reconnected + Task> SubscribeToUserDataUpdatesAsync( + Action>? onOrderMessage = null, + Action>? onBalanceMessage = null, + Action>? onPositionMessage = null, + Action>? onUserTradeMessage = null, + Action>? onAccountMessage = null, + Action>? onTriggerOrderMessage = null, + CancellationToken ct = default); + /// /// Subscribe to user data updates /// From 1a421557aa0be13383e65b82fd3d1d4d9280f2fa Mon Sep 17 00:00:00 2001 From: Jkorf Date: Wed, 17 Jun 2026 14:20:16 +0200 Subject: [PATCH 06/12] wip --- .../DeepCoinSocketClientExchangeApi.cs | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApi.cs b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApi.cs index 63b5db1..fa25779 100644 --- a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApi.cs +++ b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApi.cs @@ -26,6 +26,7 @@ using CryptoExchange.Net.Sockets.Default; using Microsoft.Extensions.Options; using CryptoExchange.Net.TokenManagement; +using CryptoExchange.Net.Sockets.Interfaces; namespace DeepCoin.Net.Clients.ExchangeApi { @@ -276,6 +277,28 @@ public async Task> SubscribeToUserDataUpdate public override string FormatSymbol(string baseAsset, string quoteAsset, TradingMode tradingMode, DateTime? deliverDate = null) => DeepCoinExchange.FormatWebsocketSymbol(baseAsset, quoteAsset, tradingMode, deliverDate); + protected override async Task GetReconnectUriAsync(ISocketConnection connection) + { + if (!connection.HasAuthenticatedSubscription) + return await base.GetReconnectUriAsync(connection).ConfigureAwait(false); + + var subscriptions = ((SocketConnection)connection).Subscriptions.Where(x => x.TokenLease != null).ToList(); + if (subscriptions.Count == 0) + return await base.GetReconnectUriAsync(connection).ConfigureAwait(false); + + var scope = new TokenScope( + DeepCoinExchange.Metadata.Id, + EnvironmentName, + "Exchange", + ApiCredentials!.Key); + + var token = await TokenManager.AcquireAndReplaceAsync(subscriptions[0], scope).ConfigureAwait(false); + if (!token.Success) + return null; + + return new Uri(BaseAddress.AppendPath("v1/private?listenKey=" + token.Data.Token.Token); + } + protected override async Task RevitalizeRequestAsync(Subscription subscription) { if (subscription.TokenLease == null) From dea8f17f34df12389fb144f5083230c07c6a6e7e Mon Sep 17 00:00:00 2001 From: Jkorf Date: Wed, 17 Jun 2026 16:13:23 +0200 Subject: [PATCH 07/12] wip --- .../DeepCoinRestClientExchangeApiShared.cs | 42 ---------------- .../DeepCoinSocketClientExchangeApi.cs | 2 +- .../DeepCoinSocketClientExchangeApiShared.cs | 49 ++++--------------- DeepCoin.Net/DeepCoinUserDataTracker.cs | 2 - .../IDeepCoinRestClientExchangeApiShared.cs | 1 - 5 files changed, 10 insertions(+), 86 deletions(-) diff --git a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiShared.cs b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiShared.cs index 33c086b..e3ad047 100644 --- a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiShared.cs +++ b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiShared.cs @@ -171,48 +171,6 @@ async Task> IKlineRestClient.GetKlinesAsync(GetKlinesR #endregion - #region Listen Key client - - StartListenKeyOptions IListenKeyRestClient.StartOptions { get; } = new StartListenKeyOptions(_exchangeName, true); - async Task> IListenKeyRestClient.StartListenKeyAsync(StartListenKeyRequest request, CancellationToken ct) - { - var validationError = SharedClient.StartOptions.ValidateRequest(request, this); - if (validationError != null) - return HttpResult.Fail(Exchange, validationError); - - // Get data - var result = await Account.StartUserStreamAsync(ct: ct).ConfigureAwait(false); - if (!result.Success) - return HttpResult.Fail(result); - - return HttpResult.Ok(result, result.Data.ListenKey); - } - KeepAliveListenKeyOptions IListenKeyRestClient.KeepAliveOptions { get; } = new KeepAliveListenKeyOptions(_exchangeName, true); - async Task> IListenKeyRestClient.KeepAliveListenKeyAsync(KeepAliveListenKeyRequest request, CancellationToken ct) - { - var validationError = SharedClient.KeepAliveOptions.ValidateRequest(request, this); - if (validationError != null) - return HttpResult.Fail(Exchange, validationError); - - // Get data - var result = await Account.KeepAliveUserStreamAsync(request.ListenKey, ct: ct).ConfigureAwait(false); - if (!result.Success) - return HttpResult.Fail(result); - - return HttpResult.Ok(result, request.ListenKey); - } - - StopListenKeyOptions IListenKeyRestClient.StopOptions { get; } = new StopListenKeyOptions(_exchangeName, true); - Task> IListenKeyRestClient.StopListenKeyAsync(StopListenKeyRequest request, CancellationToken ct) - { - var validationError = SharedClient.StopOptions.ValidateRequest(request, this); - if (validationError != null) - return Task.FromResult(HttpResult.Fail(Exchange, validationError)); - - return Task.FromResult(new HttpResult(Exchange, request.ListenKey, null)); - } - #endregion - #region Order Book client GetOrderBookOptions IOrderBookRestClient.GetOrderBookOptions { get; } = new GetOrderBookOptions(_exchangeName, 1, 400, false); async Task> IOrderBookRestClient.GetOrderBookAsync(GetOrderBookRequest request, CancellationToken ct) diff --git a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApi.cs b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApi.cs index fa25779..889b0ae 100644 --- a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApi.cs +++ b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApi.cs @@ -296,7 +296,7 @@ public override string FormatSymbol(string baseAsset, string quoteAsset, Trading if (!token.Success) return null; - return new Uri(BaseAddress.AppendPath("v1/private?listenKey=" + token.Data.Token.Token); + return new Uri(BaseAddress.AppendPath("v1/private?listenKey=" + token.Data.Token.Token)); } protected override async Task RevitalizeRequestAsync(Subscription subscription) diff --git a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApiShared.cs b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApiShared.cs index 9852142..f98ce01 100644 --- a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApiShared.cs +++ b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApiShared.cs @@ -84,20 +84,14 @@ async Task> ITradeSocketClient.SubscribeToTr #endregion #region Balance client - SubscribeBalanceOptions IBalanceSocketClient.SubscribeBalanceOptions { get; } = new SubscribeBalanceOptions(_exchangeName, false) - { - RequiredOptionalParameters = new List - { - new ParameterDescription(nameof(SubscribeBalancesRequest.ListenKey), typeof(string), "The listenkey for starting the user stream", "123123123") - } - }; + SubscribeBalanceOptions IBalanceSocketClient.SubscribeBalanceOptions { get; } = new SubscribeBalanceOptions(_exchangeName, true); async Task> IBalanceSocketClient.SubscribeToBalanceUpdatesAsync(SubscribeBalancesRequest request, Action> handler, CancellationToken ct) { var validationError = SharedClient.SubscribeBalanceOptions.ValidateRequest(request, this); if (validationError != null) return WebSocketResult.Fail(_exchangeName, validationError); - var result = await SubscribeToUserDataUpdatesAsync(request.ListenKey!, + var result = await SubscribeToUserDataUpdatesAsync( onBalanceMessage: update => handler(update.ToType(update.Data.Select(x => new SharedBalance(x.Asset, x.Available, x.Balance)).ToArray())), ct: ct).ConfigureAwait(false); @@ -108,20 +102,14 @@ async Task> IBalanceSocketClient.SubscribeTo #region Futures Order client - SubscribeFuturesOrderOptions IFuturesOrderSocketClient.SubscribeFuturesOrderOptions { get; } = new SubscribeFuturesOrderOptions(_exchangeName, false) - { - RequiredOptionalParameters = new List - { - new ParameterDescription(nameof(SubscribeFuturesOrderRequest.ListenKey), typeof(string), "The listenkey for starting the user stream", "123123123") - } - }; + SubscribeFuturesOrderOptions IFuturesOrderSocketClient.SubscribeFuturesOrderOptions { get; } = new SubscribeFuturesOrderOptions(_exchangeName, true); async Task> IFuturesOrderSocketClient.SubscribeToFuturesOrderUpdatesAsync(SubscribeFuturesOrderRequest request, Action> handler, CancellationToken ct) { var validationError = SharedClient.SubscribeFuturesOrderOptions.ValidateRequest(request, this); if (validationError != null) return WebSocketResult.Fail(_exchangeName, validationError); - var result = await SubscribeToUserDataUpdatesAsync(request.ListenKey!, + var result = await SubscribeToUserDataUpdatesAsync( onOrderMessage: update => { var futuresOrders = update.Data.Where(x => !x.Symbol.Contains("/")); @@ -157,20 +145,14 @@ async Task> IFuturesOrderSocketClient.Subscr #region Spot Order client - SubscribeSpotOrderOptions ISpotOrderSocketClient.SubscribeSpotOrderOptions { get; } = new SubscribeSpotOrderOptions(_exchangeName, false) - { - RequiredOptionalParameters = new List - { - new ParameterDescription(nameof(SubscribeSpotOrderRequest.ListenKey), typeof(string), "Listenkey for the user stream", "123123123") - } - }; + SubscribeSpotOrderOptions ISpotOrderSocketClient.SubscribeSpotOrderOptions { get; } = new SubscribeSpotOrderOptions(_exchangeName, true); async Task> ISpotOrderSocketClient.SubscribeToSpotOrderUpdatesAsync(SubscribeSpotOrderRequest request, Action> handler, CancellationToken ct) { var validationError = SharedClient.SubscribeSpotOrderOptions.ValidateRequest(request, this); if (validationError != null) return WebSocketResult.Fail(_exchangeName, validationError); - var result = await SubscribeToUserDataUpdatesAsync(request.ListenKey!, + var result = await SubscribeToUserDataUpdatesAsync( onOrderMessage: update => { var spotOrders = update.Data.Where(x => x.Symbol.Contains("/")); @@ -204,20 +186,14 @@ async Task> ISpotOrderSocketClient.Subscribe #endregion #region Position client - SubscribePositionOptions IPositionSocketClient.SubscribePositionOptions { get; } = new SubscribePositionOptions(_exchangeName, false) - { - RequiredOptionalParameters = new List - { - new ParameterDescription(nameof(SubscribePositionRequest.ListenKey), typeof(string), "The listenkey for starting the user stream", "123123123") - } - }; + SubscribePositionOptions IPositionSocketClient.SubscribePositionOptions { get; } = new SubscribePositionOptions(_exchangeName, true); async Task> IPositionSocketClient.SubscribeToPositionUpdatesAsync(SubscribePositionRequest request, Action> handler, CancellationToken ct) { var validationError = SharedClient.SubscribePositionOptions.ValidateRequest(request, this); if (validationError != null) return WebSocketResult.Fail(_exchangeName, validationError); - var result = await SubscribeToUserDataUpdatesAsync(request.ListenKey!, + var result = await SubscribeToUserDataUpdatesAsync( 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, @@ -234,13 +210,7 @@ async Task> IPositionSocketClient.SubscribeT #region User Trade client - SubscribeUserTradeOptions IUserTradeSocketClient.SubscribeUserTradeOptions { get; } = new SubscribeUserTradeOptions(_exchangeName, true) - { - RequiredOptionalParameters = new List - { - new ParameterDescription(nameof(SubscribePositionRequest.ListenKey), typeof(string), "The listenkey for starting the user stream", "123123123") - } - }; + SubscribeUserTradeOptions IUserTradeSocketClient.SubscribeUserTradeOptions { get; } = new SubscribeUserTradeOptions(_exchangeName, true); async Task> IUserTradeSocketClient.SubscribeToUserTradeUpdatesAsync(SubscribeUserTradeRequest request, Action> handler, CancellationToken ct) { var validationError = SharedClient.SubscribeUserTradeOptions.ValidateRequest(request, this); @@ -248,7 +218,6 @@ async Task> IUserTradeSocketClient.Subscribe return WebSocketResult.Fail(_exchangeName, validationError); var result = await SubscribeToUserDataUpdatesAsync( - request.ListenKey!, onUserTradeMessage: update => handler(update.ToType(update.Data.Select(x => new SharedUserTrade( ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol) ?? ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol), diff --git a/DeepCoin.Net/DeepCoinUserDataTracker.cs b/DeepCoin.Net/DeepCoinUserDataTracker.cs index 98c1180..7e0ac89 100644 --- a/DeepCoin.Net/DeepCoinUserDataTracker.cs +++ b/DeepCoin.Net/DeepCoinUserDataTracker.cs @@ -21,7 +21,6 @@ public DeepCoinUserSpotDataTracker( logger, restClient.ExchangeApi.SharedClient, restClient.ExchangeApi.SharedClient, - restClient.ExchangeApi.SharedClient, socketClient.ExchangeApi.SharedClient, restClient.ExchangeApi.SharedClient, socketClient.ExchangeApi.SharedClient, @@ -47,7 +46,6 @@ public DeepCoinUserFuturesDataTracker( IDeepCoinSocketClient socketClient, string? userIdentifier, FuturesUserDataTrackerConfig? config) : base(logger, - restClient.ExchangeApi.SharedClient, restClient.ExchangeApi.SharedClient, restClient.ExchangeApi.SharedClient, socketClient.ExchangeApi.SharedClient, diff --git a/DeepCoin.Net/Interfaces/Clients/ExchangeApi/IDeepCoinRestClientExchangeApiShared.cs b/DeepCoin.Net/Interfaces/Clients/ExchangeApi/IDeepCoinRestClientExchangeApiShared.cs index ab0bd48..fa49df1 100644 --- a/DeepCoin.Net/Interfaces/Clients/ExchangeApi/IDeepCoinRestClientExchangeApiShared.cs +++ b/DeepCoin.Net/Interfaces/Clients/ExchangeApi/IDeepCoinRestClientExchangeApiShared.cs @@ -9,7 +9,6 @@ public interface IDeepCoinRestClientExchangeApiShared : IBalanceRestClient, IDepositRestClient, IKlineRestClient, - IListenKeyRestClient, IOrderBookRestClient, IWithdrawalRestClient, ISpotTickerRestClient, From 5b22e87e6c5b6b0283f0ae61f5e9ce25fd48717e Mon Sep 17 00:00:00 2001 From: Jkorf Date: Thu, 18 Jun 2026 14:53:00 +0200 Subject: [PATCH 08/12] wip --- DeepCoin.Net/DeepCoinAuthenticationProvider.cs | 2 +- DeepCoin.Net/DeepCoinExchange.cs | 12 +++++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/DeepCoin.Net/DeepCoinAuthenticationProvider.cs b/DeepCoin.Net/DeepCoinAuthenticationProvider.cs index b006088..7a577a5 100644 --- a/DeepCoin.Net/DeepCoinAuthenticationProvider.cs +++ b/DeepCoin.Net/DeepCoinAuthenticationProvider.cs @@ -23,7 +23,7 @@ public override void ProcessRequest(RestApiClient apiClient, RestRequestConfigur var timestamp = GetTimestamp(apiClient).ToString("yyyy-MM-ddTHH:mm:ss.fffZ"); var queryParams = request.QueryParameters?.Count > 0 ? $"?{request.GetQueryString(false)}" : ""; - var bodyParams = request.BodyParameters?.Count > 0 ? GetSerializedBody(_serializer, request.BodyParameters) : ""; + var bodyParams = (request.BodyParameters != null && !request.BodyParameters.Empty) ? GetSerializedBody(_serializer, request.BodyParameters) : ""; var signStr = $"{timestamp}{request.RequestDefinition.Method}{request.RequestDefinition.Path}{queryParams}{bodyParams}"; var signature = SignHMACSHA256(signStr, SignOutputType.Base64); diff --git a/DeepCoin.Net/DeepCoinExchange.cs b/DeepCoin.Net/DeepCoinExchange.cs index 311486b..195801f 100644 --- a/DeepCoin.Net/DeepCoinExchange.cs +++ b/DeepCoin.Net/DeepCoinExchange.cs @@ -117,7 +117,7 @@ public static string FormatWebsocketSymbol(string baseAsset, string quoteAsset, /// /// Rate limiter configuration for the DeepCoin API /// - public static DeepCoinRateLimiters RateLimiter { get; } = new DeepCoinRateLimiters(); + public static DeepCoinRateLimiters RateLimiter { get; set; } = new DeepCoinRateLimiters(); } /// @@ -136,13 +136,19 @@ public class DeepCoinRateLimiters public event Action RateLimitUpdated; #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. - internal DeepCoinRateLimiters() + /// + /// ctor + /// + public DeepCoinRateLimiters() #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable. { Initialize(); } - private void Initialize() + /// + /// Initialize the rate limits + /// + protected virtual void Initialize() { DeepCoin = new RateLimitGate("DeepCoin"); DeepCoin.RateLimitTriggered += (x) => RateLimitTriggered?.Invoke(x); From 6edc0d5b387c5aed13a4f81a1a45941c9b289714 Mon Sep 17 00:00:00 2001 From: Jkorf Date: Thu, 18 Jun 2026 16:33:02 +0200 Subject: [PATCH 09/12] wip --- .../DeepCoinRestClientExchangeApiShared.cs | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiShared.cs b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiShared.cs index e3ad047..203d6fb 100644 --- a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiShared.cs +++ b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiShared.cs @@ -226,13 +226,32 @@ async Task> IWithdrawalRestClient.GetWithdrawalsA return HttpResult.Ok(result, ExchangeHelpers.ApplyFilter(result.Data.Data, x => x.CreateTime, request.StartTime, request.EndTime, direction) .Select(x => - new SharedWithdrawal(x.Asset, x.Address, x.Quantity, x.DepositStatus == Enums.WithdrawStatus.Success, x.CreateTime) + new SharedWithdrawal( + x.Asset, + x.Address, + x.Quantity, + x.DepositStatus == Enums.WithdrawStatus.Success, + x.CreateTime, + GetWithdrawalStatus(x)) { Address = x.Address }) .ToArray(), nextPageRequest); } + private SharedTransferStatus GetWithdrawalStatus(DeepCoinWithdrawal x) + { + if (x.DepositStatus == WithdrawStatus.Rejected) + return SharedTransferStatus.Failed; + + if (x.DepositStatus == WithdrawStatus.Success) + return SharedTransferStatus.Completed; + + if (x.DepositStatus == WithdrawStatus.Auditing || x.DepositStatus == WithdrawStatus.Confirming) + return SharedTransferStatus.InProgress; + + return SharedTransferStatus.Unknown; + } #endregion #region Spot Ticker client From 9ce6c4fe604b6b72d5dd5267ea53fa678eff5047 Mon Sep 17 00:00:00 2001 From: Jkorf Date: Fri, 19 Jun 2026 13:46:38 +0200 Subject: [PATCH 10/12] wip --- .../DeepCoinRestClientExchangeApiShared.cs | 62 +++++++++---------- .../DeepCoinSocketClientExchangeApiShared.cs | 12 ++-- DeepCoin.Net/DeepCoinExchange.cs | 3 +- 3 files changed, 39 insertions(+), 38 deletions(-) diff --git a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiShared.cs b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiShared.cs index 203d6fb..79543d0 100644 --- a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiShared.cs +++ b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiShared.cs @@ -23,7 +23,7 @@ internal partial class DeepCoinRestClientExchangeApi : IDeepCoinRestClientExchan public void SetDefaultExchangeParameter(string key, object value) => ExchangeParameters.SetStaticParameter(Exchange, key, value); public void ResetDefaultExchangeParameters() => ExchangeParameters.ResetStaticParameters(); - public SharedClientInfo Discover() => SharedUtils.GetClientInfo(this); + public SharedClientInfo Discover() => SharedUtils.GetClientInfo(DeepCoinExchange.Metadata, this); #region Balance client GetBalancesOptions IBalanceRestClient.GetBalancesOptions { get; } = new GetBalancesOptions(_exchangeName, AccountTypeFilter.Spot, AccountTypeFilter.Futures); @@ -271,7 +271,7 @@ async Task> ISpotTickerRestClient.GetSpotTickerAsyn if (symbol == null) return HttpResult.Fail(result, new ServerError(new ErrorInfo(ErrorType.UnknownSymbol, "Symbol not found"))); - return HttpResult.Ok(result, new SharedSpotTicker(ExchangeSymbolCache.ParseSymbol(_topicSpotId, symbol.Symbol), symbol.Symbol, symbol.LastPrice, symbol.HighPrice, symbol.LowPrice, symbol.Volume, symbol.OpenPrice == null ? null : Math.Round((symbol.LastPrice ?? 0) / symbol.OpenPrice.Value * 100 - 100, 3)) + return HttpResult.Ok(result, new SharedSpotTicker(ExchangeSymbolCache.ParseSymbol(_topicSpotId, EnvironmentName, null, symbol.Symbol), symbol.Symbol, symbol.LastPrice, symbol.HighPrice, symbol.LowPrice, symbol.Volume, symbol.OpenPrice == null ? null : Math.Round((symbol.LastPrice ?? 0) / symbol.OpenPrice.Value * 100 - 100, 3)) { // Value is incorrect from the API // QuoteVolume = symbol.QuoteVolume @@ -289,7 +289,7 @@ async Task> ISpotTickerRestClient.GetSpotTickersA if (!result.Success) return HttpResult.Fail(result); - return HttpResult.Ok(result, result.Data.Select(x => new SharedSpotTicker(ExchangeSymbolCache.ParseSymbol(_topicSpotId, x.Symbol), x.Symbol, x.LastPrice, x.HighPrice, x.LowPrice, x.Volume, x.OpenPrice == null ? null : Math.Round((x.LastPrice ?? 0) / x.OpenPrice.Value * 100 - 100, 3)) + return HttpResult.Ok(result, result.Data.Select(x => new SharedSpotTicker(ExchangeSymbolCache.ParseSymbol(_topicSpotId, EnvironmentName, null, x.Symbol), x.Symbol, x.LastPrice, x.HighPrice, x.LowPrice, x.Volume, x.OpenPrice == null ? null : Math.Round((x.LastPrice ?? 0) / x.OpenPrice.Value * 100 - 100, 3)) { // Value is incorrect from the API // QuoteVolume = symbol.QuoteVolume @@ -313,7 +313,7 @@ async Task> IBookTickerRestClient.GetBookTickerAsyn return HttpResult.Fail(resultTicker); return HttpResult.Ok(resultTicker, new SharedBookTicker( - ExchangeSymbolCache.ParseSymbol(request.Symbol.TradingMode == TradingMode.Spot ? _topicSpotId : _topicFuturesId, symbol), + ExchangeSymbolCache.ParseSymbol(request.Symbol.TradingMode == TradingMode.Spot ? _topicSpotId : _topicFuturesId, EnvironmentName, null, symbol), symbol, resultTicker.Data.Asks[0].Price, resultTicker.Data.Asks[0].Quantity, @@ -345,20 +345,20 @@ async Task> ISpotSymbolRestClient.GetSpotSymbolsA }).ToArray()); var symbolInfo = response.Data!.Concat(response.Data!.Select(x => new SharedSpotSymbol(x.BaseAsset, x.QuoteAsset, x.BaseAsset + "/" + x.QuoteAsset, x.Trading))); - ExchangeSymbolCache.UpdateSymbolInfo(_topicSpotId, symbolInfo.ToArray()); + ExchangeSymbolCache.UpdateSymbolInfo(_topicSpotId, EnvironmentName, null, symbolInfo.ToArray()); return response; } async Task> ISpotSymbolRestClient.GetSpotSymbolsForBaseAssetAsync(string baseAsset) { - if (!ExchangeSymbolCache.HasCached(_topicSpotId)) + if (!ExchangeSymbolCache.HasCached(_topicSpotId, EnvironmentName, null)) { var symbols = await ((ISpotSymbolRestClient)this).GetSpotSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false); if (!symbols.Success) return ExchangeCallResult.Fail(Exchange, symbols.Error!); } - return ExchangeCallResult.Ok(Exchange, ExchangeSymbolCache.GetSymbolsForBaseAsset(_topicSpotId, baseAsset)); + return ExchangeCallResult.Ok(Exchange, ExchangeSymbolCache.GetSymbolsForBaseAsset(_topicSpotId, EnvironmentName, null, baseAsset)); } async Task> ISpotSymbolRestClient.SupportsSpotSymbolAsync(SharedSymbol symbol) @@ -366,26 +366,26 @@ async Task> ISpotSymbolRestClient.SupportsSpotSymbolAsy if (symbol.TradingMode != TradingMode.Spot) throw new ArgumentException(nameof(symbol), "Only Spot symbols allowed"); - if (!ExchangeSymbolCache.HasCached(_topicSpotId)) + if (!ExchangeSymbolCache.HasCached(_topicSpotId, EnvironmentName, null)) { var symbols = await ((ISpotSymbolRestClient)this).GetSpotSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false); if (!symbols.Success) return ExchangeCallResult.Fail(Exchange, symbols.Error!); } - return ExchangeCallResult.Ok(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicSpotId, symbol)); + return ExchangeCallResult.Ok(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicSpotId, EnvironmentName, null, symbol)); } async Task> ISpotSymbolRestClient.SupportsSpotSymbolAsync(string symbolName) { - if (!ExchangeSymbolCache.HasCached(_topicSpotId)) + if (!ExchangeSymbolCache.HasCached(_topicSpotId, EnvironmentName, null)) { var symbols = await ((ISpotSymbolRestClient)this).GetSpotSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false); if (!symbols.Success) return ExchangeCallResult.Fail(Exchange, symbols.Error!); } - return ExchangeCallResult.Ok(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicSpotId, symbolName)); + return ExchangeCallResult.Ok(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicSpotId, EnvironmentName, null, symbolName)); } #endregion @@ -444,7 +444,7 @@ async Task> ISpotOrderRestClient.GetSpotOrderAsync(G } return HttpResult.Ok(order, new SharedSpotOrder( - ExchangeSymbolCache.ParseSymbol(_topicSpotId, order.Data.Symbol), + ExchangeSymbolCache.ParseSymbol(_topicSpotId, EnvironmentName, null, order.Data.Symbol), order.Data.Symbol, order.Data.OrderId, ParseOrderType(order.Data.OrderType), @@ -484,7 +484,7 @@ async Task> ISpotOrderRestClient.GetOpenSpotOrders return HttpResult.Fail(orders); return HttpResult.Ok(orders, orders.Data.Select(x => new SharedSpotOrder( - ExchangeSymbolCache.ParseSymbol(_topicSpotId, x.Symbol), + ExchangeSymbolCache.ParseSymbol(_topicSpotId, EnvironmentName, null, x.Symbol), x.Symbol, x.OrderId, ParseOrderType(x.OrderType), @@ -536,7 +536,7 @@ async Task> ISpotOrderRestClient.GetClosedSpotOrde ExchangeHelpers.ApplyFilter(result.Data, x => x.CreateTime, request.StartTime, request.EndTime, direction) .Select(x => new SharedSpotOrder( - ExchangeSymbolCache.ParseSymbol(_topicSpotId, x.Symbol), + ExchangeSymbolCache.ParseSymbol(_topicSpotId, EnvironmentName, null, x.Symbol), x.Symbol, x.OrderId, ParseOrderType(x.OrderType), @@ -572,7 +572,7 @@ async Task> ISpotOrderRestClient.GetSpotOrderTrade return HttpResult.Fail(orders); return HttpResult.Ok(orders, orders.Data.Select(x => new SharedUserTrade( - ExchangeSymbolCache.ParseSymbol(_topicSpotId, x.Symbol), + ExchangeSymbolCache.ParseSymbol(_topicSpotId, EnvironmentName, null, x.Symbol), x.Symbol, x.OrderId.ToString(), x.TradeId.ToString(), @@ -624,7 +624,7 @@ async Task> ISpotOrderRestClient.GetSpotUserTrades ExchangeHelpers.ApplyFilter(result.Data, x => x.Timestamp, request.StartTime, request.EndTime, direction) .Select(x => new SharedUserTrade( - ExchangeSymbolCache.ParseSymbol(_topicSpotId, x.Symbol), + ExchangeSymbolCache.ParseSymbol(_topicSpotId, EnvironmentName, null, x.Symbol), x.Symbol, x.OrderId.ToString(), x.TradeId.ToString(), @@ -743,7 +743,7 @@ async Task> IFuturesTickerRestClient.GetFuturesT if (symbol == null) return HttpResult.Fail(resultTicker, new ServerError(new ErrorInfo(ErrorType.UnknownSymbol, "Symbol not found"))); - return HttpResult.Ok(resultTicker, new SharedFuturesTicker(ExchangeSymbolCache.ParseSymbol(_topicFuturesId, symbol.Symbol), symbol.Symbol, symbol.LastPrice, symbol.HighPrice, symbol.LowPrice, symbol.Volume, symbol.OpenPrice == null ? null : Math.Round((symbol.LastPrice ?? 0) / symbol.OpenPrice.Value * 100 - 100, 3))); + return HttpResult.Ok(resultTicker, new SharedFuturesTicker(ExchangeSymbolCache.ParseSymbol(_topicFuturesId, EnvironmentName, null, symbol.Symbol), symbol.Symbol, symbol.LastPrice, symbol.HighPrice, symbol.LowPrice, symbol.Volume, symbol.OpenPrice == null ? null : Math.Round((symbol.LastPrice ?? 0) / symbol.OpenPrice.Value * 100 - 100, 3))); } GetFuturesTickersOptions IFuturesTickerRestClient.GetFuturesTickersOptions { get; } = new GetFuturesTickersOptions(_exchangeName); @@ -757,7 +757,7 @@ async Task> IFuturesTickerRestClient.GetFuture if (!result.Success) return HttpResult.Fail(result); - return HttpResult.Ok(result, result.Data.Select(x => new SharedFuturesTicker(ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol), x.Symbol, x.LastPrice, x.HighPrice, x.LowPrice, x.Volume, x.OpenPrice == null ? null : Math.Round((x.LastPrice ?? 0) / x.OpenPrice.Value * 100 - 100, 3))).ToArray()); + return HttpResult.Ok(result, result.Data.Select(x => new SharedFuturesTicker(ExchangeSymbolCache.ParseSymbol(_topicFuturesId, EnvironmentName, null, x.Symbol), x.Symbol, x.LastPrice, x.HighPrice, x.LowPrice, x.Volume, x.OpenPrice == null ? null : Math.Round((x.LastPrice ?? 0) / x.OpenPrice.Value * 100 - 100, 3))).ToArray()); } #endregion @@ -791,19 +791,19 @@ async Task> IFuturesSymbolRestClient.GetFuture 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); + ExchangeSymbolCache.UpdateSymbolInfo(_topicFuturesId, EnvironmentName, null, symbolRegistrations); return response; } async Task> IFuturesSymbolRestClient.GetFuturesSymbolsForBaseAssetAsync(string baseAsset) { - if (!ExchangeSymbolCache.HasCached(_topicFuturesId)) + if (!ExchangeSymbolCache.HasCached(_topicFuturesId, EnvironmentName, null)) { var symbols = await ((IFuturesSymbolRestClient)this).GetFuturesSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false); if (!symbols.Success) return ExchangeCallResult.Fail(Exchange, symbols.Error!); } - return ExchangeCallResult.Ok(Exchange, ExchangeSymbolCache.GetSymbolsForBaseAsset(_topicFuturesId, baseAsset)); + return ExchangeCallResult.Ok(Exchange, ExchangeSymbolCache.GetSymbolsForBaseAsset(_topicFuturesId, EnvironmentName, null, baseAsset)); } async Task> IFuturesSymbolRestClient.SupportsFuturesSymbolAsync(SharedSymbol symbol) @@ -811,26 +811,26 @@ async Task> IFuturesSymbolRestClient.SupportsFuturesSym if (symbol.TradingMode == TradingMode.Spot) throw new ArgumentException(nameof(symbol), "Spot symbols not allowed"); - if (!ExchangeSymbolCache.HasCached(_topicFuturesId)) + if (!ExchangeSymbolCache.HasCached(_topicFuturesId, EnvironmentName, null)) { var symbols = await ((IFuturesSymbolRestClient)this).GetFuturesSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false); if (!symbols.Success) return ExchangeCallResult.Fail(Exchange, symbols.Error!); } - return ExchangeCallResult.Ok(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicFuturesId, symbol)); + return ExchangeCallResult.Ok(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicFuturesId, EnvironmentName, null, symbol)); } async Task> IFuturesSymbolRestClient.SupportsFuturesSymbolAsync(string symbolName) { - if (!ExchangeSymbolCache.HasCached(_topicFuturesId)) + if (!ExchangeSymbolCache.HasCached(_topicFuturesId, EnvironmentName, null)) { var symbols = await ((IFuturesSymbolRestClient)this).GetFuturesSymbolsAsync(new GetSymbolsRequest()).ConfigureAwait(false); if (!symbols.Success) return ExchangeCallResult.Fail(Exchange, symbols.Error!); } - return ExchangeCallResult.Ok(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicFuturesId, symbolName)); + return ExchangeCallResult.Ok(Exchange, ExchangeSymbolCache.SupportsSymbol(_topicFuturesId, EnvironmentName, null, symbolName)); } #endregion @@ -910,7 +910,7 @@ async Task> IFuturesOrderRestClient.GetFuturesOrd } return HttpResult.Ok(order, new SharedFuturesOrder( - ExchangeSymbolCache.ParseSymbol(_topicFuturesId, order.Data.Symbol), + ExchangeSymbolCache.ParseSymbol(_topicFuturesId, EnvironmentName, null, order.Data.Symbol), order.Data.Symbol, order.Data.OrderId, ParseOrderType(order.Data.OrderType), @@ -953,7 +953,7 @@ async Task> IFuturesOrderRestClient.GetOpenFutu return HttpResult.Fail(orders); return HttpResult.Ok(orders, orders.Data.Select(x => new SharedFuturesOrder( - ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol), + ExchangeSymbolCache.ParseSymbol(_topicFuturesId, EnvironmentName, null, x.Symbol), x.Symbol, x.OrderId, ParseOrderType(x.OrderType), @@ -1008,7 +1008,7 @@ async Task> IFuturesOrderRestClient.GetClosedFu return HttpResult.Ok(result, ExchangeHelpers.ApplyFilter(result.Data, x => x.CreateTime, request.StartTime, request.EndTime, direction).Select(x => new SharedFuturesOrder( - ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol), + ExchangeSymbolCache.ParseSymbol(_topicFuturesId, EnvironmentName, null, x.Symbol), x.Symbol, x.OrderId, ParseOrderType(x.OrderType), @@ -1049,7 +1049,7 @@ async Task> IFuturesOrderRestClient.GetFuturesOrde return HttpResult.Fail(orders); return HttpResult.Ok(orders, orders.Data.Select(x => new SharedUserTrade( - ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol), + ExchangeSymbolCache.ParseSymbol(_topicFuturesId, EnvironmentName, null, x.Symbol), x.Symbol, x.OrderId.ToString(), x.TradeId.ToString(), @@ -1100,7 +1100,7 @@ async Task> IFuturesOrderRestClient.GetFuturesUser ExchangeHelpers.ApplyFilter(result.Data, x => x.Timestamp, request.StartTime, request.EndTime, direction) .Select(x => new SharedUserTrade( - ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol), + ExchangeSymbolCache.ParseSymbol(_topicFuturesId, EnvironmentName, null, x.Symbol), x.Symbol, x.OrderId.ToString(), x.TradeId.ToString(), @@ -1146,7 +1146,7 @@ async Task> IFuturesOrderRestClient.GetPositionsAsy data = data.Where(x => request.TradingMode == TradingMode.PerpetualInverse ? x.Symbol.Contains("_USD_") : !x.Symbol.Contains("_USD_")); var resultTypes = request.Symbol == null && request.TradingMode == null ? SupportedTradingModes : request.Symbol != null ? new[] { request.Symbol.TradingMode } : new[] { request.TradingMode!.Value }; - return HttpResult.Ok(result, data.Select(x => new SharedPosition(ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol), x.Symbol, Math.Abs(x.Size), x.UpdateTime) + return HttpResult.Ok(result, data.Select(x => new SharedPosition(ExchangeSymbolCache.ParseSymbol(_topicFuturesId, EnvironmentName, null, x.Symbol), x.Symbol, Math.Abs(x.Size), x.UpdateTime) { LiquidationPrice = x.LiquidationPrice == 0 ? null : x.LiquidationPrice, Leverage = x.Leverage, diff --git a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApiShared.cs b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApiShared.cs index f98ce01..34cba27 100644 --- a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApiShared.cs +++ b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApiShared.cs @@ -22,7 +22,7 @@ internal partial class DeepCoinSocketClientExchangeApi : IDeepCoinSocketClientEx public void SetDefaultExchangeParameter(string key, object value) => ExchangeParameters.SetStaticParameter(Exchange, key, value); public void ResetDefaultExchangeParameters() => ExchangeParameters.ResetStaticParameters(); - public SharedClientInfo Discover() => SharedUtils.GetClientInfo(this); + public SharedClientInfo Discover() => SharedUtils.GetClientInfo(DeepCoinExchange.Metadata, this); #region Kline client SubscribeKlineOptions IKlineSocketClient.SubscribeKlineOptions { get; } = new SubscribeKlineOptions(_exchangeName, false, SharedKlineInterval.OneMinute); @@ -51,7 +51,7 @@ async Task> ITickerSocketClient.SubscribeToT return WebSocketResult.Fail(_exchangeName, validationError); var symbol = request.Symbol!.GetSymbol(DeepCoinExchange.FormatSymbol); - var result = await SubscribeToSymbolUpdatesAsync(symbol, update => handler(update.ToType(new SharedSpotTicker(ExchangeSymbolCache.ParseSymbol(_topicSpotId, symbol) ?? ExchangeSymbolCache.ParseSymbol(_topicFuturesId, symbol), symbol, update.Data.LastPrice, update.Data.HighPrice, update.Data.LowPrice, update.Data.Volume, update.Data.OpenPrice == null ? null : Math.Round((update.Data.LastPrice ?? 0) / update.Data.OpenPrice.Value * 100 - 100, 3)) + var result = await SubscribeToSymbolUpdatesAsync(symbol, update => handler(update.ToType(new SharedSpotTicker(ExchangeSymbolCache.ParseSymbol(_topicSpotId, EnvironmentName, null, symbol) ?? ExchangeSymbolCache.ParseSymbol(_topicFuturesId, EnvironmentName, null, symbol), symbol, update.Data.LastPrice, update.Data.HighPrice, update.Data.LowPrice, update.Data.Volume, update.Data.OpenPrice == null ? null : Math.Round((update.Data.LastPrice ?? 0) / update.Data.OpenPrice.Value * 100 - 100, 3)) { // Value is incorrect for spot symbols QuoteVolume = request.Symbol.TradingMode == TradingMode.Spot ? null : update.Data.Turnover @@ -118,7 +118,7 @@ async Task> IFuturesOrderSocketClient.Subscr handler(update.ToType(futuresOrders.Select(x => new SharedFuturesOrder( - ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol), + ExchangeSymbolCache.ParseSymbol(_topicFuturesId, EnvironmentName, null, x.Symbol), x.Symbol, x.OrderId, ParseOrderType(x.OrderType), @@ -161,7 +161,7 @@ async Task> ISpotOrderSocketClient.Subscribe handler(update.ToType(spotOrders.Select(x => new SharedSpotOrder( - ExchangeSymbolCache.ParseSymbol(_topicSpotId, x.Symbol), + ExchangeSymbolCache.ParseSymbol(_topicSpotId, EnvironmentName, null, x.Symbol), x.Symbol, x.OrderId, ParseOrderType(x.OrderType), @@ -194,7 +194,7 @@ async Task> IPositionSocketClient.SubscribeT return WebSocketResult.Fail(_exchangeName, validationError); var result = await SubscribeToUserDataUpdatesAsync( - onPositionMessage: update => handler(update.ToType(update.Data.Select(x => new SharedPosition(ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol), x.Symbol, x.PositionSize, x.UpdateTime) + onPositionMessage: update => handler(update.ToType(update.Data.Select(x => new SharedPosition(ExchangeSymbolCache.ParseSymbol(_topicFuturesId, EnvironmentName, null, x.Symbol), x.Symbol, x.PositionSize, x.UpdateTime) { AverageOpenPrice = x.OpenPrice, PositionMode = SharedPositionMode.HedgeMode, @@ -220,7 +220,7 @@ async Task> IUserTradeSocketClient.Subscribe var result = await SubscribeToUserDataUpdatesAsync( onUserTradeMessage: update => handler(update.ToType(update.Data.Select(x => new SharedUserTrade( - ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol) ?? ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol), + ExchangeSymbolCache.ParseSymbol(_topicFuturesId, EnvironmentName, null, x.Symbol) ?? ExchangeSymbolCache.ParseSymbol(_topicFuturesId, EnvironmentName, null, x.Symbol), x.Symbol, x.OrderId.ToString(), x.TradeId.ToString(), diff --git a/DeepCoin.Net/DeepCoinExchange.cs b/DeepCoin.Net/DeepCoinExchange.cs index 195801f..83d6779 100644 --- a/DeepCoin.Net/DeepCoinExchange.cs +++ b/DeepCoin.Net/DeepCoinExchange.cs @@ -24,7 +24,8 @@ public static class DeepCoinExchange "https://www.deepcoin.com/", ["https://www.deepcoin.com/docs/authentication"], PlatformType.CryptoCurrencyExchange, - CentralizationType.Centralized + CentralizationType.Centralized, + DeepCoinEnvironment.All ); /// From aab612e6efbd67cc7e641c01283b8634427905dc Mon Sep 17 00:00:00 2001 From: Jkorf Date: Mon, 22 Jun 2026 16:37:37 +0200 Subject: [PATCH 11/12] wip --- .../ExchangeApi/DeepCoinRestClientExchangeApiShared.cs | 7 ++++++- .../Clients/ExchangeApi/DeepCoinSocketClientExchangeApi.cs | 6 +----- .../ExchangeApi/DeepCoinSocketClientExchangeApiShared.cs | 7 ++++++- DeepCoin.Net/Converters/DeepCoinSourceGenerationContext.cs | 3 +++ 4 files changed, 16 insertions(+), 7 deletions(-) diff --git a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiShared.cs b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiShared.cs index 79543d0..ad91eed 100644 --- a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiShared.cs +++ b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinRestClientExchangeApiShared.cs @@ -40,7 +40,12 @@ async Task> IBalanceRestClient.GetBalancesAsync(GetB if (!result.Success) return HttpResult.Fail(result); - return HttpResult.Ok(result, result.Data.Select(x => new SharedBalance(x.Asset, x.AvailableBalance, x.Balance)).ToArray()); + return HttpResult.Ok(result, result.Data.Select(x => + new SharedBalance( + SupportedTradingModes, + x.Asset, + x.AvailableBalance, + x.Balance)).ToArray()); } #endregion diff --git a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApi.cs b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApi.cs index 889b0ae..d898da3 100644 --- a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApi.cs +++ b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApi.cs @@ -263,11 +263,7 @@ public async Task> SubscribeToUserDataUpdate { TokenLease = lease }; - var result = await SubscribeAsync(BaseAddress.AppendPath("v1/private?listenKey=" + lk), subscription, ct).ConfigureAwait(false); - if (!result.Success && lease != null) - await lease.ReleaseAsync().ConfigureAwait(false); - - return result; + return await SubscribeAsync(BaseAddress.AppendPath("v1/private?listenKey=" + lk), subscription, ct).ConfigureAwait(false); } /// diff --git a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApiShared.cs b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApiShared.cs index 34cba27..732ac7c 100644 --- a/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApiShared.cs +++ b/DeepCoin.Net/Clients/ExchangeApi/DeepCoinSocketClientExchangeApiShared.cs @@ -92,7 +92,12 @@ async Task> IBalanceSocketClient.SubscribeTo return WebSocketResult.Fail(_exchangeName, validationError); var result = await SubscribeToUserDataUpdatesAsync( - onBalanceMessage: update => handler(update.ToType(update.Data.Select(x => new SharedBalance(x.Asset, x.Available, x.Balance)).ToArray())), + onBalanceMessage: update => handler(update.ToType(update.Data.Select(x => + new SharedBalance( + SupportedTradingModes, + x.Asset, + x.Available, + x.Balance)).ToArray())), ct: ct).ConfigureAwait(false); return result; diff --git a/DeepCoin.Net/Converters/DeepCoinSourceGenerationContext.cs b/DeepCoin.Net/Converters/DeepCoinSourceGenerationContext.cs index 9929005..5e74af2 100644 --- a/DeepCoin.Net/Converters/DeepCoinSourceGenerationContext.cs +++ b/DeepCoin.Net/Converters/DeepCoinSourceGenerationContext.cs @@ -1,3 +1,4 @@ +using CryptoExchange.Net.Objects; using DeepCoin.Net.Objects.Internal; using DeepCoin.Net.Objects.Models; using System; @@ -77,6 +78,8 @@ namespace DeepCoin.Net.Converters [JsonSerializable(typeof(decimal))] [JsonSerializable(typeof(DateTime))] [JsonSerializable(typeof(DateTime?))] + [JsonSerializable(typeof(Parameters))] + [JsonSerializable(typeof(Parameters[]))] internal partial class DeepCoinSourceGenerationContext : JsonSerializerContext { } From 05795751054bc6324b1752cb167c08bbcd53703e Mon Sep 17 00:00:00 2001 From: Jkorf Date: Mon, 29 Jun 2026 11:52:50 +0200 Subject: [PATCH 12/12] CryptoExchange.Net ref --- DeepCoin.Net/DeepCoin.Net.csproj | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/DeepCoin.Net/DeepCoin.Net.csproj b/DeepCoin.Net/DeepCoin.Net.csproj index e4ed427..7ec21d6 100644 --- a/DeepCoin.Net/DeepCoin.Net.csproj +++ b/DeepCoin.Net/DeepCoin.Net.csproj @@ -53,12 +53,10 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + all runtime; build; native; contentfiles; analyzers; buildtransitive - - - \ No newline at end of file