diff --git a/AGENTS.md b/AGENTS.md index e34f3c0..a0e1c95 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -7,6 +7,8 @@ applyTo: "**/*" Bitstamp.Net is a CryptoExchange.Net-based client for the Bitstamp REST and websocket APIs. Use this guide when generating code or documentation for this repository. +For multi-exchange code, use `CryptoExchange.Net.SharedApis` through the `.SharedClient` properties on the `ExchangeApi` surfaces. Use `.SharedClient.Discover()` to inspect supported shared features at runtime. + ## Package And Client Shape - NuGet package id: `Bitstamp.Net` @@ -159,7 +161,7 @@ Always check `subscription.Success` before using `subscription.Data`. Unsubscrib ## Result Handling -REST calls return `WebCallResult` and socket subscriptions return `CallResult`. +REST calls return `HttpResult` and socket subscriptions return `WebSocketResult`. Shared non-I/O symbol/cache helpers return `ExchangeCallResult`. ```csharp var result = await client.ExchangeApi.ExchangeData.GetTickerAsync("ETH/USD"); diff --git a/Bitstamp.Net.UnitTests/BitstampRestClientTests.cs b/Bitstamp.Net.UnitTests/BitstampRestClientTests.cs index 087ca6f..2668f44 100644 --- a/Bitstamp.Net.UnitTests/BitstampRestClientTests.cs +++ b/Bitstamp.Net.UnitTests/BitstampRestClientTests.cs @@ -6,6 +6,8 @@ using System.Net.Http; using Bitstamp.Net; using Bitstamp.Net.Clients; +using System.Data.Common; +using CryptoExchange.Net.Objects; namespace Bitstamp.Net.UnitTests { @@ -28,12 +30,11 @@ public void CheckSignatureExample1() return headers["X-Auth-Signature"].ToString(); }, "6DCA01A0817446CBC44CD792EF743262A7342A15C2DF805BC89D30DBE099EEFA", - new Dictionary + new Parameters(BitstampExchange._parameterSerializationSettings) { { "side", 0 }, }, DateTimeConverter.ParseFromDouble(1499827319559), - true, false); } diff --git a/Bitstamp.Net.UnitTests/RestRequestTests.cs b/Bitstamp.Net.UnitTests/RestRequestTests.cs index 2a6fc11..6879bb2 100644 --- a/Bitstamp.Net.UnitTests/RestRequestTests.cs +++ b/Bitstamp.Net.UnitTests/RestRequestTests.cs @@ -70,7 +70,7 @@ public async Task ValidateTradingCalls() await tester.ValidateAsync(client => client.ExchangeApi.Trading.GetOrderHistoryAsync(OrderSource.Orderbook, "123"), "GetOrderHistory", ignoreProperties: ["id_str", "datetime", "amount_str", "price_str"]); } - private bool IsAuthenticated(WebCallResult result) + private bool IsAuthenticated(IHttpResult result) { return result.RequestHeaders.SingleOrDefault(x => x.Key == "X-Auth-Signature").Key != null; } diff --git a/Bitstamp.Net.UnitTests/SocketSubscriptionTests.cs b/Bitstamp.Net.UnitTests/SocketSubscriptionTests.cs index 3c2e164..c32906b 100644 --- a/Bitstamp.Net.UnitTests/SocketSubscriptionTests.cs +++ b/Bitstamp.Net.UnitTests/SocketSubscriptionTests.cs @@ -27,7 +27,7 @@ public async Task ValidateConcurrentSubscriptions() }), logger); - var tester = new SocketSubscriptionValidator(client, "Subscriptions/ExchangeApi", "wss://ws.bitstamp.com"); + var tester = new SocketSubscriptionValidator(client, "Subscriptions/ExchangeApi", "wss://ws.bitstamp.net"); await tester.ValidateConcurrentAsync( (client, handler) => client.ExchangeApi.SubscribeToTradeUpdatesAsync("ETH/USD", handler), (client, handler) => client.ExchangeApi.SubscribeToTradeUpdatesAsync("BTC/USD", handler), diff --git a/Bitstamp.Net/Bitstamp.Net.csproj b/Bitstamp.Net/Bitstamp.Net.csproj index f6762e5..a3ef9a4 100644 --- a/Bitstamp.Net/Bitstamp.Net.csproj +++ b/Bitstamp.Net/Bitstamp.Net.csproj @@ -54,7 +54,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/Bitstamp.Net/BitstampAuthenticationProvider.cs b/Bitstamp.Net/BitstampAuthenticationProvider.cs index 9d00697..7185d47 100644 --- a/Bitstamp.Net/BitstampAuthenticationProvider.cs +++ b/Bitstamp.Net/BitstampAuthenticationProvider.cs @@ -23,7 +23,7 @@ public BitstampAuthenticationProvider(BitstampCredentials credentials, string? n #region Methods public override void ProcessRequest(RestApiClient apiClient, RestRequestConfiguration requestConfig) { - if (!requestConfig.Authenticated) + if (!requestConfig.RequestDefinition.Authenticated) return; var key = "BITSTAMP " + Credential.Key; @@ -31,7 +31,7 @@ public override void ProcessRequest(RestApiClient apiClient, RestRequestConfigur var timestamp = GetMillisecondTimestamp(apiClient); string bodyContent = ""; - if (requestConfig.BodyParameters?.Any() == true) + if (requestConfig.BodyParameters != null && !requestConfig.BodyParameters.Empty) { if (requestConfig.BodyFormat is RequestBodyFormat.Json) bodyContent = _serializer.Serialize(requestConfig.BodyParameters); @@ -56,13 +56,13 @@ public override void ProcessRequest(RestApiClient apiClient, RestRequestConfigur contentType = Constants.FormContentHeader; } - var pathAndQuery = requestConfig.Path + requestConfig.GetQueryString(true); + var pathAndQuery = requestConfig.RequestDefinition.Path + requestConfig.GetQueryString(true); if (!pathAndQuery.EndsWith("/")) pathAndQuery += "/"; var signatureText = key + - requestConfig.Method + - new Uri(requestConfig.BaseAddress).Host + + requestConfig.RequestDefinition.Method + + new Uri(requestConfig.RequestDefinition.BaseAddress).Host + pathAndQuery + contentType + nonce + diff --git a/Bitstamp.Net/BitstampExchange.cs b/Bitstamp.Net/BitstampExchange.cs index ede6d02..529cbfc 100644 --- a/Bitstamp.Net/BitstampExchange.cs +++ b/Bitstamp.Net/BitstampExchange.cs @@ -16,6 +16,12 @@ namespace Bitstamp.Net public static class BitstampExchange { internal static JsonSerializerContext _serializerContext = JsonSerializerContextCache.GetOrCreate(); + internal static ParameterSerializationSettings _parameterSerializationSettings = new ParameterSerializationSettings + { + Decimal = DecimalSerialization.Number, + Bool = BoolSerialization.String, + DateTimes = DateTimeSerialization.MillisecondsNumber + }; /// /// Platform metadata @@ -27,7 +33,8 @@ public static class BitstampExchange "https://www.bitstamp.com", ["https://www.bitstamp.net/api/"], PlatformType.CryptoCurrencyExchange, - CentralizationType.Centralized + CentralizationType.Centralized, + BitstampEnvironment.All ); /// @@ -99,7 +106,7 @@ public static string FormatSymbol(string baseAsset, string quoteAsset, TradingMo /// /// Rate limiter configuration for the Bitstamp API /// - public static BitstampRateLimiters RateLimiter { get; } = new BitstampRateLimiters(); + public static BitstampRateLimiters RateLimiter { get; set; } = new BitstampRateLimiters(); } /// @@ -118,13 +125,19 @@ public class BitstampRateLimiters 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 BitstampRateLimiters() + /// + /// ctor + /// + public BitstampRateLimiters() #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() { Rest = new RateLimitGate("Rest") .AddGuard(new RateLimitGuard(RateLimitGuard.PerHost, new PathStartFilter("api/"), 400, TimeSpan.FromSeconds(1), RateLimitWindowType.Fixed)) diff --git a/Bitstamp.Net/BitstampUserDataTracker.cs b/Bitstamp.Net/BitstampUserDataTracker.cs index df39146..1118f6e 100644 --- a/Bitstamp.Net/BitstampUserDataTracker.cs +++ b/Bitstamp.Net/BitstampUserDataTracker.cs @@ -18,7 +18,6 @@ public BitstampUserSpotDataTracker( SpotUserDataTrackerConfig? config) : base( logger, restClient.ExchangeApi.SharedClient, - null, restClient.ExchangeApi.SharedClient, null, restClient.ExchangeApi.SharedClient, @@ -45,7 +44,6 @@ public BitstampUserFuturesDataTracker( string? userIdentifier, FuturesUserDataTrackerConfig? config) : base(logger, restClient.ExchangeApi.SharedClient, - null, restClient.ExchangeApi.SharedClient, null, restClient.ExchangeApi.SharedClient, diff --git a/Bitstamp.Net/Clients/BitstampRestClient.cs b/Bitstamp.Net/Clients/BitstampRestClient.cs index a56c71c..a6dec40 100644 --- a/Bitstamp.Net/Clients/BitstampRestClient.cs +++ b/Bitstamp.Net/Clients/BitstampRestClient.cs @@ -37,7 +37,7 @@ public BitstampRestClient(HttpClient? httpClient, ILoggerFactory? loggerFactory, { Initialize(options.Value); - ExchangeApi = AddApiClient(new BitstampRestClientExchangeApi(_logger, httpClient, options.Value)); + ExchangeApi = AddApiClient(new BitstampRestClientExchangeApi(loggerFactory, httpClient, options.Value)); } #endregion diff --git a/Bitstamp.Net/Clients/BitstampSocketClient.cs b/Bitstamp.Net/Clients/BitstampSocketClient.cs index 7f79e9a..a45198b 100644 --- a/Bitstamp.Net/Clients/BitstampSocketClient.cs +++ b/Bitstamp.Net/Clients/BitstampSocketClient.cs @@ -47,7 +47,7 @@ public BitstampSocketClient(IOptions options, ILoggerFact }))); _keyGenerator = new BitstampSocketKeyGenerator(_restClient); - ExchangeApi = AddApiClient(new BitstampSocketClientExchangeApi(_logger, options.Value, _keyGenerator)); + ExchangeApi = AddApiClient(new BitstampSocketClientExchangeApi(loggerFactory, options.Value, _keyGenerator)); } #endregion diff --git a/Bitstamp.Net/Clients/BitstampUserClientProvider.cs b/Bitstamp.Net/Clients/BitstampUserClientProvider.cs index ee94d54..f7b9a59 100644 --- a/Bitstamp.Net/Clients/BitstampUserClientProvider.cs +++ b/Bitstamp.Net/Clients/BitstampUserClientProvider.cs @@ -2,24 +2,24 @@ using Bitstamp.Net.Interfaces.Clients; using Bitstamp.Net.Objects.Options; using CryptoExchange.Net.Authentication; +using CryptoExchange.Net.Clients; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; namespace Bitstamp.Net.Clients { /// - public class BitstampUserClientProvider : IBitstampUserClientProvider + public class BitstampUserClientProvider : UserClientProvider< + IBitstampRestClient, + IBitstampSocketClient, + BitstampRestOptions, + BitstampSocketOptions, + BitstampCredentials, + BitstampEnvironment + >, IBitstampUserClientProvider { - 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 => BitstampExchange.ExchangeName; + public override string ExchangeName => BitstampExchange.ExchangeName; /// /// ctor @@ -38,97 +38,15 @@ public BitstampUserClientProvider( 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, BitstampCredentials credentials, BitstampEnvironment? 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 IBitstampRestClient ConstructRestClient(HttpClient client, ILoggerFactory? loggerFactory, IOptions options) + => new BitstampRestClient(client, loggerFactory, options); /// - public IBitstampRestClient GetRestClient(string userIdentifier, BitstampCredentials? credentials = null, BitstampEnvironment? environment = null) - { - if (!_restClients.TryGetValue(userIdentifier, out var client) || client.Disposed) - client = CreateRestClient(userIdentifier, credentials, environment); - - return client; - } - - /// - public IBitstampSocketClient GetSocketClient(string userIdentifier, BitstampCredentials? credentials = null, BitstampEnvironment? environment = null) - { - if (!_socketClients.TryGetValue(userIdentifier, out var client) || client.Disposed) - client = CreateSocketClient(userIdentifier, credentials, environment); - - return client; - } - - private IBitstampRestClient CreateRestClient(string userIdentifier, BitstampCredentials? credentials, BitstampEnvironment? environment) - { - var clientRestOptions = SetRestEnvironment(environment); - var client = new BitstampRestClient(_httpClient, _loggerFactory, clientRestOptions); - if (credentials != null) - { - client.SetApiCredentials(credentials); - _restClients[userIdentifier] = client; - } - return client; - } - - private IBitstampSocketClient CreateSocketClient(string userIdentifier, BitstampCredentials? credentials, BitstampEnvironment? environment) - { - var clientSocketOptions = SetSocketEnvironment(environment); - var client = new BitstampSocketClient(clientSocketOptions!, _loggerFactory); - if (credentials != null) - { - client.SetApiCredentials(credentials); - _socketClients[userIdentifier] = client; - } - return client; - } - - private IOptions SetRestEnvironment(BitstampEnvironment? environment) - { - if (environment == null) - return _restOptions; - - var newRestClientOptions = new BitstampRestOptions(); - var restOptions = _restOptions.Value.Set(newRestClientOptions); - newRestClientOptions.Environment = environment; - return Options.Create(newRestClientOptions); - } - - private IOptions SetSocketEnvironment(BitstampEnvironment? environment) - { - if (environment == null) - return _socketOptions; - - var newSocketClientOptions = new BitstampSocketOptions(); - 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 IBitstampSocketClient ConstructSocketClient(ILoggerFactory? loggerFactory, IOptions options) + => new BitstampSocketClient(options, loggerFactory); } } diff --git a/Bitstamp.Net/Clients/ExchangeApi/BitstampRestClientExchangeApi.cs b/Bitstamp.Net/Clients/ExchangeApi/BitstampRestClientExchangeApi.cs index d4b3f87..edbd88a 100644 --- a/Bitstamp.Net/Clients/ExchangeApi/BitstampRestClientExchangeApi.cs +++ b/Bitstamp.Net/Clients/ExchangeApi/BitstampRestClientExchangeApi.cs @@ -36,8 +36,8 @@ internal partial class BitstampRestClientExchangeApi : RestApiClient public IBitstampRestClientExchangeApiTrading Trading { get; } - public BitstampRestClientExchangeApi(ILogger logger, HttpClient? httpClient, BitstampRestOptions options) : - base(logger, httpClient, options.Environment.RestBaseAddress, options, options.ApiOptions) + public BitstampRestClientExchangeApi(ILoggerFactory? loggerFactory, HttpClient? httpClient, BitstampRestOptions options) : + base(loggerFactory, BitstampExchange.Metadata.Id, httpClient, options.Environment.RestBaseAddress, options, options.ApiOptions) { RequestBodyFormat = RequestBodyFormat.FormData; RequestBodyEmptyContent = ""; @@ -62,21 +62,14 @@ public override string FormatSymbol(string baseAsset, string quoteAsset, Trading /// public IBitstampRestClientExchangeApiShared SharedClient => this; - 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) where T : class { - return await base.SendAsync(baseAddress, definition, parameters, cancellationToken, null, weight).ConfigureAwait(false); + return await base.SendAsync(definition, parameters, cancellationToken, null, weight).ConfigureAwait(false); } - - 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) { - return await base.SendAsync(baseAddress, definition, parameters, cancellationToken, null, weight).ConfigureAwait(false); + return await base.SendAsync(definition, parameters, cancellationToken, null, weight).ConfigureAwait(false); } } } diff --git a/Bitstamp.Net/Clients/ExchangeApi/BitstampRestClientExchangeApiAccount.cs b/Bitstamp.Net/Clients/ExchangeApi/BitstampRestClientExchangeApiAccount.cs index 09e8766..48f8b67 100644 --- a/Bitstamp.Net/Clients/ExchangeApi/BitstampRestClientExchangeApiAccount.cs +++ b/Bitstamp.Net/Clients/ExchangeApi/BitstampRestClientExchangeApiAccount.cs @@ -19,9 +19,9 @@ internal BitstampRestClientExchangeApiAccount(BitstampRestClientExchangeApi base #region Get All Fees /// - public Task> GetAllFeesAsync(CancellationToken cancellationToken = default) + public Task> GetAllFeesAsync(CancellationToken cancellationToken = default) { - var request = _definitions.GetOrCreate(HttpMethod.Post, "/api/v2/fees/trading/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, "/api/v2/fees/trading/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); return _baseClient.SendAsync(request, null, cancellationToken); } @@ -30,9 +30,9 @@ public Task> GetAllFeesAsync(CancellationTo #region Get Fees /// - public Task> GetFeesAsync(string symbol, CancellationToken cancellationToken = default) + public Task> GetFeesAsync(string symbol, CancellationToken cancellationToken = default) { - var request = _definitions.GetOrCreate(HttpMethod.Post, $"/api/v2/fees/trading/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, $"/api/v2/fees/trading/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); return _baseClient.SendAsync(request, null, cancellationToken); } @@ -41,9 +41,9 @@ public Task> GetFeesAsync(string symbol, Canc #region Get Withdraw Fees /// - public Task> GetWithdrawFeesAsync(CancellationToken cancellationToken = default) + public Task> GetWithdrawFeesAsync(CancellationToken cancellationToken = default) { - var request = _definitions.GetOrCreate(HttpMethod.Post, $"/api/v2/fees/withdrawal/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, $"/api/v2/fees/withdrawal/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); return _baseClient.SendAsync(request, null, cancellationToken); } @@ -52,11 +52,11 @@ public Task> GetWithdrawFeesAsync(Cancellat #region Get Withdraw Fees /// - public Task> GetWithdrawFeesAsync(string asset, string? network = null, CancellationToken cancellationToken = default) + public Task> GetWithdrawFeesAsync(string asset, string? network = null, CancellationToken cancellationToken = default) { - var parameters = new ParameterCollection(); - parameters.AddOptional("network", network); - var request = _definitions.GetOrCreate(HttpMethod.Post, $"/api/v2/fees/withdrawal/{asset}/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + parameters.Add("network", network); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, $"/api/v2/fees/withdrawal/{asset}/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); return _baseClient.SendAsync(request, parameters, cancellationToken); } @@ -65,9 +65,9 @@ public Task> GetWithdrawFeesAsync(string asse #region Generate Websocket Auth Token /// - internal Task> GenerateWebsocketAuthTokenAsync(CancellationToken ct = default) + internal Task> GenerateWebsocketAuthTokenAsync(CancellationToken ct = default) { - var request = _definitions.GetOrCreate(HttpMethod.Post, "/api/v2/websockets_token/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, "/api/v2/websockets_token/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); return _baseClient.SendAsync(request, null, ct); } @@ -76,9 +76,9 @@ internal Task> GenerateWebsocketAuthToken #region Get Account Balances /// - public Task> GetAccountBalancesAsync(CancellationToken ct = default) + public Task> GetAccountBalancesAsync(CancellationToken ct = default) { - var request = _definitions.GetOrCreate(HttpMethod.Post, "/api/v2/account_balances/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, "/api/v2/account_balances/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); return _baseClient.SendAsync(request, null, ct); } @@ -87,9 +87,9 @@ public Task> GetAccountBalancesAsync(Can #region Get Account Balance /// - public Task> GetAccountBalanceAsync(string asset, CancellationToken ct = default) + public Task> GetAccountBalanceAsync(string asset, CancellationToken ct = default) { - var request = _definitions.GetOrCreate(HttpMethod.Post, $"/api/v2/account_balances/{asset.ToLower()}/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, $"/api/v2/account_balances/{asset.ToLower()}/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); return _baseClient.SendAsync(request, null, ct); } @@ -98,17 +98,17 @@ public Task> GetAccountBalanceAsync(string #region Get User Transactions /// - public Task> GetUserTransactionsAsync(SortOrder? sort = null, long? sinceId = null, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, int? offset = null, CancellationToken ct = default) + public Task> GetUserTransactionsAsync(SortOrder? sort = null, long? sinceId = null, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, int? offset = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddOptional("offset", offset); - parameters.AddOptional("limit", limit); - parameters.AddOptionalEnum("sort", sort); - parameters.AddOptionalSeconds("since_timestamp", startTime); - parameters.AddOptionalSeconds("until_timestamp", endTime); - parameters.AddOptional("since_id", sinceId); - - var request = _definitions.GetOrCreate(HttpMethod.Post, "/api/v2/user_transactions/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + parameters.Add("offset", offset); + parameters.Add("limit", limit); + parameters.Add("sort", sort); + parameters.Add("since_timestamp", startTime, DateTimeSerialization.SecondsNumber); + parameters.Add("until_timestamp", endTime, DateTimeSerialization.SecondsNumber); + parameters.Add("since_id", sinceId); + + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, "/api/v2/user_transactions/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); return _baseClient.SendAsync(request, parameters, ct); } @@ -117,17 +117,17 @@ public Task> GetUserTransactionsAsync(S #region Get User Transactions /// - public Task> GetUserTransactionsAsync(string symbol, SortOrder? sort = null, long? sinceId = null, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, int? offset = null, CancellationToken ct = default) + public Task> GetUserTransactionsAsync(string symbol, SortOrder? sort = null, long? sinceId = null, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, int? offset = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddOptional("offset", offset); - parameters.AddOptional("limit", limit); - parameters.AddOptionalEnum("sort", sort); - parameters.AddOptionalSeconds("since_timestamp", startTime); - parameters.AddOptionalSeconds("until_timestamp", endTime); - parameters.AddOptional("since_id", sinceId); - - var request = _definitions.GetOrCreate(HttpMethod.Post, $"/api/v2/user_transactions/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + parameters.Add("offset", offset); + parameters.Add("limit", limit); + parameters.Add("sort", sort); + parameters.Add("since_timestamp", startTime, DateTimeSerialization.SecondsNumber); + parameters.Add("until_timestamp", endTime, DateTimeSerialization.SecondsNumber); + parameters.Add("since_id", sinceId); + + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, $"/api/v2/user_transactions/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); return _baseClient.SendAsync(request, parameters, ct); } @@ -136,9 +136,9 @@ public Task> GetUserTransactionsAsync(s #region Get Symbols /// - public Task> GetSymbolsAsync(CancellationToken ct = default) + public Task> GetSymbolsAsync(CancellationToken ct = default) { - var request = _definitions.GetOrCreate(HttpMethod.Post, $"/api/v2/my_markets/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, $"/api/v2/my_markets/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); return _baseClient.SendAsync(request, null, ct); } @@ -147,7 +147,7 @@ public Task> GetSymbolsAsync(Cancellation #region Get Max Trade Quantity /// - public Task> GetMaxTradeQuantityAsync( + public Task> GetMaxTradeQuantityAsync( string symbol, MarginMode marginMode, decimal leverage, @@ -160,20 +160,20 @@ public Task> GetMaxTradeQuantityAsync( Dictionary? additionalCollateral = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); parameters.Add("market", BitstampExchange.SymbolToPathParameter(symbol)); - parameters.AddEnum("margin_mode", marginMode); + parameters.Add("margin_mode", marginMode); parameters.Add("leverage", leverage); - parameters.AddEnum("order_type", orderType); - parameters.AddEnum("side", side); - parameters.AddOptional("price", price); - parameters.AddOptional("stop_price", price); - parameters.AddOptional("activation_price", price); - parameters.AddOptional("trailing_delta", price); - parameters.AddOptional("activation_price", price); - parameters.AddOptional("additional_collateral", additionalCollateral); - - var request = _definitions.GetOrCreate(HttpMethod.Post, $"/api/v2/get_max_order_amount/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + parameters.Add("order_type", orderType); + parameters.Add("side", side); + parameters.Add("price", price); + parameters.Add("stop_price", price); + parameters.Add("activation_price", price); + parameters.Add("trailing_delta", price); + parameters.Add("activation_price", price); + parameters.AddRaw("additional_collateral", additionalCollateral); + + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, $"/api/v2/get_max_order_amount/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); return _baseClient.SendAsync(request, parameters, ct); } @@ -182,14 +182,14 @@ public Task> GetMaxTradeQuantityAsync( #region Get Withdraws /// - public async Task> GetWithdrawalsAsync(string? id = null, long? maxAge = null, int? limit = null, int? offset = null, CancellationToken ct = default) + public async Task> GetWithdrawalsAsync(string? id = null, long? maxAge = null, int? limit = null, int? offset = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddOptional("id", id); - parameters.AddOptional("timedelta", maxAge); - parameters.AddOptional("limit", limit); - parameters.AddOptional("offset", offset); - var request = _definitions.GetOrCreate(HttpMethod.Post, "/api/v2/withdrawal-requests/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + parameters.Add("id", id); + parameters.Add("timedelta", maxAge); + parameters.Add("limit", limit); + parameters.Add("offset", offset); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, "/api/v2/withdrawal-requests/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -199,7 +199,7 @@ public async Task> GetWithdrawalsAsync(strin #region Withdraw Fiat /// - public async Task> WithdrawFiatAsync( + public async Task> WithdrawFiatAsync( decimal quantity, string asset, string name, @@ -220,7 +220,7 @@ public async Task> WithdrawFiatAsync( string? intermediateBankRouting = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); parameters.Add("quantity", quantity); parameters.Add("account_currency", asset); parameters.Add("name", name); @@ -230,16 +230,16 @@ public async Task> WithdrawFiatAsync( parameters.Add("postal_code", postalCode); parameters.Add("city", city); parameters.Add("country", country); - parameters.AddEnum("type", type); - parameters.AddOptional("bank_name", bankName); - parameters.AddOptional("bank_address", bankAddress); - parameters.AddOptional("bank_postal_code", bankPostalCode); - parameters.AddOptional("bank_city", bankCity); - parameters.AddOptional("bank_country", bankCountry); - parameters.AddOptional("currency", currency); - parameters.AddOptional("comment", comment); - parameters.AddOptional("intermed_routing_num_or_bic", intermediateBankRouting); - var request = _definitions.GetOrCreate(HttpMethod.Post, "/api/v2/withdrawal/open/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + parameters.Add("type", type); + parameters.Add("bank_name", bankName); + parameters.Add("bank_address", bankAddress); + parameters.Add("bank_postal_code", bankPostalCode); + parameters.Add("bank_city", bankCity); + parameters.Add("bank_country", bankCountry); + parameters.Add("currency", currency); + parameters.Add("comment", comment); + parameters.Add("intermed_routing_num_or_bic", intermediateBankRouting); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, "/api/v2/withdrawal/open/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -249,11 +249,11 @@ public async Task> WithdrawFiatAsync( #region Cancel Withdrawal /// - public async Task> CancelWithdrawalAsync(string id, CancellationToken ct = default) + public async Task> CancelWithdrawalAsync(string id, CancellationToken ct = default) { - var parameters = new ParameterCollection(); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); parameters.Add("id", id); - var request = _definitions.GetOrCreate(HttpMethod.Post, "/api/v2/withdrawal/cancel/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, "/api/v2/withdrawal/cancel/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -263,11 +263,11 @@ public async Task> CancelWithdrawa #region Get Fiat Withdrawal Status /// - public async Task> GetFiatWithdrawalStatusAsync(string id, CancellationToken ct = default) + public async Task> GetFiatWithdrawalStatusAsync(string id, CancellationToken ct = default) { - var parameters = new ParameterCollection(); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); parameters.Add("id", id); - var request = _definitions.GetOrCreate(HttpMethod.Post, "/api/v2/withdrawal/status/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, "/api/v2/withdrawal/status/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -277,7 +277,7 @@ public async Task> GetFiatWithdrawal #region Withdraw Crypto /// - public async Task> WithdrawCryptoAsync( + public async Task> WithdrawCryptoAsync( string asset, decimal quantity, string address, @@ -291,15 +291,15 @@ public async Task> WithdrawCryptoAsync( if (beneficiaryThirdparty == true) throw new NotImplementedException("Thirdparty not supported"); - var parameters = new ParameterCollection(); - parameters.AddString("amount", quantity); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + parameters.Add("amount", quantity); parameters.Add("address", address); - parameters.AddOptional("network", network); - parameters.AddOptional("memo_id", memoId); - parameters.AddOptional("destination_tag", destinationTag); - parameters.AddOptional("transfer_id", transferId); - parameters.AddOptionalBoolString("beneficiary_thirdparty", beneficiaryThirdparty); - var request = _definitions.GetOrCreate(HttpMethod.Post, $"/api/v2/{asset}_withdrawal/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + parameters.Add("network", network); + parameters.Add("memo_id", memoId); + parameters.Add("destination_tag", destinationTag); + parameters.Add("transfer_id", transferId); + parameters.Add("beneficiary_thirdparty", beneficiaryThirdparty); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, $"/api/v2/{asset}_withdrawal/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -309,11 +309,11 @@ public async Task> WithdrawCryptoAsync( #region Get Deposit Address /// - public async Task> GetDepositAddressAsync(string asset, string? network = null, CancellationToken ct = default) + public async Task> GetDepositAddressAsync(string asset, string? network = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddOptional("network", network); - var request = _definitions.GetOrCreate(HttpMethod.Post, $"/api/v2/{asset.ToLower()}_address/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + parameters.Add("network", network); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, $"/api/v2/{asset.ToLower()}_address/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -323,15 +323,15 @@ public async Task> GetDepositAddressAsync( #region Get Crypto Transactions /// - public async Task> GetCryptoTransactionsAsync(bool? includeIous = null, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, int? offset = null, CancellationToken ct = default) + public async Task> GetCryptoTransactionsAsync(bool? includeIous = null, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, int? offset = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddOptional("include_ious", includeIous); - parameters.AddOptionalMillisecondsString("since_timestamp", startTime); - parameters.AddOptionalMillisecondsString("until_timestamp", endTime); - parameters.AddOptional("limit", limit); - parameters.AddOptional("offset", offset); - var request = _definitions.GetOrCreate(HttpMethod.Post, "/api/v2/crypto-transactions/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + parameters.Add("include_ious", includeIous); + parameters.Add("since_timestamp", startTime); + parameters.Add("until_timestamp", endTime); + parameters.Add("limit", limit); + parameters.Add("offset", offset); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, "/api/v2/crypto-transactions/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -341,15 +341,15 @@ public async Task> GetCryptoTransactio #region Get Deposits /// - public async Task> GetDepositsAsync(DepositStatus? status = null, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, int? offset = null, CancellationToken ct = default) + public async Task> GetDepositsAsync(DepositStatus? status = null, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, int? offset = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddOptionalEnum("status", status); - parameters.AddOptionalMillisecondsString("since_timestamp", startTime); - parameters.AddOptionalMillisecondsString("until_timestamp", endTime); - parameters.AddOptional("limit", limit); - parameters.AddOptional("offset", offset); - var request = _definitions.GetOrCreate(HttpMethod.Get, "/api/v2/crypto-transactions/deposits/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + parameters.Add("status", status); + parameters.Add("since_timestamp", startTime); + parameters.Add("until_timestamp", endTime); + parameters.Add("limit", limit); + parameters.Add("offset", offset); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "/api/v2/crypto-transactions/deposits/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -359,10 +359,10 @@ public async Task> GetDepositsAsync(Depos #region Get Margin Info /// - public async Task> GetMarginInfoAsync(CancellationToken ct = default) + public async Task> GetMarginInfoAsync(CancellationToken ct = default) { - var parameters = new ParameterCollection(); - var request = _definitions.GetOrCreate(HttpMethod.Get, "/api/v2/margin_info/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "/api/v2/margin_info/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -372,12 +372,12 @@ public async Task> GetMarginInfoAsync(Cancella #region Get Leverage Settings /// - public async Task> GetLeverageSettingsAsync(MarginMode? marginMode = null, string? symbol = null, CancellationToken ct = default) + public async Task> GetLeverageSettingsAsync(MarginMode? marginMode = null, string? symbol = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddOptionalEnum("margin_mode", marginMode); - parameters.AddOptional("market", symbol == null ? null : BitstampExchange.SymbolToPathParameter(symbol)); - var request = _definitions.GetOrCreate(HttpMethod.Get, "/api/v2/leverage_settings/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + parameters.Add("margin_mode", marginMode); + parameters.Add("market", symbol == null ? null : BitstampExchange.SymbolToPathParameter(symbol)); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "/api/v2/leverage_settings/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -387,13 +387,13 @@ public async Task> GetLeverageSettingsA #region Set Leverage /// - public async Task> SetLeverageAsync(MarginMode marginMode, string symbol, decimal leverage, CancellationToken ct = default) + public async Task> SetLeverageAsync(MarginMode marginMode, string symbol, decimal leverage, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddEnum("margin_mode", marginMode); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + parameters.Add("margin_mode", marginMode); parameters.Add("market", BitstampExchange.SymbolToPathParameter(symbol)); parameters.Add("leverage", leverage); - var request = _definitions.GetOrCreate(HttpMethod.Post, "/api/v2/leverage_settings/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, "/api/v2/leverage_settings/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } diff --git a/Bitstamp.Net/Clients/ExchangeApi/BitstampRestClientExchangeApiExchangeData.cs b/Bitstamp.Net/Clients/ExchangeApi/BitstampRestClientExchangeApiExchangeData.cs index 86bd586..bcac44f 100644 --- a/Bitstamp.Net/Clients/ExchangeApi/BitstampRestClientExchangeApiExchangeData.cs +++ b/Bitstamp.Net/Clients/ExchangeApi/BitstampRestClientExchangeApiExchangeData.cs @@ -20,9 +20,9 @@ internal BitstampRestClientExchangeApiExchangeData(BitstampRestClientExchangeApi #region Get Symbols /// - public Task> GetSymbolsAsync(CancellationToken ct = default) + public Task> GetSymbolsAsync(CancellationToken ct = default) { - var request = _definitions.GetOrCreate(HttpMethod.Get, "/api/v2/markets/", BitstampExchange.RateLimiter.Rest); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "/api/v2/markets/", BitstampExchange.RateLimiter.Rest); return _baseClient.SendAsync(request, null, ct); } @@ -31,9 +31,9 @@ public Task> GetSymbolsAsync(CancellationToken c #region Get Assets /// - public Task> GetAssetsAsync(CancellationToken ct = default) + public Task> GetAssetsAsync(CancellationToken ct = default) { - var request = _definitions.GetOrCreate(HttpMethod.Get, "/api/v2/currencies/", BitstampExchange.RateLimiter.Rest); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "/api/v2/currencies/", BitstampExchange.RateLimiter.Rest); return _baseClient.SendAsync(request, null, ct); } @@ -42,9 +42,9 @@ public Task> GetAssetsAsync(CancellationToken ct #region Get All Tickers /// - public Task> GetAllTickersAsync(CancellationToken ct = default) + public Task> GetAllTickersAsync(CancellationToken ct = default) { - var request = _definitions.GetOrCreate(HttpMethod.Get, "/api/v2/ticker/", BitstampExchange.RateLimiter.Rest); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "/api/v2/ticker/", BitstampExchange.RateLimiter.Rest); return _baseClient.SendAsync(request, null, ct); } @@ -53,9 +53,9 @@ public Task> GetAllTickersAsync(CancellationToke #region Get Ticker /// - public Task> GetTickerAsync(string symbol, CancellationToken ct = default) + public Task> GetTickerAsync(string symbol, CancellationToken ct = default) { - var request = _definitions.GetOrCreate(HttpMethod.Get, $"/api/v2/ticker/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, $"/api/v2/ticker/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest); return _baseClient.SendAsync(request, null, ct); } @@ -64,9 +64,9 @@ public Task> GetTickerAsync(string symbol, Cancell #region Get Hour Ticker /// - public Task> GetHourTickerAsync(string symbol, CancellationToken ct = default) + public Task> GetHourTickerAsync(string symbol, CancellationToken ct = default) { - var request = _definitions.GetOrCreate(HttpMethod.Get, $"/api/v2/ticker_hour/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, $"/api/v2/ticker_hour/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest); return _baseClient.SendAsync(request, null, ct); } @@ -75,7 +75,7 @@ public Task> GetHourTickerAsync(string symbol, Can #region Get Klines /// - public async Task> GetKlinesAsync( + public async Task> GetKlinesAsync( string symbol, KlineInterval interval, int? limit = null, @@ -87,16 +87,19 @@ public async Task> GetKlinesAsync( if (limit <= 0 || limit > 1000) limit = 1000; - var parameters = new ParameterCollection(); - parameters.AddEnum("step", interval); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + parameters.Add("step", interval); parameters.Add("limit", limit ?? 1000); - parameters.AddOptionalSeconds("start", startTime); - parameters.AddOptionalSeconds("end", endTime); - parameters.AddOptionalBoolString("exclude_current_candle", excludeCurrentCandle); + parameters.Add("start", startTime, DateTimeSerialization.SecondsNumber); + parameters.Add("end", endTime, DateTimeSerialization.SecondsNumber); + parameters.Add("exclude_current_candle", excludeCurrentCandle); - var request = _definitions.GetOrCreate(HttpMethod.Get, $"/api/v2/ohlc/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest, 1, false); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, $"/api/v2/ohlc/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest, 1, false); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); - return result.As(result.Data?.Data?.KLines ?? []); + if (!result.Success) + return HttpResult.Fail(result); + + return HttpResult.Ok(result, result.Data?.Data?.KLines ?? []); } #endregion @@ -104,14 +107,14 @@ public async Task> GetKlinesAsync( #region Get Order Book /// - public Task> GetOrderBookAsync(string symbol, CancellationToken ct = default) + public Task> GetOrderBookAsync(string symbol, CancellationToken ct = default) { - var parameters = new ParameterCollection() + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings) { { "group", 1 } }; - var request = _definitions.GetOrCreate(HttpMethod.Get, $"/api/v2/order_book/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest, 1, false); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, $"/api/v2/order_book/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest, 1, false); return _baseClient.SendAsync(request, parameters, ct); } @@ -120,12 +123,12 @@ public Task> GetOrderBookAsync(string sym #region Get Trades /// - public Task> GetTradesAsync(string symbol, Period? period = null, CancellationToken ct = default) + public Task> GetTradesAsync(string symbol, Period? period = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddOptionalEnum("time", period); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + parameters.Add("time", period); - var request = _definitions.GetOrCreate(HttpMethod.Get, $"/api/v2/transactions/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest, 1, false); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, $"/api/v2/transactions/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest, 1, false); return _baseClient.SendAsync(request, parameters, ct); } @@ -134,9 +137,9 @@ public Task> GetTradesAsync(string symbol, Period #region Get Eur Usd Conversion Rate /// - public Task> GetEurUsdConversionRateAsync(CancellationToken ct = default) + public Task> GetEurUsdConversionRateAsync(CancellationToken ct = default) { - var request = _definitions.GetOrCreate(HttpMethod.Get, $"/api/v2/eur_usd/", BitstampExchange.RateLimiter.Rest, 1, false); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, $"/api/v2/eur_usd/", BitstampExchange.RateLimiter.Rest, 1, false); return _baseClient.SendAsync(request, null, ct); } @@ -145,9 +148,9 @@ public Task> GetEurUsdConversionRateAsync( #region Get Funding Rate /// - public Task> GetFundingRateAsync(string symbol, CancellationToken ct = default) + public Task> GetFundingRateAsync(string symbol, CancellationToken ct = default) { - var request = _definitions.GetOrCreate(HttpMethod.Get, $"/api/v2/funding_rate/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest, 1, false); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, $"/api/v2/funding_rate/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest, 1, false); return _baseClient.SendAsync(request, null, ct); } @@ -156,21 +159,24 @@ public Task> GetFundingRateAsync(string symbo #region Get Funding Rate History /// - public async Task> GetFundingRateHistoryAsync( + public async Task> GetFundingRateHistoryAsync( string symbol, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddOptional("limit", limit); - parameters.AddOptionalSeconds("since_timestamp", startTime); - parameters.AddOptionalSeconds("until_timestamp", endTime); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + parameters.Add("limit", limit); + parameters.Add("since_timestamp", startTime, DateTimeSerialization.SecondsNumber); + parameters.Add("until_timestamp", endTime, DateTimeSerialization.SecondsNumber); - var request = _definitions.GetOrCreate(HttpMethod.Get, $"/api/v2/funding_rate_history/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest, 1, false); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, $"/api/v2/funding_rate_history/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest, 1, false); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); - return result.As(result.Data?.History); + if (!result.Success) + return HttpResult.Fail(result); + + return HttpResult.Ok(result, result.Data.History); } #endregion @@ -178,10 +184,10 @@ public async Task> GetFundingRateHis #region Get Margin Tiers /// - public async Task> GetMarginTiersAsync(CancellationToken ct = default) + public async Task> GetMarginTiersAsync(CancellationToken ct = default) { - var parameters = new ParameterCollection(); - var request = _definitions.GetOrCreate(HttpMethod.Get, "/api/v2/margin_tiers/", BitstampExchange.RateLimiter.Rest, 1, false); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "/api/v2/margin_tiers/", BitstampExchange.RateLimiter.Rest, 1, false); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -191,10 +197,10 @@ public async Task> GetMarginTiersAsync(Canc #region Get Collateral Assets /// - public async Task> GetCollateralAssetsAsync(CancellationToken ct = default) + public async Task> GetCollateralAssetsAsync(CancellationToken ct = default) { - var parameters = new ParameterCollection(); - var request = _definitions.GetOrCreate(HttpMethod.Get, "/api/v2/collateral_currencies/", BitstampExchange.RateLimiter.Rest, 1, false, forcePathEndWithSlash: true); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "/api/v2/collateral_currencies/", BitstampExchange.RateLimiter.Rest, 1, false, forcePathEndWithSlash: true); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } diff --git a/Bitstamp.Net/Clients/ExchangeApi/BitstampRestClientExchangeApiShared.cs b/Bitstamp.Net/Clients/ExchangeApi/BitstampRestClientExchangeApiShared.cs index ddf001a..1651408 100644 --- a/Bitstamp.Net/Clients/ExchangeApi/BitstampRestClientExchangeApiShared.cs +++ b/Bitstamp.Net/Clients/ExchangeApi/BitstampRestClientExchangeApiShared.cs @@ -12,16 +12,18 @@ internal partial class BitstampRestClientExchangeApi : IBitstampRestClientExchan { private const string _topicSpotId = "BitstampSpot"; private const string _topicFuturesId = "BitstampFutures"; + private const string _exchangeName = "Bitstamp"; - public string Exchange => BitstampExchange.ExchangeName; public TradingMode[] SupportedTradingModes { get; } = new[] { TradingMode.Spot, TradingMode.PerpetualLinear }; + public TradingMode[] SupportedFuturesTradingModes => SupportedTradingModes.Except(new[] { TradingMode.Spot }).ToArray(); public void SetDefaultExchangeParameter(string key, object value) => ExchangeParameters.SetStaticParameter(Exchange, key, value); public void ResetDefaultExchangeParameters() => ExchangeParameters.ResetStaticParameters(); + public SharedClientInfo Discover() => SharedUtils.GetClientInfo(BitstampExchange.Metadata, this); #region Kline client - GetKlinesOptions IKlineRestClient.GetKlinesOptions { get; } = new GetKlinesOptions(false, true, true, 1000, false, + GetKlinesOptions IKlineRestClient.GetKlinesOptions { get; } = new GetKlinesOptions(_exchangeName, false, true, true, 1000, false, SharedKlineInterval.OneMinute, SharedKlineInterval.ThreeMinutes, SharedKlineInterval.FiveMinutes, @@ -36,15 +38,13 @@ internal partial class BitstampRestClientExchangeApi : IBitstampRestClientExchan { }; - 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.Symbol!.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetKlinesOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var direction = request.Direction ?? DataDirection.Descending; var symbol = request.Symbol!.GetSymbol(FormatSymbol); @@ -60,8 +60,8 @@ async Task> IKlineRestClient.GetKlinesAsync(Get limit: 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).AddSeconds(-(int)(interval))), @@ -71,10 +71,7 @@ async Task> IKlineRestClient.GetKlinesAsync(Get request.EndTime ?? DateTime.UtcNow, pageParams); - return result.AsExchangeResult( - Exchange, - TradingMode.Spot, - ExchangeHelpers.ApplyFilter(result.Data, x => x.OpenTime, request.StartTime, request.EndTime, direction) + return HttpResult.Ok(result, ExchangeHelpers.ApplyFilter(result.Data, x => x.OpenTime, request.StartTime, request.EndTime, direction) .Select(x => new SharedKline( request.Symbol, @@ -92,21 +89,18 @@ async Task> IKlineRestClient.GetKlinesAsync(Get #region Spot Symbol client - EndpointOptions ISpotSymbolRestClient.GetSpotSymbolsOptions { get; } = new EndpointOptions(false); - async Task> ISpotSymbolRestClient.GetSpotSymbolsAsync(GetSymbolsRequest request, CancellationToken ct) + GetSpotSymbolsOptions ISpotSymbolRestClient.GetSpotSymbolsOptions { get; } = new GetSpotSymbolsOptions(_exchangeName, false); + 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(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.Where(x => x.MarketType == MarketType.Spot).Select(s => + var response = HttpResult.Ok(result, result.Data.Where(x => x.MarketType == MarketType.Spot).Select(s => new SharedSpotSymbol(s.BaseAsset, s.QuoteAsset, s.Name, s.Status == EnabledStatus.Enabled) { MinTradeQuantity = s.MinimumOrderQuantity, @@ -116,68 +110,65 @@ async Task> ISpotSymbolRestClient.GetSpotS QuantityDecimals = s.BaseDecimals }).ToArray()); - ExchangeSymbolCache.UpdateSymbolInfo(_topicSpotId, response.Data); + ExchangeSymbolCache.UpdateSymbolInfo(_topicSpotId, EnvironmentName, null, response.Data!); return response; } - async Task> ISpotSymbolRestClient.GetSpotSymbolsForBaseAssetAsync(string baseAsset) + 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) - 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, EnvironmentName, null, 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"); - if (!ExchangeSymbolCache.HasCached(_topicSpotId)) + if (!ExchangeSymbolCache.HasCached(_topicSpotId, EnvironmentName, null)) { 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, EnvironmentName, null, symbol)); } - async Task> ISpotSymbolRestClient.SupportsSpotSymbolAsync(string symbolName) + 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) - 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, EnvironmentName, null, symbolName)); } #endregion #region 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.Symbol!.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetSpotTickerOptions.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 ExchangeData.GetTickerAsync(symbol, 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 SharedSpotTicker( - ExchangeSymbolCache.ParseSymbol(_topicSpotId, result.Data.Symbol), + return HttpResult.Ok(result, new SharedSpotTicker( + ExchangeSymbolCache.ParseSymbol(_topicSpotId, EnvironmentName, null, result.Data.Symbol), symbol, result.Data.LastPrice, result.Data.HighPrice, @@ -189,23 +180,20 @@ async Task> ISpotTickerRestClient.GetSpotTic }); } - 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.GetAllTickersAsync(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.Where(x => x.MarketType == MarketType.Spot).Select(x => + return HttpResult.Ok(result, result.Data.Where(x => x.MarketType == MarketType.Spot).Select(x => new SharedSpotTicker( - ExchangeSymbolCache.ParseSymbol(_topicSpotId, x.Symbol), + ExchangeSymbolCache.ParseSymbol(_topicSpotId, EnvironmentName, null, x.Symbol), x.Symbol!, x.LastPrice, x.HighPrice, @@ -221,19 +209,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.Symbol!.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, 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( request.Symbol, symbol, resultTicker.Data.Asks[0].Price, @@ -246,23 +234,23 @@ async Task> IBookTickerRestClient.GetBookTic #region Recent Trades - GetRecentTradesOptions IRecentTradeRestClient.GetRecentTradesOptions { get; } = new GetRecentTradesOptions(1000, false); + GetRecentTradesOptions IRecentTradeRestClient.GetRecentTradesOptions { get; } = new GetRecentTradesOptions(_exchangeName, 1000, false); - async Task> IRecentTradeRestClient.GetRecentTradesAsync(GetRecentTradesRequest request, CancellationToken ct) + async Task> IRecentTradeRestClient.GetRecentTradesAsync(GetRecentTradesRequest request, CancellationToken ct) { - var validationError = ((IRecentTradeRestClient)this).GetRecentTradesOptions.ValidateRequest(Exchange, request, request.Symbol!.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetRecentTradesOptions.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 ExchangeData.GetTradesAsync( symbol, Period.Hour, 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, result.Data.Take(request.Limit ?? 1000).Select(x => + return HttpResult.Ok(result, result.Data.Take(request.Limit ?? 1000).Select(x => new SharedTrade(request.Symbol, symbol, x.Quantity, x.Price, x.Timestamp) { Side = x.Side == OrderSide.Buy ? SharedOrderSide.Buy : SharedOrderSide.Sell @@ -272,33 +260,43 @@ async Task> IRecentTradeRestClient.GetRecentTra #endregion #region Balance client - GetBalancesOptions IBalanceRestClient.GetBalancesOptions { get; } = new GetBalancesOptions(AccountTypeFilter.Spot, AccountTypeFilter.Margin); + GetBalancesOptions IBalanceRestClient.GetBalancesOptions { get; } = new GetBalancesOptions(_exchangeName, AccountTypeFilter.Spot, AccountTypeFilter.Margin); - 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); if (request.AccountType == SharedAccountType.Spot || request.AccountType == null) { var result = await Account.GetAccountBalancesAsync(ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); - - return result.AsExchangeResult(Exchange, TradingMode.Spot, result.Data.Select(x => new SharedBalance(x.Asset, x.Available, x.Total)).ToArray()); + if (!result.Success) + return HttpResult.Fail(result); + + return HttpResult.Ok(result, result.Data.Select(x => + new SharedBalance( + TradingMode.Spot, + x.Asset, + x.Available, + x.Total)).ToArray()); } else { var result = await Account.GetMarginInfoAsync(ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); var resultList = new List(); foreach (var item in result.Data.Assets) - resultList.Add(new SharedBalance(item.Asset, item.Available, item.TotalQuantity)); - - return result.AsExchangeResult(Exchange, TradingMode.Spot, resultList.ToArray()); + resultList.Add( + new SharedBalance( + SupportedFuturesTradingModes, + item.Asset, + item.Available, + item.TotalQuantity)); + + return HttpResult.Ok(result, resultList.ToArray()); } } @@ -306,7 +304,7 @@ async Task> IBalanceRestClient.GetBalancesAsy #region Spot Order client - PlaceSpotOrderOptions ISpotOrderRestClient.PlaceSpotOrderOptions { get; } = new PlaceSpotOrderOptions(); + PlaceSpotOrderOptions ISpotOrderRestClient.PlaceSpotOrderOptions { get; } = new PlaceSpotOrderOptions(_exchangeName); SharedFeeDeductionType ISpotOrderRestClient.SpotFeeDeductionType => SharedFeeDeductionType.DeductFromOutput; SharedFeeAssetType ISpotOrderRestClient.SpotFeeAssetType => SharedFeeAssetType.QuoteAsset; @@ -321,18 +319,11 @@ async Task> IBalanceRestClient.GetBalancesAsy string ISpotOrderRestClient.GenerateClientOrderId() => ExchangeHelpers.RandomString(20); - async Task> ISpotOrderRestClient.PlaceSpotOrderAsync(PlaceSpotOrderRequest request, CancellationToken ct) + async Task> ISpotOrderRestClient.PlaceSpotOrderAsync(PlaceSpotOrderRequest request, CancellationToken ct) { - var validationError = ((ISpotOrderRestClient)this).PlaceSpotOrderOptions.ValidateRequest( - Exchange, - request, - request.Symbol!.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); if (request.OrderType == SharedOrderType.Market) { @@ -344,10 +335,10 @@ async Task> ISpotOrderRestClient.PlaceSpotOrderAsync 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.Id.ToString())); + return HttpResult.Ok(result, new SharedId(result.Data.Id.ToString())); } else { @@ -363,29 +354,29 @@ async Task> ISpotOrderRestClient.PlaceSpotOrderAsync 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.Id.ToString())); + return HttpResult.Ok(result, new SharedId(result.Data.Id.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.Symbol!.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetSpotOrderByClientOrderIdOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); if (!long.TryParse(request.OrderId, out var orderId)) - return new ExchangeWebResult(Exchange, ArgumentError.Invalid(nameof(GetOrderRequest.OrderId), "Invalid order id")); + return HttpResult.Fail(Exchange, ArgumentError.Invalid(nameof(GetOrderRequest.OrderId), "Invalid order id")); var orders = await Trading.GetOrderAsync(orderId, includeTrades: true, 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, new SharedSpotOrder( - ExchangeSymbolCache.ParseSymbol(_topicSpotId, orders.Data.Symbol), + return HttpResult.Ok(orders, new SharedSpotOrder( + ExchangeSymbolCache.ParseSymbol(_topicSpotId, EnvironmentName, null, orders.Data.Symbol), orders.Data.Symbol, orders.Data.Id.ToString(), ParseOrderType(orders.Data.OrderType), @@ -402,31 +393,28 @@ async Task> ISpotOrderRestClient.GetSpotOrder }); } - EndpointOptions ISpotOrderRestClient.GetOpenSpotOrdersOptions { get; } = new EndpointOptions(true); - async Task> ISpotOrderRestClient.GetOpenSpotOrdersAsync(GetOpenOrdersRequest request, CancellationToken ct) + GetOpenSpotOrdersOptions ISpotOrderRestClient.GetOpenSpotOrdersOptions { get; } = new GetOpenSpotOrdersOptions(_exchangeName, true); + 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); string? symbol = request.Symbol?.GetSymbol(FormatSymbol); - WebCallResult orders; + HttpResult orders; if (symbol != null) orders = await Trading.GetOpenOrdersAsync(symbol, ct: ct).ConfigureAwait(false); else orders = await Trading.GetOpenOrdersAsync(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.Where(x => !x.Symbol.EndsWith("-perp", StringComparison.InvariantCultureIgnoreCase)) + return HttpResult.Ok(orders, orders.Data.Where(x => !x.Symbol.EndsWith("-perp", StringComparison.InvariantCultureIgnoreCase)) .Select(x => new SharedSpotOrder( - ExchangeSymbolCache.ParseSymbol(_topicSpotId, x.Symbol), + ExchangeSymbolCache.ParseSymbol(_topicSpotId, EnvironmentName, null, x.Symbol), x.Symbol, x.Id.ToString(), SharedOrderType.Limit, @@ -441,12 +429,12 @@ async Task> ISpotOrderRestClient.GetOpenSpo }).ToArray()); } - GetClosedOrdersOptions ISpotOrderRestClient.GetClosedSpotOrdersOptions { get; } = new GetClosedOrdersOptions(true, true, false, 500); - async Task> ISpotOrderRestClient.GetClosedSpotOrdersAsync(GetClosedOrdersRequest request, PageRequest? pageRequest, CancellationToken ct) + GetSpotClosedOrdersOptions ISpotOrderRestClient.GetClosedSpotOrdersOptions { get; } = new GetSpotClosedOrdersOptions(_exchangeName, true, true, false, 500); + async Task> ISpotOrderRestClient.GetClosedSpotOrdersAsync(GetClosedOrdersRequest request, PageRequest? pageRequest, CancellationToken ct) { - var validationError = ((ISpotOrderRestClient)this).GetClosedSpotOrdersOptions.ValidateRequest(Exchange, request, request.Symbol!.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetClosedSpotOrdersOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); // Determine page token var direction = request.Direction ?? DataDirection.Descending; @@ -461,8 +449,8 @@ async Task> ISpotOrderRestClient.GetClosedS fromEventId: direction == DataDirection.Ascending ? pageParams.FromId : null, toEventId: direction == DataDirection.Descending ? pageParams.FromId : null, ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); var closeData = result.Data.Where(x => x.Event == OrderEvent.OrderDeleted); var nextPageRequest = Pagination.GetNextPageRequest( @@ -475,10 +463,7 @@ async Task> ISpotOrderRestClient.GetClosedS request.EndTime ?? DateTime.UtcNow, pageParams); - return result.AsExchangeResult( - Exchange, - TradingMode.Spot, - ExchangeHelpers.ApplyFilter(closeData, x => x.Data.Timestamp, request.StartTime, request.EndTime, direction) + return HttpResult.Ok(result, ExchangeHelpers.ApplyFilter(closeData, x => x.Data.Timestamp, request.StartTime, request.EndTime, direction) .Select(x => new SharedSpotOrder( request.Symbol, symbol, @@ -504,27 +489,27 @@ private SharedOrderStatus ParseOrderStatus(BitstampOrderEventData data) return SharedOrderStatus.Filled; } - 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.Symbol!.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetSpotOrderTradesOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); if (!long.TryParse(request.OrderId, out var orderId)) - return new ExchangeWebResult(Exchange, ArgumentError.Invalid(nameof(GetOrderTradesRequest.OrderId), "Invalid order id")); + return HttpResult.Fail(Exchange, ArgumentError.Invalid(nameof(GetOrderTradesRequest.OrderId), "Invalid order id")); var orders = await Trading.GetOrderAsync(orderId, includeTrades: true, 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.Trades?.Select(x => new SharedUserTrade( + return HttpResult.Ok(orders, orders.Data.Trades?.Select(x => new SharedUserTrade( request.Symbol, orders.Data.Symbol, orders.Data.Id.ToString(), x.TradeId.ToString(), orders.Data.Side == OrderSide.Buy ? SharedOrderSide.Buy : SharedOrderSide.Sell, - x.Quantities.TryGetValue(request.Symbol.BaseAsset, out var quantity) ? quantity: 0, + x.Quantities.TryGetValue(request.Symbol!.BaseAsset, out var quantity) ? quantity: 0, x.Price, x.Timestamp) { @@ -533,12 +518,12 @@ async Task> ISpotOrderRestClient.GetSpotOrd })?.ToArray() ?? []); } - GetUserTradesOptions ISpotOrderRestClient.GetSpotUserTradesOptions { get; } = new GetUserTradesOptions(true, true, true, 1000); - async Task> ISpotOrderRestClient.GetSpotUserTradesAsync(GetUserTradesRequest request, PageRequest? pageRequest, CancellationToken ct) + GetSpotUserTradesOptions ISpotOrderRestClient.GetSpotUserTradesOptions { get; } = new GetSpotUserTradesOptions(_exchangeName, true, true, true, 1000); + async Task> ISpotOrderRestClient.GetSpotUserTradesAsync(GetUserTradesRequest request, PageRequest? pageRequest, CancellationToken ct) { - var validationError = ((ISpotOrderRestClient)this).GetSpotUserTradesOptions.ValidateRequest(Exchange, request, request.Symbol!.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetSpotUserTradesOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); // Determine page token int limit = request.Limit ?? 1000; @@ -554,8 +539,8 @@ async Task> ISpotOrderRestClient.GetSpotUse limit: pageParams.Limit, offset: pageParams.Offset, ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); var trades = result.Data.Where(x => x.Type == TransactionType.MarketTrade); var nextPageRequest = Pagination.GetNextPageRequest( @@ -566,12 +551,9 @@ async Task> ISpotOrderRestClient.GetSpotUse request.EndTime ?? DateTime.UtcNow, pageParams); - return result.AsExchangeResult( - Exchange, - TradingMode.Spot, - ExchangeHelpers.ApplyFilter(trades, x => x.Timestamp, request.StartTime, request.EndTime, direction) + return HttpResult.Ok(result, ExchangeHelpers.ApplyFilter(trades, 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.Id.ToString(), @@ -593,21 +575,21 @@ private SharedOrderSide GetSide(SharedSymbol symbol, BitstampUserTransaction tra return SharedOrderSide.Buy; } - 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.Symbol!.TradingMode, SupportedTradingModes); + var validationError = SharedClient.CancelSpotOrderByClientOrderIdOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); if (!long.TryParse(request.OrderId, out var orderId)) - return new ExchangeWebResult(Exchange, ArgumentError.Invalid(nameof(CancelOrderRequest.OrderId), "Invalid order id")); + return HttpResult.Fail(Exchange, ArgumentError.Invalid(nameof(CancelOrderRequest.OrderId), "Invalid order id")); var order = await Trading.CancelOrderAsync(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.Id.ToString())); + return HttpResult.Ok(order, new SharedId(order.Data.Id.ToString())); } private SharedOrderStatus ParseOrderStatus(OrderStatus status) @@ -631,19 +613,19 @@ private SharedOrderType ParseOrderType(OrderType type) #region Spot Client Id Order Client - EndpointOptions ISpotOrderClientIdRestClient.GetSpotOrderByClientOrderIdOptions { get; } = new EndpointOptions(true); - async Task> ISpotOrderClientIdRestClient.GetSpotOrderByClientOrderIdAsync(GetOrderRequest request, CancellationToken ct) + GetSpotOrderByClientOrderIdOptions ISpotOrderClientIdRestClient.GetSpotOrderByClientOrderIdOptions { get; } = new GetSpotOrderByClientOrderIdOptions(_exchangeName, true); + async Task> ISpotOrderClientIdRestClient.GetSpotOrderByClientOrderIdAsync(GetOrderRequest request, CancellationToken ct) { - var validationError = ((ISpotOrderRestClient)this).GetSpotOrderOptions.ValidateRequest(Exchange, request, request.Symbol!.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetSpotOrderOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var order = await Trading.GetOrderAsync(clientOrderId: 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( - ExchangeSymbolCache.ParseSymbol(_topicSpotId, order.Data.Symbol), + return HttpResult.Ok(order, new SharedSpotOrder( + ExchangeSymbolCache.ParseSymbol(_topicSpotId, EnvironmentName, null, order.Data.Symbol), order.Data.Symbol, order.Data.Id.ToString(), ParseOrderType(order.Data.OrderType), @@ -660,39 +642,39 @@ async Task> ISpotOrderClientIdRestClient.GetS }); } - EndpointOptions ISpotOrderClientIdRestClient.CancelSpotOrderByClientOrderIdOptions { get; } = new EndpointOptions(true); - async Task> ISpotOrderClientIdRestClient.CancelSpotOrderByClientOrderIdAsync(CancelOrderRequest request, CancellationToken ct) + CancelSpotOrderByClientOrderIdOptions ISpotOrderClientIdRestClient.CancelSpotOrderByClientOrderIdOptions { get; } = new CancelSpotOrderByClientOrderIdOptions(_exchangeName, true); + async Task> ISpotOrderClientIdRestClient.CancelSpotOrderByClientOrderIdAsync(CancelOrderRequest request, CancellationToken ct) { - var validationError = ((ISpotOrderRestClient)this).CancelSpotOrderOptions.ValidateRequest(Exchange, request, request.Symbol!.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(clientOrderId: 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.Id.ToString() ?? request.OrderId)); + return HttpResult.Ok(order, new SharedId(order.Data.Id.ToString() ?? request.OrderId)); } #endregion #region Asset client - EndpointOptions IAssetsRestClient.GetAssetOptions { get; } = new EndpointOptions(false); - async Task> IAssetsRestClient.GetAssetAsync(GetAssetRequest request, CancellationToken ct) + GetAssetOptions IAssetsRestClient.GetAssetOptions { get; } = new GetAssetOptions(_exchangeName, false); + async Task> IAssetsRestClient.GetAssetAsync(GetAssetRequest request, CancellationToken ct) { - var validationError = ((IAssetsRestClient)this).GetAssetOptions.ValidateRequest(Exchange, request, TradingMode.Spot, SupportedTradingModes); + var validationError = SharedClient.GetAssetOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var assets = await ExchangeData.GetAssetsAsync(ct: ct).ConfigureAwait(false); - if (!assets) - return assets.AsExchangeResult(Exchange, null, default); + if (!assets.Success) + return HttpResult.Fail(assets); var asset = assets.Data.SingleOrDefault(x => string.Equals(x.Asset, request.Asset, StringComparison.InvariantCultureIgnoreCase)); if (asset == null) - return new ExchangeWebResult(Exchange, new ServerError(new ErrorInfo(ErrorType.UnknownAsset, "Asset not found"))); + return HttpResult.Fail(Exchange, new ServerError(new ErrorInfo(ErrorType.UnknownAsset, "Asset not found"))); - return assets.AsExchangeResult(Exchange, TradingMode.Spot, new SharedAsset(asset.Asset) + return HttpResult.Ok(assets, new SharedAsset(asset.Asset) { FullName = asset.Name, Networks = asset.Networks.Select(x => new SharedAssetNetwork(x.Network) @@ -704,18 +686,18 @@ async Task> IAssetsRestClient.GetAssetAsync(GetAs }); } - EndpointOptions IAssetsRestClient.GetAssetsOptions { get; } = new EndpointOptions(false); - async Task> IAssetsRestClient.GetAssetsAsync(GetAssetsRequest request, CancellationToken ct) + GetAssetsOptions IAssetsRestClient.GetAssetsOptions { get; } = new GetAssetsOptions(_exchangeName, false); + async Task> IAssetsRestClient.GetAssetsAsync(GetAssetsRequest request, CancellationToken ct) { - var validationError = ((IAssetsRestClient)this).GetAssetsOptions.ValidateRequest(Exchange, request, TradingMode.Spot, SupportedTradingModes); + var validationError = SharedClient.GetAssetsOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var assets = await ExchangeData.GetAssetsAsync(ct: ct).ConfigureAwait(false); - if (!assets) - return assets.AsExchangeResult(Exchange, null, default); + if (!assets.Success) + return HttpResult.Fail(assets); - return assets.AsExchangeResult(Exchange, TradingMode.Spot, assets.Data.Select(x => new SharedAsset(x.Asset) + return HttpResult.Ok(assets, assets.Data.Select(x => new SharedAsset(x.Asset) { FullName = x.Name, Networks = x.Networks.Select(x => new SharedAssetNetwork(x.Network) @@ -730,18 +712,18 @@ async Task> IAssetsRestClient.GetAssetsAsync(Ge #endregion #region Deposit client - EndpointOptions IDepositRestClient.GetDepositAddressesOptions { get; } = new EndpointOptions(true); - async Task> IDepositRestClient.GetDepositAddressesAsync(GetDepositAddressesRequest request, CancellationToken ct) + GetDepositAddressesOptions IDepositRestClient.GetDepositAddressesOptions { get; } = new GetDepositAddressesOptions(_exchangeName, true); + async Task> IDepositRestClient.GetDepositAddressesAsync(GetDepositAddressesRequest request, CancellationToken ct) { - var validationError = ((IDepositRestClient)this).GetDepositAddressesOptions.ValidateRequest(Exchange, request, TradingMode.Spot, SupportedTradingModes); + var validationError = SharedClient.GetDepositAddressesOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var depositAddresses = await Account.GetDepositAddressAsync(request.Asset, request.Network!, ct: ct).ConfigureAwait(false); - if (!depositAddresses) - return depositAddresses.AsExchangeResult(Exchange, null, default); + if (!depositAddresses.Success) + return HttpResult.Fail(depositAddresses); - return depositAddresses.AsExchangeResult(Exchange, TradingMode.Spot, new[] { new SharedDepositAddress(request.Asset, depositAddresses.Data.Address) + return HttpResult.Ok(depositAddresses, new[] { new SharedDepositAddress(request.Asset, depositAddresses.Data.Address) { Network = request.Network, TagOrMemo = depositAddresses.Data.DestinationTag?.ToString() ?? depositAddresses.Data.MemoId @@ -749,12 +731,12 @@ async Task> IDepositRestClient.GetDepo }); } - GetDepositsOptions IDepositRestClient.GetDepositsOptions { get; } = new GetDepositsOptions(false, true, false, 1000); - async Task> IDepositRestClient.GetDepositsAsync(GetDepositsRequest request, PageRequest? pageRequest, CancellationToken ct) + GetDepositsOptions IDepositRestClient.GetDepositsOptions { get; } = new GetDepositsOptions(_exchangeName, false, true, false, 1000); + 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); // Determine page token int limit = request.Limit ?? 100; @@ -768,8 +750,8 @@ async Task> IDepositRestClient.GetDepositsAsy limit: pageParams.Limit, offset: pageParams.Offset, ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); var nextPageRequest = Pagination.GetNextPageRequest( () => Pagination.NextPageFromOffset(pageParams, result.Data.Length), @@ -779,10 +761,7 @@ async Task> IDepositRestClient.GetDepositsAsy request.EndTime ?? DateTime.UtcNow, pageParams); - return result.AsExchangeResult( - Exchange, - TradingMode.Spot, - ExchangeHelpers.ApplyFilter(result.Data, x => x.Timestamp, request.StartTime, request.EndTime, direction) + return HttpResult.Ok(result, ExchangeHelpers.ApplyFilter(result.Data, x => x.Timestamp, request.StartTime, request.EndTime, direction) .Select(x => new SharedDeposit( x.Asset, @@ -813,31 +792,31 @@ private SharedTransferStatus ParseTransferStatus(DepositStatus status) #endregion #region Order Book client - GetOrderBookOptions IOrderBookRestClient.GetOrderBookOptions { get; } = new GetOrderBookOptions(1, 1000, false); - async Task> IOrderBookRestClient.GetOrderBookAsync(GetOrderBookRequest request, CancellationToken ct) + GetOrderBookOptions IOrderBookRestClient.GetOrderBookOptions { get; } = new GetOrderBookOptions(_exchangeName, 1, 1000, false); + async Task> IOrderBookRestClient.GetOrderBookAsync(GetOrderBookRequest request, CancellationToken ct) { - var validationError = ((IOrderBookRestClient)this).GetOrderBookOptions.ValidateRequest(Exchange, request, request.Symbol!.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), 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, false, 1000); - async Task> IWithdrawalRestClient.GetWithdrawalsAsync(GetWithdrawalsRequest request, PageRequest? pageRequest, CancellationToken ct) + GetWithdrawalsOptions IWithdrawalRestClient.GetWithdrawalsOptions { get; } = new GetWithdrawalsOptions(_exchangeName, false, true, false, 1000); + 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); // Determine page token int limit = request.Limit ?? 100; @@ -850,8 +829,8 @@ async Task> IWithdrawalRestClient.GetWithd limit: limit, offset: pageParams.Offset ?? 0, ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); var nextPageRequest = Pagination.GetNextPageRequest( () => Pagination.NextPageFromOffset(pageParams, result.Data.Length), @@ -861,12 +840,15 @@ async Task> IWithdrawalRestClient.GetWithd request.EndTime ?? DateTime.UtcNow, pageParams); - return result.AsExchangeResult( - Exchange, - TradingMode.Spot, - ExchangeHelpers.ApplyFilter(result.Data, x => x.Timestamp, request.StartTime, request.EndTime, direction) + return HttpResult.Ok(result, ExchangeHelpers.ApplyFilter(result.Data, x => x.Timestamp, request.StartTime, request.EndTime, direction) .Select(x => - new SharedWithdrawal(x.Asset, x.Address, x.Quantity, x.Status == WithdrawalStatus.Finished, x.Timestamp) + new SharedWithdrawal( + x.Asset, + x.Address, + x.Quantity, + x.Status == WithdrawalStatus.Finished, + x.Timestamp, + GetWithdrawalStatus(x)) { Id = x.Id.ToString(), Network = x.Network, @@ -875,17 +857,31 @@ async Task> IWithdrawalRestClient.GetWithd .ToArray(), nextPageRequest); } + private SharedTransferStatus GetWithdrawalStatus(BitstampWithdrawal x) + { + if (x.Status == WithdrawalStatus.Canceled || x.Status == WithdrawalStatus.Failed) + return SharedTransferStatus.Failed; + + if (x.Status == WithdrawalStatus.Finished) + return SharedTransferStatus.Completed; + + if (x.Status == WithdrawalStatus.InProcess || x.Status == WithdrawalStatus.Open) + return SharedTransferStatus.InProgress; + + return SharedTransferStatus.Unknown; + } + #endregion #region Withdraw client - WithdrawOptions IWithdrawRestClient.WithdrawOptions { get; } = new WithdrawOptions(); + WithdrawOptions IWithdrawRestClient.WithdrawOptions { get; } = new WithdrawOptions(_exchangeName); - async Task> IWithdrawRestClient.WithdrawAsync(WithdrawRequest request, CancellationToken ct) + async Task> IWithdrawRestClient.WithdrawAsync(WithdrawRequest request, CancellationToken ct) { - var validationError = ((IWithdrawRestClient)this).WithdrawOptions.ValidateRequest(Exchange, request, TradingMode.Spot, SupportedTradingModes); + var validationError = SharedClient.WithdrawOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); // Get data var withdrawal = await Account.WithdrawCryptoAsync( @@ -896,41 +892,41 @@ async Task> IWithdrawRestClient.WithdrawAsync(Withdr memoId: request.AddressTag, destinationTag: request.AddressTag, ct: ct).ConfigureAwait(false); - if (!withdrawal) - return withdrawal.AsExchangeResult(Exchange, null, default); + if (!withdrawal.Success) + return HttpResult.Fail(withdrawal); - return withdrawal.AsExchangeResult(Exchange, TradingMode.Spot, new SharedId(withdrawal.Data.WithdrawalId.ToString())); + return HttpResult.Ok(withdrawal, new SharedId(withdrawal.Data.WithdrawalId.ToString())); } #endregion #region Fee Client - EndpointOptions IFeeRestClient.GetFeeOptions { get; } = new EndpointOptions(true); + GetFeeOptions IFeeRestClient.GetFeeOptions { get; } = new GetFeeOptions(_exchangeName, true); - async Task> IFeeRestClient.GetFeesAsync(GetFeeRequest request, CancellationToken ct) + async Task> IFeeRestClient.GetFeesAsync(GetFeeRequest request, CancellationToken ct) { - var validationError = ((IFeeRestClient)this).GetFeeOptions.ValidateRequest(Exchange, request, request.Symbol!.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetFeeOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); // Get data var result = await Account.GetFeesAsync(request.Symbol!.GetSymbol(FormatSymbol), ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); // Return - return result.AsExchangeResult(Exchange, TradingMode.Spot, new SharedFee(result.Data.Fees.MakerFee, result.Data.Fees.TakerFee)); + return HttpResult.Ok(result, new SharedFee(result.Data.Fees.MakerFee, result.Data.Fees.TakerFee)); } #endregion #region Funding Rate client - GetFundingRateHistoryOptions IFundingRateRestClient.GetFundingRateHistoryOptions { get; } = new GetFundingRateHistoryOptions(false, true, true, 1000, false); + GetFundingRateHistoryOptions IFundingRateRestClient.GetFundingRateHistoryOptions { get; } = new GetFundingRateHistoryOptions(_exchangeName, false, true, true, 1000, false); - async Task> IFundingRateRestClient.GetFundingRateHistoryAsync(GetFundingRateHistoryRequest request, PageRequest? pageRequest, CancellationToken ct) + async Task> IFundingRateRestClient.GetFundingRateHistoryAsync(GetFundingRateHistoryRequest request, PageRequest? pageRequest, CancellationToken ct) { - var validationError = ((IFundingRateRestClient)this).GetFundingRateHistoryOptions.ValidateRequest(Exchange, request, request.Symbol!.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetFundingRateHistoryOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var direction = DataDirection.Descending; var symbol = request.Symbol!.GetSymbol(FormatSymbol); @@ -944,8 +940,8 @@ async Task> IFundingRateRestClient.GetFun endTime: pageParams.EndTime, limit: 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.Timestamp).AddHours(-1)), @@ -956,10 +952,7 @@ async Task> IFundingRateRestClient.GetFun pageParams); // Return - return result.AsExchangeResult( - Exchange, - request.Symbol.TradingMode, - ExchangeHelpers.ApplyFilter(result.Data, x => x.Timestamp, request.StartTime, request.EndTime, direction) + return HttpResult.Ok(result, ExchangeHelpers.ApplyFilter(result.Data, x => x.Timestamp, request.StartTime, request.EndTime, direction) .Select(x => new SharedFundingRate(x.FundingRate, x.Timestamp)) .ToArray(), nextPageRequest); @@ -968,21 +961,18 @@ async Task> IFundingRateRestClient.GetFun #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(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.PerpetualLinear, - result.Data.Where(x => x.MarketType == MarketType.Perpetual).Select(s => + var response = HttpResult.Ok(result, result.Data.Where(x => x.MarketType == MarketType.Perpetual).Select(s => new SharedFuturesSymbol( TradingMode.PerpetualLinear, s.BaseAsset, @@ -998,68 +988,65 @@ async Task> IFuturesSymbolRestClient.Ge ContractSize = s.ContractSize }).ToArray()); - ExchangeSymbolCache.UpdateSymbolInfo(_topicFuturesId, response.Data); + ExchangeSymbolCache.UpdateSymbolInfo(_topicFuturesId, EnvironmentName, null, response.Data!); return response; } - async Task> IFuturesSymbolRestClient.GetFuturesSymbolsForBaseAssetAsync(string baseAsset) + 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) - 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, EnvironmentName, null, baseAsset)); } - async Task> IFuturesSymbolRestClient.SupportsFuturesSymbolAsync(SharedSymbol symbol) + async Task> IFuturesSymbolRestClient.SupportsFuturesSymbolAsync(SharedSymbol symbol) { if (symbol.TradingMode == TradingMode.Spot) throw new ArgumentException(nameof(symbol), "Only Futures symbols allowed"); - if (!ExchangeSymbolCache.HasCached(_topicFuturesId)) + if (!ExchangeSymbolCache.HasCached(_topicFuturesId, EnvironmentName, null)) { 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, EnvironmentName, null, symbol)); } - async Task> IFuturesSymbolRestClient.SupportsFuturesSymbolAsync(string symbolName) + 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) - 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, EnvironmentName, null, symbolName)); } #endregion #region 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.Symbol!.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetFuturesTickerOptions.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 ExchangeData.GetTickerAsync(symbol, ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); - return result.AsExchangeResult( - Exchange, - TradingMode.PerpetualLinear, - new SharedFuturesTicker( - ExchangeSymbolCache.ParseSymbol(_topicFuturesId, result.Data.Symbol), + return HttpResult.Ok(result, new SharedFuturesTicker( + ExchangeSymbolCache.ParseSymbol(_topicFuturesId, EnvironmentName, null, result.Data.Symbol), symbol, result.Data.LastPrice, result.Data.HighPrice, @@ -1072,23 +1059,20 @@ async Task> IFuturesTickerRestClient.GetF }); } - 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, TradingMode.PerpetualLinear, 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.GetAllTickersAsync(ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); - return result.AsExchangeResult( - Exchange, - TradingMode.PerpetualLinear, - result.Data.Where(x => x.MarketType == MarketType.Perpetual).Select(x => + return HttpResult.Ok(result, result.Data.Where(x => x.MarketType == MarketType.Perpetual).Select(x => new SharedFuturesTicker( - ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol), + ExchangeSymbolCache.ParseSymbol(_topicFuturesId, EnvironmentName, null, x.Symbol), x.Symbol!, x.LastPrice, x.HighPrice, @@ -1106,86 +1090,86 @@ async Task> IFuturesTickerRestClient.Ge #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.Symbol!.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 Account.GetLeverageSettingsAsync( marginMode: request.MarginMode == SharedMarginMode.Cross ? MarginMode.Cross: MarginMode.Isolated, 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.UnknownSymbol, false, "Symbol not found"))); + return HttpResult.Fail(result, new ServerError(new ErrorInfo(ErrorType.UnknownSymbol, false, "Symbol not found"))); - return result.AsExchangeResult(Exchange, request.Symbol!.TradingMode, new SharedLeverage(result.Data.First().LeverageCurrent) + return HttpResult.Ok(result, new SharedLeverage(result.Data.First().LeverageCurrent) { Side = request.PositionSide }); } - SetLeverageOptions ILeverageRestClient.SetLeverageOptions { get; } = new SetLeverageOptions() + SetLeverageOptions ILeverageRestClient.SetLeverageOptions { get; } = new SetLeverageOptions(_exchangeName) { RequiredOptionalParameters = new List { new ParameterDescription(nameof(SetLeverageRequest.MarginMode), typeof(SharedMarginMode), "Margin mode", SharedMarginMode.Cross) } }; - 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.Symbol!.TradingMode, SupportedTradingModes); + var validationError = SharedClient.SetLeverageOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var result = await Account.SetLeverageAsync( marginMode: request.MarginMode == SharedMarginMode.Cross ? MarginMode.Cross: MarginMode.Isolated, symbol: request.Symbol!.GetSymbol(FormatSymbol), (int)request.Leverage, 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.LeverageCurrent)); + return HttpResult.Ok(result, new SharedLeverage(result.Data.LeverageCurrent)); } #endregion #region Open Interest client - EndpointOptions IOpenInterestRestClient.GetOpenInterestOptions { get; } = new EndpointOptions(true); - async Task> IOpenInterestRestClient.GetOpenInterestAsync(GetOpenInterestRequest request, CancellationToken ct) + GetOpenInterestOptions IOpenInterestRestClient.GetOpenInterestOptions { get; } = new GetOpenInterestOptions(_exchangeName, true); + async Task> IOpenInterestRestClient.GetOpenInterestAsync(GetOpenInterestRequest request, CancellationToken ct) { - var validationError = ((IOpenInterestRestClient)this).GetOpenInterestOptions.ValidateRequest(Exchange, request, request.Symbol!.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetOpenInterestOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var result = await ExchangeData.GetTickerAsync(request.Symbol!.GetSymbol(FormatSymbol), 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 SharedOpenInterest(result.Data.OpenInterest ?? 0)); + return HttpResult.Ok(result, new SharedOpenInterest(result.Data.OpenInterest ?? 0)); } #endregion #region Position History client - GetPositionHistoryOptions IPositionHistoryRestClient.GetPositionHistoryOptions { get; } = new GetPositionHistoryOptions(false, true, false, 1000) + GetPositionHistoryOptions IPositionHistoryRestClient.GetPositionHistoryOptions { get; } = new GetPositionHistoryOptions(_exchangeName, false, true, false, 1000) { RequiredOptionalParameters = new List { new ParameterDescription(nameof(GetPositionHistoryRequest.Symbol), typeof(SharedSymbol), "The symbol to get position history for", "ETH-USDT") } }; - async Task> IPositionHistoryRestClient.GetPositionHistoryAsync(GetPositionHistoryRequest request, PageRequest? pageRequest, CancellationToken ct) + async Task> IPositionHistoryRestClient.GetPositionHistoryAsync(GetPositionHistoryRequest request, PageRequest? pageRequest, CancellationToken ct) { - var validationError = ((IPositionHistoryRestClient)this).GetPositionHistoryOptions.ValidateRequest(Exchange, request, request.Symbol?.TradingMode ?? request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetPositionHistoryOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); var direction = DataDirection.Descending; var symbol = request.Symbol!.GetSymbol(FormatSymbol); @@ -1199,8 +1183,8 @@ async Task> IPositionHistoryRestClien sort: SortOrder.Descending, limit: 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.NextPageFromId(result.Data.OrderBy(x => x.CloseTime).First().Id), @@ -1210,13 +1194,10 @@ async Task> IPositionHistoryRestClien request.EndTime ?? DateTime.UtcNow, pageParams); - return result.AsExchangeResult( - Exchange, - TradingMode.Spot, - ExchangeHelpers.ApplyFilter(result.Data, x => x.OpenTime, request.StartTime, request.EndTime, direction) + return HttpResult.Ok(result, ExchangeHelpers.ApplyFilter(result.Data, x => x.OpenTime, request.StartTime, request.EndTime, direction) .Select(x => new SharedPositionHistory( - ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol), + ExchangeSymbolCache.ParseSymbol(_topicFuturesId, EnvironmentName, null, x.Symbol), x.Symbol, x.QuantityDelta >= 0 ? SharedPositionSide.Short : SharedPositionSide.Long, x.EntryPrice, @@ -1234,7 +1215,7 @@ async Task> IPositionHistoryRestClien #region Futures Order client - PlaceFuturesOrderOptions IFuturesOrderRestClient.PlaceFuturesOrderOptions { get; } = new PlaceFuturesOrderOptions(false); + PlaceFuturesOrderOptions IFuturesOrderRestClient.PlaceFuturesOrderOptions { get; } = new PlaceFuturesOrderOptions(_exchangeName, false); SharedFeeDeductionType IFuturesOrderRestClient.FuturesFeeDeductionType => SharedFeeDeductionType.DeductFromOutput; SharedFeeAssetType IFuturesOrderRestClient.FuturesFeeAssetType => SharedFeeAssetType.QuoteAsset; @@ -1249,18 +1230,11 @@ async Task> IPositionHistoryRestClien string IFuturesOrderRestClient.GenerateClientOrderId() => ExchangeHelpers.RandomString(20); - 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.Symbol!.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); if (request.OrderType == SharedOrderType.Market) { @@ -1275,10 +1249,10 @@ async Task> IFuturesOrderRestClient.PlaceFuturesOrde 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.Id.ToString())); + return HttpResult.Ok(result, new SharedId(result.Data.Id.ToString())); } else { @@ -1297,32 +1271,29 @@ async Task> IFuturesOrderRestClient.PlaceFuturesOrde 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.Id.ToString())); + return HttpResult.Ok(result, new SharedId(result.Data.Id.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.Symbol!.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetFuturesOrderOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); if (!long.TryParse(request.OrderId, out var orderId)) - return new ExchangeWebResult(Exchange, ArgumentError.Invalid(nameof(GetOrderRequest.OrderId), "Invalid order id")); + return HttpResult.Fail(Exchange, ArgumentError.Invalid(nameof(GetOrderRequest.OrderId), "Invalid order id")); var orders = await Trading.GetOrderAsync(orderId, includeTrades: true, ct: ct).ConfigureAwait(false); - if (!orders) - return orders.AsExchangeResult(Exchange, null, default); - - return orders.AsExchangeResult( - Exchange, - TradingMode.PerpetualLinear, - new SharedFuturesOrder( - ExchangeSymbolCache.ParseSymbol(_topicFuturesId, orders.Data.Symbol), + if (!orders.Success) + return HttpResult.Fail(orders); + + return HttpResult.Ok(orders, new SharedFuturesOrder( + ExchangeSymbolCache.ParseSymbol(_topicFuturesId, EnvironmentName, null, orders.Data.Symbol), orders.Data.Symbol, orders.Data.Id.ToString(), ParseOrderType(orders.Data.OrderType), @@ -1341,30 +1312,27 @@ async Task> IFuturesOrderRestClient.GetFut }); } - EndpointOptions IFuturesOrderRestClient.GetOpenFuturesOrdersOptions { get; } = new EndpointOptions(true); - async Task> IFuturesOrderRestClient.GetOpenFuturesOrdersAsync(GetOpenOrdersRequest request, CancellationToken ct) + GetOpenFuturesOrdersOptions IFuturesOrderRestClient.GetOpenFuturesOrdersOptions { get; } = new GetOpenFuturesOrdersOptions(_exchangeName, true); + 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); string? symbol = request.Symbol?.GetSymbol(FormatSymbol); - WebCallResult orders; + HttpResult orders; if (symbol != null) orders = await Trading.GetOpenOrdersAsync(symbol, ct: ct).ConfigureAwait(false); else orders = await Trading.GetOpenOrdersAsync(ct: ct).ConfigureAwait(false); - if (!orders) - return orders.AsExchangeResult(Exchange, null, default); + if (!orders.Success) + return HttpResult.Fail(orders); - return orders.AsExchangeResult( - Exchange, - TradingMode.PerpetualLinear, - orders.Data.Where(x => x.Symbol.EndsWith("-perp", StringComparison.InvariantCultureIgnoreCase)).Select(x => + return HttpResult.Ok(orders, orders.Data.Where(x => x.Symbol.EndsWith("-perp", StringComparison.InvariantCultureIgnoreCase)).Select(x => new SharedFuturesOrder( - ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol), + ExchangeSymbolCache.ParseSymbol(_topicFuturesId, EnvironmentName, null, x.Symbol), x.Symbol, x.Id.ToString(), SharedOrderType.Limit, @@ -1379,12 +1347,12 @@ async Task> IFuturesOrderRestClient.GetO }).ToArray()); } - GetClosedOrdersOptions IFuturesOrderRestClient.GetClosedFuturesOrdersOptions { get; } = new GetClosedOrdersOptions(true, true, false, 500); - async Task> IFuturesOrderRestClient.GetClosedFuturesOrdersAsync(GetClosedOrdersRequest request, PageRequest? pageRequest, CancellationToken ct) + GetFuturesClosedOrdersOptions IFuturesOrderRestClient.GetClosedFuturesOrdersOptions { get; } = new GetFuturesClosedOrdersOptions(_exchangeName, true, true, false, 500); + async Task> IFuturesOrderRestClient.GetClosedFuturesOrdersAsync(GetClosedOrdersRequest request, PageRequest? pageRequest, CancellationToken ct) { - var validationError = ((IFuturesOrderRestClient)this).GetClosedFuturesOrdersOptions.ValidateRequest(Exchange, request, request.Symbol!.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetClosedFuturesOrdersOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); // Determine page token var direction = request.Direction ?? DataDirection.Descending; @@ -1399,8 +1367,8 @@ async Task> IFuturesOrderRestClient.GetC fromEventId: direction == DataDirection.Ascending ? pageParams.FromId : null, toEventId: direction == DataDirection.Descending ? pageParams.FromId : null, ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); var closeData = result.Data.Where(x => x.Event == OrderEvent.OrderDeleted); var nextPageRequest = Pagination.GetNextPageRequest( @@ -1413,10 +1381,7 @@ async Task> IFuturesOrderRestClient.GetC request.EndTime ?? DateTime.UtcNow, pageParams); - return result.AsExchangeResult( - Exchange, - TradingMode.PerpetualLinear, - ExchangeHelpers.ApplyFilter(closeData, x => x.Data.Timestamp, request.StartTime, request.EndTime, direction) + return HttpResult.Ok(result, ExchangeHelpers.ApplyFilter(closeData, x => x.Data.Timestamp, request.StartTime, request.EndTime, direction) .Select(x => new SharedFuturesOrder( request.Symbol, symbol, @@ -1434,27 +1399,27 @@ 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.Symbol!.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetFuturesOrderTradesOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); if (!long.TryParse(request.OrderId, out var orderId)) - return new ExchangeWebResult(Exchange, ArgumentError.Invalid(nameof(GetOrderTradesRequest.OrderId), "Invalid order id")); + return HttpResult.Fail(Exchange, ArgumentError.Invalid(nameof(GetOrderTradesRequest.OrderId), "Invalid order id")); var orders = await Trading.GetOrderAsync(orderId, includeTrades: true, 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.Trades?.Select(x => new SharedUserTrade( + return HttpResult.Ok(orders, orders.Data.Trades?.Select(x => new SharedUserTrade( request.Symbol, orders.Data.Symbol, orders.Data.Id.ToString(), x.TradeId.ToString(), orders.Data.Side == OrderSide.Buy ? SharedOrderSide.Buy : SharedOrderSide.Sell, - x.Quantities.TryGetValue(request.Symbol.BaseAsset, out var quantity) ? quantity : 0, + x.Quantities.TryGetValue(request.Symbol!.BaseAsset, out var quantity) ? quantity : 0, x.Price, x.Timestamp) { @@ -1463,12 +1428,12 @@ async Task> IFuturesOrderRestClient.GetFutu })?.ToArray() ?? []); } - GetUserTradesOptions IFuturesOrderRestClient.GetFuturesUserTradesOptions { get; } = new GetUserTradesOptions(false, true, true, 1000); - async Task> IFuturesOrderRestClient.GetFuturesUserTradesAsync(GetUserTradesRequest request, PageRequest? pageRequest, CancellationToken ct) + GetFuturesUserTradesOptions IFuturesOrderRestClient.GetFuturesUserTradesOptions { get; } = new GetFuturesUserTradesOptions(_exchangeName, false, true, true, 1000); + async Task> IFuturesOrderRestClient.GetFuturesUserTradesAsync(GetUserTradesRequest request, PageRequest? pageRequest, CancellationToken ct) { - var validationError = ((IFuturesOrderRestClient)this).GetFuturesUserTradesOptions.ValidateRequest(Exchange, request, request.Symbol!.TradingMode, SupportedTradingModes); + var validationError = SharedClient.GetFuturesUserTradesOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); // Determine page token int limit = request.Limit ?? 1000; @@ -1484,8 +1449,8 @@ async Task> IFuturesOrderRestClient.GetFutu limit: pageParams.Limit, afterId: pageParams.FromId == null ? null : long.Parse(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.Min(x => x.TradeId)), @@ -1495,12 +1460,9 @@ async Task> IFuturesOrderRestClient.GetFutu request.EndTime ?? DateTime.UtcNow, pageParams); - return result.AsExchangeResult( - Exchange, - TradingMode.PerpetualLinear, - ExchangeHelpers.ApplyFilter(result.Data, x => x.Timestamp, request.StartTime, request.EndTime, direction) + return HttpResult.Ok(result, 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(), @@ -1515,45 +1477,42 @@ 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.Symbol!.TradingMode, SupportedTradingModes); + var validationError = SharedClient.CancelFuturesOrderOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeWebResult(Exchange, validationError); + return HttpResult.Fail(Exchange, validationError); if (!long.TryParse(request.OrderId, out var orderId)) - return new ExchangeWebResult(Exchange, ArgumentError.Invalid(nameof(CancelOrderRequest.OrderId), "Invalid order id")); + return HttpResult.Fail(Exchange, ArgumentError.Invalid(nameof(CancelOrderRequest.OrderId), "Invalid order id")); var order = await Trading.CancelOrderAsync(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.Id.ToString())); + return HttpResult.Ok(order, new SharedId(order.Data.Id.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); - WebCallResult result; + HttpResult result; if (request.Symbol != null) result = await Trading.GetOpenPositionsAsync(symbol: request.Symbol!.GetSymbol(FormatSymbol), ct: ct).ConfigureAwait(false); else result = await Trading.GetOpenPositionsAsync(ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); - return result.AsExchangeResult( - Exchange, - TradingMode.PerpetualLinear, - result.Data.Select(x => + return HttpResult.Ok(result, result.Data.Select(x => new SharedPosition( - ExchangeSymbolCache.ParseSymbol(_topicFuturesId, x.Symbol), + ExchangeSymbolCache.ParseSymbol(_topicFuturesId, EnvironmentName, null, x.Symbol), x.Symbol, Math.Abs(x.Quantity), default) @@ -1567,7 +1526,7 @@ async Task> IFuturesOrderRestClient.GetPosit }).ToArray()); } - EndpointOptions IFuturesOrderRestClient.ClosePositionOptions { get; } = new EndpointOptions(true) + ClosePositionOptions IFuturesOrderRestClient.ClosePositionOptions { get; } = new ClosePositionOptions(_exchangeName, true) { RequiredOptionalParameters = new List { @@ -1575,22 +1534,22 @@ 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.Symbol!.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.ClosePositionsAsync( symbol, request.MarginMode == null ? null : request.MarginMode == SharedMarginMode.Isolated ? MarginMode.Isolated : MarginMode.Cross, ct: ct).ConfigureAwait(false); - if (!result) - return result.AsExchangeResult(Exchange, null, default); + if (!result.Success) + return HttpResult.Fail(result); var id = result.Data.Closed.FirstOrDefault()?.Id ?? result.Data.Failed.FirstOrDefault()?.Id; - return result.AsExchangeResult(Exchange, request.Symbol!.TradingMode, new SharedId(id?.ToString() ?? string.Empty)); + return HttpResult.Ok(result, new SharedId(id?.ToString() ?? string.Empty)); } #endregion } diff --git a/Bitstamp.Net/Clients/ExchangeApi/BitstampRestClientExchangeApiTrading.cs b/Bitstamp.Net/Clients/ExchangeApi/BitstampRestClientExchangeApiTrading.cs index 262e2b9..084e6b0 100644 --- a/Bitstamp.Net/Clients/ExchangeApi/BitstampRestClientExchangeApiTrading.cs +++ b/Bitstamp.Net/Clients/ExchangeApi/BitstampRestClientExchangeApiTrading.cs @@ -19,7 +19,7 @@ internal BitstampRestClientExchangeApiTrading(BitstampRestClientExchangeApi base #region Place Limit Order /// - public Task> PlaceLimitOrderAsync( + public Task> PlaceLimitOrderAsync( string symbol, OrderSide side, decimal price, @@ -42,30 +42,31 @@ public Task> PlaceLimitOrderAsync( string? clientOrderId = null, CancellationToken ct = default) { - var parameters = new ParameterCollection + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings) { { "price", price }, }; - parameters.AddOptionalEnum("subtype", orderType); - parameters.AddOptional("amount", quantity); - parameters.AddOptional("limit_price", limitPrice); - parameters.AddOptional("client_order_id", clientOrderId); - parameters.AddOptionalBoolString("daily_order", dailyOrder); - parameters.AddOptionalBoolString("ioc_order", iocOrder); - parameters.AddOptionalBoolString("fok_order", fokOrder); - parameters.AddOptionalBoolString("moc_order", mocOrder); - parameters.AddOptionalBoolString("gtd_order", gtdOrder); - parameters.AddOptionalMilliseconds("expire_time", expireTime); - parameters.AddOptionalEnum("margin_mode", marginMode); - parameters.AddOptionalString("leverage", leverage); - parameters.AddOptionalString("stop_price", stopPrice); - parameters.AddOptionalString("activation_price", activationPrice); - parameters.AddOptionalString("trailing_delta", trailingDelta); - parameters.AddOptionalEnum("trigger", triggerType); - parameters.AddOptionalBoolString("trigger", reduceOnly); +#warning test amount/price decimal or string + parameters.Add("subtype", orderType); + parameters.Add("amount", quantity); + parameters.Add("limit_price", limitPrice); + parameters.Add("client_order_id", clientOrderId); + parameters.Add("daily_order", dailyOrder); + parameters.Add("ioc_order", iocOrder); + parameters.Add("fok_order", fokOrder); + parameters.Add("moc_order", mocOrder); + parameters.Add("gtd_order", gtdOrder); + parameters.Add("expire_time", expireTime); + parameters.Add("margin_mode", marginMode); + parameters.Add("leverage", leverage); + parameters.Add("stop_price", stopPrice); + parameters.Add("activation_price", activationPrice); + parameters.Add("trailing_delta", trailingDelta); + parameters.Add("trigger", triggerType); + parameters.Add("trigger", reduceOnly); var uri = side == OrderSide.Buy ? $"/api/v2/buy/{BitstampExchange.SymbolToPathParameter(symbol)}/" : $"/api/v2/sell/{BitstampExchange.SymbolToPathParameter(symbol)}/"; - var request = _definitions.GetOrCreate(HttpMethod.Post, uri, BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, uri, BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); return _baseClient.SendAsync(request, parameters, ct); } @@ -74,7 +75,7 @@ public Task> PlaceLimitOrderAsync( #region Place Market Order /// - public Task> PlaceMarketOrderAsync( + public Task> PlaceMarketOrderAsync( string symbol, OrderSide side, OrderType? orderType = null, @@ -89,20 +90,20 @@ public Task> PlaceMarketOrderAsync( string? clientOrderId = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddOptionalEnum("subtype", orderType); - parameters.AddOptional("amount", quantity); - parameters.AddOptional("client_order_id", clientOrderId); - parameters.AddOptionalEnum("margin_mode", marginMode); - parameters.AddOptionalString("leverage", leverage); - parameters.AddOptionalString("stop_price", stopPrice); - parameters.AddOptionalString("activation_price", activationPrice); - parameters.AddOptionalString("trailing_delta", trailingDelta); - parameters.AddOptionalEnum("trigger", triggerType); - parameters.AddOptionalBoolString("trigger", reduceOnly); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + parameters.Add("subtype", orderType); + parameters.Add("amount", quantity); + parameters.Add("client_order_id", clientOrderId); + parameters.Add("margin_mode", marginMode); + parameters.Add("leverage", leverage); + parameters.Add("stop_price", stopPrice); + parameters.Add("activation_price", activationPrice); + parameters.Add("trailing_delta", trailingDelta); + parameters.Add("trigger", triggerType); + parameters.Add("trigger", reduceOnly); var uri = side == OrderSide.Buy ? $"/api/v2/buy/market/{BitstampExchange.SymbolToPathParameter(symbol)}/" : $"/api/v2/sell/market/{BitstampExchange.SymbolToPathParameter(symbol)}/"; - var request = _definitions.GetOrCreate(HttpMethod.Post, uri, BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, uri, BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); return _baseClient.SendAsync(request, parameters, ct); } @@ -111,14 +112,14 @@ public Task> PlaceMarketOrderAsync( #region Get Order History /// - public async Task> GetOrderHistoryAsync(OrderSource orderSource, string symbol, string? fromEventId = null, string? toEventId = null, CancellationToken ct = default) + public async Task> GetOrderHistoryAsync(OrderSource orderSource, string symbol, string? fromEventId = null, string? toEventId = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddEnum("order_source", orderSource); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + parameters.Add("order_source", orderSource); parameters.Add("market", symbol); - parameters.AddOptional("since_id", fromEventId); - parameters.AddOptional("until_id", toEventId); - var request = _definitions.GetOrCreate(HttpMethod.Post, "/api/v2/account_order_data/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + parameters.Add("since_id", fromEventId); + parameters.Add("until_id", toEventId); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, "/api/v2/account_order_data/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -128,12 +129,12 @@ public async Task> GetOrderHistoryAsync(Orde #region Cancel Order /// - public Task> CancelOrderAsync(long? orderId = null, string? clientOrderId = null, CancellationToken ct = default) + public Task> CancelOrderAsync(long? orderId = null, string? clientOrderId = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddOptional("id", orderId); - parameters.AddOptional("client_order_id", clientOrderId); - var request = _definitions.GetOrCreate(HttpMethod.Post, "/api/v2/cancel_order/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + parameters.Add("id", orderId); + parameters.Add("client_order_id", clientOrderId); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, "/api/v2/cancel_order/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); return _baseClient.SendAsync(request, parameters, ct); } @@ -142,9 +143,9 @@ public Task> CancelOrderAsync(long? o #region Cancel All Orders /// - public Task> CancelAllOrdersAsync(CancellationToken ct = default) + public Task> CancelAllOrdersAsync(CancellationToken ct = default) { - var request = _definitions.GetOrCreate(HttpMethod.Post, "/api/v2/cancel_all_order/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, "/api/v2/cancel_all_order/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); return _baseClient.SendAsync(request, null, ct); } @@ -153,9 +154,9 @@ public Task> CancelAllOrdersAsync( #region Cancel All Orders /// - public Task> CancelAllOrdersAsync(string symbol, CancellationToken ct = default) + public Task> CancelAllOrdersAsync(string symbol, CancellationToken ct = default) { - var request = _definitions.GetOrCreate(HttpMethod.Post, $"/api/v2/cancel_all_order/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, $"/api/v2/cancel_all_order/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); return _baseClient.SendAsync(request, null, ct); } @@ -164,7 +165,7 @@ public Task> CancelAllOrdersAsync( #region Replace Order /// - public Task> ReplaceOrderAsync( + public Task> ReplaceOrderAsync( decimal price, decimal quantity, long? id = null, @@ -172,14 +173,14 @@ public Task> ReplaceOrderAsync( string? newClientOrderId = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddOptional("id", id); - parameters.AddOptional("orig_client_order_id", clientOrderId); - parameters.AddOptional("client_order_id", newClientOrderId); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + parameters.Add("id", id); + parameters.Add("orig_client_order_id", clientOrderId); + parameters.Add("client_order_id", newClientOrderId); parameters.Add("amount", quantity); parameters.Add("price", price); - var request = _definitions.GetOrCreate(HttpMethod.Post, $"/api/v2/replace_order/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, $"/api/v2/replace_order/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); return _baseClient.SendAsync(request, parameters, ct); } @@ -188,14 +189,14 @@ public Task> ReplaceOrderAsync( #region Get Order /// - public Task> GetOrderAsync(long? orderId = null, string? clientOrderId = null, bool? includeTrades = null, CancellationToken ct = default) + public Task> GetOrderAsync(long? orderId = null, string? clientOrderId = null, bool? includeTrades = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddOptional("id", orderId); - parameters.AddOptional("client_order_id", clientOrderId); - parameters.AddOptionalBoolString("omit_transactions", includeTrades); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + parameters.Add("id", orderId); + parameters.Add("client_order_id", clientOrderId); + parameters.Add("omit_transactions", includeTrades); - var request = _definitions.GetOrCreate(HttpMethod.Post, "/api/v2/order_status/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, "/api/v2/order_status/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); return _baseClient.SendAsync(request, parameters, ct); } @@ -204,9 +205,9 @@ public Task> GetOrderAsync(long? orderId = null, st #region Get Open Orders /// - public Task> GetOpenOrdersAsync(CancellationToken ct = default) + public Task> GetOpenOrdersAsync(CancellationToken ct = default) { - var request = _definitions.GetOrCreate(HttpMethod.Post, "/api/v2/open_orders/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, "/api/v2/open_orders/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); return _baseClient.SendAsync(request, null, ct); } @@ -215,9 +216,9 @@ public Task> GetOpenOrdersAsync(CancellationT #region Get Open Orders /// - public Task> GetOpenOrdersAsync(string symbol, CancellationToken ct = default) + public Task> GetOpenOrdersAsync(string symbol, CancellationToken ct = default) { - var request = _definitions.GetOrCreate(HttpMethod.Post, $"/api/v2/open_orders/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, $"/api/v2/open_orders/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); return _baseClient.SendAsync(request, null, ct); } @@ -226,7 +227,7 @@ public Task> GetOpenOrdersAsync(string symbol #region Get Derivatives User Trades /// - public Task> GetDerivativesUserTradesAsync( + public Task> GetDerivativesUserTradesAsync( long? orderId = null, long? afterId = null, SortOrder? sort = null, @@ -235,15 +236,15 @@ public Task> GetDerivativesUserTradesAsync( int? limit = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddOptional("limit", limit); - parameters.AddOptionalEnum("sort", sort); - parameters.AddOptional("order_id", orderId); - parameters.AddOptionalMilliseconds("since_timestamp", startTime); - parameters.AddOptionalMilliseconds("until_timestamp", endTime); - parameters.AddOptional("after_id", afterId); - - var request = _definitions.GetOrCreate(HttpMethod.Get, "/api/v2/trade_history/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + parameters.Add("limit", limit); + parameters.Add("sort", sort); + parameters.Add("order_id", orderId); + parameters.Add("since_timestamp", startTime); + parameters.Add("until_timestamp", endTime); + parameters.Add("after_id", afterId); + + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "/api/v2/trade_history/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); return _baseClient.SendAsync(request, parameters, ct); } @@ -252,7 +253,7 @@ public Task> GetDerivativesUserTradesAsync( #region Get Derivatives User Trades /// - public Task> GetDerivativesUserTradesAsync( + public Task> GetDerivativesUserTradesAsync( string symbol, long? orderId = null, long? afterId = null, @@ -262,15 +263,15 @@ public Task> GetDerivativesUserTradesAsync( int? limit = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddOptional("limit", limit); - parameters.AddOptionalEnum("sort", sort); - parameters.AddOptional("order_id", orderId); - parameters.AddOptionalMilliseconds("since_timestamp", startTime); - parameters.AddOptionalMilliseconds("until_timestamp", endTime); - parameters.AddOptional("after_id", afterId); - - var request = _definitions.GetOrCreate(HttpMethod.Get, $"/api/v2/trade_history/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + parameters.Add("limit", limit); + parameters.Add("sort", sort); + parameters.Add("order_id", orderId); + parameters.Add("since_timestamp", startTime); + parameters.Add("until_timestamp", endTime); + parameters.Add("after_id", afterId); + + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, $"/api/v2/trade_history/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); return _baseClient.SendAsync(request, parameters, ct); } @@ -279,10 +280,10 @@ public Task> GetDerivativesUserTradesAsync( #region Get Open Positions /// - public async Task> GetOpenPositionsAsync(CancellationToken ct = default) + public async Task> GetOpenPositionsAsync(CancellationToken ct = default) { - var parameters = new ParameterCollection(); - var request = _definitions.GetOrCreate(HttpMethod.Get, "/api/v2/open_positions/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "/api/v2/open_positions/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -292,10 +293,10 @@ public async Task> GetOpenPositionsAsync(Cance #region Get Open Positions /// - public async Task> GetOpenPositionsAsync(string symbol, CancellationToken ct = default) + public async Task> GetOpenPositionsAsync(string symbol, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - var request = _definitions.GetOrCreate(HttpMethod.Get, $"/api/v2/open_positions/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, $"/api/v2/open_positions/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -305,10 +306,10 @@ public async Task> GetOpenPositionsAsync(strin #region Get Position Status /// - public async Task> GetPositionStatusAsync(string positionId, CancellationToken ct = default) + public async Task> GetPositionStatusAsync(string positionId, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - var request = _definitions.GetOrCreate(HttpMethod.Get, $"/api/v2/position_status/{positionId}/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, $"/api/v2/position_status/{positionId}/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -318,14 +319,14 @@ public async Task> GetPositionStatusAsync(string #region Get Position History /// - public async Task> GetPositionHistoryAsync(string? sinceId = null, SortOrder? sort = null, int? limit = null, int? offset = null, CancellationToken ct = default) + public async Task> GetPositionHistoryAsync(string? sinceId = null, SortOrder? sort = null, int? limit = null, int? offset = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddOptional("since_id", sinceId); - parameters.AddOptionalEnum("sort", sort); - parameters.AddOptional("limit", limit); - parameters.AddOptional("offset", offset); - var request = _definitions.GetOrCreate(HttpMethod.Get, "/api/v2/position_history/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + parameters.Add("since_id", sinceId); + parameters.Add("sort", sort); + parameters.Add("limit", limit); + parameters.Add("offset", offset); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "/api/v2/position_history/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -335,14 +336,14 @@ public async Task> GetPositionHistoryAs #region Get Position History /// - public async Task> GetPositionHistoryAsync(string symbol, string? sinceId = null, SortOrder? sort = null, int? limit = null, int? offset = null, CancellationToken ct = default) + public async Task> GetPositionHistoryAsync(string symbol, string? sinceId = null, SortOrder? sort = null, int? limit = null, int? offset = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddOptional("since_id", sinceId); - parameters.AddOptionalEnum("sort", sort); - parameters.AddOptional("limit", limit); - parameters.AddOptional("offset", offset); - var request = _definitions.GetOrCreate(HttpMethod.Get, $"/api/v2/position_history/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + parameters.Add("since_id", sinceId); + parameters.Add("sort", sort); + parameters.Add("limit", limit); + parameters.Add("offset", offset); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, $"/api/v2/position_history/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -352,13 +353,13 @@ public async Task> GetPositionHistoryAs #region Close Positions /// - public async Task> ClosePositionsAsync(string? symbol = null, MarginMode? marginMode = null, CancellationToken ct = default) + public async Task> ClosePositionsAsync(string? symbol = null, MarginMode? marginMode = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddEnum("order_type", OrderType.Market); - parameters.AddOptional("market", symbol == null ? null : BitstampExchange.SymbolToPathParameter(symbol)); - parameters.AddOptionalEnum("margin_mode", marginMode); - var request = _definitions.GetOrCreate(HttpMethod.Post, "/api/v2/close_positions/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + parameters.Add("order_type", OrderType.Market); + parameters.Add("market", symbol == null ? null : BitstampExchange.SymbolToPathParameter(symbol)); + parameters.Add("margin_mode", marginMode); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, "/api/v2/close_positions/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -368,11 +369,11 @@ public async Task> ClosePositionsA #region Close Position /// - public async Task> ClosePositionAsync(string positionId, CancellationToken ct = default) + public async Task> ClosePositionAsync(string positionId, CancellationToken ct = default) { - var parameters = new ParameterCollection(); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); parameters.Add("position_id", positionId); - var request = _definitions.GetOrCreate(HttpMethod.Post, "/api/v2/close_position/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, "/api/v2/close_position/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -382,16 +383,16 @@ public async Task> ClosePositionAsync(string pos #region Get Position Settlement Transactions /// - public async Task> GetPositionSettlementTransactionsAsync(string? sinceId = null, string? sort = null, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, int? offset = null, CancellationToken ct = default) + public async Task> GetPositionSettlementTransactionsAsync(string? sinceId = null, string? sort = null, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, int? offset = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddOptional("since_id", sinceId); - parameters.AddOptional("sort", sort); - parameters.AddOptionalMillisecondsString("since_timestamp", startTime); - parameters.AddOptionalMillisecondsString("until_timestamp", endTime); - parameters.AddOptional("limit", limit); - parameters.AddOptional("offset", offset); - var request = _definitions.GetOrCreate(HttpMethod.Get, "/api/v2/position_settlement_transactions/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + parameters.Add("since_id", sinceId); + parameters.Add("sort", sort); + parameters.Add("since_timestamp", startTime); + parameters.Add("until_timestamp", endTime); + parameters.Add("limit", limit); + parameters.Add("offset", offset); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, "/api/v2/position_settlement_transactions/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -401,16 +402,16 @@ public async Task> GetPositionSettlem #region Get Position Settlement Transactions /// - public async Task> GetPositionSettlementTransactionsAsync(string symbol, string? sinceId = null, string? sort = null, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, int? offset = null, CancellationToken ct = default) + public async Task> GetPositionSettlementTransactionsAsync(string symbol, string? sinceId = null, string? sort = null, DateTime? startTime = null, DateTime? endTime = null, int? limit = null, int? offset = null, CancellationToken ct = default) { - var parameters = new ParameterCollection(); - parameters.AddOptional("since_id", sinceId); - parameters.AddOptional("sort", sort); - parameters.AddOptionalMillisecondsString("since_timestamp", startTime); - parameters.AddOptionalMillisecondsString("until_timestamp", endTime); - parameters.AddOptional("limit", limit); - parameters.AddOptional("offset", offset); - var request = _definitions.GetOrCreate(HttpMethod.Get, $"/api/v2/position_settlement_transactions/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); + parameters.Add("since_id", sinceId); + parameters.Add("sort", sort); + parameters.Add("since_timestamp", startTime); + parameters.Add("until_timestamp", endTime); + parameters.Add("limit", limit); + parameters.Add("offset", offset); + var request = _definitions.GetOrCreate(HttpMethod.Get, _baseClient.BaseAddress, $"/api/v2/position_settlement_transactions/{BitstampExchange.SymbolToPathParameter(symbol)}/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } @@ -420,12 +421,12 @@ public async Task> GetPositionSettlem #region Update Position Collateral /// - public async Task UpdatePositionCollateralAsync(string positionId, decimal newCollateralQuantity, CancellationToken ct = default) + public async Task UpdatePositionCollateralAsync(string positionId, decimal newCollateralQuantity, CancellationToken ct = default) { - var parameters = new ParameterCollection(); + var parameters = new Parameters(BitstampExchange._parameterSerializationSettings); parameters.Add("position_id", positionId); parameters.Add("new_account", newCollateralQuantity); - var request = _definitions.GetOrCreate(HttpMethod.Post, "/api/v2/adjust_position_collateral/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); + var request = _definitions.GetOrCreate(HttpMethod.Post, _baseClient.BaseAddress, "/api/v2/adjust_position_collateral/", BitstampExchange.RateLimiter.Rest, 1, true, forcePathEndWithSlash: true); var result = await _baseClient.SendAsync(request, parameters, ct).ConfigureAwait(false); return result; } diff --git a/Bitstamp.Net/Clients/ExchangeApi/BitstampSocketClientExchangeApi.cs b/Bitstamp.Net/Clients/ExchangeApi/BitstampSocketClientExchangeApi.cs index 2fbaeda..c93ce27 100644 --- a/Bitstamp.Net/Clients/ExchangeApi/BitstampSocketClientExchangeApi.cs +++ b/Bitstamp.Net/Clients/ExchangeApi/BitstampSocketClientExchangeApi.cs @@ -36,10 +36,10 @@ internal partial class BitstampSocketClientExchangeApi : SocketApiClient /// Create a new instance of BitstampSocketClient with default options /// - internal BitstampSocketClientExchangeApi(ILogger logger, BitstampSocketOptions options, BitstampSocketKeyGenerator keyGenerator) - : base(logger, options.Environment.SocketBaseAddress, options, options.ApiOptions) + internal BitstampSocketClientExchangeApi(ILoggerFactory? loggerFactory, BitstampSocketOptions options, BitstampSocketKeyGenerator keyGenerator) + : base(loggerFactory, BitstampExchange.Metadata.Id, options.Environment.SocketBaseAddress, options, options.ApiOptions) { - AddSystemSubscription(new BitstampReconnectSubsciption(logger, keyGenerator)); + AddSystemSubscription(new BitstampReconnectSubscription(_logger, keyGenerator)); _keyGenerator = keyGenerator; RegisterPeriodicQuery( @@ -70,14 +70,14 @@ protected override IMessageSerializer CreateSerializer() protected override async Task RevitalizeRequestAsync(Subscription subscription) { if (subscription is not BitstampSubscription authSubscription || !authSubscription.Authenticated) - return new CallResult(null); + return CallResult.Ok(); var newToken = await _keyGenerator.GenerateWebsocketKeyAsync().ConfigureAwait(false); if (!newToken.Success) - return newToken.AsDataless(); + return newToken; authSubscription.AuthToken = newToken.Data; - return new CallResult(null); + return CallResult.Ok(); } protected override Task GetAuthenticationRequestAsync(SocketConnection connection) @@ -94,7 +94,7 @@ public override string FormatSymbol(string baseAsset, string quoteAsset, Trading #endregion #region Subscriptions - public Task> SubscribeToTradeUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default) + public Task> SubscribeToTradeUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default) { var internalHandler = new Action>((receiveTime, originalData, data) => { @@ -113,7 +113,7 @@ public Task> SubscribeToTradeUpdatesAsync(string return SubscribeAsync(subscription, ct); } - public Task> SubscribeToFullOrderBookUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default) + public Task> SubscribeToFullOrderBookUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default) { var internalHandler = new Action>((receiveTime, originalData, data) => { @@ -132,7 +132,7 @@ public Task> SubscribeToFullOrderBookUpdatesAsync return SubscribeAsync(subscription, ct); } - public Task> SubscribeToOrderBookSnapshotUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default) + public Task> SubscribeToOrderBookSnapshotUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default) { var internalHandler = new Action>((receiveTime, originalData, data) => { @@ -151,7 +151,7 @@ public Task> SubscribeToOrderBookSnapshotUpdatesA return SubscribeAsync(subscription, ct); } - public Task> SubscribeToFundingRateUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default) + public Task> SubscribeToFundingRateUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default) { var internalHandler = new Action>((receiveTime, originalData, data) => { @@ -171,11 +171,11 @@ public Task> SubscribeToFundingRateUpdatesAsync(s } - public async Task> SubscribeToOrderUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default) + public async Task> SubscribeToOrderUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default) { var authToken = await _keyGenerator.GenerateWebsocketKeyAsync().ConfigureAwait(false); if (!authToken.Success) - return new CallResult(authToken.Error!); + return WebSocketResult.Fail(Exchange, authToken.Error!); var internalHandler = new Action>((receiveTime, originalData, data) => { @@ -205,11 +205,11 @@ private OrderEvent GetOrderEvent(SocketEventType @event) _ => OrderEvent.OrderChanged }; - public async Task> SubscribeToUserTradeUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default) + public async Task> SubscribeToUserTradeUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default) { var authToken = await _keyGenerator.GenerateWebsocketKeyAsync().ConfigureAwait(false); if (!authToken.Success) - return new CallResult(authToken.Error!); + return WebSocketResult.Fail(Exchange, authToken.Error!); var internalHandler = new Action>((receiveTime, originalData, data) => { diff --git a/Bitstamp.Net/Clients/ExchangeApi/BitstampSocketClientExchangeApiShared.cs b/Bitstamp.Net/Clients/ExchangeApi/BitstampSocketClientExchangeApiShared.cs index 3be39b9..000ea87 100644 --- a/Bitstamp.Net/Clients/ExchangeApi/BitstampSocketClientExchangeApiShared.cs +++ b/Bitstamp.Net/Clients/ExchangeApi/BitstampSocketClientExchangeApiShared.cs @@ -9,52 +9,52 @@ namespace Bitstamp.Net.Clients.ExchangeApi { internal partial class BitstampSocketClientExchangeApi : IBitstampSocketClientExchangeApiShared { - private const string _topicId = "Bitstamp"; + private const string _exchangeName = "Bitstamp"; - public string Exchange => BitstampExchange.ExchangeName; public TradingMode[] SupportedTradingModes { get; } = new[] { TradingMode.Spot }; public void SetDefaultExchangeParameter(string key, object value) => ExchangeParameters.SetStaticParameter(Exchange, key, value); public void ResetDefaultExchangeParameters() => ExchangeParameters.ResetStaticParameters(); + public SharedClientInfo Discover() => SharedUtils.GetClientInfo(BitstampExchange.Metadata, this); #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 result = await SubscribeToTradeUpdatesAsync(request.Symbol!.GetSymbol(FormatSymbol), update => { if (update.UpdateType == SocketUpdateType.Snapshot) return; - handler(update.ToType([new SharedTrade(ExchangeSymbolCache.ParseSymbol(_topicId, update.Symbol), update.Symbol!, update.Data.Quantity, update.Data.Price, update.Data.Timestamp) + handler(update.ToType([new SharedTrade(request.Symbol, update.Symbol!, update.Data.Quantity, update.Data.Price, update.Data.Timestamp) { Side = update.Data.Side == OrderSide.Buy ? SharedOrderSide.Buy : SharedOrderSide.Sell } ])); }, ct).ConfigureAwait(false); - return new ExchangeResult(Exchange, result); + return result; } #endregion #region Order Book client - SubscribeOrderBookOptions IOrderBookSocketClient.SubscribeOrderBookOptions { get; } = new SubscribeOrderBookOptions(false, [100]); - async Task> IOrderBookSocketClient.SubscribeToOrderBookUpdatesAsync(SubscribeOrderBookRequest request, Action> handler, CancellationToken ct) + SubscribeOrderBookOptions IOrderBookSocketClient.SubscribeOrderBookOptions { get; } = new SubscribeOrderBookOptions(_exchangeName, false, [100]); + async Task> IOrderBookSocketClient.SubscribeToOrderBookUpdatesAsync(SubscribeOrderBookRequest request, Action> handler, CancellationToken ct) { - var validationError = ((IOrderBookSocketClient)this).SubscribeOrderBookOptions.ValidateRequest(Exchange, request, request.TradingMode, SupportedTradingModes); + var validationError = SharedClient.SubscribeOrderBookOptions.ValidateRequest(request, this); if (validationError != null) - return new ExchangeResult(Exchange, validationError); + return WebSocketResult.Fail(_exchangeName, validationError); var result = await SubscribeToOrderBookSnapshotUpdatesAsync( request.Symbol!.GetSymbol(FormatSymbol), update => handler(update.ToType(new SharedOrderBook(update.Data.Asks, update.Data.Bids))), ct).ConfigureAwait(false); - return new ExchangeResult(Exchange, result); + return result; } #endregion } diff --git a/Bitstamp.Net/Converters/BitstampSourceGenerationContext.cs b/Bitstamp.Net/Converters/BitstampSourceGenerationContext.cs index 35d56ee..653776b 100644 --- a/Bitstamp.Net/Converters/BitstampSourceGenerationContext.cs +++ b/Bitstamp.Net/Converters/BitstampSourceGenerationContext.cs @@ -1,6 +1,7 @@ using System.Text.Json.Serialization; using Bitstamp.Net.Objects.Models; using Bitstamp.Net.Objects.Models.Socket; +using CryptoExchange.Net.Objects; namespace Bitstamp.Net.Converters { @@ -65,6 +66,8 @@ namespace Bitstamp.Net.Converters [JsonSerializable(typeof(string))] [JsonSerializable(typeof(string[]))] [JsonSerializable(typeof(bool))] + [JsonSerializable(typeof(Parameters))] + [JsonSerializable(typeof(Parameters[]))] internal partial class BitstampSourceGenerationContext : JsonSerializerContext { } } diff --git a/Bitstamp.Net/Interfaces/Clients/ExchangeApi/IBitstampRestClientExchangeApiAccount.cs b/Bitstamp.Net/Interfaces/Clients/ExchangeApi/IBitstampRestClientExchangeApiAccount.cs index 4da91a5..65149dd 100644 --- a/Bitstamp.Net/Interfaces/Clients/ExchangeApi/IBitstampRestClientExchangeApiAccount.cs +++ b/Bitstamp.Net/Interfaces/Clients/ExchangeApi/IBitstampRestClientExchangeApiAccount.cs @@ -19,7 +19,7 @@ public interface IBitstampRestClientExchangeApiAccount /// /// /// Cancellation token - Task> GetAccountBalancesAsync(CancellationToken ct = default); + Task> GetAccountBalancesAsync(CancellationToken ct = default); /// /// Get user account balance @@ -32,7 +32,7 @@ public interface IBitstampRestClientExchangeApiAccount /// /// ["asset"] Asset name /// Cancellation token - Task> GetAccountBalanceAsync(string asset, CancellationToken ct = default); + Task> GetAccountBalanceAsync(string asset, CancellationToken ct = default); /// /// Get withdraw fees @@ -44,7 +44,7 @@ public interface IBitstampRestClientExchangeApiAccount /// /// /// Cancellation token - Task> GetWithdrawFeesAsync(CancellationToken ct = default); + Task> GetWithdrawFeesAsync(CancellationToken ct = default); /// /// Get withdraw fees @@ -58,7 +58,7 @@ public interface IBitstampRestClientExchangeApiAccount /// ["asset"] Asset name /// ["network"] Network /// Cancellation token - Task> GetWithdrawFeesAsync(string asset, string? network = null, CancellationToken ct = default); + Task> GetWithdrawFeesAsync(string asset, string? network = null, CancellationToken ct = default); /// /// Get trading fees @@ -70,7 +70,7 @@ public interface IBitstampRestClientExchangeApiAccount /// /// /// Cancellation token - Task> GetAllFeesAsync(CancellationToken ct = default); + Task> GetAllFeesAsync(CancellationToken ct = default); /// /// Get trading fees for a symbol @@ -83,7 +83,7 @@ public interface IBitstampRestClientExchangeApiAccount /// /// ["symbol"] Symbol, for example `ETH/USD` /// Cancellation token - Task> GetFeesAsync(string symbol, CancellationToken ct = default); + Task> GetFeesAsync(string symbol, CancellationToken ct = default); /// /// Get user transaction history @@ -101,7 +101,7 @@ public interface IBitstampRestClientExchangeApiAccount /// ["until_timestamp"] Filter by end time /// ["since_id"] Filter results since id /// Cancellation token - Task> GetUserTransactionsAsync( + Task> GetUserTransactionsAsync( SortOrder? sort = null, long? sinceId = null, DateTime? startTime = null, @@ -127,7 +127,7 @@ Task> GetUserTransactionsAsync( /// ["until_timestamp"] Filter by end time /// ["since_id"] Filter results since id /// Cancellation token - Task> GetUserTransactionsAsync( + Task> GetUserTransactionsAsync( string symbol, SortOrder? sort = null, long? sinceId = null, @@ -147,7 +147,7 @@ Task> GetUserTransactionsAsync( /// /// /// Cancellation token - Task> GetSymbolsAsync(CancellationToken ct = default); + Task> GetSymbolsAsync(CancellationToken ct = default); /// /// Get max tradable quantity for the parameters @@ -169,7 +169,7 @@ Task> GetUserTransactionsAsync( /// ["trailing_delta"] Trailing delta /// ["additional_collateral"] Additional collateral asset->quantity /// Cancellation token - Task> GetMaxTradeQuantityAsync( + Task> GetMaxTradeQuantityAsync( string symbol, MarginMode marginMode, decimal leverage, @@ -196,7 +196,7 @@ Task> GetMaxTradeQuantityAsync( /// ["limit"] Max number of results /// ["offset"] Result offset /// Cancellation token - Task> GetWithdrawalsAsync( + Task> GetWithdrawalsAsync( string? id = null, long? maxAge = null, int? limit = null, @@ -231,7 +231,7 @@ Task> GetWithdrawalsAsync( /// ["comment"] /// ["intermed_routing_num_or_bic"] Intermediary bank routing number / bic /// Cancellation token - Task> WithdrawFiatAsync( + Task> WithdrawFiatAsync( decimal quantity, string asset, string name, @@ -263,7 +263,7 @@ Task> WithdrawFiatAsync( /// /// ["id"] Withdrawal id /// Cancellation token - Task> CancelWithdrawalAsync(string id, CancellationToken ct = default); + Task> CancelWithdrawalAsync(string id, CancellationToken ct = default); /// /// Get fiat withdrawal status @@ -276,7 +276,7 @@ Task> WithdrawFiatAsync( /// /// ["id"] The withdrawal id /// Cancellation token - Task> GetFiatWithdrawalStatusAsync(string id, CancellationToken ct = default); + Task> GetFiatWithdrawalStatusAsync(string id, CancellationToken ct = default); /// /// Withdraw crypto. Only non-thirdparty withdrawals supported atm @@ -296,7 +296,7 @@ Task> WithdrawFiatAsync( /// ["transfer_id"] Transfer id /// ["beneficiary_thirdparty"] If the address you are withdrawing to is in your name (regardless of if this is a hosted or unhosted wallet), this should be set to False. If you are withdrawing to a third party, set it to True. /// Cancellation token - Task> WithdrawCryptoAsync( + Task> WithdrawCryptoAsync( string asset, decimal quantity, string address, @@ -319,7 +319,7 @@ Task> WithdrawCryptoAsync( /// ["asset"] The asset, for example `ETH` /// ["network"] Network /// Cancellation token - Task> GetDepositAddressAsync(string asset, string? network = null, CancellationToken ct = default); + Task> GetDepositAddressAsync(string asset, string? network = null, CancellationToken ct = default); /// /// Get crypto transactions @@ -336,7 +336,7 @@ Task> WithdrawCryptoAsync( /// ["limit"] Max number of results /// ["offset"] Result offset /// Cancellation token - Task> GetCryptoTransactionsAsync( + Task> GetCryptoTransactionsAsync( bool? includeIous = null, DateTime? startTime = null, DateTime? endTime = null, @@ -359,7 +359,7 @@ Task> GetCryptoTransactionsAsync( /// ["limit"] Max number of results /// ["offset"] Result offset /// Cancellation token - Task> GetDepositsAsync( + Task> GetDepositsAsync( DepositStatus? status = null, DateTime? startTime = null, DateTime? endTime = null, @@ -377,7 +377,7 @@ Task> GetDepositsAsync( /// /// /// Cancellation token - Task> GetMarginInfoAsync(CancellationToken ct = default); + Task> GetMarginInfoAsync(CancellationToken ct = default); /// /// Get leverage settings @@ -391,7 +391,7 @@ Task> GetDepositsAsync( /// ["margin_mode"] Margin mode /// ["market"] Symbol name /// Cancellation token - Task> GetLeverageSettingsAsync(MarginMode? marginMode = null, string? symbol = null, CancellationToken ct = default); + Task> GetLeverageSettingsAsync(MarginMode? marginMode = null, string? symbol = null, CancellationToken ct = default); /// /// Set leverage @@ -406,7 +406,7 @@ Task> GetDepositsAsync( /// ["market"] Symbol /// ["leverage"] Leverage setting /// Cancellation token - Task> SetLeverageAsync(MarginMode marginMode, string symbol, decimal leverage, CancellationToken ct = default); + Task> SetLeverageAsync(MarginMode marginMode, string symbol, decimal leverage, CancellationToken ct = default); } } diff --git a/Bitstamp.Net/Interfaces/Clients/ExchangeApi/IBitstampRestClientExchangeApiExchangeData.cs b/Bitstamp.Net/Interfaces/Clients/ExchangeApi/IBitstampRestClientExchangeApiExchangeData.cs index a5c0f78..bbf608e 100644 --- a/Bitstamp.Net/Interfaces/Clients/ExchangeApi/IBitstampRestClientExchangeApiExchangeData.cs +++ b/Bitstamp.Net/Interfaces/Clients/ExchangeApi/IBitstampRestClientExchangeApiExchangeData.cs @@ -20,7 +20,7 @@ public interface IBitstampRestClientExchangeApiExchangeData /// /// /// Cancellation token - Task> GetSymbolsAsync(CancellationToken ct = default); + Task> GetSymbolsAsync(CancellationToken ct = default); /// /// Get assets supported @@ -32,7 +32,7 @@ public interface IBitstampRestClientExchangeApiExchangeData /// /// /// Cancellation token - Task> GetAssetsAsync(CancellationToken ct = default); + Task> GetAssetsAsync(CancellationToken ct = default); /// /// Get 24h ticker data for all symbols @@ -44,7 +44,7 @@ public interface IBitstampRestClientExchangeApiExchangeData /// /// /// Cancellation token - Task> GetAllTickersAsync(CancellationToken ct = default); + Task> GetAllTickersAsync(CancellationToken ct = default); /// /// Get 24h ticker data @@ -57,7 +57,7 @@ public interface IBitstampRestClientExchangeApiExchangeData /// /// ["symbol"] Symbol, for example `ETH/USD` /// Cancellation token - Task> GetTickerAsync(string symbol, CancellationToken ct = default); + Task> GetTickerAsync(string symbol, CancellationToken ct = default); /// /// Get ticker data for the last hour @@ -70,7 +70,7 @@ public interface IBitstampRestClientExchangeApiExchangeData /// /// ["symbol"] Symbol, for example `ETH/USD` /// Cancellation token - Task> GetHourTickerAsync(string symbol, CancellationToken ct = default); + Task> GetHourTickerAsync(string symbol, CancellationToken ct = default); /// /// Get kline/candlestick data @@ -88,7 +88,7 @@ public interface IBitstampRestClientExchangeApiExchangeData /// ["end"] Filter by end time /// ["exclude_current_candle"] Whether to exclude the current in-progress candle /// Cancellation token - Task> GetKlinesAsync( + Task> GetKlinesAsync( string symbol, KlineInterval interval, int? limit = null, @@ -108,7 +108,7 @@ Task> GetKlinesAsync( /// /// ["symbol"] Symbol, for example `ETH/USD` /// Cancellation token - Task> GetOrderBookAsync(string symbol, CancellationToken ct = default); + Task> GetOrderBookAsync(string symbol, CancellationToken ct = default); /// /// Get recent trades @@ -122,7 +122,7 @@ Task> GetKlinesAsync( /// ["symbol"] Symbol, for example `ETH/USD` /// ["time"] The period to get trades for /// Cancellation token - Task> GetTradesAsync(string symbol, Period? period = null, CancellationToken ct = default); + Task> GetTradesAsync(string symbol, Period? period = null, CancellationToken ct = default); /// /// Get Euro / USD conversion rate @@ -134,7 +134,7 @@ Task> GetKlinesAsync( /// /// /// Cancellation token - Task> GetEurUsdConversionRateAsync(CancellationToken ct = default); + Task> GetEurUsdConversionRateAsync(CancellationToken ct = default); /// /// Get current funding rate info @@ -147,7 +147,7 @@ Task> GetKlinesAsync( /// /// ["symbol"] Symbol, for example `ETH/USD-PERP` /// Cancellation token - Task> GetFundingRateAsync(string symbol, CancellationToken ct = default); + Task> GetFundingRateAsync(string symbol, CancellationToken ct = default); /// /// Get funding rate history @@ -163,7 +163,7 @@ Task> GetKlinesAsync( /// ["until_timestamp"] Filter by end time /// ["limit"] Max number of results /// Cancellation token - Task> GetFundingRateHistoryAsync( + Task> GetFundingRateHistoryAsync( string symbol, DateTime? startTime = null, DateTime? endTime = null, @@ -180,7 +180,7 @@ Task> GetFundingRateHistoryAsync( /// /// /// Cancellation token - Task> GetMarginTiersAsync(CancellationToken ct = default); + Task> GetMarginTiersAsync(CancellationToken ct = default); /// /// Get collateral assets @@ -192,7 +192,7 @@ Task> GetFundingRateHistoryAsync( /// /// /// Cancellation token - Task> GetCollateralAssetsAsync(CancellationToken ct = default); + Task> GetCollateralAssetsAsync(CancellationToken ct = default); } } diff --git a/Bitstamp.Net/Interfaces/Clients/ExchangeApi/IBitstampRestClientExchangeApiTrading.cs b/Bitstamp.Net/Interfaces/Clients/ExchangeApi/IBitstampRestClientExchangeApiTrading.cs index b60f04a..225262b 100644 --- a/Bitstamp.Net/Interfaces/Clients/ExchangeApi/IBitstampRestClientExchangeApiTrading.cs +++ b/Bitstamp.Net/Interfaces/Clients/ExchangeApi/IBitstampRestClientExchangeApiTrading.cs @@ -41,7 +41,7 @@ public interface IBitstampRestClientExchangeApiTrading /// ["expire_time"] Expire time for GTD orders /// ["client_order_id"] Client order id /// Cancellation token - Task> PlaceLimitOrderAsync( + Task> PlaceLimitOrderAsync( string symbol, OrderSide side, decimal price, @@ -88,7 +88,7 @@ Task> PlaceLimitOrderAsync( /// ["margin_mode"] Margin mode /// ["client_order_id"] Client order id /// Cancellation token - Task> PlaceMarketOrderAsync( + Task> PlaceMarketOrderAsync( string symbol, OrderSide side, OrderType? orderType = null, @@ -115,7 +115,7 @@ Task> PlaceMarketOrderAsync( /// ["id"] Order id, either this or clientOrderId should be provided /// ["client_order_id"] Client order id, either this or orderId should be provided /// Cancellation token - Task> CancelOrderAsync(long? orderId = null, string? clientOrderId = null, CancellationToken ct = default); + Task> CancelOrderAsync(long? orderId = null, string? clientOrderId = null, CancellationToken ct = default); /// /// Get order history @@ -131,7 +131,7 @@ Task> PlaceMarketOrderAsync( /// ["since_id"] Filter by from event id /// ["until_id"] Filter by to event id /// Cancellation token - Task> GetOrderHistoryAsync(OrderSource orderSource, string symbol, string? fromEventId = null, string? toEventId = null, CancellationToken ct = default); + Task> GetOrderHistoryAsync(OrderSource orderSource, string symbol, string? fromEventId = null, string? toEventId = null, CancellationToken ct = default); /// /// Cancel all open orders @@ -143,7 +143,7 @@ Task> PlaceMarketOrderAsync( /// /// /// Cancellation token - Task> CancelAllOrdersAsync(CancellationToken ct = default); + Task> CancelAllOrdersAsync(CancellationToken ct = default); /// /// Cancel all open orders on a symbol @@ -156,7 +156,7 @@ Task> PlaceMarketOrderAsync( /// /// ["symbol"] Symbol, for example `ETH/USD` /// Cancellation token - Task> CancelAllOrdersAsync(string symbol, CancellationToken ct = default); + Task> CancelAllOrdersAsync(string symbol, CancellationToken ct = default); /// /// Get order info @@ -171,7 +171,7 @@ Task> PlaceMarketOrderAsync( /// ["client_order_id"] Client order id, either this or orderId should be provided /// ["omit_transactions"] Whether to include trades /// Cancellation token - Task> GetOrderAsync(long? orderId = null, string? clientOrderId = null, bool? includeTrades = null, CancellationToken ct = default); + Task> GetOrderAsync(long? orderId = null, string? clientOrderId = null, bool? includeTrades = null, CancellationToken ct = default); /// /// Replace an open order @@ -188,7 +188,7 @@ Task> PlaceMarketOrderAsync( /// ["orig_client_order_id"] Client order id, either this or orderId should be provided /// ["client_order_id"] New client order id /// Cancellation token - Task> ReplaceOrderAsync( + Task> ReplaceOrderAsync( decimal price, decimal quantity, long? id = null, @@ -206,7 +206,7 @@ Task> ReplaceOrderAsync( /// /// /// Cancellation token - Task> GetOpenOrdersAsync(CancellationToken ct = default); + Task> GetOpenOrdersAsync(CancellationToken ct = default); /// /// Get open orders for a symbol @@ -219,7 +219,7 @@ Task> ReplaceOrderAsync( /// /// ["symbol"] Symbol, for example `ETH/USD` /// Cancellation token - Task> GetOpenOrdersAsync(string symbol, CancellationToken ct = default); + Task> GetOpenOrdersAsync(string symbol, CancellationToken ct = default); /// /// Get user trades for derivatives @@ -237,7 +237,7 @@ Task> ReplaceOrderAsync( /// ["until_timestamp"] Filter by end time /// ["after_id"] Return results after this id /// Cancellation token - Task> GetDerivativesUserTradesAsync( + Task> GetDerivativesUserTradesAsync( long? orderId = null, long? afterId = null, SortOrder? sort = null, @@ -263,7 +263,7 @@ Task> GetDerivativesUserTradesAsync( /// ["until_timestamp"] Filter by end time /// ["after_id"] Return results after this id /// Cancellation token - Task> GetDerivativesUserTradesAsync( + Task> GetDerivativesUserTradesAsync( string symbol, long? orderId = null, long? afterId = null, @@ -284,7 +284,7 @@ Task> GetDerivativesUserTradesAsync( /// /// /// Cancellation token - Task> GetOpenPositionsAsync(CancellationToken ct = default); + Task> GetOpenPositionsAsync(CancellationToken ct = default); /// /// Get open positions for a symbol @@ -297,7 +297,7 @@ Task> GetDerivativesUserTradesAsync( /// /// ["symbol"] Symbol, for example `ETH/USD-PERP` /// Cancellation token - Task> GetOpenPositionsAsync(string symbol, CancellationToken ct = default); + Task> GetOpenPositionsAsync(string symbol, CancellationToken ct = default); /// /// Get position status @@ -310,7 +310,7 @@ Task> GetDerivativesUserTradesAsync( /// /// ["positionId"] Position id /// Cancellation token - Task> GetPositionStatusAsync(string positionId, CancellationToken ct = default); + Task> GetPositionStatusAsync(string positionId, CancellationToken ct = default); /// /// Get position history @@ -326,7 +326,7 @@ Task> GetDerivativesUserTradesAsync( /// ["limit"] Max number of results /// ["offset"] Result offset /// Cancellation token - Task> GetPositionHistoryAsync( + Task> GetPositionHistoryAsync( string? sinceId = null, SortOrder? sort = null, int? limit = null, @@ -347,7 +347,7 @@ Task> GetPositionHistoryAsync( /// ["limit"] Max number of results /// ["offset"] Result offset /// Cancellation token - Task> GetPositionHistoryAsync( + Task> GetPositionHistoryAsync( string symbol, string? sinceId = null, SortOrder? sort = null, @@ -367,7 +367,7 @@ Task> GetPositionHistoryAsync( /// ["market"] The symbol, for example `ETHUSD-PERP` /// ["margin_mode"] Margin mode /// Cancellation token - Task> ClosePositionsAsync(string? symbol = null, MarginMode? marginMode = null, CancellationToken ct = default); + Task> ClosePositionsAsync(string? symbol = null, MarginMode? marginMode = null, CancellationToken ct = default); /// /// Close position @@ -380,7 +380,7 @@ Task> GetPositionHistoryAsync( /// /// ["position_id"] The position id /// Cancellation token - Task> ClosePositionAsync(string positionId, CancellationToken ct = default); + Task> ClosePositionAsync(string positionId, CancellationToken ct = default); /// /// Get position settlement transaction history @@ -398,7 +398,7 @@ Task> GetPositionHistoryAsync( /// ["limit"] Max number of results /// ["offset"] Result offset /// Cancellation token - Task> GetPositionSettlementTransactionsAsync( + Task> GetPositionSettlementTransactionsAsync( string? sinceId = null, string? sort = null, DateTime? startTime = null, @@ -424,7 +424,7 @@ Task> GetPositionSettlementTransactio /// ["limit"] Max number of results /// ["offset"] Result offset /// Cancellation token - Task> GetPositionSettlementTransactionsAsync( + Task> GetPositionSettlementTransactionsAsync( string symbol, string? sinceId = null, string? sort = null, @@ -446,7 +446,7 @@ Task> GetPositionSettlementTransactio /// ["position_id"] The position id /// ["new_account"] New collateral quantity /// Cancellation token - Task UpdatePositionCollateralAsync(string positionId, decimal newCollateralQuantity, CancellationToken ct = default); + Task UpdatePositionCollateralAsync(string positionId, decimal newCollateralQuantity, CancellationToken ct = default); } } diff --git a/Bitstamp.Net/Interfaces/Clients/ExchangeApi/IBitstampSocketClientExchangeApi.cs b/Bitstamp.Net/Interfaces/Clients/ExchangeApi/IBitstampSocketClientExchangeApi.cs index fa5805e..80764d2 100644 --- a/Bitstamp.Net/Interfaces/Clients/ExchangeApi/IBitstampSocketClientExchangeApi.cs +++ b/Bitstamp.Net/Interfaces/Clients/ExchangeApi/IBitstampSocketClientExchangeApi.cs @@ -24,7 +24,7 @@ public interface IBitstampSocketClientExchangeApi : 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> handler, CancellationToken ct = default); + Task> SubscribeToTradeUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default); /// /// Subscribe to order book diff updates @@ -34,7 +34,7 @@ public interface IBitstampSocketClientExchangeApi : 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> SubscribeToFullOrderBookUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default); + Task> SubscribeToFullOrderBookUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default); /// /// Subscribe to funding rate updates /// @@ -43,7 +43,7 @@ public interface IBitstampSocketClientExchangeApi : 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> SubscribeToFundingRateUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default); + Task> SubscribeToFundingRateUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default); /// /// Subscribe to orderbook snapshot updates /// @@ -52,7 +52,7 @@ public interface IBitstampSocketClientExchangeApi : 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> SubscribeToOrderBookSnapshotUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default); + Task> SubscribeToOrderBookSnapshotUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default); /// /// Subscribe to user order updates /// @@ -61,7 +61,7 @@ public interface IBitstampSocketClientExchangeApi : 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> SubscribeToOrderUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default); + Task> SubscribeToOrderUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default); /// /// Subscribe to user trade updates /// @@ -70,6 +70,6 @@ public interface IBitstampSocketClientExchangeApi : 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> SubscribeToUserTradeUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default); + Task> SubscribeToUserTradeUpdatesAsync(string symbol, Action> handler, CancellationToken ct = default); } } diff --git a/Bitstamp.Net/Interfaces/Clients/IBitstampUserClientProvider.cs b/Bitstamp.Net/Interfaces/Clients/IBitstampUserClientProvider.cs index 777eec9..fedac60 100644 --- a/Bitstamp.Net/Interfaces/Clients/IBitstampUserClientProvider.cs +++ b/Bitstamp.Net/Interfaces/Clients/IBitstampUserClientProvider.cs @@ -21,6 +21,11 @@ public interface IBitstampUserClientProvider : 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/Bitstamp.Net/Objects/Sockets/BitstampPingQuery.cs b/Bitstamp.Net/Objects/Sockets/BitstampPingQuery.cs index ad4b1cb..d830da5 100644 --- a/Bitstamp.Net/Objects/Sockets/BitstampPingQuery.cs +++ b/Bitstamp.Net/Objects/Sockets/BitstampPingQuery.cs @@ -13,15 +13,15 @@ internal class BitstampPingQuery : Query(SocketEventType.Heartbeat, null), true) { - MessageRouter = MessageRouter.CreateWithoutTopicFilter>("bts:heartbeat", HandleMessage); + MessageRouter = MessageRouter.CreateForQuery>("bts:heartbeat", HandleMessage); } public CallResult> HandleMessage(SocketConnection connection, DateTime receiveTime, string? originalData, BitstampSocketData message) { if (message.Data?.Status != "success") - return new CallResult>(new ServerError("Ping failed", new(ErrorType.Unknown, $"Ping failed. Status: {message.Data?.Status}"))); + return CallResult>.Fail(new ServerError("Ping failed", new(ErrorType.Unknown, $"Ping failed. Status: {message.Data?.Status}")), originalData); - return new CallResult>(message, originalData, null); + return CallResult>.Ok(message, originalData); } } } diff --git a/Bitstamp.Net/Objects/Sockets/BitstampQuery.cs b/Bitstamp.Net/Objects/Sockets/BitstampQuery.cs index a5de54c..eb96798 100644 --- a/Bitstamp.Net/Objects/Sockets/BitstampQuery.cs +++ b/Bitstamp.Net/Objects/Sockets/BitstampQuery.cs @@ -14,16 +14,16 @@ internal class BitstampQuery : Query> public BitstampQuery(SocketEventType type, string? channel, BitstampSocketAuthToken? token = null) : base(new BitstampSocketData(type, channel == null ? null : new BitstampSubscriptionData(channel, token?.Token)), true) { - MessageRouter = MessageRouter.CreateWithoutTopicFilter>("query_" + channel, HandleMessage); + MessageRouter = MessageRouter.CreateForQuery>("query_" + channel, HandleMessage); } public CallResult> HandleMessage(SocketConnection connection, DateTime receiveTime, string? originalData, BitstampSocketData message) { if (message.Event == SocketEventType.Error && message.Data is BitstampSubscriptionData subData) - return new CallResult>(new ServerError(subData.Code?.ToString() ?? string.Empty, new(ErrorType.Unknown, subData.Message))); + return CallResult.Fail>(new ServerError(subData.Code?.ToString() ?? string.Empty, new(ErrorType.Unknown, subData.Message)), originalData); - return new CallResult>(message, originalData, null); + return CallResult.Ok>(message, originalData); } } } diff --git a/Bitstamp.Net/Objects/Sockets/BitstampReconnectSubscription.cs b/Bitstamp.Net/Objects/Sockets/BitstampReconnectSubscription.cs index 959f7a7..df2e631 100644 --- a/Bitstamp.Net/Objects/Sockets/BitstampReconnectSubscription.cs +++ b/Bitstamp.Net/Objects/Sockets/BitstampReconnectSubscription.cs @@ -7,14 +7,14 @@ namespace Bitstamp.Net.Objects.Sockets { - internal class BitstampReconnectSubsciption : SystemSubscription + internal class BitstampReconnectSubscription : SystemSubscription { private readonly BitstampSocketKeyGenerator _keyGenerator; - public BitstampReconnectSubsciption(ILogger logger, BitstampSocketKeyGenerator keyGenerator) : base(logger, false) + public BitstampReconnectSubscription(ILogger logger, BitstampSocketKeyGenerator keyGenerator) : base(logger, false) { _keyGenerator = keyGenerator; - MessageRouter = MessageRouter.CreateWithoutTopicFilter>("bts:request_reconnect", HandleMessage); + MessageRouter = MessageRouter.CreateForEvent>("bts:request_reconnect", HandleMessage); } public CallResult HandleMessage(SocketConnection connection, DateTime receiveTime, string? originalData, BitstampSocketData message) @@ -31,7 +31,7 @@ public CallResult HandleMessage(SocketConnection connection, DateTime receiveTim _logger.LogError(r.Exception, "Error while reconnecting"); })); - return CallResult.SuccessResult; + return CallResult.Ok(); } } } diff --git a/Bitstamp.Net/Objects/Sockets/BitstampSocketKeyGenerator.cs b/Bitstamp.Net/Objects/Sockets/BitstampSocketKeyGenerator.cs index fffa749..a6b577e 100644 --- a/Bitstamp.Net/Objects/Sockets/BitstampSocketKeyGenerator.cs +++ b/Bitstamp.Net/Objects/Sockets/BitstampSocketKeyGenerator.cs @@ -24,21 +24,24 @@ public void InvalidateKey() public async Task> GenerateWebsocketKeyAsync() { if (ValidKeyExists()) - return new CallResult(_lastKey!); + return CallResult.Ok(_lastKey!); await _semaphore.WaitAsync().ConfigureAwait(false); if (ValidKeyExists()) { _semaphore.Release(); - return new CallResult(_lastKey!); + return CallResult.Ok(_lastKey!); } try { var newKey = await ((BitstampRestClientExchangeApiAccount)_client.ExchangeApi.Account).GenerateWebsocketAuthTokenAsync().ConfigureAwait(false); + if (!newKey.Success) + return CallResult.Fail(newKey.Error); + _lastKey = newKey.Data; _lastKeyValidUntil = DateTime.UtcNow.AddSeconds((newKey.Data?.ValidSeconds ?? 0) - 10); // New key will be instant invalid if no valid response is returned - return newKey; + return CallResult.Ok(newKey.Data!); } finally { diff --git a/Bitstamp.Net/Objects/Sockets/BitstampSubscription.cs b/Bitstamp.Net/Objects/Sockets/BitstampSubscription.cs index d137c0d..5f4eaa5 100644 --- a/Bitstamp.Net/Objects/Sockets/BitstampSubscription.cs +++ b/Bitstamp.Net/Objects/Sockets/BitstampSubscription.cs @@ -30,7 +30,7 @@ public BitstampSubscription(ILogger logger, string channel, string symbol, Actio _channel += $"-{authToken.UserId}"; _handler = handler; - MessageRouter = MessageRouter.CreateWithoutTopicFilter>(_channel, DoHandleMessage); + MessageRouter = MessageRouter.CreateForEvent>(_channel, DoHandleMessage); AuthToken = authToken; } @@ -40,7 +40,7 @@ public CallResult DoHandleMessage(SocketConnection connection, DateTime receiveT if (message != null && message.Event is not Enums.SocketEventType.SubscriptionSucceeded) _handler?.Invoke(receiveTime, originalData, message); - return CallResult.SuccessResult; + return CallResult.Ok(); } protected override Query? GetSubQuery(SocketConnection connection) diff --git a/Bitstamp.Net/SymbolOrderBooks/BitstampSymbolOrderBook.cs b/Bitstamp.Net/SymbolOrderBooks/BitstampSymbolOrderBook.cs index 7079a38..3f3241b 100644 --- a/Bitstamp.Net/SymbolOrderBooks/BitstampSymbolOrderBook.cs +++ b/Bitstamp.Net/SymbolOrderBooks/BitstampSymbolOrderBook.cs @@ -61,13 +61,13 @@ public BitstampSymbolOrderBook(string symbol, protected override async Task> DoStartAsync(CancellationToken ct) { var subResult = await _socketClient.ExchangeApi.SubscribeToFullOrderBookUpdatesAsync(Symbol, HandleUpdate).ConfigureAwait(false); - if (!subResult) - return subResult; + 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; @@ -76,14 +76,14 @@ protected override async Task> DoStartAsync(Cance await WaitUntilFirstUpdateBufferedAsync(TimeSpan.FromMilliseconds(1000), TimeSpan.FromMilliseconds(2000), ct).ConfigureAwait(false); var bookResult = await _restClient.ExchangeApi.ExchangeData.GetOrderBookAsync(Symbol).ConfigureAwait(false); - if (!bookResult) + if (!bookResult.Success) { await _socketClient.UnsubscribeAsync(subResult.Data).ConfigureAwait(false); - return new CallResult(bookResult.Error!); + return CallResult.Fail(bookResult.Error!); } SetSnapshot(bookResult.Data.Timestamp.Ticks, bookResult.Data.Bids, bookResult.Data.Asks); - return new CallResult(subResult.Data); + return CallResult.Ok(subResult.Data); } private void HandleUpdate(DataEvent @event) @@ -92,17 +92,17 @@ private void HandleUpdate(DataEvent @event) } /// - protected override async Task> DoResyncAsync(CancellationToken ct) + protected override async Task DoResyncAsync(CancellationToken ct) { // Wait 1000ms until the first update has been received await WaitUntilFirstUpdateBufferedAsync(TimeSpan.FromMilliseconds(1000), TimeSpan.FromMilliseconds(2000), ct).ConfigureAwait(false); var bookResult = await _restClient.ExchangeApi.ExchangeData.GetOrderBookAsync(Symbol).ConfigureAwait(false); - if (!bookResult) - return new CallResult(bookResult.Error!); + if (!bookResult.Success) + return CallResult.Fail(bookResult.Error!); SetSnapshot(bookResult.Data.Timestamp.Ticks, bookResult.Data.Bids, bookResult.Data.Asks); - return new CallResult(true); + return CallResult.Ok(); } /// diff --git a/Examples/ai-friendly/03-websocket.cs b/Examples/ai-friendly/03-websocket.cs index 5243be4..dd8bf9c 100644 --- a/Examples/ai-friendly/03-websocket.cs +++ b/Examples/ai-friendly/03-websocket.cs @@ -15,6 +15,7 @@ const string spotSymbol = "ETH/USD"; const string derivativeSymbol = "ETH/USD-PERP"; +// Subscription methods return WebSocketResult. var tradeSubscription = await socketClient.ExchangeApi.SubscribeToTradeUpdatesAsync( spotSymbol, update => diff --git a/Examples/ai-friendly/04-shared-client.cs b/Examples/ai-friendly/04-shared-client.cs index 0c201a1..8a53321 100644 --- a/Examples/ai-friendly/04-shared-client.cs +++ b/Examples/ai-friendly/04-shared-client.cs @@ -17,6 +17,12 @@ Console.WriteLine($"Exchange: {shared.Exchange}"); Console.WriteLine($"Trading modes: {string.Join(", ", shared.SupportedTradingModes)}"); +var info = shared.Discover(); +var supportedFeatures = info.Features + .Where(x => x.Supported) + .Select(x => x.EndpointName); +Console.WriteLine($"{info.Exchange} {info.TypeName}: {string.Join(", ", supportedFeatures)}"); + var spotSymbols = await shared.GetSpotSymbolsAsync(new GetSymbolsRequest()); if (!spotSymbols.Success) { diff --git a/Examples/ai-friendly/05-error-handling.cs b/Examples/ai-friendly/05-error-handling.cs index e846ced..936f854 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: reusable Bitstamp.Net REST and socket result handling. +// Demonstrates: reusable Bitstamp.Net REST, socket, and shared helper result handling. // // Setup: dotnet add package Bitstamp.Net @@ -11,6 +11,10 @@ var restClient = new BitstampRestClient(); var socketClient = new BitstampSocketClient(); +// REST methods return HttpResult or HttpResult. +// WebSocket subscriptions return WebSocketResult. +// Shared non-I/O symbol/cache helpers return ExchangeCallResult. + var book = await restClient.ExchangeApi.ExchangeData.GetOrderBookAsync("ETH/USD"); if (!EnsureSuccess(book, "load order book")) return; @@ -29,7 +33,7 @@ await socketClient.UnsubscribeAsync(subscription.Data); -static bool EnsureSuccess(WebCallResult result, string action) +static bool EnsureSuccess(HttpResult result, string action) { if (result.Success) return true; @@ -38,7 +42,7 @@ static bool EnsureSuccess(WebCallResult result, string action) return false; } -static bool EnsureSuccessSocket(CallResult result, string action) +static bool EnsureSuccessSocket(WebSocketResult result, string action) { if (result.Success) return true; diff --git a/Examples/ai-friendly/README.md b/Examples/ai-friendly/README.md index d376018..f9f0323 100644 --- a/Examples/ai-friendly/README.md +++ b/Examples/ai-friendly/README.md @@ -8,9 +8,9 @@ These examples are intentionally small console programs that AI assistants can c | --- | --- | | `01-market-and-account.cs` | Public market data, balances, symbols and result handling | | `02-trading-and-positions.cs` | Limit orders, open orders, cancellation and derivatives positions | -| `03-websocket.cs` | Public and private websocket subscriptions | -| `04-shared-client.cs` | CryptoExchange.Net shared client access from Bitstamp.Net | -| `05-error-handling.cs` | Reusable REST/socket result handling helpers | +| `03-websocket.cs` | Public and private websocket subscriptions with teardown | +| `04-shared-client.cs` | CryptoExchange.Net shared client access, capability discovery, and shared symbols | +| `05-error-handling.cs` | Reusable `HttpResult`, `WebSocketResult`, and `ExchangeCallResult` result handling notes | ## Bitstamp Shape To Remember diff --git a/docs/ai-api-map.md b/docs/ai-api-map.md index a97a697..5f152f5 100644 --- a/docs/ai-api-map.md +++ b/docs/ai-api-map.md @@ -14,6 +14,7 @@ This map helps AI assistants route common user intents to the actual Bitstamp.Ne | Websocket subscriptions | `socketClient.ExchangeApi` | | Shared REST abstraction | `restClient.ExchangeApi.SharedClient` | | Shared socket abstraction | `socketClient.ExchangeApi.SharedClient` | +| Discover shared capabilities | `client.ExchangeApi.SharedClient.Discover()` | ## Market Data @@ -86,6 +87,33 @@ This map helps AI assistants route common user intents to the actual Bitstamp.Ne | Private order updates | `SubscribeToOrderUpdatesAsync(symbol, ...)` | | Private user trade updates | `SubscribeToUserTradeUpdatesAsync(symbol, ...)` | +## Shared APIs + +Use SharedApis for exchange-agnostic code across Bitstamp.Net and other CryptoExchange.Net exchange libraries. + +| Intent | Pattern | +| --- | --- | +| Shared REST client | `new BitstampRestClient().ExchangeApi.SharedClient` | +| Shared socket client | `new BitstampSocketClient().ExchangeApi.SharedClient` | +| Discover shared capabilities | `client.ExchangeApi.SharedClient.Discover()` | +| Shared spot symbols | `ISpotSymbolRestClient.GetSpotSymbolsAsync(new GetSymbolsRequest())` | +| Shared spot ticker | `ISpotTickerRestClient.GetSpotTickerAsync(new GetTickerRequest(symbol))` | +| Shared futures symbols | `IFuturesSymbolRestClient.GetFuturesSymbolsAsync(new GetSymbolsRequest())` | +| Shared order book socket | `IOrderBookSocketClient.SubscribeToOrderBookUpdatesAsync(...)` | + +Shared REST calls return `HttpResult` / `HttpResult`. Shared socket subscriptions return `WebSocketResult`. Shared non-I/O symbol/cache helpers such as symbol support checks return `ExchangeCallResult`. + +For shared socket subscriptions, keep the concrete socket client and unsubscribe with `await socketClient.UnsubscribeAsync(subscription.Data)`. + +## Result Handling + +| Situation | Pattern | +| --- | --- | +| REST success check | `if (!result.Success) { Console.WriteLine(result.Error); return; }` | +| Socket subscription success check | `WebSocketResult sub = await ...; if (!sub.Success) { Console.WriteLine(sub.Error); return; }` | +| Read REST data | Read `result.Data` only after `result.Success` | +| Shared helper data | Read `ExchangeCallResult.Data` only after `result.Success` | + ## Avoid / Replace | Avoid | Use | diff --git a/llms-full.txt b/llms-full.txt index 17e8db3..b98e2ff 100644 --- a/llms-full.txt +++ b/llms-full.txt @@ -12,7 +12,7 @@ This file is optimized for AI coding assistants. It maps the actual Bitstamp.Net - Socket client: `BitstampSocketClient` - Credentials: `BitstampCredentials(string key, string secret)` -Bitstamp.Net is organized around one `ExchangeApi` root. +Bitstamp.Net is organized around one `ExchangeApi` root. REST methods return `HttpResult` / `HttpResult`, WebSocket subscriptions return `WebSocketResult`, and shared symbol/cache helpers return `ExchangeCallResult`. ## Correct Setup @@ -224,6 +224,16 @@ Bitstamp.Net implements CryptoExchange.Net shared APIs: Supported trading modes include spot and linear perpetuals. +Use `SharedClient.Discover()` before routing optional shared features across exchanges: + +```csharp +var info = restClient.ExchangeApi.SharedClient.Discover(); +var supported = info.Features.Where(x => x.Supported).Select(x => x.EndpointName); +Console.WriteLine($"{info.Exchange} {info.TypeName}: {string.Join(", ", supported)}"); +``` + +Shared REST calls return `HttpResult` / `HttpResult`. Shared socket subscriptions return `WebSocketResult`. Shared non-I/O symbol/cache helpers, such as symbol support checks, return `ExchangeCallResult`. + ## Local State Helpers Use built-in helpers instead of manually maintaining complex local state: diff --git a/llms.txt b/llms.txt index fb63f37..3dfc66e 100644 --- a/llms.txt +++ b/llms.txt @@ -1,6 +1,6 @@ # Bitstamp.Net -Bitstamp.Net is a CryptoExchange.Net-based .NET client for Bitstamp REST and websocket APIs. +Bitstamp.Net is a CryptoExchange.Net-based .NET client for Bitstamp REST and websocket APIs. REST methods return `HttpResult` / `HttpResult`, WebSocket subscriptions return `WebSocketResult`, and shared symbol/cache helpers return `ExchangeCallResult`. ## Install @@ -29,11 +29,13 @@ REST: - `restClient.ExchangeApi.Account` - `restClient.ExchangeApi.Trading` - `restClient.ExchangeApi.SharedClient` +- Call `restClient.ExchangeApi.SharedClient.Discover()` to inspect supported shared REST features Sockets: - `socketClient.ExchangeApi` - `socketClient.ExchangeApi.SharedClient` +- Call `socketClient.ExchangeApi.SharedClient.Discover()` to inspect supported shared socket features There is no `SpotApi`, `UsdFuturesApi`, `FuturesApiV2`, `SpotApiV3`, `CoinFuturesApi`, or passphrase credential.