1
0
mirror of https://github.com/JKorf/CryptoExchange.Net.git synced 2026-08-14 01:42:55 +00:00

Compare commits

..

8 Commits

10 changed files with 72 additions and 18 deletions
@@ -443,6 +443,16 @@ namespace CryptoExchange.Net.Authentication
return DateTimeConverter.ConvertToMilliseconds(GetTimestamp(apiClient)).Value.ToString(CultureInfo.InvariantCulture); return DateTimeConverter.ConvertToMilliseconds(GetTimestamp(apiClient)).Value.ToString(CultureInfo.InvariantCulture);
} }
/// <summary>
/// Get millisecond timestamp as a long including the time sync offset from the api client
/// </summary>
/// <param name="apiClient"></param>
/// <returns></returns>
protected long GetMillisecondTimestampLong(RestApiClient apiClient)
{
return DateTimeConverter.ConvertToMilliseconds(GetTimestamp(apiClient)).Value;
}
/// <summary> /// <summary>
/// Return the serialized request body /// Return the serialized request body
/// </summary> /// </summary>
+2 -2
View File
@@ -163,7 +163,7 @@ namespace CryptoExchange.Net.Clients
CancellationToken cancellationToken, CancellationToken cancellationToken,
Dictionary<string, string>? additionalHeaders = null, Dictionary<string, string>? additionalHeaders = null,
int? weight = null, int? weight = null,
int? weightSingleLimiter = null) where T : class int? weightSingleLimiter = null)
{ {
var parameterPosition = definition.ParameterPosition ?? ParameterPositions[definition.Method]; var parameterPosition = definition.ParameterPosition ?? ParameterPositions[definition.Method];
return SendAsync<T>( return SendAsync<T>(
@@ -198,7 +198,7 @@ namespace CryptoExchange.Net.Clients
CancellationToken cancellationToken, CancellationToken cancellationToken,
Dictionary<string, string>? additionalHeaders = null, Dictionary<string, string>? additionalHeaders = null,
int? weight = null, int? weight = null,
int? weightSingleLimiter = null) where T : class int? weightSingleLimiter = null)
{ {
string? cacheKey = null; string? cacheKey = null;
if (ShouldCache(definition)) if (ShouldCache(definition))
+3 -3
View File
@@ -6,9 +6,9 @@
<PackageId>CryptoExchange.Net</PackageId> <PackageId>CryptoExchange.Net</PackageId>
<Authors>JKorf</Authors> <Authors>JKorf</Authors>
<Description>CryptoExchange.Net is a base library which is used to implement different cryptocurrency (exchange) API's. It provides a standardized way of implementing different API's, which results in a very similar experience for users of the API implementations.</Description> <Description>CryptoExchange.Net is a base library which is used to implement different cryptocurrency (exchange) API's. It provides a standardized way of implementing different API's, which results in a very similar experience for users of the API implementations.</Description>
<PackageVersion>8.6.0</PackageVersion> <PackageVersion>8.7.0</PackageVersion>
<AssemblyVersion>8.6.0</AssemblyVersion> <AssemblyVersion>8.7.0</AssemblyVersion>
<FileVersion>8.6.0</FileVersion> <FileVersion>8.7.0</FileVersion>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance> <PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<PackageTags>OKX;OKX.Net;Mexc;Mexc.Net;Kucoin;Kucoin.Net;Kraken;Kraken.Net;Huobi;Huobi.Net;CoinEx;CoinEx.Net;Bybit;Bybit.Net;Bitget;Bitget.Net;Bitfinex;Bitfinex.Net;Binance;Binance.Net;CryptoCurrency;CryptoCurrency Exchange</PackageTags> <PackageTags>OKX;OKX.Net;Mexc;Mexc.Net;Kucoin;Kucoin.Net;Kraken;Kraken.Net;Huobi;Huobi.Net;CoinEx;CoinEx.Net;Bybit;Bybit.Net;Bitget;Bitget.Net;Bitfinex;Bitfinex.Net;Binance;Binance.Net;CryptoCurrency;CryptoCurrency Exchange</PackageTags>
<RepositoryType>git</RepositoryType> <RepositoryType>git</RepositoryType>
+1
View File
@@ -261,6 +261,7 @@ namespace CryptoExchange.Net
if (price != null) if (price != null)
{ {
adjustedPrice = AdjustValueStep(0, decimal.MaxValue, symbol.PriceStep, RoundingType.Down, price.Value); adjustedPrice = AdjustValueStep(0, decimal.MaxValue, symbol.PriceStep, RoundingType.Down, price.Value);
adjustedPrice = symbol.PriceSignificantFigures.HasValue ? RoundToSignificantDigits(adjustedPrice.Value, symbol.PriceSignificantFigures.Value, RoundingType.Closest) : adjustedPrice;
adjustedPrice = symbol.PriceDecimals.HasValue ? RoundDown(price.Value, symbol.PriceDecimals.Value) : adjustedPrice; adjustedPrice = symbol.PriceDecimals.HasValue ? RoundDown(price.Value, symbol.PriceDecimals.Value) : adjustedPrice;
if (adjustedPrice != 0 && adjustedPrice * quantity < symbol.MinNotionalValue) if (adjustedPrice != 0 && adjustedPrice * quantity < symbol.MinNotionalValue)
{ {
@@ -35,6 +35,7 @@ namespace CryptoExchange.Net.Logging.Extensions
private static readonly Action<ILogger, int, TimeSpan?, Exception?> _startingTaskForNoDataReceivedCheck; private static readonly Action<ILogger, int, TimeSpan?, Exception?> _startingTaskForNoDataReceivedCheck;
private static readonly Action<ILogger, int, TimeSpan?, Exception?> _noDataReceiveTimoutReconnect; private static readonly Action<ILogger, int, TimeSpan?, Exception?> _noDataReceiveTimoutReconnect;
private static readonly Action<ILogger, int, string, string, Exception?> _socketProcessingStateChanged; private static readonly Action<ILogger, int, string, string, Exception?> _socketProcessingStateChanged;
private static readonly Action<ILogger, int, Exception?> _socketPingTimeout;
static CryptoExchangeWebSocketClientLoggingExtension() static CryptoExchangeWebSocketClientLoggingExtension()
{ {
@@ -180,9 +181,14 @@ namespace CryptoExchange.Net.Logging.Extensions
_socketProcessingStateChanged = LoggerMessage.Define<int, string, string>( _socketProcessingStateChanged = LoggerMessage.Define<int, string, string>(
LogLevel.Trace, LogLevel.Trace,
new EventId(1028, "SocketProcessingStateChanged"), new EventId(1029, "SocketProcessingStateChanged"),
"[Sckt {Id}] processing state change: {PreviousState} -> {NewState}"); "[Sckt {Id}] processing state change: {PreviousState} -> {NewState}");
_socketPingTimeout = LoggerMessage.Define<int>(
LogLevel.Warning,
new EventId(1030, "SocketPingTimeout"),
"[Sckt {Id}] ping frame timeout; reconnecting socket");
} }
public static void SocketConnecting( public static void SocketConnecting(
@@ -358,5 +364,11 @@ namespace CryptoExchange.Net.Logging.Extensions
{ {
_socketProcessingStateChanged(logger, socketId, prevState, newState, null); _socketProcessingStateChanged(logger, socketId, prevState, newState, null);
} }
public static void SocketPingTimeout(
this ILogger logger, int socketId)
{
_socketPingTimeout(logger, socketId, null);
}
} }
} }
@@ -183,7 +183,7 @@ namespace CryptoExchange.Net.Logging.Extensions
_sendingData = LoggerMessage.Define<int, int, string>( _sendingData = LoggerMessage.Define<int, int, string>(
LogLevel.Trace, LogLevel.Trace,
new EventId(2028, "SendingData"), new EventId(2028, "SendingData"),
"[Sckt {SocketId}] [Req {RequestId}] sending messsage: {Data}"); "[Sckt {SocketId}] [Req {RequestId}] sending message: {Data}");
_receivedMessageNotMatchedToAnyListener = LoggerMessage.Define<int, string, string>( _receivedMessageNotMatchedToAnyListener = LoggerMessage.Define<int, string, string>(
LogLevel.Warning, LogLevel.Warning,
@@ -46,6 +46,10 @@
/// </summary> /// </summary>
public int? PriceDecimals { get; set; } public int? PriceDecimals { get; set; }
/// <summary> /// <summary>
/// The max amount of significant figures to use for price. For example with value of 5 these values are valid: 0.00001, 0.12300, 123.53, 12345, but this is not: 12345.1
/// </summary>
public int? PriceSignificantFigures { get; set; }
/// <summary>
/// Whether the symbol is currently available for trading /// Whether the symbol is currently available for trading
/// </summary> /// </summary>
public bool Trading { get; set; } public bool Trading { get; set; }
@@ -587,15 +587,27 @@ namespace CryptoExchange.Net.Sockets
lock (_receivedMessagesLock) lock (_receivedMessagesLock)
_receivedMessages.Add(new ReceiveItem(DateTime.UtcNow, receiveResult.Count)); _receivedMessages.Add(new ReceiveItem(DateTime.UtcNow, receiveResult.Count));
} }
catch (OperationCanceledException) catch (OperationCanceledException ex)
{ {
if (ex.InnerException?.InnerException?.Message.Equals("The WebSocket didn't recieve a Pong frame in response to a Ping frame within the configured KeepAliveTimeout.") == true)
{
// Spefic case that the websocket connection got closed because of a ping frame timeout
// Unfortunately doesn't seem to be a nicer way to catch
_logger.SocketPingTimeout(Id);
}
if (_closeTask?.IsCompleted != false)
_closeTask = CloseInternalAsync();
// canceled // canceled
break; break;
} }
catch (Exception wse) catch (Exception wse)
{ {
// Connection closed unexpectedly if (!_ctsSource.Token.IsCancellationRequested)
await (OnError?.Invoke(wse) ?? Task.CompletedTask).ConfigureAwait(false); // Connection closed unexpectedly
await (OnError?.Invoke(wse) ?? Task.CompletedTask).ConfigureAwait(false);
if (_closeTask?.IsCompleted != false) if (_closeTask?.IsCompleted != false)
_closeTask = CloseInternalAsync(); _closeTask = CloseInternalAsync();
break; break;
+14 -8
View File
@@ -150,9 +150,9 @@ namespace CryptoExchange.Net.Testing
/// </summary> /// </summary>
/// <typeparam name="TClient"></typeparam> /// <typeparam name="TClient"></typeparam>
/// <exception cref="Exception"></exception> /// <exception cref="Exception"></exception>
public static void CheckForMissingRestInterfaces<TClient>() public static void CheckForMissingRestInterfaces<TClient>(string[]? excludeInterfaces = null)
{ {
CheckForMissingInterfaces(typeof(TClient), typeof(Task)); CheckForMissingInterfaces(typeof(TClient), typeof(Task), excludeInterfaces);
} }
/// <summary> /// <summary>
@@ -160,26 +160,32 @@ namespace CryptoExchange.Net.Testing
/// </summary> /// </summary>
/// <typeparam name="TClient"></typeparam> /// <typeparam name="TClient"></typeparam>
/// <exception cref="Exception"></exception> /// <exception cref="Exception"></exception>
public static void CheckForMissingSocketInterfaces<TClient>() public static void CheckForMissingSocketInterfaces<TClient>(string[]? excludeInterfaces = null)
{ {
CheckForMissingInterfaces(typeof(TClient), typeof(Task<CallResult<UpdateSubscription>>)); CheckForMissingInterfaces(typeof(TClient), typeof(Task<CallResult<UpdateSubscription>>), excludeInterfaces);
} }
private static void CheckForMissingInterfaces(Type clientType, Type implementationTypes) private static void CheckForMissingInterfaces(Type clientType, Type implementationTypes, string[]? excludeInterfaces = null)
{ {
var assembly = Assembly.GetAssembly(clientType); var assembly = Assembly.GetAssembly(clientType);
var interfaceType = clientType.GetInterface("I" + clientType.Name); var interfaceType = clientType.GetInterface("I" + clientType.Name);
var clientInterfaces = assembly!.GetTypes().Where(t => t.Name.StartsWith("I" + clientType.Name) && !t.Name.EndsWith("Shared")); var clientInterfaces = assembly!.GetTypes()
.Where(t => t.Name.StartsWith("I" + clientType.Name)
&& !t.Name.EndsWith("Shared")
&& (excludeInterfaces?.Contains(t.Name) != true));
foreach (var clientInterface in clientInterfaces) foreach (var clientInterface in clientInterfaces)
{ {
var implementations = assembly.GetTypes().Where(t => clientInterface.IsAssignableFrom(t) && t != clientInterface); var implementations = assembly.GetTypes().Where(t => clientInterface.IsAssignableFrom(t) && !t.IsInterface && t != clientInterface);
foreach (var implementation in implementations) foreach (var implementation in implementations)
{ {
int methods = 0; int methods = 0;
foreach (var method in implementation.GetMethods().Where(m => implementationTypes.IsAssignableFrom(m.ReturnType))) foreach (var method in implementation.GetMethods().Where(m => implementationTypes.IsAssignableFrom(m.ReturnType)))
{ {
var interfaceMethod = clientInterface.GetMethod(method.Name, method.GetParameters().Select(p => p.ParameterType).ToArray()) ?? throw new Exception($"Missing interface for method {method.Name} in {implementation.Name} implementing interface {clientInterface.Name}"); var interfaceMethod =
clientInterface.GetMethod(method.Name, method.GetParameters().Select(p => p.ParameterType).ToArray())
?? clientInterface.GetInterfaces().Select(x => x.GetMethod(method.Name, method.GetParameters().Select(p => p.ParameterType).ToArray())).FirstOrDefault()
?? throw new Exception($"Missing interface for method {method.Name} in {implementation.Name} implementing interface {clientInterface.Name}");
methods++; methods++;
} }
+9
View File
@@ -66,6 +66,15 @@ Make a one time donation in a crypto currency of your choice. If you prefer to d
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 8.7.0 - 21 Jan 2025
* Added GetMillisecondTimestampLong helper method to AuthenticationProvider
* Added PriceSignificationFigures to SharedSpotSymbol model
* Version 8.6.1 - 09 Jan 2025
* Fixed websocket connection getting stuck after a ping frame timeout
* Removed websocket Error callback when exception is expected
* Removed unnecessary type restraints on RestApiClient.SendAsync methods
* Version 8.6.0 - 07 Jan 2025 * Version 8.6.0 - 07 Jan 2025
* Added support for passing weight to apply to an individual ratelimit guard * Added support for passing weight to apply to an individual ratelimit guard
* Added IFeeRestClient to service registration * Added IFeeRestClient to service registration