From f9085edc0ad83964f8b5366a9948c8123858e0f6 Mon Sep 17 00:00:00 2001 From: Jan Korf Date: Tue, 19 Nov 2024 13:37:54 +0100 Subject: [PATCH] Client Configuration (#243) Updated CryptoExchange.Net to version 8.3.0 Added support for loading client settings from IConfiguration Added DI registration method for configuring Rest and Socket options at the same time Added DisplayName and ImageUrl properties to BybitExchange class Updated client constructors to accept IOptions from DI Removed redundant BybitSocketClient constructor --- ByBit.Net/Bybit.Net.csproj | 4 +- ByBit.Net/BybitEnvironment.cs | 33 ++++- ByBit.Net/BybitExchange.cs | 10 ++ ByBit.Net/Clients/BybitRestClient.cs | 25 ++-- ByBit.Net/Clients/BybitSocketClient.cs | 42 +++---- .../ServiceCollectionExtensions.cs | 116 ++++++++++++++---- ByBit.Net/Objects/Options/BybitOptions.cs | 39 ++++++ ByBit.Net/Objects/Options/BybitRestOptions.cs | 28 +++-- .../Objects/Options/BybitSocketApiOptions.cs | 8 +- .../Objects/Options/BybitSocketOptions.cs | 26 ++-- Bybit.UnitTests/BybitRestClientTests.cs | 104 ++++++++++++++++ Bybit.UnitTests/BybitRestIntegrationTests.cs | 9 +- docs/index.html | 10 +- 13 files changed, 354 insertions(+), 100 deletions(-) create mode 100644 ByBit.Net/Objects/Options/BybitOptions.cs diff --git a/ByBit.Net/Bybit.Net.csproj b/ByBit.Net/Bybit.Net.csproj index 5172a474..c213ee3f 100644 --- a/ByBit.Net/Bybit.Net.csproj +++ b/ByBit.Net/Bybit.Net.csproj @@ -1,4 +1,4 @@ - + netstandard2.0;netstandard2.1 enable @@ -48,7 +48,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/ByBit.Net/BybitEnvironment.cs b/ByBit.Net/BybitEnvironment.cs index 36294b8c..ff29144a 100644 --- a/ByBit.Net/BybitEnvironment.cs +++ b/ByBit.Net/BybitEnvironment.cs @@ -26,6 +26,31 @@ internal BybitEnvironment(string name, SocketBaseAddress = socketBaseAddress; } + /// + /// ctor for DI, use for creating a custom environment + /// +#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + public BybitEnvironment() : base(TradeEnvironmentNames.Live) +#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider adding the 'required' modifier or declaring as nullable. + { } + + /// + /// Get the Bybit environment by name + /// + public static BybitEnvironment? GetEnvironmentByName(string? name) + => name switch + { + TradeEnvironmentNames.Live => Live, + TradeEnvironmentNames.Testnet => Testnet, + "Netherlands" => Netherlands, + "Hongkong" => HongKong, + "Turkey" => Turkey, + "Demo" => DemoTrading, + "" => Live, + null => Live, + _ => default + }; + /// /// Live environment /// @@ -46,7 +71,7 @@ internal BybitEnvironment(string name, /// Live environment for users from The Netherlands /// public static BybitEnvironment Netherlands { get; } - = new BybitEnvironment(TradeEnvironmentNames.Live, + = new BybitEnvironment("Netherlands", BybitApiAddresses.Netherlands.RestBaseAddress, BybitApiAddresses.Netherlands.SocketBaseAddress); @@ -54,7 +79,7 @@ internal BybitEnvironment(string name, /// Live environment for users from HongKong /// public static BybitEnvironment HongKong { get; } - = new BybitEnvironment(TradeEnvironmentNames.Live, + = new BybitEnvironment("HongKong", BybitApiAddresses.HongKong.RestBaseAddress, BybitApiAddresses.HongKong.SocketBaseAddress); @@ -62,7 +87,7 @@ internal BybitEnvironment(string name, /// Live environment for users from Turkey /// public static BybitEnvironment Turkey { get; } - = new BybitEnvironment(TradeEnvironmentNames.Live, + = new BybitEnvironment("Turkey", BybitApiAddresses.Turkey.RestBaseAddress, BybitApiAddresses.Turkey.SocketBaseAddress); @@ -70,7 +95,7 @@ internal BybitEnvironment(string name, /// Demo trading environment, needs seperate API key. See https://bybit-exchange.github.io/docs/v5/demo /// public static BybitEnvironment DemoTrading { get; } - = new BybitEnvironment(TradeEnvironmentNames.Live, + = new BybitEnvironment("Demo", BybitApiAddresses.DemoTrading.RestBaseAddress, BybitApiAddresses.DemoTrading.SocketBaseAddress); diff --git a/ByBit.Net/BybitExchange.cs b/ByBit.Net/BybitExchange.cs index f9498d48..47a7f5b7 100644 --- a/ByBit.Net/BybitExchange.cs +++ b/ByBit.Net/BybitExchange.cs @@ -14,6 +14,16 @@ public static class BybitExchange /// public static string ExchangeName => "Bybit"; + /// + /// Exchange name + /// + public static string DisplayName => "Bybit"; + + /// + /// Url to exchange image + /// + public static string ImageUrl { get; } = "https://raw.githubusercontent.com/JKorf/Bybit.Net/master/ByBit.Net/Icon/icon.png"; + /// /// Url to the main website /// diff --git a/ByBit.Net/Clients/BybitRestClient.cs b/ByBit.Net/Clients/BybitRestClient.cs index b1b54e40..ef95bd7f 100644 --- a/ByBit.Net/Clients/BybitRestClient.cs +++ b/ByBit.Net/Clients/BybitRestClient.cs @@ -11,6 +11,7 @@ using System; using Microsoft.Extensions.Logging; using CryptoExchange.Net.Clients; +using Microsoft.Extensions.Options; namespace Bybit.Net.Clients { @@ -31,28 +32,26 @@ public class BybitRestClient : BaseRestClient, IBybitRestClient /// Create a new instance of the BybitRestClient using provided options /// /// Option configuration delegate - public BybitRestClient(Action? optionsDelegate = null) : this(null, null, optionsDelegate) + public BybitRestClient(Action? optionsDelegate = null) + : this(null, null, Options.Create(ApplyOptionsDelegate(optionsDelegate))) { } /// /// Create a new instance of the BybitRestClient /// - /// Option configuration delegate + /// Option configuration delegate /// The logger factory /// Http client for this client - public BybitRestClient(HttpClient? httpClient, ILoggerFactory? loggerFactory, Action? optionsDelegate = null) + public BybitRestClient(HttpClient? httpClient, ILoggerFactory? loggerFactory, IOptions options) : base(loggerFactory, "Bybit") { - var options = BybitRestOptions.Default.Copy(); - if (optionsDelegate != null) - optionsDelegate(options); - Initialize(options); + Initialize(options.Value); - SpotApiV3 = AddApiClient(new BybitRestClientSpotApiV3(_logger, httpClient, options)); - CopyTradingApi = AddApiClient(new BybitRestClientCopyTradingApi(_logger, httpClient, options)); - DerivativesApi = AddApiClient(new BybitRestClientDerivativesApi(_logger, httpClient, options)); - V5Api = AddApiClient(new V5.BybitRestClientApi(_logger, httpClient, options)); + SpotApiV3 = AddApiClient(new BybitRestClientSpotApiV3(_logger, httpClient, options.Value)); + CopyTradingApi = AddApiClient(new BybitRestClientCopyTradingApi(_logger, httpClient, options.Value)); + DerivativesApi = AddApiClient(new BybitRestClientDerivativesApi(_logger, httpClient, options.Value)); + V5Api = AddApiClient(new V5.BybitRestClientApi(_logger, httpClient, options.Value)); } #endregion @@ -63,9 +62,7 @@ public BybitRestClient(HttpClient? httpClient, ILoggerFactory? loggerFactory, Ac /// Option configuration delegate public static void SetDefaultOptions(Action optionsDelegate) { - var options = BybitRestOptions.Default.Copy(); - optionsDelegate(options); - BybitRestOptions.Default = options; + BybitRestOptions.Default = ApplyOptionsDelegate(optionsDelegate); } /// diff --git a/ByBit.Net/Clients/BybitSocketClient.cs b/ByBit.Net/Clients/BybitSocketClient.cs index 7a910294..35fc312e 100644 --- a/ByBit.Net/Clients/BybitSocketClient.cs +++ b/ByBit.Net/Clients/BybitSocketClient.cs @@ -14,6 +14,7 @@ using Bybit.Net.Clients.DerivativesApi.UnifiedMarginApi; using Bybit.Net.Clients.SpotApi.v3; using CryptoExchange.Net.Clients; +using Microsoft.Extensions.Options; namespace Bybit.Net.Clients { @@ -39,19 +40,12 @@ public class BybitSocketClient : BaseSocketClient, IBybitSocketClient /// public IBybitSocketClientPrivateApi V5PrivateApi { get; } - /// - /// Create a new instance of the BybitSocketClient - /// - /// The logger factory - public BybitSocketClient(ILoggerFactory? loggerFactory = null) : this((x) => { }, loggerFactory) - { - } - /// /// Create a new instance of the BybitSocketClient /// /// Option configuration delegate - public BybitSocketClient(Action optionsDelegate) : this(optionsDelegate, null) + public BybitSocketClient(Action? optionsDelegate = null) + : this(Options.Create(ApplyOptionsDelegate(optionsDelegate)), null) { } @@ -59,24 +53,22 @@ public BybitSocketClient(Action optionsDelegate) : this(opti /// Create a new instance of the BybitSocketClient /// /// The logger factory - /// Option configuration delegate - public BybitSocketClient(Action optionsDelegate, ILoggerFactory? loggerFactory = null) : base(loggerFactory, "Bybit") + /// Option configuration + public BybitSocketClient(IOptions options, ILoggerFactory? loggerFactory = null) : base(loggerFactory, "Bybit") { - var options = BybitSocketOptions.Default.Copy(); - optionsDelegate(options); - Initialize(options); + Initialize(options.Value); - SpotV3Api = AddApiClient(new BybitSocketClientSpotApiV3(_logger, options)); + SpotV3Api = AddApiClient(new BybitSocketClientSpotApiV3(_logger, options.Value)); - DerivativesApi = AddApiClient(new BybitSocketClientDerivativesPublicApi(_logger, options)); - UnifiedMarginApi = AddApiClient(new BybitSocketClientUnifiedMarginApi(_logger, options)); - ContractApi = AddApiClient(new BybitSocketClientContractApi(_logger, options)); + DerivativesApi = AddApiClient(new BybitSocketClientDerivativesPublicApi(_logger, options.Value)); + UnifiedMarginApi = AddApiClient(new BybitSocketClientUnifiedMarginApi(_logger, options.Value)); + ContractApi = AddApiClient(new BybitSocketClientContractApi(_logger, options.Value)); - V5SpotApi = AddApiClient(new BybitSocketClientSpotApi(_logger, options)); - V5InverseApi = AddApiClient(new BybitSocketClientInverseApi(_logger, options)); - V5LinearApi = AddApiClient(new BybitSocketClientLinearApi(_logger, options)); - V5OptionsApi = AddApiClient(new BybitSocketClientOptionApi(_logger, options)); - V5PrivateApi = AddApiClient(new BybitSocketClientPrivateApi(_logger, options)); + V5SpotApi = AddApiClient(new BybitSocketClientSpotApi(_logger, options.Value)); + V5InverseApi = AddApiClient(new BybitSocketClientInverseApi(_logger, options.Value)); + V5LinearApi = AddApiClient(new BybitSocketClientLinearApi(_logger, options.Value)); + V5OptionsApi = AddApiClient(new BybitSocketClientOptionApi(_logger, options.Value)); + V5PrivateApi = AddApiClient(new BybitSocketClientPrivateApi(_logger, options.Value)); } /// @@ -85,9 +77,7 @@ public BybitSocketClient(Action optionsDelegate, ILoggerFact /// Option configuration delegate public static void SetDefaultOptions(Action optionsDelegate) { - var options = BybitSocketOptions.Default.Copy(); - optionsDelegate(options); - BybitSocketOptions.Default = options; + BybitSocketOptions.Default = ApplyOptionsDelegate(optionsDelegate); } /// diff --git a/ByBit.Net/ExtensionMethods/ServiceCollectionExtensions.cs b/ByBit.Net/ExtensionMethods/ServiceCollectionExtensions.cs index 0d37d7de..cb2a93d1 100644 --- a/ByBit.Net/ExtensionMethods/ServiceCollectionExtensions.cs +++ b/ByBit.Net/ExtensionMethods/ServiceCollectionExtensions.cs @@ -7,6 +7,9 @@ using CryptoExchange.Net; using CryptoExchange.Net.Clients; using CryptoExchange.Net.Interfaces; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using System; using System.Net; using System.Net.Http; @@ -19,45 +22,112 @@ namespace Microsoft.Extensions.DependencyInjection public static class ServiceCollectionExtensions { /// - /// Add the IBybitClient and IBybitSocketClient to the sevice collection so they can be injected + /// Add services such as the IBybitRestClient and IBybitSocketClient. Configures the services based on the provided configuration. /// /// The service collection - /// Set default options for the rest client - /// Set default options for the socket client - /// The lifetime of the IBybitSocketClient for the service collection. Defaults to Singleton. + /// The configuration(section) containing the options /// public static IServiceCollection AddBybit( this IServiceCollection services, - Action? defaultRestOptionsDelegate = null, - Action? defaultSocketOptionsDelegate = null, - ServiceLifetime? socketClientLifeTime = null) + IConfiguration configuration) { - var restOptions = BybitRestOptions.Default.Copy(); + var options = new BybitOptions(); + // Reset environment so we know if theyre overriden + options.Rest.Environment = null!; + options.Socket.Environment = null!; + configuration.Bind(options); - if (defaultRestOptionsDelegate != null) - { - defaultRestOptionsDelegate(restOptions); - BybitRestClient.SetDefaultOptions(defaultRestOptionsDelegate); - } + if (options.Rest == null || options.Socket == null) + throw new ArgumentException("Options null"); + + var restEnvName = options.Rest.Environment?.Name ?? options.Environment?.Name ?? BybitEnvironment.Live.Name; + var socketEnvName = options.Socket.Environment?.Name ?? options.Environment?.Name ?? BybitEnvironment.Live.Name; + options.Rest.Environment = BybitEnvironment.GetEnvironmentByName(restEnvName) ?? options.Rest.Environment!; + options.Rest.ApiCredentials = options.Rest.ApiCredentials ?? options.ApiCredentials; + options.Socket.Environment = BybitEnvironment.GetEnvironmentByName(socketEnvName) ?? options.Socket.Environment!; + options.Socket.ApiCredentials = options.Socket.ApiCredentials ?? options.ApiCredentials; - if (defaultSocketOptionsDelegate != null) - BybitSocketClient.SetDefaultOptions(defaultSocketOptionsDelegate); - services.AddHttpClient(options => + services.AddSingleton(x => Options.Options.Create(options.Rest)); + services.AddSingleton(x => Options.Options.Create(options.Socket)); + + return AddBybitCore(services, options.SocketClientLifeTime); + } + + /// + /// Add services such as the IBybitRestClient and IBybitSocketClient. Services will be configured based on the provided options. + /// + /// The service collection + /// Set options for the Bybit services + /// + public static IServiceCollection AddBybit( + this IServiceCollection services, + Action? optionsDelegate = null) + { + var options = new BybitOptions(); + // Reset environment so we know if theyre overriden + options.Rest.Environment = null!; + options.Socket.Environment = null!; + optionsDelegate?.Invoke(options); + if (options.Rest == null || options.Socket == null) + throw new ArgumentException("Options null"); + + options.Rest.Environment = options.Rest.Environment ?? options.Environment ?? BybitEnvironment.Live; + options.Rest.ApiCredentials = options.Rest.ApiCredentials ?? options.ApiCredentials; + options.Socket.Environment = options.Socket.Environment ?? options.Environment ?? BybitEnvironment.Live; + options.Socket.ApiCredentials = options.Socket.ApiCredentials ?? options.ApiCredentials; + + services.AddSingleton(x => Options.Options.Create(options.Rest)); + services.AddSingleton(x => Options.Options.Create(options.Socket)); + + return AddBybitCore(services, options.SocketClientLifeTime); + } + + /// + /// DEPRECATED; use instead + /// + public static IServiceCollection AddBybit( + this IServiceCollection services, + Action restDelegate, + Action? socketDelegate = null, + ServiceLifetime? socketClientLifeTime = null) + { + services.Configure((x) => { restDelegate?.Invoke(x); }); + services.Configure((x) => { socketDelegate?.Invoke(x); }); + + return AddBybitCore(services, socketClientLifeTime); + } + + private static IServiceCollection AddBybitCore( + this IServiceCollection services, + ServiceLifetime? socketClientLifeTime = null) + { + services.AddHttpClient((client, serviceProvider) => { - options.Timeout = restOptions.RequestTimeout; - }).ConfigurePrimaryHttpMessageHandler(() => { + var options = serviceProvider.GetRequiredService>().Value; + client.Timeout = options.RequestTimeout; + return new BybitRestClient(client, serviceProvider.GetRequiredService(), serviceProvider.GetRequiredService>()); + }).ConfigurePrimaryHttpMessageHandler((serviceProvider) => { var handler = new HttpClientHandler(); - if (restOptions.Proxy != null) + try + { + handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate; + } + catch (PlatformNotSupportedException) + { } + + var options = serviceProvider.GetRequiredService>().Value; + if (options.Proxy != null) { handler.Proxy = new WebProxy { - Address = new Uri($"{restOptions.Proxy.Host}:{restOptions.Proxy.Port}"), - Credentials = restOptions.Proxy.Password == null ? null : new NetworkCredential(restOptions.Proxy.Login, restOptions.Proxy.Password) + Address = new Uri($"{options.Proxy.Host}:{options.Proxy.Port}"), + Credentials = options.Proxy.Password == null ? null : new NetworkCredential(options.Proxy.Login, options.Proxy.Password) }; } return handler; }); + services.Add(new ServiceDescriptor(typeof(IBybitSocketClient), x => { return new BybitSocketClient(x.GetRequiredService>(), x.GetRequiredService()); }, socketClientLifeTime ?? ServiceLifetime.Singleton)); services.AddTransient(); services.AddTransient(); @@ -71,10 +141,6 @@ public static IServiceCollection AddBybit( services.RegisterSharedSocketInterfaces(x => x.GetRequiredService().V5InverseApi.SharedClient); services.RegisterSharedSocketInterfaces(x => x.GetRequiredService().V5PrivateApi.SharedClient); - if (socketClientLifeTime == null) - services.AddSingleton(); - else - services.Add(new ServiceDescriptor(typeof(IBybitSocketClient), typeof(BybitSocketClient), socketClientLifeTime.Value)); return services; } } diff --git a/ByBit.Net/Objects/Options/BybitOptions.cs b/ByBit.Net/Objects/Options/BybitOptions.cs new file mode 100644 index 00000000..3071197f --- /dev/null +++ b/ByBit.Net/Objects/Options/BybitOptions.cs @@ -0,0 +1,39 @@ +using CryptoExchange.Net.Authentication; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Bybit.Net.Objects.Options +{ + /// + /// Bybit options + /// + public class BybitOptions + { + /// + /// Rest client options + /// + public BybitRestOptions Rest { get; set; } = new BybitRestOptions(); + + /// + /// Socket client options + /// + public BybitSocketOptions Socket { get; set; } = new BybitSocketOptions(); + + /// + /// Trade environment. Contains info about URL's to use to connect to the API. Use `BybitEnvironment` to swap environment, for example `Environment = BybitEnvironment.Live` + /// + public BybitEnvironment? Environment { get; set; } + + /// + /// The api credentials used for signing requests. + /// + public ApiCredentials? ApiCredentials { get; set; } + + /// + /// The DI service lifetime for the IBybitSocketClient + /// + public ServiceLifetime? SocketClientLifeTime { get; set; } + } +} diff --git a/ByBit.Net/Objects/Options/BybitRestOptions.cs b/ByBit.Net/Objects/Options/BybitRestOptions.cs index c6a79e63..721e6648 100644 --- a/ByBit.Net/Objects/Options/BybitRestOptions.cs +++ b/ByBit.Net/Objects/Options/BybitRestOptions.cs @@ -12,11 +12,19 @@ public class BybitRestOptions : RestExchangeOptions /// Default options for the rest client /// - public static BybitRestOptions Default { get; set; } = new BybitRestOptions + internal static BybitRestOptions Default { get; set; } = new BybitRestOptions { Environment = BybitEnvironment.Live }; + /// + /// ctor + /// + public BybitRestOptions() + { + Default?.Set(this); + } + /// /// A referer, will be sent in the Referer header /// @@ -47,16 +55,16 @@ public class BybitRestOptions : RestExchangeOptions public RestApiOptions V5Options { get; private set; } = new RestApiOptions(); - internal BybitRestOptions Copy() + internal BybitRestOptions Set(BybitRestOptions targetOptions) { - var options = Copy(); - options.Referer = Referer; - options.ReceiveWindow = ReceiveWindow; - options.SpotOptions = SpotOptions.Copy(); - options.CopyTradingOptions = CopyTradingOptions.Copy(); - options.DerivativesOptions = DerivativesOptions.Copy(); - options.V5Options = V5Options.Copy(); - return options; + targetOptions = base.Set(targetOptions); + targetOptions.Referer = Referer; + targetOptions.ReceiveWindow = ReceiveWindow; + targetOptions.SpotOptions = SpotOptions.Set(targetOptions.SpotOptions); + targetOptions.CopyTradingOptions = CopyTradingOptions.Set(targetOptions.CopyTradingOptions); + targetOptions.DerivativesOptions = DerivativesOptions.Set(targetOptions.DerivativesOptions); + targetOptions.V5Options = V5Options.Set(targetOptions.V5Options); + return targetOptions; } } } diff --git a/ByBit.Net/Objects/Options/BybitSocketApiOptions.cs b/ByBit.Net/Objects/Options/BybitSocketApiOptions.cs index 1eb7c01b..4f1e6754 100644 --- a/ByBit.Net/Objects/Options/BybitSocketApiOptions.cs +++ b/ByBit.Net/Objects/Options/BybitSocketApiOptions.cs @@ -13,11 +13,11 @@ public class BybitSocketApiOptions : SocketApiOptions /// public TimeSpan PingInterval { get; set; } = TimeSpan.FromSeconds(20); - internal BybitSocketApiOptions Copy() + internal BybitSocketApiOptions Set(BybitSocketApiOptions targetOptions) { - var result = Copy(); - result.PingInterval = PingInterval; - return result; + targetOptions = base.Set(targetOptions); + targetOptions.PingInterval = PingInterval; + return targetOptions; } } } diff --git a/ByBit.Net/Objects/Options/BybitSocketOptions.cs b/ByBit.Net/Objects/Options/BybitSocketOptions.cs index 9cf1ea5c..fb037ea9 100644 --- a/ByBit.Net/Objects/Options/BybitSocketOptions.cs +++ b/ByBit.Net/Objects/Options/BybitSocketOptions.cs @@ -18,6 +18,14 @@ public class BybitSocketOptions : SocketExchangeOptions SocketNoDataTimeout = TimeSpan.FromSeconds(30) }; + /// + /// ctor + /// + public BybitSocketOptions() + { + Default?.Set(this); + } + /// /// A referer, will be sent in the Referer header /// @@ -44,16 +52,16 @@ public class BybitSocketOptions : SocketExchangeOptions /// public BybitSocketApiOptions V5Options { get; private set; } = new BybitSocketApiOptions(); - internal BybitSocketOptions Copy() + internal BybitSocketOptions Set(BybitSocketOptions targetOptions) { - var options = Copy(); - options.Referer = Referer; - options.SpotV3Options = SpotV3Options.Copy(); - options.DerivativesPublicOptions = DerivativesPublicOptions.Copy(); - options.UnifiedMarginOptions = UnifiedMarginOptions.Copy(); - options.ContractOptions = ContractOptions.Copy(); - options.V5Options = V5Options.Copy(); - return options; + targetOptions = base.Set(targetOptions); + targetOptions.Referer = Referer; + targetOptions.SpotV3Options = SpotV3Options.Set(targetOptions.SpotV3Options); + targetOptions.DerivativesPublicOptions = DerivativesPublicOptions.Set(targetOptions.DerivativesPublicOptions); + targetOptions.UnifiedMarginOptions = UnifiedMarginOptions.Set(targetOptions.UnifiedMarginOptions); + targetOptions.ContractOptions = ContractOptions.Set(targetOptions.ContractOptions); + targetOptions.V5Options = V5Options.Set(targetOptions.V5Options); + return targetOptions; } } } diff --git a/Bybit.UnitTests/BybitRestClientTests.cs b/Bybit.UnitTests/BybitRestClientTests.cs index 84be6e62..7f2dc7a3 100644 --- a/Bybit.UnitTests/BybitRestClientTests.cs +++ b/Bybit.UnitTests/BybitRestClientTests.cs @@ -13,6 +13,10 @@ using System.Text; using System.Threading.Tasks; using CryptoExchange.Net.Converters.JsonNet; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using CryptoExchange.Net.Objects; +using Bybit.Net.Interfaces.Clients; namespace Bybit.UnitTests { @@ -129,5 +133,105 @@ public void CheckSignatureExample2() true, false); } + + [Test] + [TestCase(TradeEnvironmentNames.Live, "https://api.bybit.com")] + [TestCase("", "https://api.bybit.com")] + public void TestConstructorEnvironments(string environmentName, string expected) + { + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + { "Bybit:Environment:Name", environmentName }, + }).Build(); + + var collection = new ServiceCollection(); + collection.AddBybit(configuration.GetSection("Bybit")); + var provider = collection.BuildServiceProvider(); + + var client = provider.GetRequiredService(); + + var address = client.V5Api.BaseAddress; + + Assert.That(address, Is.EqualTo(expected)); + } + + [Test] + public void TestConstructorNullEnvironment() + { + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + { "Bybit", null }, + }).Build(); + + var collection = new ServiceCollection(); + collection.AddBybit(configuration.GetSection("Bybit")); + var provider = collection.BuildServiceProvider(); + + var client = provider.GetRequiredService(); + + var address = client.V5Api.BaseAddress; + + Assert.That(address, Is.EqualTo("https://api.bybit.com")); + } + + [Test] + public void TestConstructorApiOverwriteEnvironment() + { + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + { "Bybit:Environment:Name", "test" }, + { "Bybit:Rest:Environment:Name", "live" }, + }).Build(); + + var collection = new ServiceCollection(); + collection.AddBybit(configuration.GetSection("Bybit")); + var provider = collection.BuildServiceProvider(); + + var client = provider.GetRequiredService(); + + var address = client.V5Api.BaseAddress; + + Assert.That(address, Is.EqualTo("https://api.bybit.com")); + } + + [Test] + public void TestConstructorConfiguration() + { + var configuration = new ConfigurationBuilder() + .AddInMemoryCollection(new Dictionary + { + { "ApiCredentials:Key", "123" }, + { "ApiCredentials:Secret", "456" }, + { "ApiCredentials:Memo", "000" }, + { "Socket:ApiCredentials:Key", "456" }, + { "Socket:ApiCredentials:Secret", "789" }, + { "Socket:ApiCredentials:Memo", "xxx" }, + { "Rest:OutputOriginalData", "true" }, + { "Socket:OutputOriginalData", "false" }, + { "Rest:Proxy:Host", "host" }, + { "Rest:Proxy:Port", "80" }, + { "Socket:Proxy:Host", "host2" }, + { "Socket:Proxy:Port", "81" }, + }).Build(); + + var collection = new ServiceCollection(); + collection.AddBybit(configuration); + var provider = collection.BuildServiceProvider(); + + var restClient = provider.GetRequiredService(); + var socketClient = provider.GetRequiredService(); + + Assert.That(((BaseApiClient)restClient.V5Api).OutputOriginalData, Is.True); + Assert.That(((BaseApiClient)socketClient.V5SpotApi).OutputOriginalData, Is.False); + Assert.That(((BaseApiClient)restClient.V5Api).AuthenticationProvider.ApiKey, Is.EqualTo("123")); + Assert.That(((BaseApiClient)socketClient.V5SpotApi).AuthenticationProvider.ApiKey, Is.EqualTo("456")); + Assert.That(((BaseApiClient)restClient.V5Api).ClientOptions.Proxy.Host, Is.EqualTo("host")); + Assert.That(((BaseApiClient)restClient.V5Api).ClientOptions.Proxy.Port, Is.EqualTo(80)); + Assert.That(((BaseApiClient)socketClient.V5SpotApi).ClientOptions.Proxy.Host, Is.EqualTo("host2")); + Assert.That(((BaseApiClient)socketClient.V5SpotApi).ClientOptions.Proxy.Port, Is.EqualTo(81)); + } } } diff --git a/Bybit.UnitTests/BybitRestIntegrationTests.cs b/Bybit.UnitTests/BybitRestIntegrationTests.cs index abaa2fed..6089d68f 100644 --- a/Bybit.UnitTests/BybitRestIntegrationTests.cs +++ b/Bybit.UnitTests/BybitRestIntegrationTests.cs @@ -3,6 +3,7 @@ using CryptoExchange.Net.Authentication; using CryptoExchange.Net.Testing; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; using NUnit.Framework; using System; using System.Collections.Generic; @@ -27,11 +28,11 @@ public override BybitRestClient GetClient(ILoggerFactory loggerFactory) var sec = Environment.GetEnvironmentVariable("APISECRET"); Authenticated = key != null && sec != null; - return new BybitRestClient(null, loggerFactory, opts => + return new BybitRestClient(null, loggerFactory, Options.Create(new Objects.Options.BybitRestOptions { - opts.OutputOriginalData = true; - opts.ApiCredentials = Authenticated ? new ApiCredentials(key, sec) : null; - }); + OutputOriginalData = true, + ApiCredentials = Authenticated ? new ApiCredentials(key, sec) : null + })); } [Test] diff --git a/docs/index.html b/docs/index.html index 6030be09..159b3c60 100644 --- a/docs/index.html +++ b/docs/index.html @@ -188,8 +188,14 @@

API Access

Bybit.Net can be configured using Dotnet dependency injection, after which the clients can be injected into your services. It also correctly configures logging and HttpClient usage.

-
builder.Services.AddBybit(options => {
-  // Options can be configured here, for example:
+		  
// Configure options from config file
+// see https://github.com/JKorf/CryptoExchange.Net/tree/master/Examples/example-config.json for an example
+builder.Services.AddBybit(builder.Configuration.GetSection("Bybit"));
+		  
+// OR
+		  
+ builder.Services.AddBybit(options => {
+  // Configure options in code
   options.ApiCredentials = new ApiCredentials("APIKEY", "APISECRET");
 });