1
0
mirror of https://github.com/JKorf/CryptoExchange.Net.git synced 2026-08-12 17:03:10 +00:00

Compare commits

...

11 Commits

Author SHA1 Message Date
JKorf ac5f333766 Updated version 2023-09-23 21:16:09 +02:00
JKorf 640e4387c1 Added BoolConverter, added parameter for showing warning message to EnumConverter 2023-09-23 21:13:49 +02:00
JKorf a16b19019f Updated version 2023-09-18 20:13:00 +02:00
JKorf 2443f576ac Fix for concurrency exception 2023-09-18 20:02:01 +02:00
JKorf 4fd7e44015 Logging 2023-09-16 18:26:10 +02:00
JKorf a0a3bda1c5 Updated version 2023-09-11 20:59:44 +02:00
JKorf 6bda7a3c73 Fixed nullreference if no Retry-After is returned after with a ratelimit error 2023-09-11 20:57:46 +02:00
JKorf 69a7a714cd Updated rate limiters to support multiple instances 2023-09-11 20:16:50 +02:00
JKorf 48e2e6468e Updated version 2023-09-04 18:10:18 +02:00
JKorf 4017ac780f ArrayConverter update for handling exponent notation, EnumConverter fix for writing enum values 2023-09-04 18:09:01 +02:00
JKorf a55cd1bb13 Docs 2023-08-26 20:04:38 +02:00
11 changed files with 151 additions and 33 deletions
+1 -2
View File
@@ -15,7 +15,6 @@ using CryptoExchange.Net.Requests;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using static CryptoExchange.Net.Objects.RateLimiter;
namespace CryptoExchange.Net
{
@@ -558,7 +557,7 @@ namespace CryptoExchange.Net
{
// Handle retry after header
var retryAfterHeader = responseHeaders.SingleOrDefault(r => r.Key.Equals("Retry-After", StringComparison.InvariantCultureIgnoreCase));
if (!retryAfterHeader.Value.Any())
if (retryAfterHeader.Value?.Any() != true)
return new ServerRateLimitError(data);
var value = retryAfterHeader.Value.First();
@@ -109,13 +109,21 @@ namespace CryptoExchange.Net.Converters
{
if (token.Type == JTokenType.Null)
value = null;
if (token.Type == JTokenType.Float)
value = token.Value<decimal>();
}
if ((property.PropertyType == typeof(decimal)
if (value is decimal)
{
property.SetValue(result, value);
}
else if ((property.PropertyType == typeof(decimal)
|| property.PropertyType == typeof(decimal?))
&& (value != null && value.ToString().IndexOf("e", StringComparison.OrdinalIgnoreCase) >= 0))
{
if (decimal.TryParse(value.ToString(), NumberStyles.Float, CultureInfo.InvariantCulture, out var dec))
var v = value.ToString();
if (decimal.TryParse(v, NumberStyles.Float, CultureInfo.InvariantCulture, out var dec))
property.SetValue(result, dec);
}
else
@@ -0,0 +1,68 @@
using System;
using Newtonsoft.Json;
namespace CryptoExchange.Net.Converters
{
/// <summary>
/// Boolean converter with support for "0"/"1" (strings)
/// </summary>
public class BoolConverter : JsonConverter
{
/// <summary>
/// Determines whether this instance can convert the specified object type.
/// </summary>
/// <param name="objectType">Type of the object.</param>
/// <returns>
/// <c>true</c> if this instance can convert the specified object type; otherwise, <c>false</c>.
/// </returns>
public override bool CanConvert(Type objectType)
{
if (Nullable.GetUnderlyingType(objectType) != null)
return Nullable.GetUnderlyingType(objectType) == typeof(bool);
return objectType == typeof(bool);
}
/// <summary>
/// Reads the JSON representation of the object.
/// </summary>
/// <param name="reader">The <see cref="T:Newtonsoft.Json.JsonReader"/> to read from.</param>
/// <param name="objectType">Type of the object.</param>
/// <param name="existingValue">The existing value of object being read.</param>
/// <param name="serializer">The calling serializer.</param>
/// <returns>
/// The object value.
/// </returns>
public override object? ReadJson(JsonReader reader, Type objectType, object? existingValue, JsonSerializer serializer)
{
switch (reader.Value?.ToString().ToLower().Trim())
{
case "true":
case "yes":
case "y":
case "1":
return true;
case "false":
case "no":
case "n":
case "0":
return false;
}
// If we reach here, we're pretty much going to throw an error so let's let Json.NET throw it's pretty-fied error message.
return new JsonSerializer().Deserialize(reader, objectType);
}
/// <summary>
/// Specifies that this converter will not participate in writing results.
/// </summary>
public override bool CanWrite { get { return false; } }
/// <summary>
/// Writes the JSON representation of the object.
/// </summary>
/// <param name="writer">The <see cref="T:Newtonsoft.Json.JsonWriter"/> to write to.</param><param name="value">The value.</param><param name="serializer">The calling serializer.</param>
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
}
}
}
+34 -6
View File
@@ -14,6 +14,20 @@ namespace CryptoExchange.Net.Converters
/// </summary>
public class EnumConverter : JsonConverter
{
private bool _warnOnMissingEntry = true;
/// <summary>
/// </summary>
public EnumConverter() { }
/// <summary>
/// </summary>
/// <param name="warnOnMissingEntry"></param>
public EnumConverter(bool warnOnMissingEntry)
{
_warnOnMissingEntry = warnOnMissingEntry;
}
private static readonly ConcurrentDictionary<Type, List<KeyValuePair<object, string>>> _mapping = new();
/// <inheritdoc />
@@ -51,8 +65,12 @@ namespace CryptoExchange.Net.Converters
Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Received empty string as enum value, but property type is not a nullable enum. EnumType: {enumType.Name}. If you think {enumType.Name} should be nullable please open an issue on the Github repo");
}
else
{
// We received an enum value but weren't able to parse it.
Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Cannot map enum value. EnumType: {enumType.Name}, Value: {reader.Value}, Known values: {string.Join(", ", mapping.Select(m => m.Value))}. If you think {reader.Value} should added please open an issue on the Github repo");
if (_warnOnMissingEntry)
Trace.WriteLine($"{DateTime.Now:yyyy/MM/dd HH:mm:ss:fff} | Warning | Cannot map enum value. EnumType: {enumType.Name}, Value: {reader.Value}, Known values: {string.Join(", ", mapping.Select(m => m.Value))}. If you think {reader.Value} should added please open an issue on the Github repo");
}
return defaultValue;
}
@@ -117,22 +135,32 @@ namespace CryptoExchange.Net.Converters
/// <param name="enumValue"></param>
/// <returns></returns>
[return: NotNullIfNotNull("enumValue")]
public static string? GetString<T>(T enumValue)
public static string? GetString<T>(T enumValue) => GetString(typeof(T), enumValue);
[return: NotNullIfNotNull("enumValue")]
private static string? GetString(Type objectType, object? enumValue)
{
var objectType = typeof(T);
objectType = Nullable.GetUnderlyingType(objectType) ?? objectType;
if (!_mapping.TryGetValue(objectType, out var mapping))
mapping = AddMapping(objectType);
return enumValue == null ? null : (mapping.FirstOrDefault(v => v.Key.Equals(enumValue)).Value ?? enumValue.ToString());
return enumValue == null ? null : (mapping.FirstOrDefault(v => v.Key.Equals(enumValue)).Value ?? enumValue.ToString());
}
/// <inheritdoc />
public override void WriteJson(JsonWriter writer, object? value, JsonSerializer serializer)
{
var stringValue = GetString(value);
writer.WriteValue(stringValue);
if (value == null)
{
writer.WriteNull();
}
else
{
var stringValue = GetString(value.GetType(), value);
writer.WriteValue(stringValue);
}
}
}
}
+4 -4
View File
@@ -6,16 +6,16 @@
<PackageId>CryptoExchange.Net</PackageId>
<Authors>JKorf</Authors>
<Description>A base package for implementing cryptocurrency API's</Description>
<PackageVersion>6.1.0</PackageVersion>
<AssemblyVersion>6.1.0</AssemblyVersion>
<FileVersion>6.1.0</FileVersion>
<PackageVersion>6.1.4</PackageVersion>
<AssemblyVersion>6.1.4</AssemblyVersion>
<FileVersion>6.1.4</FileVersion>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/JKorf/CryptoExchange.Net.git</RepositoryUrl>
<PackageProjectUrl>https://github.com/JKorf/CryptoExchange.Net</PackageProjectUrl>
<NeutralLanguage>en</NeutralLanguage>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<PackageReleaseNotes>6.1.0 - Added support for ratelimiting on socket connections, Added rest ratelimit handling and parsing, Added ServerRatelimitError error</PackageReleaseNotes>
<PackageReleaseNotes>6.1.4 - Added BoolConverter, Added parameter for logging warning message on missing enum entry to EnumConverter</PackageReleaseNotes>
<Nullable>enable</Nullable>
<LangVersion>10.0</LangVersion>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
-1
View File
@@ -128,7 +128,6 @@ namespace CryptoExchange.Net
return value / 1.000000000000000000000000000000000m;
}
/// <summary>
/// Generate a new unique id. The id is staticly stored so it is guarenteed to be unique
/// </summary>
+11 -11
View File
@@ -117,10 +117,10 @@ namespace CryptoExchange.Net.Objects
{
int totalWaitTime = 0;
EndpointRateLimiter? endpointLimit;
List<EndpointRateLimiter> endpointLimits;
lock (_limiterLock)
endpointLimit = _limiters.OfType<EndpointRateLimiter>().SingleOrDefault(h => h.Endpoints.Contains(endpoint) && (h.Method == null || h.Method == method));
if(endpointLimit != null)
endpointLimits = _limiters.OfType<EndpointRateLimiter>().Where(h => h.Endpoints.Contains(endpoint) && (h.Method == null || h.Method == method)).ToList();
foreach (var endpointLimit in endpointLimits)
{
var waitResult = await ProcessTopic(logger, endpointLimit, endpoint, requestWeight, limitBehaviour, ct).ConfigureAwait(false);
if (!waitResult)
@@ -129,7 +129,7 @@ namespace CryptoExchange.Net.Objects
totalWaitTime += waitResult.Data;
}
if (endpointLimit?.IgnoreOtherRateLimits == true)
if (endpointLimits.Any(l => l.IgnoreOtherRateLimits))
return new CallResult<int>(totalWaitTime);
List<PartialEndpointRateLimiter> partialEndpointLimits;
@@ -169,10 +169,10 @@ namespace CryptoExchange.Net.Objects
if(partialEndpointLimits.Any(p => p.IgnoreOtherRateLimits))
return new CallResult<int>(totalWaitTime);
ApiKeyRateLimiter? apiLimit;
List<ApiKeyRateLimiter> apiLimits;
lock (_limiterLock)
apiLimit = _limiters.OfType<ApiKeyRateLimiter>().SingleOrDefault(h => h.Type == RateLimitType.ApiKey);
if (apiLimit != null)
apiLimits = _limiters.OfType<ApiKeyRateLimiter>().Where(h => h.Type == RateLimitType.ApiKey).ToList();
foreach (var apiLimit in apiLimits)
{
if(apiKey == null)
{
@@ -206,13 +206,13 @@ namespace CryptoExchange.Net.Objects
}
}
if ((signed || apiLimit?.OnlyForSignedRequests == false) && apiLimit?.IgnoreTotalRateLimit == true)
if ((signed || apiLimits.All(l => !l.OnlyForSignedRequests)) && apiLimits.Any(l => l.IgnoreTotalRateLimit))
return new CallResult<int>(totalWaitTime);
TotalRateLimiter? totalLimit;
List<TotalRateLimiter> totalLimits;
lock (_limiterLock)
totalLimit = _limiters.OfType<TotalRateLimiter>().SingleOrDefault();
if (totalLimit != null)
totalLimits = _limiters.OfType<TotalRateLimiter>().ToList();
foreach(var totalLimit in totalLimits)
{
var waitResult = await ProcessTopic(logger, totalLimit, endpoint, requestWeight, limitBehaviour, ct).ConfigureAwait(false);
if (!waitResult)
@@ -278,7 +278,7 @@ namespace CryptoExchange.Net.Sockets
return;
var bytes = Parameters.Encoding.GetBytes(data);
_logger.Log(LogLevel.Trace, $"Socket {Id} - msg {id} - Adding {bytes.Length} to send buffer");
_logger.Log(LogLevel.Trace, $"Socket {Id} - msg {id} - Adding {bytes.Length} bytes to send buffer");
_sendBuffer.Enqueue(new SendItem { Id = id, Weight = weight, Bytes = bytes });
_sendEvent.Set();
}
@@ -291,10 +291,13 @@ namespace CryptoExchange.Net.Sockets
/// <param name="requestId">Id of the request sent</param>
protected virtual void HandleRequestSent(int requestId)
{
var pendingRequest = _pendingRequests.SingleOrDefault(p => p.Id == requestId);
PendingRequest pendingRequest;
lock (_pendingRequests)
pendingRequest = _pendingRequests.SingleOrDefault(p => p.Id == requestId);
if (pendingRequest == null)
{
_logger.Log(LogLevel.Debug, $"Socket {SocketId} - msg {requestId} - message sent, but not pending");
_logger.Log(LogLevel.Debug, $"Socket {SocketId} - msg {requestId} - message sent, but not pending");
return;
}
@@ -380,7 +383,7 @@ namespace CryptoExchange.Net.Sockets
"Data from this socket may arrive late or not at all if message processing is continuously slow.");
}
_logger.Log(LogLevel.Trace, $"Socket {SocketId}{(subscription == null ? "" : " subscription " + subscription!.Id)} message processed in {(int)total.TotalMilliseconds}ms, ({(int)userProcessTime.TotalMilliseconds}ms user code)");
_logger.Log(LogLevel.Trace, $"Socket {SocketId}{(subscription == null ? "" : " subscription " + subscription!.Id)} message processed in {(int)total.TotalMilliseconds}ms ({(int)userProcessTime.TotalMilliseconds}ms user code)");
}
/// <summary>
+15 -2
View File
@@ -1,5 +1,5 @@
# CryptoExchange.Net
[![.NET](https://github.com/JKorf/CryptoExchange.Net/actions/workflows/dotnet.yml/badge.svg?branch=master)](https://github.com/JKorf/CryptoExchange.Net/actions/workflows/dotnet.yml) ![Nuget version](https://img.shields.io/nuget/v/CryptoExchange.Net.svg) ![Nuget downloads](https://img.shields.io/nuget/dt/CryptoExchange.Net.svg)
[![.NET](https://github.com/JKorf/CryptoExchange.Net/actions/workflows/dotnet.yml/badge.svg?branch=master)](https://github.com/JKorf/CryptoExchange.Net/actions/workflows/dotnet.yml) [![Nuget version](https://img.shields.io/nuget/v/CryptoExchange.Net.svg)](https://www.nuget.org/packages/CryptoExchange.Net) [![Nuget downloads](https://img.shields.io/nuget/dt/CryptoExchange.Net.svg)](https://www.nuget.org/packages/CryptoExchange.Net)
CryptoExchange.Net is a base package which can be used to easily implement crypto currency exchange API's in C#. This library offers base classes for creating rest and websocket clients, and includes additional features like an automatically synchronizing order book implementation, error handling and automatic reconnects on websocket connections.
@@ -18,7 +18,6 @@ Use one of the following following referral links to signup to a new exchange to
[Bittrex](https://bittrex.com/discover/join?referralCode=TST-DJM-CSX)
[Bybit](https://partner.bybit.com/b/jkorf)
[CoinEx](https://www.coinex.com/register?refer_code=hd6gn)
[FTX](https://ftx.com/referrals#a=31620192)
[Huobi](https://www.huobi.com/en-us/v/register/double-invite/?inviter_id=11343840&invite_code=fxp93)
[Kucoin](https://www.kucoin.com/ucenter/signup?rcode=RguMux)
@@ -32,6 +31,20 @@ 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).
## Release notes
* Version 6.1.4 - 23 Sep 2023
* Added BoolConverter
* Added parameter for logging warning message on missing enum entry to EnumConverter
* Version 6.1.3 - 18 Sep 2023
* Fix for concurrency exception in socket subscription
* Version 6.1.2 - 11 Sep 2023
* Added support for multiple of the same ratelimiting type in the same rate limiter
* Fixed nullreference on rate limit error if no Retry-After header is returned
* Version 6.1.1 - 04 Sep 2023
* Fixes for json converters
* Version 6.1.0 - 24 Aug 2023
* Added support for ratelimiting on socket connections
* Added rest ratelimit handling and parsing
+1 -1
View File
@@ -3,7 +3,7 @@ title: Home
nav_order: 1
---
[![.NET](https://github.com/JKorf/CryptoExchange.Net/actions/workflows/dotnet.yml/badge.svg?branch=master)](https://github.com/JKorf/CryptoExchange.Net/actions/workflows/dotnet.yml) ![Nuget version](https://img.shields.io/nuget/v/CryptoExchange.Net.svg) ![Nuget downloads](https://img.shields.io/nuget/dt/CryptoExchange.Net.svg)
[![.NET](https://github.com/JKorf/CryptoExchange.Net/actions/workflows/dotnet.yml/badge.svg?branch=master)](https://github.com/JKorf/CryptoExchange.Net/actions/workflows/dotnet.yml) [![Nuget version](https://img.shields.io/nuget/v/CryptoExchange.Net.svg)](https://www.nuget.org/packages/CryptoExchange.Net) [![Nuget downloads](https://img.shields.io/nuget/dt/CryptoExchange.Net.svg)](https://www.nuget.org/packages/CryptoExchange.Net)
The CryptoExchange.Net library is a base package for exchange API implementations. It offers base classes for creating clients for exchange API's. Basing exchange implementation on the common CryptoExchange.Net library allows for ease of implementation for new exchanges, as only the endpoints and models have to implemented, but not all systems around requests and connections, and it makes it easier for users to implement a new library in their code base as all base principles and configuration are the same for different exchanges.