1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2025-07-08 15:16:15 +00:00

Compare commits

..

No commits in common. "9450d447b9822470504e3031e57a65146c838e0e" and "1df63ab60c5e0f63f64d16a07ae452dd6bd92ee3" have entirely different histories.

15 changed files with 131 additions and 144 deletions

View File

@ -19,9 +19,9 @@ namespace CryptoExchange.Net
public abstract class BaseClient : IDisposable public abstract class BaseClient : IDisposable
{ {
/// <summary> /// <summary>
/// The name of the API the client is for /// The name of the exchange the client is for
/// </summary> /// </summary>
internal string Name { get; } internal string ExchangeName { get; }
/// <summary> /// <summary>
/// Api clients in this client /// Api clients in this client
/// </summary> /// </summary>
@ -56,19 +56,19 @@ namespace CryptoExchange.Net
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
/// <param name="name">The name of the API this client is for</param> /// <param name="exchangeName">The name of the exchange this client is for</param>
/// <param name="options">The options for this client</param> /// <param name="options">The options for this client</param>
protected BaseClient(string name, BaseClientOptions options) protected BaseClient(string exchangeName, BaseClientOptions options)
{ {
log = new Log(name); log = new Log(exchangeName);
log.UpdateWriters(options.LogWriters); log.UpdateWriters(options.LogWriters);
log.Level = options.LogLevel; log.Level = options.LogLevel;
ClientOptions = options; ClientOptions = options;
Name = name; ExchangeName = exchangeName;
log.Write(LogLevel.Trace, $"Client configuration: {options}, CryptoExchange.Net: v{typeof(BaseClient).Assembly.GetName().Version}, {name}.Net: v{GetType().Assembly.GetName().Version}"); log.Write(LogLevel.Trace, $"Client configuration: {options}, CryptoExchange.Net: v{typeof(BaseClient).Assembly.GetName().Version}, {ExchangeName}.Net: v{GetType().Assembly.GetName().Version}");
} }
/// <summary> /// <summary>
@ -278,7 +278,7 @@ namespace CryptoExchange.Net
/// </summary> /// </summary>
public virtual void Dispose() public virtual void Dispose()
{ {
log.Write(LogLevel.Debug, "Disposing client"); log.Write(LogLevel.Debug, "Disposing exchange client");
foreach (var client in ApiClients) foreach (var client in ApiClients)
client.Dispose(); client.Dispose();
} }

View File

@ -74,15 +74,15 @@ namespace CryptoExchange.Net
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
/// <param name="name">The name of the API this client is for</param> /// <param name="exchangeName">The name of the exchange this client is for</param>
/// <param name="options">The options for this client</param> /// <param name="exchangeOptions">The options for this client</param>
protected BaseRestClient(string name, BaseRestClientOptions options) : base(name, options) protected BaseRestClient(string exchangeName, BaseRestClientOptions exchangeOptions) : base(exchangeName, exchangeOptions)
{ {
if (options == null) if (exchangeOptions == null)
throw new ArgumentNullException(nameof(options)); throw new ArgumentNullException(nameof(exchangeOptions));
ClientOptions = options; ClientOptions = exchangeOptions;
RequestFactory.Configure(options.RequestTimeout, options.Proxy, options.HttpClient); RequestFactory.Configure(exchangeOptions.RequestTimeout, exchangeOptions.Proxy, exchangeOptions.HttpClient);
} }
/// <inheritdoc /> /// <inheritdoc />

View File

@ -101,14 +101,14 @@ namespace CryptoExchange.Net
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
/// <param name="name">The name of the API this client is for</param> /// <param name="exchangeName">The name of the exchange this client is for</param>
/// <param name="options">The options for this client</param> /// <param name="exchangeOptions">The options for this client</param>
protected BaseSocketClient(string name, BaseSocketClientOptions options) : base(name, options) protected BaseSocketClient(string exchangeName, BaseSocketClientOptions exchangeOptions): base(exchangeName, exchangeOptions)
{ {
if (options == null) if (exchangeOptions == null)
throw new ArgumentNullException(nameof(options)); throw new ArgumentNullException(nameof(exchangeOptions));
ClientOptions = options; ClientOptions = exchangeOptions;
} }
/// <summary> /// <summary>

View File

@ -65,7 +65,7 @@ namespace CryptoExchange.Net
var timeSyncParams = GetTimeSyncInfo(); var timeSyncParams = GetTimeSyncInfo();
if (await timeSyncParams.TimeSyncState.Semaphore.WaitAsync(0).ConfigureAwait(false)) if (await timeSyncParams.TimeSyncState.Semaphore.WaitAsync(0).ConfigureAwait(false))
{ {
if (!timeSyncParams.SyncTime || (DateTime.UtcNow - timeSyncParams.TimeSyncState.LastSyncTime < timeSyncParams.RecalculationInterval)) if (!timeSyncParams.SyncTime || (DateTime.UtcNow - timeSyncParams.TimeSyncState.LastSyncTime < TimeSpan.FromHours(1)))
{ {
timeSyncParams.TimeSyncState.Semaphore.Release(); timeSyncParams.TimeSyncState.Semaphore.Release();
return new WebCallResult<bool>(null, null, null, null, null, null, null, null, true, null); return new WebCallResult<bool>(null, null, null, null, null, null, null, null, true, null);

View File

@ -5,17 +5,17 @@
<PropertyGroup> <PropertyGroup>
<PackageId>CryptoExchange.Net</PackageId> <PackageId>CryptoExchange.Net</PackageId>
<Authors>JKorf</Authors> <Authors>JKorf</Authors>
<Description>A base package for implementing cryptocurrency API's</Description> <Description>A base package for implementing cryptocurrency exchange API's</Description>
<PackageVersion>5.0.0</PackageVersion> <PackageVersion>5.0.0-beta8</PackageVersion>
<AssemblyVersion>5.0.0</AssemblyVersion> <AssemblyVersion>5.0.0</AssemblyVersion>
<FileVersion>5.0.0</FileVersion> <FileVersion>5.0.0-beta8</FileVersion>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance> <PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/JKorf/CryptoExchange.Net.git</RepositoryUrl> <RepositoryUrl>https://github.com/JKorf/CryptoExchange.Net.git</RepositoryUrl>
<PackageProjectUrl>https://github.com/JKorf/CryptoExchange.Net</PackageProjectUrl> <PackageProjectUrl>https://github.com/JKorf/CryptoExchange.Net</PackageProjectUrl>
<NeutralLanguage>en</NeutralLanguage> <NeutralLanguage>en</NeutralLanguage>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild> <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageReleaseNotes>See https://github.com/JKorf/CryptoExchange.Net#release-notes</PackageReleaseNotes> <PackageReleaseNotes>5.0.0-beta8 - Fix for time offset calculation not updating the time offset when time offset is smaller than 500ms</PackageReleaseNotes>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<LangVersion>9.0</LangVersion> <LangVersion>9.0</LangVersion>
<PackageLicenseExpression>MIT</PackageLicenseExpression> <PackageLicenseExpression>MIT</PackageLicenseExpression>

View File

@ -1,37 +0,0 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using CryptoExchange.Net.CommonObjects;
using CryptoExchange.Net.Interfaces.CommonClients;
using CryptoExchange.Net.Objects;
namespace CryptoExchange.Net.Interfaces.CommonClients
{
/// <summary>
/// Common futures endpoints
/// </summary>
public interface IFuturesClient : IBaseRestClient
{
/// <summary>
/// Place an order
/// </summary>
/// <param name="symbol">The symbol the order is for</param>
/// <param name="side">The side of the order</param>
/// <param name="type">The type of the order</param>
/// <param name="quantity">The quantity of the order</param>
/// <param name="price">The price of the order, only for limit orders</param>
/// <param name="accountId">[Optional] The account id to place the order on, required for some exchanges, ignored otherwise</param>
/// <param name="leverage">[Optional] Leverage for this order. This is needed for some exchanges. For exchanges where this is not needed this parameter is ignored (and should be set before hand)</param>
/// <param name="clientOrderId">[Optional] Client specified id for this order</param>
/// <param name="ct">[Optional] Cancellation token for cancelling the request</param>
/// <returns>The id of the resulting order</returns>
Task<WebCallResult<OrderId>> PlaceOrderAsync(string symbol, CommonOrderSide side, CommonOrderType type, decimal quantity, decimal? price = null, int? leverage = null, string? accountId = null, string? clientOrderId = null, CancellationToken ct = default);
/// <summary>
/// Get position
/// </summary>
/// <param name="ct">[Optional] Cancellation token for cancelling the request</param>
/// <returns></returns>
Task<WebCallResult<IEnumerable<Position>>> GetPositionsAsync(CancellationToken ct = default);
}
}

View File

@ -1,28 +0,0 @@
using CryptoExchange.Net.CommonObjects;
using CryptoExchange.Net.Interfaces.CommonClients;
using CryptoExchange.Net.Objects;
using System.Threading;
using System.Threading.Tasks;
namespace CryptoExchange.Net.Interfaces.CommonClients
{
/// <summary>
/// Common spot endpoints
/// </summary>
public interface ISpotClient: IBaseRestClient
{
/// <summary>
/// Place an order
/// </summary>
/// <param name="symbol">The symbol the order is for</param>
/// <param name="side">The side of the order</param>
/// <param name="type">The type of the order</param>
/// <param name="quantity">The quantity of the order</param>
/// <param name="price">The price of the order, only for limit orders</param>
/// <param name="accountId">[Optional] The account id to place the order on, required for some exchanges, ignored otherwise</param>
/// <param name="clientOrderId">[Optional] Client specified id for this order</param>
/// <param name="ct">[Optional] Cancellation token for cancelling the request</param>
/// <returns>The id of the resulting order</returns>
Task<WebCallResult<OrderId>> PlaceOrderAsync(string symbol, CommonOrderSide side, CommonOrderType type, decimal quantity, decimal? price = null, string? accountId = null, string? clientOrderId = null, CancellationToken ct = default);
}
}

View File

@ -5,7 +5,7 @@ using System.Collections.Generic;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace CryptoExchange.Net.Interfaces.CommonClients namespace CryptoExchange.Net.Interfaces
{ {
/// <summary> /// <summary>
/// Common rest client endpoints /// Common rest client endpoints
@ -135,4 +135,50 @@ namespace CryptoExchange.Net.Interfaces.CommonClients
/// <returns></returns> /// <returns></returns>
Task<WebCallResult<OrderId>> CancelOrderAsync(string orderId, string? symbol = null, CancellationToken ct = default); Task<WebCallResult<OrderId>> CancelOrderAsync(string orderId, string? symbol = null, CancellationToken ct = default);
} }
/// <summary>
/// Common futures endpoints
/// </summary>
public interface IFuturesClient: IBaseRestClient
{
/// <summary>
/// Place an order
/// </summary>
/// <param name="symbol">The symbol the order is for</param>
/// <param name="side">The side of the order</param>
/// <param name="type">The type of the order</param>
/// <param name="quantity">The quantity of the order</param>
/// <param name="price">The price of the order, only for limit orders</param>
/// <param name="accountId">[Optional] The account id to place the order on, required for some exchanges, ignored otherwise</param>
/// <param name="leverage">[Optional] Leverage for this order. This is needed for some exchanges. For exchanges where this is not needed this parameter is ignored (and should be set before hand)</param>
/// <param name="ct">[Optional] Cancellation token for cancelling the request</param>
/// <returns>The id of the resulting order</returns>
Task<WebCallResult<OrderId>> PlaceOrderAsync(string symbol, CommonOrderSide side, CommonOrderType type, decimal quantity, decimal? price = null, int? leverage = null, string? accountId = null, CancellationToken ct = default);
/// <summary>
/// Get position
/// </summary>
/// <param name="ct">[Optional] Cancellation token for cancelling the request</param>
/// <returns></returns>
Task<WebCallResult<IEnumerable<Position>>> GetPositionsAsync(CancellationToken ct = default);
}
/// <summary>
/// Common spot endpoints
/// </summary>
public interface ISpotClient: IBaseRestClient
{
/// <summary>
/// Place an order
/// </summary>
/// <param name="symbol">The symbol the order is for</param>
/// <param name="side">The side of the order</param>
/// <param name="type">The type of the order</param>
/// <param name="quantity">The quantity of the order</param>
/// <param name="price">The price of the order, only for limit orders</param>
/// <param name="accountId">[Optional] The account id to place the order on, required for some exchanges, ignored otherwise</param>
/// <param name="ct">[Optional] Cancellation token for cancelling the request</param>
/// <returns>The id of the resulting order</returns>
Task<WebCallResult<OrderId>> PlaceOrderAsync(string symbol, CommonOrderSide side, CommonOrderType type, decimal quantity, decimal? price = null, string? accountId = null, CancellationToken ct = default);
}
} }

View File

@ -163,7 +163,7 @@ namespace CryptoExchange.Net.Objects
public TimeSpan SocketNoDataTimeout { get; set; } public TimeSpan SocketNoDataTimeout { get; set; }
/// <summary> /// <summary>
/// The amount of subscriptions that should be made on a single socket connection. Not all API's support multiple subscriptions on a single socket. /// The amount of subscriptions that should be made on a single socket connection. Not all exchanges support multiple subscriptions on a single socket.
/// Setting this to a higher number increases subscription speed because not every subscription needs to connect to the server, but having more subscriptions on a /// Setting this to a higher number increases subscription speed because not every subscription needs to connect to the server, but having more subscriptions on a
/// single connection will also increase the amount of traffic on that single connection, potentially leading to issues. /// single connection will also increase the amount of traffic on that single connection, potentially leading to issues.
/// </summary> /// </summary>
@ -280,11 +280,6 @@ namespace CryptoExchange.Net.Objects
/// </summary> /// </summary>
public bool AutoTimestamp { get; set; } public bool AutoTimestamp { get; set; }
/// <summary>
/// How often the timestamp adjustment between client and server is recalculated. If you need a very small TimeSpan here you're probably better of syncing your server time more often
/// </summary>
public TimeSpan TimestampRecalculationInterval { get; set; } = TimeSpan.FromHours(1);
/// <summary> /// <summary>
/// ctor /// ctor
/// </summary> /// </summary>
@ -323,13 +318,12 @@ namespace CryptoExchange.Net.Objects
input.RateLimiters = def.RateLimiters.ToList(); input.RateLimiters = def.RateLimiters.ToList();
input.RateLimitingBehaviour = def.RateLimitingBehaviour; input.RateLimitingBehaviour = def.RateLimitingBehaviour;
input.AutoTimestamp = def.AutoTimestamp; input.AutoTimestamp = def.AutoTimestamp;
input.TimestampRecalculationInterval = def.TimestampRecalculationInterval;
} }
/// <inheritdoc /> /// <inheritdoc />
public override string ToString() public override string ToString()
{ {
return $"{base.ToString()}, RateLimiters: {RateLimiters?.Count}, RateLimitBehaviour: {RateLimitingBehaviour}, AutoTimestamp: {AutoTimestamp}, TimestampRecalculationInterval: {TimestampRecalculationInterval}"; return $"{base.ToString()}, RateLimiters: {RateLimiters?.Count}, RateLimitBehaviour: {RateLimitingBehaviour}, AutoTimestamp: {AutoTimestamp}";
} }
} }

View File

@ -46,10 +46,6 @@ namespace CryptoExchange.Net.Objects
/// </summary> /// </summary>
public bool SyncTime { get; } public bool SyncTime { get; }
/// <summary> /// <summary>
/// Timestamp recalulcation interval
/// </summary>
public TimeSpan RecalculationInterval { get; }
/// <summary>
/// Time sync state for the API client /// Time sync state for the API client
/// </summary> /// </summary>
public TimeSyncState TimeSyncState { get; } public TimeSyncState TimeSyncState { get; }

View File

@ -377,7 +377,7 @@ namespace CryptoExchange.Net.Sockets
Socket.Reconnecting = true; Socket.Reconnecting = true;
DisconnectTime = DateTime.UtcNow; DisconnectTime = DateTime.UtcNow;
log.Write(LogLevel.Information, $"Socket {Socket.Id} Connection lost, will try to reconnect{(ReconnectTry == 0 ? "": $" after {socketClient.ClientOptions.ReconnectInterval}")}"); log.Write(LogLevel.Information, $"Socket {Socket.Id} Connection lost, will try to reconnect after {socketClient.ClientOptions.ReconnectInterval}");
if (!lostTriggered) if (!lostTriggered)
{ {
lostTriggered = true; lostTriggered = true;
@ -387,13 +387,9 @@ namespace CryptoExchange.Net.Sockets
Task.Run(async () => Task.Run(async () =>
{ {
while (ShouldReconnect) while (ShouldReconnect)
{
if (ReconnectTry > 0)
{ {
// Wait a bit before attempting reconnect // Wait a bit before attempting reconnect
await Task.Delay(socketClient.ClientOptions.ReconnectInterval).ConfigureAwait(false); await Task.Delay(socketClient.ClientOptions.ReconnectInterval).ConfigureAwait(false);
}
if (!ShouldReconnect) if (!ShouldReconnect)
{ {
// Should reconnect changed to false while waiting to reconnect // Should reconnect changed to false while waiting to reconnect
@ -420,7 +416,7 @@ namespace CryptoExchange.Net.Sockets
break; break;
} }
log.Write(LogLevel.Debug, $"Socket {Socket.Id} failed to reconnect{(socketClient.ClientOptions.MaxReconnectTries != null ? $", try {ReconnectTry}/{socketClient.ClientOptions.MaxReconnectTries}": "")}, will try again in {socketClient.ClientOptions.ReconnectInterval}"); log.Write(LogLevel.Debug, $"Socket {Socket.Id} failed to reconnect{(socketClient.ClientOptions.MaxReconnectTries != null ? $", try {ReconnectTry}/{socketClient.ClientOptions.MaxReconnectTries}": "")}");
continue; continue;
} }

View File

@ -49,6 +49,7 @@
foreach(var task in tasks) foreach(var task in tasks)
{ {
if(task.Item2.Result.Success) if(task.Item2.Result.Success)
// TODO new version
_prices.Add(task.Item1, task.Item2.Result.Data.HighPrice); _prices.Add(task.Item1, task.Item2.Result.Data.HighPrice);
} }
} }

View File

@ -18,21 +18,56 @@ I develop and maintain this package on my own for free in my spare time. Donatio
Alternatively, sponsor me on Github using [Github Sponsors](https://github.com/sponsors/JKorf) Alternatively, sponsor me on Github using [Github Sponsors](https://github.com/sponsors/JKorf)
## Release notes ## Release notes
* Version 5.0.0 * Version 5.0.0-beta8 - 09 Feb 2022
* Added Github.io page for documentation: https://jkorf.github.io/CryptoExchange.Net/ * Fix for time offset calculation not updating the time offset when time offset is smaller than 500ms
* Added single DateTimeConverter replacing the different timestamp converters
* Added additional request related properties to WebCallResult * Version 5.0.0-beta7 - 05 Feb 2022
* Added CancelationToken support for websockets * Added CancellationToken support on Common client interfaces
* Added CancelationToken support for SymbolOrderBook starting * Added CancellationToken support on SymbolOrderBook
* Added TimeSync support * Improved SymbolOrderBook Start/Stop robustness
* Refactored base client classes into BaseClient and ApiClient to provide a more defined client structure
* Refactored client options to have better control over each different ApiClient * Version 5.0.0-beta6 - 02 Feb 2022
* Refactored authentication provider to be more flexible * Fix for deserialization of DateTime string "0" to a non-nullable DateTime property in .net framework
* Refactored rate limiter implementation
* Refactored IExchangeClient interface to ISpotClient and IFuturesClient * Version 5.0.0-beta5 - 25 Jan 2022
* Refactored socket reconnection to immediately try to reconnect before waiting the ReconnectTimeout * Fixed DateTime string including nanoseconds deserialization in DateTimeConverter
* Improved SymbolOrderBook stability * Refactored SymbolOrderBook to use AsyncResetEvent instead of AutoResetEvent to prevent thread blocking
* Updated code docs
* Version 5.0.0-beta4 - 24 Jan 2022
* Fixed dependencies
* Version 5.0.0-beta3 - 24 Jan 2022
* Re-added Common- prefixes for Common client enums to avoid Ambiguous references
* Version 5.0.0-beta2 - 21 Jan 2022
* Replaced Debug.WriteLine with Trace.WriteLine
* Version 5.0.0-beta1 - 15 Jan 2022
* Added additional properties to WebCallResult
* Added CallResult unit tests
* Updated some logging for client options
* Version 5.0.0-alpha7 - 07 Jan 2022
* Fixed SymbolOrderBook ToString() not being threadsafe, Potential fix request timeout unclear error message
* Version 5.0.0-alpha6 - 03 Jan 2022
* Fixed typo Comon -> Common
* Version 5.0.0-alpha5 - 01 Jan 2022
* Reverted conditional dependencies, not working as expected
* Version 5.0.0-alpha4 - 01 Jan 2022
* New comon interface structure
* Added Microsoft.Extensions.DependencyInjection.Abstractions to allow a AddXXX extension method on IServiceCollection for implementations
* Version 5.0.0-alpha3 - 27 Dec 2021
* Added ExchangeName to IExchangeClient interface
* Version 5.0.0-alpha2 - 21 Dec 2021
* Rework authentication provider, support for time syncing, various internal updates
* Version 5.0.0-alpha1 - 07 Dec 2021
* New client structures, multiple improvements and changes. Details will be available later
* Version 4.2.8 - 08 Oct 2021 * Version 4.2.8 - 08 Oct 2021
* Fixed deadlock in socket receive * Fixed deadlock in socket receive

View File

@ -46,19 +46,3 @@ private void SomeMethod()
} }
``` ```
### Can I use the TestNet/US/other API with this library
Yes, generally these are all supported and can be configured by setting the BaseAddress in the client options. Some known API addresses should be available in the [Exchange]ApiAddresses class. For example:
```csharp
var client = new BinanceClient(new BinanceClientOptions
{
SpotApiOptions = new BinanceApiClientOptions
{
BaseAddress = BinanceApiAddresses.TestNet.RestClientAddress
},
UsdFuturesApiOptions = new BinanceApiClientOptions
{
BaseAddress = BinanceApiAddresses.TestNet.UsdFuturesRestClientAddress
}
});
```

View File

@ -109,7 +109,7 @@ All clients have access to the following options, specific implementations might
|Option|Description|Default| |Option|Description|Default|
|------|-----------|-------| |------|-----------|-------|
|`ApiCredentials`|The API credentials to use for this specific API client. Will override any credentials provided in the base client options| |`ApiCredentials`|The API credentials to use for this specific API client. Will override any credentials provided in the base client options|
|`BaseAddress`|The base address to the API. All calls to the API will use this base address as basis for the endpoints. This allows for swapping to test API's or swapping to a different cluster for example. Available base addresses are defined in the [Library]ApiAddresses helper class, for example `KucoinApiAddresses`|Depends on implementation |`BaseAddress`|The base address to the API. All calls to the API will use this base address as basis for the endpoints. This allows for swapping to test API's or swapping to a different cluster for example.|Depends on implementation
**Options for Rest Api Client (extension of base api client options)** **Options for Rest Api Client (extension of base api client options)**