mirror of
https://github.com/JKorf/CryptoExchange.Net.git
synced 2026-08-15 02:13:01 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f2cf70b02f | |||
| 9ff417bba8 | |||
| 6b43d08a4d | |||
| 39bf7fe9b9 | |||
| b5893c3b60 | |||
| 15657ba683 | |||
| 1aed9f0c67 | |||
| 17f1560310 | |||
| 41de0a3150 | |||
| 3e410be611 | |||
| be75449e4a | |||
| b1b05c8f6b |
@@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net8.0</TargetFramework>
|
||||||
@@ -6,10 +6,10 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.9.0"></PackageReference>
|
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.11.1"></PackageReference>
|
||||||
<PackageReference Include="Moq" Version="4.20.70" />
|
<PackageReference Include="Moq" Version="4.20.72" />
|
||||||
<PackageReference Include="NUnit" Version="4.1.0"></PackageReference>
|
<PackageReference Include="NUnit" Version="4.2.2"></PackageReference>
|
||||||
<PackageReference Include="NUnit3TestAdapter" Version="4.5.0"></PackageReference>
|
<PackageReference Include="NUnit3TestAdapter" Version="4.6.0"></PackageReference>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -72,6 +72,11 @@ namespace CryptoExchange.Net.Clients
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
protected List<DedicatedConnectionConfig> DedicatedConnectionConfigs { get; set; } = new List<DedicatedConnectionConfig>();
|
protected List<DedicatedConnectionConfig> DedicatedConnectionConfigs { get; set; } = new List<DedicatedConnectionConfig>();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether to allow multiple subscriptions with the same topic on the same connection
|
||||||
|
/// </summary>
|
||||||
|
protected bool AllowTopicsOnTheSameConnection { get; set; } = true;
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public double IncomingKbps
|
public double IncomingKbps
|
||||||
{
|
{
|
||||||
@@ -211,7 +216,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
while (true)
|
while (true)
|
||||||
{
|
{
|
||||||
// Get a new or existing socket connection
|
// Get a new or existing socket connection
|
||||||
var socketResult = await GetSocketConnection(url, subscription.Authenticated, false).ConfigureAwait(false);
|
var socketResult = await GetSocketConnection(url, subscription.Authenticated, false, subscription.Topic).ConfigureAwait(false);
|
||||||
if (!socketResult)
|
if (!socketResult)
|
||||||
return socketResult.As<UpdateSubscription>(null);
|
return socketResult.As<UpdateSubscription>(null);
|
||||||
|
|
||||||
@@ -403,7 +408,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
return new CallResult(new NoApiCredentialsError());
|
return new CallResult(new NoApiCredentialsError());
|
||||||
|
|
||||||
_logger.AttemptingToAuthenticate(socket.SocketId);
|
_logger.AttemptingToAuthenticate(socket.SocketId);
|
||||||
var authRequest = GetAuthenticationRequest(socket);
|
var authRequest = await GetAuthenticationRequestAsync(socket).ConfigureAwait(false);
|
||||||
if (authRequest != null)
|
if (authRequest != null)
|
||||||
{
|
{
|
||||||
var result = await socket.SendAndWaitQueryAsync(authRequest).ConfigureAwait(false);
|
var result = await socket.SendAndWaitQueryAsync(authRequest).ConfigureAwait(false);
|
||||||
@@ -428,7 +433,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
/// Should return the request which can be used to authenticate a socket connection
|
/// Should return the request which can be used to authenticate a socket connection
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected internal virtual Query? GetAuthenticationRequest(SocketConnection connection) => throw new NotImplementedException();
|
protected internal virtual Task<Query?> GetAuthenticationRequestAsync(SocketConnection connection) => throw new NotImplementedException();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Adds a system subscription. Used for example to reply to ping requests
|
/// Adds a system subscription. Used for example to reply to ping requests
|
||||||
@@ -478,23 +483,28 @@ namespace CryptoExchange.Net.Clients
|
|||||||
/// <param name="address">The address the socket is for</param>
|
/// <param name="address">The address the socket is for</param>
|
||||||
/// <param name="authenticated">Whether the socket should be authenticated</param>
|
/// <param name="authenticated">Whether the socket should be authenticated</param>
|
||||||
/// <param name="dedicatedRequestConnection">Whether a dedicated request connection should be returned</param>
|
/// <param name="dedicatedRequestConnection">Whether a dedicated request connection should be returned</param>
|
||||||
|
/// <param name="topic">The subscription topic, can be provided when multiple of the same topics are not allowed on a connection</param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
protected virtual async Task<CallResult<SocketConnection>> GetSocketConnection(string address, bool authenticated, bool dedicatedRequestConnection)
|
protected virtual async Task<CallResult<SocketConnection>> GetSocketConnection(string address, bool authenticated, bool dedicatedRequestConnection, string? topic = null)
|
||||||
{
|
{
|
||||||
var socketQuery = socketConnections.Where(s => (s.Value.Status == SocketConnection.SocketStatus.None || s.Value.Status == SocketConnection.SocketStatus.Connected)
|
var socketQuery = socketConnections.Where(s => (s.Value.Status == SocketConnection.SocketStatus.None || s.Value.Status == SocketConnection.SocketStatus.Connected)
|
||||||
&& s.Value.Tag.TrimEnd('/') == address.TrimEnd('/')
|
&& s.Value.Tag.TrimEnd('/') == address.TrimEnd('/')
|
||||||
&& s.Value.ApiClient.GetType() == GetType()
|
&& s.Value.ApiClient.GetType() == GetType()
|
||||||
&& (s.Value.Authenticated == authenticated || !authenticated)
|
&& (s.Value.Authenticated == authenticated || !authenticated)
|
||||||
|
&& (AllowTopicsOnTheSameConnection || !s.Value.Topics.Contains(topic))
|
||||||
&& s.Value.Connected);
|
&& s.Value.Connected);
|
||||||
|
|
||||||
SocketConnection connection;
|
SocketConnection connection;
|
||||||
if (!dedicatedRequestConnection)
|
if (!dedicatedRequestConnection)
|
||||||
{
|
{
|
||||||
connection = socketQuery.Where(s => !s.Value.DedicatedRequestConnection).OrderBy(s => s.Value.UserSubscriptionCount).FirstOrDefault().Value;
|
connection = socketQuery.Where(s => !s.Value.DedicatedRequestConnection.IsDedicatedRequestConnection).OrderBy(s => s.Value.UserSubscriptionCount).FirstOrDefault().Value;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
connection = socketQuery.Where(s => s.Value.DedicatedRequestConnection).FirstOrDefault().Value;
|
connection = socketQuery.Where(s => s.Value.DedicatedRequestConnection.IsDedicatedRequestConnection).FirstOrDefault().Value;
|
||||||
|
if (connection != null && !connection.DedicatedRequestConnection.Authenticated)
|
||||||
|
// Mark dedicated request connection as authenticated if the request is authenticated
|
||||||
|
connection.DedicatedRequestConnection.Authenticated = authenticated;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (connection != null)
|
if (connection != null)
|
||||||
@@ -519,7 +529,14 @@ namespace CryptoExchange.Net.Clients
|
|||||||
var socketConnection = new SocketConnection(_logger, this, socket, address);
|
var socketConnection = new SocketConnection(_logger, this, socket, address);
|
||||||
socketConnection.UnhandledMessage += HandleUnhandledMessage;
|
socketConnection.UnhandledMessage += HandleUnhandledMessage;
|
||||||
socketConnection.ConnectRateLimitedAsync += HandleConnectRateLimitedAsync;
|
socketConnection.ConnectRateLimitedAsync += HandleConnectRateLimitedAsync;
|
||||||
socketConnection.DedicatedRequestConnection = dedicatedRequestConnection;
|
if (dedicatedRequestConnection)
|
||||||
|
{
|
||||||
|
socketConnection.DedicatedRequestConnection = new DedicatedConnectionState
|
||||||
|
{
|
||||||
|
IsDedicatedRequestConnection = dedicatedRequestConnection,
|
||||||
|
Authenticated = authenticated
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
foreach (var ptg in PeriodicTaskRegistrations)
|
foreach (var ptg in PeriodicTaskRegistrations)
|
||||||
socketConnection.QueryPeriodic(ptg.Identifier, ptg.Interval, ptg.QueryDelegate, ptg.Callback);
|
socketConnection.QueryPeriodic(ptg.Identifier, ptg.Interval, ptg.QueryDelegate, ptg.Callback);
|
||||||
@@ -652,7 +669,7 @@ namespace CryptoExchange.Net.Clients
|
|||||||
var tasks = new List<Task>();
|
var tasks = new List<Task>();
|
||||||
{
|
{
|
||||||
var socketList = socketConnections.Values;
|
var socketList = socketConnections.Values;
|
||||||
foreach (var connection in socketList.Where(s => !s.DedicatedRequestConnection))
|
foreach (var connection in socketList.Where(s => !s.DedicatedRequestConnection.IsDedicatedRequestConnection))
|
||||||
tasks.Add(connection.CloseAsync());
|
tasks.Add(connection.CloseAsync());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
|
|||||||
private class ArrayConverterInner<T> : JsonConverter<T>
|
private class ArrayConverterInner<T> : JsonConverter<T>
|
||||||
{
|
{
|
||||||
private static readonly ConcurrentDictionary<Type, List<ArrayPropertyInfo>> _typeAttributesCache = new ConcurrentDictionary<Type, List<ArrayPropertyInfo>>();
|
private static readonly ConcurrentDictionary<Type, List<ArrayPropertyInfo>> _typeAttributesCache = new ConcurrentDictionary<Type, List<ArrayPropertyInfo>>();
|
||||||
|
private static readonly ConcurrentDictionary<Type, JsonSerializerOptions> _converterOptionsCache = new ConcurrentDictionary<Type, JsonSerializerOptions>();
|
||||||
|
|
||||||
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
|
public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options)
|
||||||
{
|
{
|
||||||
@@ -108,7 +108,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
|
|||||||
return default;
|
return default;
|
||||||
|
|
||||||
var result = Activator.CreateInstance(typeToConvert);
|
var result = Activator.CreateInstance(typeToConvert);
|
||||||
return (T)ParseObject(ref reader, result, typeToConvert);
|
return (T)ParseObject(ref reader, result, typeToConvert, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static bool IsSimple(Type type)
|
private static bool IsSimple(Type type)
|
||||||
@@ -148,7 +148,7 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
|
|||||||
return attributes;
|
return attributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static object ParseObject(ref Utf8JsonReader reader, object result, Type objectType)
|
private static object ParseObject(ref Utf8JsonReader reader, object result, Type objectType, JsonSerializerOptions options)
|
||||||
{
|
{
|
||||||
if (reader.TokenType != JsonTokenType.StartArray)
|
if (reader.TokenType != JsonTokenType.StartArray)
|
||||||
throw new Exception("Not an array");
|
throw new Exception("Not an array");
|
||||||
@@ -175,15 +175,24 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
|
|||||||
object? value = null;
|
object? value = null;
|
||||||
if (attribute.JsonConverterType != null)
|
if (attribute.JsonConverterType != null)
|
||||||
{
|
{
|
||||||
// Has JsonConverter attribute
|
if (!_converterOptionsCache.TryGetValue(attribute.JsonConverterType, out var newOptions))
|
||||||
var options = new JsonSerializerOptions();
|
{
|
||||||
options.Converters.Add((JsonConverter)Activator.CreateInstance(attribute.JsonConverterType));
|
var converter = (JsonConverter)Activator.CreateInstance(attribute.JsonConverterType);
|
||||||
value = JsonDocument.ParseValue(ref reader).Deserialize(targetType, options);
|
newOptions = new JsonSerializerOptions
|
||||||
|
{
|
||||||
|
NumberHandling = SerializerOptions.WithConverters.NumberHandling,
|
||||||
|
PropertyNameCaseInsensitive = SerializerOptions.WithConverters.PropertyNameCaseInsensitive,
|
||||||
|
Converters = { converter },
|
||||||
|
};
|
||||||
|
_converterOptionsCache.TryAdd(attribute.JsonConverterType, newOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
value = JsonDocument.ParseValue(ref reader).Deserialize(targetType, newOptions);
|
||||||
}
|
}
|
||||||
else if (attribute.DefaultDeserialization)
|
else if (attribute.DefaultDeserialization)
|
||||||
{
|
{
|
||||||
// Use default deserialization
|
// Use default deserialization
|
||||||
value = JsonDocument.ParseValue(ref reader).Deserialize(targetType);
|
value = JsonDocument.ParseValue(ref reader).Deserialize(targetType, options);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -194,12 +203,15 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
|
|||||||
JsonTokenType.True => true,
|
JsonTokenType.True => true,
|
||||||
JsonTokenType.String => reader.GetString(),
|
JsonTokenType.String => reader.GetString(),
|
||||||
JsonTokenType.Number => reader.GetDecimal(),
|
JsonTokenType.Number => reader.GetDecimal(),
|
||||||
JsonTokenType.StartObject => JsonSerializer.Deserialize(ref reader, attribute.TargetType),
|
JsonTokenType.StartObject => JsonSerializer.Deserialize(ref reader, attribute.TargetType, options),
|
||||||
_ => throw new NotImplementedException($"Array deserialization of type {reader.TokenType} not supported"),
|
_ => throw new NotImplementedException($"Array deserialization of type {reader.TokenType} not supported"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
attribute.PropertyInfo.SetValue(result, value == null ? null : Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture));
|
if (targetType.IsAssignableFrom(value?.GetType()))
|
||||||
|
attribute.PropertyInfo.SetValue(result, value == null ? null : value);
|
||||||
|
else
|
||||||
|
attribute.PropertyInfo.SetValue(result, value == null ? null : Convert.ChangeType(value, targetType, CultureInfo.InvariantCulture));
|
||||||
}
|
}
|
||||||
|
|
||||||
index++;
|
index++;
|
||||||
|
|||||||
@@ -50,6 +50,11 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
|
|||||||
var info = $"Deserialize JsonException: {ex.Message}, Path: {ex.Path}, LineNumber: {ex.LineNumber}, LinePosition: {ex.BytePositionInLine}";
|
var info = $"Deserialize JsonException: {ex.Message}, Path: {ex.Path}, LineNumber: {ex.LineNumber}, LinePosition: {ex.BytePositionInLine}";
|
||||||
return new CallResult<object>(new DeserializeError(info, OriginalDataAvailable ? GetOriginalString() : "[Data only available when OutputOriginal = true in client options]"));
|
return new CallResult<object>(new DeserializeError(info, OriginalDataAvailable ? GetOriginalString() : "[Data only available when OutputOriginal = true in client options]"));
|
||||||
}
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
var info = $"Deserialize unknown Exception: {ex.Message}";
|
||||||
|
return new CallResult<object>(new DeserializeError(info, OriginalDataAvailable ? GetOriginalString() : "[Data only available when OutputOriginal = true in client options]"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
@@ -121,7 +126,14 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
|
|||||||
return default;
|
return default;
|
||||||
|
|
||||||
if (value.Value.ValueKind == JsonValueKind.Object || value.Value.ValueKind == JsonValueKind.Array)
|
if (value.Value.ValueKind == JsonValueKind.Object || value.Value.ValueKind == JsonValueKind.Array)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return value.Value.Deserialize<T>(_serializerOptions);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
return default;
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
if (typeof(T) == typeof(string))
|
if (typeof(T) == typeof(string))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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.1.0</PackageVersion>
|
<PackageVersion>8.2.0</PackageVersion>
|
||||||
<AssemblyVersion>8.1.0</AssemblyVersion>
|
<AssemblyVersion>8.2.0</AssemblyVersion>
|
||||||
<FileVersion>8.1.0</FileVersion>
|
<FileVersion>8.2.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>
|
||||||
|
|||||||
@@ -19,7 +19,12 @@ namespace CryptoExchange.Net.Requests
|
|||||||
if (client == null)
|
if (client == null)
|
||||||
{
|
{
|
||||||
var handler = new HttpClientHandler();
|
var handler = new HttpClientHandler();
|
||||||
handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
|
try
|
||||||
|
{
|
||||||
|
handler.AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate;
|
||||||
|
}
|
||||||
|
catch (PlatformNotSupportedException) { }
|
||||||
|
|
||||||
if (proxy != null)
|
if (proxy != null)
|
||||||
{
|
{
|
||||||
handler.Proxy = new WebProxy
|
handler.Proxy = new WebProxy
|
||||||
|
|||||||
@@ -12,6 +12,10 @@
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Leverage is configured for the symbol
|
/// Leverage is configured for the symbol
|
||||||
/// </summary>
|
/// </summary>
|
||||||
PerSymbol
|
PerSymbol,
|
||||||
|
/// <summary>
|
||||||
|
/// Leverage is configured for the entire account
|
||||||
|
/// </summary>
|
||||||
|
PerAccount
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ namespace CryptoExchange.Net.SharedApis
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Side of the trade
|
/// Side of the trade
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SharedOrderSide Side { get; set; }
|
public SharedOrderSide? Side { get; set; }
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fee paid for the trade
|
/// Fee paid for the trade
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -51,7 +51,7 @@ namespace CryptoExchange.Net.SharedApis
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// ctor
|
/// ctor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public SharedUserTrade(string symbol, string orderId, string id, SharedOrderSide side, decimal quantity, decimal price, DateTime timestamp)
|
public SharedUserTrade(string symbol, string orderId, string id, SharedOrderSide? side, decimal quantity, decimal price, DateTime timestamp)
|
||||||
{
|
{
|
||||||
Symbol = symbol;
|
Symbol = symbol;
|
||||||
OrderId = orderId;
|
OrderId = orderId;
|
||||||
|
|||||||
@@ -14,4 +14,19 @@
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public bool Authenticated { get; set; }
|
public bool Authenticated { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Dedicated connection state
|
||||||
|
/// </summary>
|
||||||
|
public class DedicatedConnectionState
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the connection is a dedicated request connection
|
||||||
|
/// </summary>
|
||||||
|
public bool IsDedicatedRequestConnection { get; set; }
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the dedication request connection should be authenticated
|
||||||
|
/// </summary>
|
||||||
|
public bool Authenticated { get; set; }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,9 +186,21 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Whether this connection should be kept alive even when there is no subscription
|
/// Info on whether this connection is a dedicated request connection
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool DedicatedRequestConnection { get; internal set; }
|
public DedicatedConnectionState DedicatedRequestConnection { get; internal set; } = new DedicatedConnectionState();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current subscription topics on this connection
|
||||||
|
/// </summary>
|
||||||
|
public IEnumerable<string> Topics
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
lock (_listenersLock)
|
||||||
|
return _listeners.OfType<Subscription>().Select(x => x.Topic).Where(t => t != null).ToList()!;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private bool _pausedActivity;
|
private bool _pausedActivity;
|
||||||
private readonly object _listenersLock;
|
private readonly object _listenersLock;
|
||||||
@@ -618,7 +630,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
|
|
||||||
bool shouldCloseConnection;
|
bool shouldCloseConnection;
|
||||||
lock (_listenersLock)
|
lock (_listenersLock)
|
||||||
shouldCloseConnection = _listeners.OfType<Subscription>().All(r => !r.UserSubscription || r.Closed) && !DedicatedRequestConnection;
|
shouldCloseConnection = _listeners.OfType<Subscription>().All(r => !r.UserSubscription || r.Closed) && !DedicatedRequestConnection.IsDedicatedRequestConnection;
|
||||||
|
|
||||||
if (!anyDuplicateSubscription)
|
if (!anyDuplicateSubscription)
|
||||||
{
|
{
|
||||||
@@ -841,7 +853,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
if (!_socket.IsOpen)
|
if (!_socket.IsOpen)
|
||||||
return new CallResult(new WebError("Socket not connected"));
|
return new CallResult(new WebError("Socket not connected"));
|
||||||
|
|
||||||
if (!DedicatedRequestConnection)
|
if (!DedicatedRequestConnection.IsDedicatedRequestConnection)
|
||||||
{
|
{
|
||||||
bool anySubscriptions;
|
bool anySubscriptions;
|
||||||
lock (_listenersLock)
|
lock (_listenersLock)
|
||||||
@@ -859,7 +871,7 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
lock (_listenersLock)
|
lock (_listenersLock)
|
||||||
{
|
{
|
||||||
anyAuthenticated = _listeners.OfType<Subscription>().Any(s => s.Authenticated)
|
anyAuthenticated = _listeners.OfType<Subscription>().Any(s => s.Authenticated)
|
||||||
|| (DedicatedRequestConnection && ApiClient.AuthenticationProvider != null);
|
|| (DedicatedRequestConnection.IsDedicatedRequestConnection && DedicatedRequestConnection.Authenticated);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (anyAuthenticated)
|
if (anyAuthenticated)
|
||||||
|
|||||||
@@ -76,6 +76,11 @@ namespace CryptoExchange.Net.Sockets
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public abstract Type? GetMessageType(IMessageAccessor message);
|
public abstract Type? GetMessageType(IMessageAccessor message);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Subscription topic
|
||||||
|
/// </summary>
|
||||||
|
public string? Topic { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// ctor
|
/// ctor
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -5,22 +5,22 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Binance.Net" Version="10.7.0" />
|
<PackageReference Include="Binance.Net" Version="10.8.0" />
|
||||||
<PackageReference Include="Bitfinex.Net" Version="7.8.2" />
|
<PackageReference Include="Bitfinex.Net" Version="7.9.0" />
|
||||||
<PackageReference Include="BitMart.Net" Version="1.4.0" />
|
<PackageReference Include="BitMart.Net" Version="1.5.0" />
|
||||||
<PackageReference Include="Bybit.Net" Version="3.14.3" />
|
<PackageReference Include="Bybit.Net" Version="3.15.0" />
|
||||||
<PackageReference Include="CoinEx.Net" Version="7.7.2" />
|
<PackageReference Include="CoinEx.Net" Version="7.8.0" />
|
||||||
<PackageReference Include="CryptoCom.Net" Version="1.0.1" />
|
<PackageReference Include="CryptoCom.Net" Version="1.1.0" />
|
||||||
<PackageReference Include="GateIo.Net" Version="1.9.0" />
|
<PackageReference Include="GateIo.Net" Version="1.10.0" />
|
||||||
<PackageReference Include="JK.BingX.Net" Version="1.11.2" />
|
<PackageReference Include="JK.BingX.Net" Version="1.12.0" />
|
||||||
<PackageReference Include="JK.Bitget.Net" Version="1.10.4" />
|
<PackageReference Include="JK.Bitget.Net" Version="1.11.0" />
|
||||||
<PackageReference Include="JK.Mexc.Net" Version="1.9.0" />
|
<PackageReference Include="JK.Mexc.Net" Version="1.10.0" />
|
||||||
<PackageReference Include="JK.OKX.Net" Version="2.6.0" />
|
<PackageReference Include="JK.OKX.Net" Version="2.7.0" />
|
||||||
<PackageReference Include="JKorf.Coinbase.Net" Version="1.1.2" />
|
<PackageReference Include="JKorf.Coinbase.Net" Version="1.2.0" />
|
||||||
<PackageReference Include="JKorf.HTX.Net" Version="6.2.0" />
|
<PackageReference Include="JKorf.HTX.Net" Version="6.3.0" />
|
||||||
<PackageReference Include="KrakenExchange.Net" Version="5.0.2" />
|
<PackageReference Include="KrakenExchange.Net" Version="5.1.0" />
|
||||||
<PackageReference Include="Kucoin.Net" Version="5.16.0" />
|
<PackageReference Include="Kucoin.Net" Version="5.17.0" />
|
||||||
<PackageReference Include="Serilog.AspNetCore" Version="8.0.2" />
|
<PackageReference Include="Serilog.AspNetCore" Version="8.0.3" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -74,7 +74,7 @@
|
|||||||
{ "Bybit", bybitFactory.Create("ETHBTC", Bybit.Net.Enums.Category.Spot) },
|
{ "Bybit", bybitFactory.Create("ETHBTC", Bybit.Net.Enums.Category.Spot) },
|
||||||
{ "Coinbase", coinbaseFactory.Create("ETH-BTC", null) },
|
{ "Coinbase", coinbaseFactory.Create("ETH-BTC", null) },
|
||||||
{ "CoinEx", coinExFactory.CreateSpot("ETHBTC") },
|
{ "CoinEx", coinExFactory.CreateSpot("ETHBTC") },
|
||||||
{ "CryptoCom", cryptocomFactory.CreateExchange("ETH_BTC") },
|
{ "CryptoCom", cryptocomFactory.Create("ETH_BTC") },
|
||||||
{ "GateIo", gateioFactory.CreateSpot("ETH_BTC") },
|
{ "GateIo", gateioFactory.CreateSpot("ETH_BTC") },
|
||||||
{ "HTX", htxFactory.CreateSpot("ethbtc") },
|
{ "HTX", htxFactory.CreateSpot("ethbtc") },
|
||||||
{ "Kraken", krakenFactory.CreateSpot("ETH/XBT") },
|
{ "Kraken", krakenFactory.CreateSpot("ETH/XBT") },
|
||||||
|
|||||||
@@ -0,0 +1,111 @@
|
|||||||
|
@page "/Trackers"
|
||||||
|
@using System.Collections.Concurrent
|
||||||
|
@using System.Timers
|
||||||
|
@using Binance.Net.Interfaces
|
||||||
|
@using BingX.Net.Interfaces
|
||||||
|
@using Bitfinex.Net.Interfaces
|
||||||
|
@using Bitget.Net.Interfaces;
|
||||||
|
@using BitMart.Net.Interfaces;
|
||||||
|
@using Bybit.Net.Interfaces
|
||||||
|
@using CoinEx.Net.Interfaces
|
||||||
|
@using Coinbase.Net.Interfaces
|
||||||
|
@using CryptoExchange.Net.Interfaces
|
||||||
|
@using CryptoCom.Net.Interfaces
|
||||||
|
@using CryptoExchange.Net.SharedApis
|
||||||
|
@using CryptoExchange.Net.Trackers.Trades
|
||||||
|
@using GateIo.Net.Interfaces
|
||||||
|
@using HTX.Net.Interfaces
|
||||||
|
@using Kraken.Net.Interfaces
|
||||||
|
@using Kucoin.Net.Clients
|
||||||
|
@using Kucoin.Net.Interfaces
|
||||||
|
@using Mexc.Net.Interfaces
|
||||||
|
@using OKX.Net.Interfaces;
|
||||||
|
@inject IBinanceTrackerFactory binanceFactory
|
||||||
|
@inject IBingXTrackerFactory bingXFactory
|
||||||
|
@inject IBitfinexTrackerFactory bitfinexFactory
|
||||||
|
@inject IBitgetTrackerFactory bitgetFactory
|
||||||
|
@inject IBitMartTrackerFactory bitmartFactory
|
||||||
|
@inject IBybitTrackerFactory bybitFactory
|
||||||
|
@inject ICoinbaseTrackerFactory coinbaseFactory
|
||||||
|
@inject ICoinExTrackerFactory coinExFactory
|
||||||
|
@inject ICryptoComTrackerFactory cryptocomFactory
|
||||||
|
@inject IGateIoTrackerFactory gateioFactory
|
||||||
|
@inject IHTXTrackerFactory htxFactory
|
||||||
|
@inject IKrakenTrackerFactory krakenFactory
|
||||||
|
@inject IKucoinTrackerFactory kucoinFactory
|
||||||
|
@inject IMexcTrackerFactory mexcFactory
|
||||||
|
@inject IOKXTrackerFactory okxFactory
|
||||||
|
@implements IDisposable
|
||||||
|
|
||||||
|
<h3>ETH-BTC trade Trackers, live updates:</h3>
|
||||||
|
<div style="display:flex; flex-wrap: wrap;">
|
||||||
|
@foreach (var tracker in _trackers.OrderBy(p => p.Exchange))
|
||||||
|
{
|
||||||
|
<div style="margin-bottom: 20px; flex: 1; min-width: 700px;">
|
||||||
|
<h4>@tracker.Exchange</h4>
|
||||||
|
@foreach(var line in GetInfo(tracker))
|
||||||
|
{
|
||||||
|
<div>@line</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
@code{
|
||||||
|
private List<ITradeTracker> _trackers = new List<ITradeTracker>();
|
||||||
|
private Timer _timer;
|
||||||
|
|
||||||
|
protected override async Task OnInitializedAsync()
|
||||||
|
{
|
||||||
|
var symbol = new SharedSymbol(TradingMode.Spot, "BTC", "USDT");
|
||||||
|
|
||||||
|
_trackers = new List<ITradeTracker>
|
||||||
|
{
|
||||||
|
{ binanceFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
|
{ bingXFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
|
{ bitfinexFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
|
{ bitgetFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
|
{ bitmartFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
|
{ bybitFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
|
{ coinbaseFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
|
{ coinExFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
|
{ cryptocomFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
|
{ gateioFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
|
{ htxFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
|
{ krakenFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
|
{ kucoinFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
|
{ mexcFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
|
{ okxFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
|
||||||
|
};
|
||||||
|
|
||||||
|
await Task.WhenAll(_trackers.Select(b => b.StartAsync()));
|
||||||
|
|
||||||
|
// Use a manual update timer so the page isn't refreshed too often
|
||||||
|
_timer = new Timer(500);
|
||||||
|
_timer.Start();
|
||||||
|
_timer.Elapsed += (o, e) => InvokeAsync(StateHasChanged);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string[] GetInfo(ITradeTracker tracker)
|
||||||
|
{
|
||||||
|
var secondLastMinute = tracker.GetStats(DateTime.UtcNow.AddMinutes(-2), DateTime.UtcNow.AddMinutes(-1));
|
||||||
|
var lastMinute = tracker.GetStats(DateTime.UtcNow.AddMinutes(-1));
|
||||||
|
var compare = lastMinute.CompareTo(secondLastMinute);
|
||||||
|
|
||||||
|
return [
|
||||||
|
$"{tracker.SymbolName} | {tracker.Status} - Synced from {tracker.SyncedFrom}",
|
||||||
|
$"Total trades: {tracker.Count}",
|
||||||
|
$"Trades last minute: {lastMinute.TradeCount}, minute before: {secondLastMinute.TradeCount}",
|
||||||
|
$"Average weighted price: {lastMinute.VolumeWeightedAveragePrice}, minute before: {secondLastMinute.VolumeWeightedAveragePrice}, dif: {compare.VolumeWeightedAveragePriceDif.PercentageDifference}%"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_timer.Stop();
|
||||||
|
_timer.Dispose();
|
||||||
|
foreach (var tracker in _trackers.Where(b => b.Status != CryptoExchange.Net.Objects.SyncStatus.Disconnected))
|
||||||
|
// It's not necessary to wait for this
|
||||||
|
_ = tracker.StopAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -27,6 +27,11 @@
|
|||||||
Order books
|
Order books
|
||||||
</NavLink>
|
</NavLink>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item px-3">
|
||||||
|
<NavLink class="nav-link" href="Trackers">
|
||||||
|
Trackers
|
||||||
|
</NavLink>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -6,20 +6,20 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Binance.Net" Version="10.7.0" />
|
<PackageReference Include="Binance.Net" Version="10.8.0" />
|
||||||
<PackageReference Include="Bitfinex.Net" Version="7.8.2" />
|
<PackageReference Include="Bitfinex.Net" Version="7.9.0" />
|
||||||
<PackageReference Include="BitMart.Net" Version="1.4.0" />
|
<PackageReference Include="BitMart.Net" Version="1.5.0" />
|
||||||
<PackageReference Include="Bybit.Net" Version="3.14.3" />
|
<PackageReference Include="Bybit.Net" Version="3.15.0" />
|
||||||
<PackageReference Include="CoinEx.Net" Version="7.7.2" />
|
<PackageReference Include="CoinEx.Net" Version="7.8.0" />
|
||||||
<PackageReference Include="CryptoCom.Net" Version="1.0.1" />
|
<PackageReference Include="CryptoCom.Net" Version="1.1.0" />
|
||||||
<PackageReference Include="GateIo.Net" Version="1.9.0" />
|
<PackageReference Include="GateIo.Net" Version="1.10.0" />
|
||||||
<PackageReference Include="JK.Bitget.Net" Version="1.10.4" />
|
<PackageReference Include="JK.Bitget.Net" Version="1.11.0" />
|
||||||
<PackageReference Include="JK.Mexc.Net" Version="1.9.0" />
|
<PackageReference Include="JK.Mexc.Net" Version="1.10.0" />
|
||||||
<PackageReference Include="JK.OKX.Net" Version="2.6.0" />
|
<PackageReference Include="JK.OKX.Net" Version="2.7.0" />
|
||||||
<PackageReference Include="JKorf.Coinbase.Net" Version="1.1.2" />
|
<PackageReference Include="JKorf.Coinbase.Net" Version="1.2.0" />
|
||||||
<PackageReference Include="JKorf.HTX.Net" Version="6.2.0" />
|
<PackageReference Include="JKorf.HTX.Net" Version="6.3.0" />
|
||||||
<PackageReference Include="KrakenExchange.Net" Version="5.0.2" />
|
<PackageReference Include="KrakenExchange.Net" Version="5.1.0" />
|
||||||
<PackageReference Include="Kucoin.Net" Version="5.16.0" />
|
<PackageReference Include="Kucoin.Net" Version="5.17.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -8,9 +8,9 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Binance.Net" Version="10.7.0" />
|
<PackageReference Include="Binance.Net" Version="10.8.0" />
|
||||||
<PackageReference Include="BitMart.Net" Version="1.4.0" />
|
<PackageReference Include="BitMart.Net" Version="1.5.0" />
|
||||||
<PackageReference Include="JK.OKX.Net" Version="2.6.0" />
|
<PackageReference Include="JK.OKX.Net" Version="2.7.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -49,6 +49,17 @@ 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.2.0 - 06 Nov 2024
|
||||||
|
* Added support for not allowing duplicate subscription topics on the same websocket connection
|
||||||
|
* Added PerAccount SharedLeverageSettingMode enum value, changed Side on SharedUserTrade to nullable
|
||||||
|
* Added support for object deserialization in SystemTextJsonMessageAccessor.GetValue<T>
|
||||||
|
* Changed SocketApiClient GetAuthenticationRequest to GetAuthenticationRequestAsync to allow for requesting token
|
||||||
|
|
||||||
|
* Version 8.1.1 - 01 Nov 2024
|
||||||
|
* Fixed socket connections trying to authenticated connection when it's marked as dedicated request connection even when no authentication is needed
|
||||||
|
* Fixed System.Text.Json ArrayConverter not passing serializer options to nested deserialization
|
||||||
|
* Fixed System.Text.Json ArrayConverter creating new serializer options each time a JsonConverter attribute is encountered
|
||||||
|
|
||||||
* Version 8.1.0 - 28 Oct 2024
|
* Version 8.1.0 - 28 Oct 2024
|
||||||
* Added KlineTracker and TradeTracker implementation
|
* Added KlineTracker and TradeTracker implementation
|
||||||
* Added Side to SharedTrade model
|
* Added Side to SharedTrade model
|
||||||
|
|||||||
+4
-1
@@ -3265,6 +3265,9 @@ foreach (var book in books.Where(b => b.Status == OrderBookStatus.Synced))
|
|||||||
<h2>Trackers</h2>
|
<h2>Trackers</h2>
|
||||||
<p>
|
<p>
|
||||||
Trackers offer a way to keep track of live data. This data can than be aggregated into statistics and different time slices can be compared to get realtime insights.
|
Trackers offer a way to keep track of live data. This data can than be aggregated into statistics and different time slices can be compared to get realtime insights.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
The basic workings of the trackers are simple, an initial request is made for a snapshot of the history (or a partial snapshot depending on what the API supports). At the same time a websocket subscription is set up to provide the tracker with new data.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
Currently there are 2 different trackers available, the <code>TradeTracker</code> and the <code>KlineTracker</code>.
|
Currently there are 2 different trackers available, the <code>TradeTracker</code> and the <code>KlineTracker</code>.
|
||||||
@@ -3653,7 +3656,7 @@ await tracker.StopAsync();
|
|||||||
<p>
|
<p>
|
||||||
<b>Stats and comparing data</b><br />
|
<b>Stats and comparing data</b><br />
|
||||||
|
|
||||||
Using the <code>tracker.GetData(fromTime, toTime)</code> method the trackers expose the data, or a subset of the data, can be retrieved:
|
Using the <code>tracker.GetData(fromTime, toTime)</code> method the trackers exposes the data, or a subset of the data, can be retrieved:
|
||||||
<pre><code>// Get all the data currently tracked:
|
<pre><code>// Get all the data currently tracked:
|
||||||
var data = tracker.GetData();
|
var data = tracker.GetData();
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user