mirror of
https://github.com/JKorf/CryptoExchange.Net.git
synced 2026-08-12 00:43:03 +00:00
Compare commits
12 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a832f0e4d4 | |||
| 6ab4d005a0 | |||
| 18dc935038 | |||
| bb3a534f75 | |||
| 649ba370c6 | |||
| 51732c5ce6 | |||
| 0ba7b46680 | |||
| 94dfbb7b9e | |||
| b8b7512b35 | |||
| aba6b773ce | |||
| d88fb0d356 | |||
| b8c6d55156 |
@@ -6,9 +6,9 @@
|
||||
<PackageId>CryptoExchange.Net.Protobuf</PackageId>
|
||||
<Authors>JKorf</Authors>
|
||||
<Description>Protobuf support for CryptoExchange.Net</Description>
|
||||
<PackageVersion>9.7.0</PackageVersion>
|
||||
<AssemblyVersion>9.7.0</AssemblyVersion>
|
||||
<FileVersion>9.7.0</FileVersion>
|
||||
<PackageVersion>9.8.0</PackageVersion>
|
||||
<AssemblyVersion>9.8.0</AssemblyVersion>
|
||||
<FileVersion>9.8.0</FileVersion>
|
||||
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
|
||||
<PackageTags>CryptoExchange;CryptoExchange.Net</PackageTags>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
@@ -41,7 +41,7 @@
|
||||
<DocumentationFile>CryptoExchange.Net.Protobuf.xml</DocumentationFile>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="CryptoExchange.Net" Version="9.7.0" />
|
||||
<PackageReference Include="CryptoExchange.Net" Version="9.8.0" />
|
||||
<PackageReference Include="protobuf-net" Version="3.2.56" />
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -5,6 +5,9 @@
|
||||
Protobuf support for CryptoExchange.Net.
|
||||
|
||||
## Release notes
|
||||
* Version 9.8.0 - 30 Sep 2025
|
||||
* Updated CryptoExchange.Net version to 9.8.0, see https://github.com/JKorf/CryptoExchange.Net/releases/
|
||||
|
||||
* Version 9.7.0 - 01 Sep 2025
|
||||
* Updated CryptoExchange.Net version to 9.7.0, see https://github.com/JKorf/CryptoExchange.Net/releases/
|
||||
|
||||
|
||||
@@ -32,6 +32,7 @@ namespace CryptoExchange.Net.UnitTests
|
||||
[TestCase(0.1, 1, 0.0001, RoundingType.Closest, 0.532, 0.532)]
|
||||
[TestCase(0.1, 1, 0.0001, RoundingType.Down, 0.5516592, 0.5516)]
|
||||
[TestCase(0.1, 1, 0.0001, RoundingType.Closest, 0.5516592, 0.5517)]
|
||||
[TestCase(0, 1, 0.000000001, RoundingType.Closest, 0.0000097232, 0.000009723)]
|
||||
public void AdjustValueStepTests(decimal min, decimal max, decimal? step, RoundingType roundingType, decimal input, decimal expected)
|
||||
{
|
||||
var result = ExchangeHelpers.AdjustValueStep(min, max, step, roundingType, input);
|
||||
|
||||
@@ -227,7 +227,7 @@ namespace CryptoExchange.Net.Clients
|
||||
while (true)
|
||||
{
|
||||
// Get a new or existing socket connection
|
||||
var socketResult = await GetSocketConnection(url, subscription.Authenticated, false, subscription.Topic).ConfigureAwait(false);
|
||||
var socketResult = await GetSocketConnection(url, subscription.Authenticated, false, ct, subscription.Topic).ConfigureAwait(false);
|
||||
if (!socketResult)
|
||||
return socketResult.As<UpdateSubscription>(null);
|
||||
|
||||
@@ -343,7 +343,7 @@ namespace CryptoExchange.Net.Clients
|
||||
await semaphoreSlim.WaitAsync().ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
var socketResult = await GetSocketConnection(url, query.Authenticated, true).ConfigureAwait(false);
|
||||
var socketResult = await GetSocketConnection(url, query.Authenticated, true, ct).ConfigureAwait(false);
|
||||
if (!socketResult)
|
||||
return socketResult.As<THandlerResponse>(default);
|
||||
|
||||
@@ -494,25 +494,56 @@ namespace CryptoExchange.Net.Clients
|
||||
/// <param name="address">The address the socket is for</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="ct">Cancellation token</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>
|
||||
protected virtual async Task<CallResult<SocketConnection>> GetSocketConnection(string address, bool authenticated, bool dedicatedRequestConnection, string? topic = null)
|
||||
protected virtual async Task<CallResult<SocketConnection>> GetSocketConnection(string address, bool authenticated, bool dedicatedRequestConnection, CancellationToken ct, string? topic = null)
|
||||
{
|
||||
var socketQuery = socketConnections.Where(s => (s.Value.Status == SocketConnection.SocketStatus.None || s.Value.Status == SocketConnection.SocketStatus.Connected)
|
||||
&& s.Value.Tag.TrimEnd('/') == address.TrimEnd('/')
|
||||
var socketQuery = socketConnections.Where(s => s.Value.Tag.TrimEnd('/') == address.TrimEnd('/')
|
||||
&& s.Value.ApiClient.GetType() == GetType()
|
||||
&& (s.Value.Authenticated == authenticated || !authenticated)
|
||||
&& (AllowTopicsOnTheSameConnection || !s.Value.Topics.Contains(topic))
|
||||
&& s.Value.Connected);
|
||||
&& (AllowTopicsOnTheSameConnection || !s.Value.Topics.Contains(topic)))
|
||||
.Select(x => x.Value)
|
||||
.ToList();
|
||||
|
||||
SocketConnection connection;
|
||||
// If all current socket connections are reconnecting or resubscribing wait for that to finish as we can probably use the existing connection
|
||||
var delayStart = DateTime.UtcNow;
|
||||
var delayed = false;
|
||||
while (socketQuery.Count >= 1 && socketQuery.All(x => x.Status == SocketConnection.SocketStatus.Reconnecting || x.Status == SocketConnection.SocketStatus.Resubscribing))
|
||||
{
|
||||
if (DateTime.UtcNow - delayStart > TimeSpan.FromSeconds(10))
|
||||
{
|
||||
if (socketQuery.Count >= 1 && socketQuery.All(x => x.Status == SocketConnection.SocketStatus.Reconnecting || x.Status == SocketConnection.SocketStatus.Resubscribing))
|
||||
{
|
||||
// If after this time we still trying to reconnect/reprocess there is some issue in the connection
|
||||
_logger.TimeoutWaitingForReconnectingSocket();
|
||||
return new CallResult<SocketConnection>(new CantConnectError());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
delayed = true;
|
||||
try { await Task.Delay(50, ct).ConfigureAwait(false); } catch (Exception) { }
|
||||
|
||||
if (ct.IsCancellationRequested)
|
||||
return new CallResult<SocketConnection>(new CancellationRequestedError());
|
||||
}
|
||||
|
||||
if (delayed)
|
||||
_logger.WaitedForReconnectingSocket((long)(DateTime.UtcNow - delayStart).TotalMilliseconds);
|
||||
|
||||
socketQuery = socketQuery.Where(s => (s.Status == SocketConnection.SocketStatus.None || s.Status == SocketConnection.SocketStatus.Connected)
|
||||
&& (s.Authenticated == authenticated || !authenticated)
|
||||
&& s.Connected).ToList();
|
||||
|
||||
SocketConnection? connection;
|
||||
if (!dedicatedRequestConnection)
|
||||
{
|
||||
connection = socketQuery.Where(s => !s.Value.DedicatedRequestConnection.IsDedicatedRequestConnection).OrderBy(s => s.Value.UserSubscriptionCount).FirstOrDefault().Value;
|
||||
connection = socketQuery.Where(s => !s.DedicatedRequestConnection.IsDedicatedRequestConnection).OrderBy(s => s.UserSubscriptionCount).FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
connection = socketQuery.Where(s => s.Value.DedicatedRequestConnection.IsDedicatedRequestConnection).FirstOrDefault().Value;
|
||||
connection = socketQuery.Where(s => s.DedicatedRequestConnection.IsDedicatedRequestConnection).FirstOrDefault();
|
||||
if (connection != null && !connection.DedicatedRequestConnection.Authenticated)
|
||||
// Mark dedicated request connection as authenticated if the request is authenticated
|
||||
connection.DedicatedRequestConnection.Authenticated = authenticated;
|
||||
@@ -520,9 +551,12 @@ namespace CryptoExchange.Net.Clients
|
||||
|
||||
if (connection != null)
|
||||
{
|
||||
if (connection.UserSubscriptionCount < ClientOptions.SocketSubscriptionsCombineTarget || (socketConnections.Count >= (ApiOptions.MaxSocketConnections ?? ClientOptions.MaxSocketConnections) && socketConnections.All(s => s.Value.UserSubscriptionCount >= ClientOptions.SocketSubscriptionsCombineTarget)))
|
||||
if (connection.UserSubscriptionCount < ClientOptions.SocketSubscriptionsCombineTarget
|
||||
|| (socketConnections.Count >= (ApiOptions.MaxSocketConnections ?? ClientOptions.MaxSocketConnections) && socketConnections.All(s => s.Value.UserSubscriptionCount >= ClientOptions.SocketSubscriptionsCombineTarget)))
|
||||
{
|
||||
// Use existing socket if it has less than target connections OR it has the least connections and we can't make new
|
||||
return new CallResult<SocketConnection>(connection);
|
||||
}
|
||||
}
|
||||
|
||||
var connectionAddress = await GetConnectionUrlAsync(address, authenticated).ConfigureAwait(false);
|
||||
@@ -716,7 +750,7 @@ namespace CryptoExchange.Net.Clients
|
||||
{
|
||||
foreach (var item in DedicatedConnectionConfigs)
|
||||
{
|
||||
var socketResult = await GetSocketConnection(item.SocketAddress, item.Authenticated, true).ConfigureAwait(false);
|
||||
var socketResult = await GetSocketConnection(item.SocketAddress, item.Authenticated, true, CancellationToken.None).ConfigureAwait(false);
|
||||
if (!socketResult)
|
||||
return socketResult.AsDataless();
|
||||
|
||||
|
||||
@@ -140,10 +140,10 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
|
||||
_ => throw new Exception("Invalid token type for enum deserialization: " + reader.TokenType)
|
||||
};
|
||||
|
||||
if (string.IsNullOrEmpty(stringValue))
|
||||
if (stringValue is null)
|
||||
return null;
|
||||
|
||||
if (!GetValue(enumType, stringValue!, out var result))
|
||||
if (!GetValue(enumType, stringValue, out var result))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(stringValue))
|
||||
{
|
||||
@@ -204,6 +204,13 @@ namespace CryptoExchange.Net.Converters.SystemTextJson
|
||||
return false;
|
||||
}
|
||||
|
||||
if (String.IsNullOrEmpty(value))
|
||||
{
|
||||
// An empty/null value will always fail when parsing, so just return here
|
||||
result = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
// If no explicit mapping is found try to parse string
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
<PackageId>CryptoExchange.Net</PackageId>
|
||||
<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>
|
||||
<PackageVersion>9.7.0</PackageVersion>
|
||||
<AssemblyVersion>9.7.0</AssemblyVersion>
|
||||
<FileVersion>9.7.0</FileVersion>
|
||||
<PackageVersion>9.8.0</PackageVersion>
|
||||
<AssemblyVersion>9.8.0</AssemblyVersion>
|
||||
<FileVersion>9.8.0</FileVersion>
|
||||
<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;CryptoExchange.Net</PackageTags>
|
||||
<RepositoryType>git</RepositoryType>
|
||||
|
||||
@@ -88,8 +88,6 @@ namespace CryptoExchange.Net
|
||||
value -= offset;
|
||||
else value += (step.Value - offset);
|
||||
}
|
||||
|
||||
value = RoundDown(value, 8);
|
||||
|
||||
return value.Normalize();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
using CryptoExchange.Net.SharedApis;
|
||||
using CryptoExchange.Net.Trackers.Klines;
|
||||
using CryptoExchange.Net.Trackers.Trades;
|
||||
using System;
|
||||
|
||||
namespace CryptoExchange.Net.Interfaces
|
||||
{
|
||||
/// <summary>
|
||||
/// Tracker factory
|
||||
/// </summary>
|
||||
public interface ITrackerFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// Whether the factory supports creating a KlineTracker instance for this symbol and interval
|
||||
/// </summary>
|
||||
/// <param name="symbol">The symbol</param>
|
||||
/// <param name="interval">The kline interval</param>
|
||||
bool CanCreateKlineTracker(SharedSymbol symbol, SharedKlineInterval interval);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new kline tracker
|
||||
/// </summary>
|
||||
/// <param name="symbol">The symbol</param>
|
||||
/// <param name="interval">Kline interval</param>
|
||||
/// <param name="limit">The max amount of klines to retain</param>
|
||||
/// <param name="period">The max period the data should be retained</param>
|
||||
/// <returns></returns>
|
||||
IKlineTracker CreateKlineTracker(SharedSymbol symbol, SharedKlineInterval interval, int? limit = null, TimeSpan? period = null);
|
||||
|
||||
/// <summary>
|
||||
/// Whether the factory supports creating a TradeTracker instance for this symbol
|
||||
/// </summary>
|
||||
/// <param name="symbol">The symbol</param>
|
||||
bool CanCreateTradeTracker(SharedSymbol symbol);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new trade tracker for a symbol
|
||||
/// </summary>
|
||||
/// <param name="symbol">The symbol</param>
|
||||
/// <param name="limit">The max amount of trades to retain</param>
|
||||
/// <param name="period">The max period the data should be retained</param>
|
||||
/// <returns></returns>
|
||||
ITradeTracker CreateTradeTracker(SharedSymbol symbol, int? limit = null, TimeSpan? period = null);
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,8 @@ namespace CryptoExchange.Net.Logging.Extensions
|
||||
private static readonly Action<ILogger, int, int, Exception?> _unsubscribingSubscription;
|
||||
private static readonly Action<ILogger, int, Exception?> _reconnectingAllConnections;
|
||||
private static readonly Action<ILogger, DateTime, Exception?> _addingRetryAfterGuard;
|
||||
private static readonly Action<ILogger, Exception?> _timeoutWaitingForReconnectingSocket;
|
||||
private static readonly Action<ILogger, long, Exception?> _waitedForReconnectingSocket;
|
||||
|
||||
static SocketApiClientLoggingExtension()
|
||||
{
|
||||
@@ -110,6 +112,16 @@ namespace CryptoExchange.Net.Logging.Extensions
|
||||
LogLevel.Warning,
|
||||
new EventId(3018, "AddRetryAfterGuard"),
|
||||
"Adding RetryAfterGuard ({RetryAfter}) because the connection attempt was rate limited");
|
||||
|
||||
_timeoutWaitingForReconnectingSocket = LoggerMessage.Define(
|
||||
LogLevel.Debug,
|
||||
new EventId(3019, "TimeoutWaitingForReconnectingSocket"),
|
||||
"Timeout while waiting for existing socket reconnection, failing request");
|
||||
|
||||
_waitedForReconnectingSocket = LoggerMessage.Define<long>(
|
||||
LogLevel.Trace,
|
||||
new EventId(3020, "WaitedForReconnectingSocket"),
|
||||
"Waited for reconnecting socket for {Timespan}ms");
|
||||
}
|
||||
|
||||
public static void FailedToAddSubscriptionRetryOnDifferentConnection(this ILogger logger, int socketId)
|
||||
@@ -196,5 +208,14 @@ namespace CryptoExchange.Net.Logging.Extensions
|
||||
{
|
||||
_addingRetryAfterGuard(logger, retryAfter, null);
|
||||
}
|
||||
|
||||
public static void TimeoutWaitingForReconnectingSocket(this ILogger logger)
|
||||
{
|
||||
_timeoutWaitingForReconnectingSocket(logger, null);
|
||||
}
|
||||
public static void WaitedForReconnectingSocket(this ILogger logger, long milliseconds)
|
||||
{
|
||||
_waitedForReconnectingSocket(logger, milliseconds, null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
using CryptoExchange.Net.Sockets;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CryptoExchange.Net.Objects.Sockets
|
||||
@@ -12,13 +14,21 @@ namespace CryptoExchange.Net.Objects.Sockets
|
||||
private readonly SocketConnection _connection;
|
||||
private readonly Subscription _listener;
|
||||
|
||||
private object _eventLock = new object();
|
||||
private List<Action> _connectionClosedEventHandlers = new List<Action>();
|
||||
private List<Action> _connectionLostEventHandlers = new List<Action>();
|
||||
private List<Action<Error>> _resubscribeFailedEventHandlers = new List<Action<Error>>();
|
||||
private List<Action<TimeSpan>> _connectionRestoredEventHandlers = new List<Action<TimeSpan>>();
|
||||
private List<Action> _activityPausedEventHandlers = new List<Action>();
|
||||
private List<Action> _activityUnpausedEventHandlers = new List<Action>();
|
||||
|
||||
/// <summary>
|
||||
/// Event when the connection is lost. The socket will automatically reconnect when possible.
|
||||
/// </summary>
|
||||
public event Action ConnectionLost
|
||||
{
|
||||
add => _connection.ConnectionLost += value;
|
||||
remove => _connection.ConnectionLost -= value;
|
||||
add { lock (_eventLock) _connectionLostEventHandlers.Add(value); }
|
||||
remove { lock (_eventLock) _connectionLostEventHandlers.Remove(value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -26,8 +36,8 @@ namespace CryptoExchange.Net.Objects.Sockets
|
||||
/// </summary>
|
||||
public event Action ConnectionClosed
|
||||
{
|
||||
add => _connection.ConnectionClosed += value;
|
||||
remove => _connection.ConnectionClosed -= value;
|
||||
add { lock (_eventLock) _connectionClosedEventHandlers.Add(value); }
|
||||
remove { lock (_eventLock) _connectionClosedEventHandlers.Remove(value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -35,8 +45,8 @@ namespace CryptoExchange.Net.Objects.Sockets
|
||||
/// </summary>
|
||||
public event Action<Error> ResubscribingFailed
|
||||
{
|
||||
add => _connection.ResubscribingFailed += value;
|
||||
remove => _connection.ResubscribingFailed -= value;
|
||||
add { lock (_eventLock) _resubscribeFailedEventHandlers.Add(value); }
|
||||
remove { lock (_eventLock) _resubscribeFailedEventHandlers.Remove(value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -46,8 +56,8 @@ namespace CryptoExchange.Net.Objects.Sockets
|
||||
/// </summary>
|
||||
public event Action<TimeSpan> ConnectionRestored
|
||||
{
|
||||
add => _connection.ConnectionRestored += value;
|
||||
remove => _connection.ConnectionRestored -= value;
|
||||
add { lock (_eventLock) _connectionRestoredEventHandlers.Add(value); }
|
||||
remove { lock (_eventLock) _connectionRestoredEventHandlers.Remove(value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -55,8 +65,8 @@ namespace CryptoExchange.Net.Objects.Sockets
|
||||
/// </summary>
|
||||
public event Action ActivityPaused
|
||||
{
|
||||
add => _connection.ActivityPaused += value;
|
||||
remove => _connection.ActivityPaused -= value;
|
||||
add { lock (_eventLock) _activityPausedEventHandlers.Add(value); }
|
||||
remove { lock (_eventLock) _activityPausedEventHandlers.Remove(value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -64,8 +74,8 @@ namespace CryptoExchange.Net.Objects.Sockets
|
||||
/// </summary>
|
||||
public event Action ActivityUnpaused
|
||||
{
|
||||
add => _connection.ActivityUnpaused += value;
|
||||
remove => _connection.ActivityUnpaused -= value;
|
||||
add { lock (_eventLock) _activityUnpausedEventHandlers.Add(value); }
|
||||
remove { lock (_eventLock) _activityUnpausedEventHandlers.Remove(value); }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -95,7 +105,85 @@ namespace CryptoExchange.Net.Objects.Sockets
|
||||
public UpdateSubscription(SocketConnection connection, Subscription subscription)
|
||||
{
|
||||
_connection = connection;
|
||||
_connection.ConnectionClosed += HandleConnectionClosedEvent;
|
||||
_connection.ConnectionLost += HandleConnectionLostEvent;
|
||||
_connection.ConnectionRestored += HandleConnectionRestoredEvent;
|
||||
_connection.ResubscribingFailed += HandleResubscribeFailedEvent;
|
||||
_connection.ActivityPaused += HandlePausedEvent;
|
||||
_connection.ActivityUnpaused += HandleUnpausedEvent;
|
||||
|
||||
_listener = subscription;
|
||||
_listener.Unsubscribed += HandleUnsubscribed;
|
||||
}
|
||||
|
||||
private void HandleUnsubscribed()
|
||||
{
|
||||
_connection.ConnectionClosed -= HandleConnectionClosedEvent;
|
||||
_connection.ConnectionLost -= HandleConnectionLostEvent;
|
||||
_connection.ConnectionRestored -= HandleConnectionRestoredEvent;
|
||||
_connection.ResubscribingFailed -= HandleResubscribeFailedEvent;
|
||||
_connection.ActivityPaused -= HandlePausedEvent;
|
||||
_connection.ActivityUnpaused -= HandleUnpausedEvent;
|
||||
}
|
||||
|
||||
private void HandleConnectionClosedEvent()
|
||||
{
|
||||
List<Action> handlers;
|
||||
lock (_eventLock)
|
||||
handlers = _connectionClosedEventHandlers.ToList();
|
||||
|
||||
foreach(var callback in handlers)
|
||||
callback();
|
||||
}
|
||||
|
||||
private void HandleConnectionLostEvent()
|
||||
{
|
||||
List<Action> handlers;
|
||||
lock (_eventLock)
|
||||
handlers = _connectionLostEventHandlers.ToList();
|
||||
|
||||
foreach (var callback in handlers)
|
||||
callback();
|
||||
}
|
||||
|
||||
private void HandleConnectionRestoredEvent(TimeSpan period)
|
||||
{
|
||||
List<Action<TimeSpan>> handlers;
|
||||
lock (_eventLock)
|
||||
handlers = _connectionRestoredEventHandlers.ToList();
|
||||
|
||||
foreach (var callback in handlers)
|
||||
callback(period);
|
||||
}
|
||||
|
||||
private void HandleResubscribeFailedEvent(Error error)
|
||||
{
|
||||
List<Action<Error>> handlers;
|
||||
lock (_eventLock)
|
||||
handlers = _resubscribeFailedEventHandlers.ToList();
|
||||
|
||||
foreach (var callback in handlers)
|
||||
callback(error);
|
||||
}
|
||||
|
||||
private void HandlePausedEvent()
|
||||
{
|
||||
List<Action> handlers;
|
||||
lock (_eventLock)
|
||||
handlers = _activityPausedEventHandlers.ToList();
|
||||
|
||||
foreach (var callback in handlers)
|
||||
callback();
|
||||
}
|
||||
|
||||
private void HandleUnpausedEvent()
|
||||
{
|
||||
List<Action> handlers;
|
||||
lock (_eventLock)
|
||||
handlers = _activityUnpausedEventHandlers.ToList();
|
||||
|
||||
foreach (var callback in handlers)
|
||||
callback();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -67,6 +67,10 @@ namespace CryptoExchange.Net.SharedApis
|
||||
/// Min number of confirmations
|
||||
/// </summary>
|
||||
public int? MinConfirmations { get; set; }
|
||||
/// <summary>
|
||||
/// The contract address
|
||||
/// </summary>
|
||||
public string? ContractAddress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
|
||||
@@ -706,6 +706,8 @@ namespace CryptoExchange.Net.Sockets
|
||||
|
||||
lock (_listenersLock)
|
||||
_listeners.Remove(subscription);
|
||||
|
||||
subscription.InvokeUnsubscribedHandler();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -74,6 +74,10 @@ namespace CryptoExchange.Net.Sockets
|
||||
/// Exception event
|
||||
/// </summary>
|
||||
public event Action<Exception>? Exception;
|
||||
/// <summary>
|
||||
/// Listener unsubscribed event
|
||||
/// </summary>
|
||||
public event Action? Unsubscribed;
|
||||
|
||||
/// <summary>
|
||||
/// Subscription topic
|
||||
@@ -181,6 +185,14 @@ namespace CryptoExchange.Net.Sockets
|
||||
Exception?.Invoke(e);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Invoke the unsubscribed event
|
||||
/// </summary>
|
||||
public void InvokeUnsubscribedHandler()
|
||||
{
|
||||
Unsubscribed?.Invoke();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// State of this subscription
|
||||
/// </summary>
|
||||
|
||||
@@ -5,29 +5,30 @@
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Binance.Net" Version="11.3.0" />
|
||||
<PackageReference Include="Bitfinex.Net" Version="9.3.0" />
|
||||
<PackageReference Include="BitMart.Net" Version="2.4.1" />
|
||||
<PackageReference Include="Bybit.Net" Version="5.4.0" />
|
||||
<PackageReference Include="CoinEx.Net" Version="9.3.0" />
|
||||
<PackageReference Include="CoinW.Net" Version="1.0.1" />
|
||||
<PackageReference Include="CryptoCom.Net" Version="2.4.0" />
|
||||
<PackageReference Include="DeepCoin.Net" Version="2.3.0" />
|
||||
<PackageReference Include="GateIo.Net" Version="2.4.0" />
|
||||
<PackageReference Include="HyperLiquid.Net" Version="2.4.0" />
|
||||
<PackageReference Include="JK.BingX.Net" Version="2.3.0" />
|
||||
<PackageReference Include="JK.Bitget.Net" Version="2.3.0" />
|
||||
<PackageReference Include="JK.Mexc.Net" Version="3.3.0" />
|
||||
<PackageReference Include="JK.OKX.Net" Version="3.3.1" />
|
||||
<PackageReference Include="JKorf.BitMEX.Net" Version="2.3.0" />
|
||||
<PackageReference Include="JKorf.Coinbase.Net" Version="2.3.0" />
|
||||
<PackageReference Include="JKorf.HTX.Net" Version="7.3.0" />
|
||||
<PackageReference Include="KrakenExchange.Net" Version="6.3.1" />
|
||||
<PackageReference Include="Kucoin.Net" Version="7.3.0" />
|
||||
<PackageReference Include="Binance.Net" Version="11.7.1" />
|
||||
<PackageReference Include="Bitfinex.Net" Version="9.7.0" />
|
||||
<PackageReference Include="BitMart.Net" Version="2.8.0" />
|
||||
<PackageReference Include="BloFin.Net" Version="1.0.0" />
|
||||
<PackageReference Include="Bybit.Net" Version="5.8.0" />
|
||||
<PackageReference Include="CoinEx.Net" Version="9.7.0" />
|
||||
<PackageReference Include="CoinW.Net" Version="1.4.0" />
|
||||
<PackageReference Include="CryptoCom.Net" Version="2.8.0" />
|
||||
<PackageReference Include="DeepCoin.Net" Version="2.7.0" />
|
||||
<PackageReference Include="GateIo.Net" Version="2.8.1" />
|
||||
<PackageReference Include="HyperLiquid.Net" Version="2.12.0" />
|
||||
<PackageReference Include="JK.BingX.Net" Version="2.7.0" />
|
||||
<PackageReference Include="JK.Bitget.Net" Version="2.7.1" />
|
||||
<PackageReference Include="JK.Mexc.Net" Version="3.8.0" />
|
||||
<PackageReference Include="JK.OKX.Net" Version="3.7.1" />
|
||||
<PackageReference Include="JKorf.BitMEX.Net" Version="2.7.0" />
|
||||
<PackageReference Include="JKorf.Coinbase.Net" Version="2.7.0" />
|
||||
<PackageReference Include="JKorf.HTX.Net" Version="7.7.0" />
|
||||
<PackageReference Include="KrakenExchange.Net" Version="6.7.0" />
|
||||
<PackageReference Include="Kucoin.Net" Version="7.7.1" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="9.0.0" />
|
||||
<PackageReference Include="Toobit.Net" Version="1.2.1" />
|
||||
<PackageReference Include="WhiteBit.Net" Version="2.4.0" />
|
||||
<PackageReference Include="XT.Net" Version="2.3.1" />
|
||||
<PackageReference Include="Toobit.Net" Version="1.6.0" />
|
||||
<PackageReference Include="WhiteBit.Net" Version="2.8.0" />
|
||||
<PackageReference Include="XT.Net" Version="2.7.0" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
@inject IBitgetRestClient bitgetClient
|
||||
@inject IBitMartRestClient bitmartClient
|
||||
@inject IBitMEXRestClient bitmexClient
|
||||
@inject IBloFinRestClient bloFinClient
|
||||
@inject IBybitRestClient bybitClient
|
||||
@inject ICoinbaseRestClient coinbaseClient
|
||||
@inject ICoinExRestClient coinexClient
|
||||
@@ -39,6 +40,7 @@
|
||||
var bitgetTask = bitgetClient.SpotApiV2.ExchangeData.GetTickersAsync("BTCUSDT");
|
||||
var bitmartTask = bitmartClient.SpotApi.ExchangeData.GetTickerAsync("BTC_USDT");
|
||||
var bitmexTask = bitmexClient.ExchangeApi.ExchangeData.GetSymbolsAsync("XBT_USDT");
|
||||
var bloFinTask = bloFinClient.FuturesApi.ExchangeData.GetTickersAsync("BTC-USDT");
|
||||
var bybitTask = bybitClient.V5Api.ExchangeData.GetSpotTickersAsync("BTCUSDT");
|
||||
var coinbaseTask = coinbaseClient.AdvancedTradeApi.ExchangeData.GetSymbolAsync("BTC-USDT");
|
||||
var coinexTask = coinexClient.SpotApiV2.ExchangeData.GetTickersAsync(["BTCUSDT"]);
|
||||
@@ -76,6 +78,9 @@
|
||||
if (bitmexTask.Result.Success)
|
||||
_prices.Add("BitMEX", bitmexTask.Result.Data.First().LastPrice);
|
||||
|
||||
if (bloFinTask.Result.Success)
|
||||
_prices.Add("BloFin", bloFinTask.Result.Data.First().LastPrice);
|
||||
|
||||
if (bybitTask.Result.Success)
|
||||
_prices.Add("Bybit", bybitTask.Result.Data.List.First().LastPrice);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
@inject IBitgetSocketClient bitgetSocketClient
|
||||
@inject IBitMartSocketClient bitmartSocketClient
|
||||
@inject IBitMEXSocketClient bitmexSocketClient
|
||||
@inject IBloFinSocketClient bloFinSocketClient
|
||||
@inject IBybitSocketClient bybitSocketClient
|
||||
@inject ICoinbaseSocketClient coinbaseSocketClient
|
||||
@inject ICoinExSocketClient coinExSocketClient
|
||||
@@ -48,6 +49,8 @@
|
||||
bitgetSocketClient.SpotApiV2.SubscribeToTickerUpdatesAsync("ETHBTC", data => UpdateData("Bitget", data.Data.LastPrice)),
|
||||
bitmartSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH_BTC", data => UpdateData("BitMart", data.Data.LastPrice)),
|
||||
bitmexSocketClient.ExchangeApi.SubscribeToSymbolUpdatesAsync("ETH_XBT", data => UpdateData("BitMEX", data.Data.LastPrice ?? 0)),
|
||||
// BloFin doesn't support the ETH/BTC pair
|
||||
//bloFinSocketClient.FuturesApi.SubscribeToTickerUpdatesAsync("ETH-BTC", data => UpdateData("BloFin", data.Data.LastPrice)),
|
||||
bybitSocketClient.V5SpotApi.SubscribeToTickerUpdatesAsync("ETHBTC", data => UpdateData("Bybit", data.Data.LastPrice)),
|
||||
coinExSocketClient.SpotApiV2.SubscribeToTickerUpdatesAsync(["ETHBTC"], data => UpdateData("CoinEx", data.Data.First().LastPrice)),
|
||||
coinWSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH_BTC", data => UpdateData("CoinW", data.Data.LastPrice)),
|
||||
@@ -61,7 +64,8 @@
|
||||
//hyperLiquidSocketClient.SpotApi.SubscribeToSymbolUpdatesAsync("ETH", data => UpdateData("HyperLiquid", data.Data.MidPrice ?? 0)),
|
||||
krakenSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH/BTC", data => UpdateData("Kraken", data.Data.LastPrice)),
|
||||
kucoinSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH-BTC", data => UpdateData("Kucoin", data.Data.LastPrice ?? 0)),
|
||||
mexcSocketClient.SpotApi.SubscribeToMiniTickerUpdatesAsync("ETHBTC", data => UpdateData("Mexc", data.Data.LastPrice)),
|
||||
// Mexc doesn't offer a ticker stream currently
|
||||
//mexcSocketClient.SpotApi.SubscribeToMiniTickerUpdatesAsync("ETHBTC", data => UpdateData("Mexc", data.Data.LastPrice)),
|
||||
okxSocketClient.UnifiedApi.ExchangeData.SubscribeToTickerUpdatesAsync("ETH-BTC", data => UpdateData("OKX", data.Data.LastPrice ?? 0)),
|
||||
// Toobit doesn't support the ETH/BTC pair
|
||||
//toobitSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETHBTC", data => UpdateData("Toobit", data.Data.LastPrice ?? 0)),
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
@using Bitget.Net.Interfaces;
|
||||
@using BitMart.Net.Interfaces;
|
||||
@using BitMEX.Net.Interfaces;
|
||||
@using BloFin.Net.Interfaces;
|
||||
@using Bybit.Net.Interfaces
|
||||
@using CoinEx.Net.Interfaces
|
||||
@using CoinW.Net.Interfaces
|
||||
@@ -32,6 +33,7 @@
|
||||
@inject IBitgetOrderBookFactory bitgetFactory
|
||||
@inject IBitMartOrderBookFactory bitmartFactory
|
||||
@inject IBitMEXOrderBookFactory bitmexFactory
|
||||
@inject IBloFinOrderBookFactory bloFinFactory
|
||||
@inject IBybitOrderBookFactory bybitFactory
|
||||
@inject ICoinbaseOrderBookFactory coinbaseFactory
|
||||
@inject ICoinExOrderBookFactory coinExFactory
|
||||
@@ -55,7 +57,7 @@
|
||||
@foreach(var book in _books.OrderBy(p => p.Key))
|
||||
{
|
||||
<div style="margin-bottom: 20px; flex: 1; min-width: 300px;">
|
||||
<h4>@book.Key</h4>
|
||||
<h4>@book.Key - @book.Value.Symbol</h4>
|
||||
@if (book.Value.AskCount >= 3 && book.Value.BidCount >= 3)
|
||||
{
|
||||
for (var i = 0; i < 3; i++)
|
||||
@@ -87,23 +89,21 @@
|
||||
{ "Bitget", bitgetFactory.CreateSpot("ETHBTC") },
|
||||
{ "BitMart", bitmartFactory.CreateSpot("ETH_BTC", null) },
|
||||
{ "BitMEX", bitmexFactory.Create("ETH_XBT") },
|
||||
{ "BloFin", bloFinFactory.CreateFutures("ETH-USDT") },
|
||||
{ "Bybit", bybitFactory.Create("ETHBTC", Bybit.Net.Enums.Category.Spot) },
|
||||
{ "Coinbase", coinbaseFactory.Create("ETH-BTC", null) },
|
||||
{ "CoinEx", coinExFactory.CreateSpot("ETHBTC") },
|
||||
{ "CoinW", coinWFactory.CreateSpot("ETH_BTC") },
|
||||
{ "CryptoCom", cryptocomFactory.Create("ETH_BTC") },
|
||||
{ "GateIo", gateioFactory.CreateSpot("ETH_BTC") },
|
||||
// DeepCoin does not support the ETH/BTC pair
|
||||
//{ "DeepCoin", deepCoinFactory.Create("ETH-BTC") },
|
||||
{ "DeepCoin", deepCoinFactory.Create("ETH-USDT") },
|
||||
{ "HTX", htxFactory.CreateSpot("ethbtc") },
|
||||
// HyperLiquid does not support the ETH/BTC pair
|
||||
//{ "HyperLiquid", hyperLiquidFactory.Create("ETH/BTC") },
|
||||
{ "HyperLiquid", hyperLiquidFactory.Create("UETH/USDC") },
|
||||
{ "Kraken", krakenFactory.CreateSpot("ETH/BTC") },
|
||||
{ "Kucoin", kucoinFactory.CreateSpot("ETH-BTC") },
|
||||
{ "Mexc", mexcFactory.CreateSpot("ETHBTC") },
|
||||
{ "OKX", okxFactory.Create("ETH-BTC") },
|
||||
// Toobit does not support the ETH/BTC pair
|
||||
//{ "Toobit", toobitFactory.Create("ETH/BTC") },
|
||||
{ "Toobit", toobitFactory.CreateSpot("ETHUSDT") },
|
||||
{ "WhiteBit", whitebitFactory.CreateV4("ETH_BTC") },
|
||||
{ "XT", xtFactory.CreateSpot("eth_btc") },
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
@using Bitget.Net.Interfaces;
|
||||
@using BitMEX.Net.Interfaces;
|
||||
@using BitMart.Net.Interfaces;
|
||||
@using BloFin.Net.Interfaces
|
||||
@using Bybit.Net.Interfaces
|
||||
@using CoinEx.Net.Interfaces
|
||||
@using CoinW.Net.Interfaces
|
||||
@@ -33,6 +34,7 @@
|
||||
@inject IBitgetTrackerFactory bitgetFactory
|
||||
@inject IBitMartTrackerFactory bitmartFactory
|
||||
@inject IBitMEXTrackerFactory bitmexFactory
|
||||
@inject IBloFinTrackerFactory bloFinFactory
|
||||
@inject IBybitTrackerFactory bybitFactory
|
||||
@inject ICoinbaseTrackerFactory coinbaseFactory
|
||||
@inject ICoinExTrackerFactory coinExFactory
|
||||
@@ -71,33 +73,35 @@
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var usdtSymbol = new SharedSymbol(TradingMode.Spot, "BTC", "USDT");
|
||||
var usdcSpotSymbol = new SharedSymbol(TradingMode.Spot, "BTC", "USDC");
|
||||
var usdtSpotSymbol = new SharedSymbol(TradingMode.Spot, "BTC", "USDT");
|
||||
var usdtPerpLinSymbol = new SharedSymbol(TradingMode.PerpetualLinear, "BTC", "USDT");
|
||||
|
||||
_trackers = new List<ITradeTracker>
|
||||
{
|
||||
{ binanceFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ bingXFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ bitfinexFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ bitgetFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ bitmartFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ bitmexFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ bybitFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ coinbaseFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ coinExFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ coinWFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ cryptocomFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ deepCoinFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ gateioFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ htxFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
// HyperLiquid doesn't support spot pair, but does have a futures BTC/USDC pair
|
||||
{ hyperLiquidFactory.CreateTradeTracker(new SharedSymbol(TradingMode.PerpetualLinear, "BTC", "USDC"), period: TimeSpan.FromMinutes(5)) },
|
||||
{ krakenFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ kucoinFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ mexcFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ okxFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ toobitFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ whitebitFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ xtFactory.CreateTradeTracker(usdtSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ binanceFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ bingXFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ bitfinexFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ bitgetFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ bitmartFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ bitmexFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ bloFinFactory.CreateTradeTracker(usdtPerpLinSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ bybitFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ coinbaseFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ coinExFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ coinWFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ cryptocomFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ deepCoinFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ gateioFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ htxFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ hyperLiquidFactory.CreateTradeTracker(usdcSpotSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ krakenFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ kucoinFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ mexcFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ okxFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ toobitFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ whitebitFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
{ xtFactory.CreateTradeTracker(usdtSpotSymbol, period: TimeSpan.FromMinutes(5)) },
|
||||
};
|
||||
|
||||
await Task.WhenAll(_trackers.Select(b => b.StartAsync()));
|
||||
|
||||
@@ -38,6 +38,7 @@ namespace BlazorClient
|
||||
services.AddBitget();
|
||||
services.AddBitMart();
|
||||
services.AddBitMEX();
|
||||
services.AddBloFin();
|
||||
services.AddBybit();
|
||||
services.AddCoinbase();
|
||||
services.AddCoinEx();
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
@using Bitget.Net.Interfaces.Clients;
|
||||
@using BitMart.Net.Interfaces.Clients;
|
||||
@using BitMEX.Net.Interfaces.Clients;
|
||||
@using BloFin.Net.Interfaces.Clients;
|
||||
@using Bybit.Net.Interfaces.Clients;
|
||||
@using Coinbase.Net.Interfaces.Clients;
|
||||
@using CoinEx.Net.Interfaces.Clients;
|
||||
|
||||
@@ -18,6 +18,7 @@ Full list of all libraries part of the CryptoExchange.Net ecosystem. Consider us
|
||||
||Bitget|CEX|[JKorf/Bitget.Net](https://github.com/JKorf/Bitget.Net)|[](https://www.nuget.org/packages/JK.Bitget.Net)|[Link](https://partner.bitget.com/bg/1qlf6pj1)|20%|
|
||||
||BitMart|CEX|[JKorf/BitMart.Net](https://github.com/JKorf/BitMart.Net)|[](https://www.nuget.org/packages/BitMart.Net)|[Link](https://www.bitmart.com/invite/JKorfAPI/en-US)|30%|
|
||||
||BitMEX|CEX|[JKorf/BitMEX.Net](https://github.com/JKorf/BitMEX.Net)|[](https://www.nuget.org/packages/JKorf.BitMEX.Net)|[Link](https://www.bitmex.com/app/register/94f98e)|30%|
|
||||
||BloFin|CEX|[JKorf/BloFin.Net](https://github.com/JKorf/BloFin.Net)|[](https://www.nuget.org/packages/BloFin.Net)|-|-|
|
||||
||Bybit|CEX|[JKorf/Bybit.Net](https://github.com/JKorf/Bybit.Net)|[](https://www.nuget.org/packages/Bybit.Net)|[Link](https://partner.bybit.com/b/jkorf)|-|
|
||||
||Coinbase|CEX|[JKorf/Coinbase.Net](https://github.com/JKorf/Coinbase.Net)|[](https://www.nuget.org/packages/JKorf.Coinbase.Net)|[Link](https://advanced.coinbase.com/join/T6H54H8)|-|
|
||||
||CoinEx|CEX|[JKorf/CoinEx.Net](https://github.com/JKorf/CoinEx.Net)|[](https://www.nuget.org/packages/CoinEx.Net)|[Link](https://www.coinex.com/register?rc=rbtnp)|20%|
|
||||
@@ -38,6 +39,10 @@ Full list of all libraries part of the CryptoExchange.Net ecosystem. Consider us
|
||||
|
||||
Any of these can be installed independently or install [CryptoClients.Net](https://github.com/jkorf/CryptoClients.Net) which includes all exchange API's.
|
||||
|
||||
### Full demo application
|
||||
A full demo application is available using the [CryptoClients.Net](https://github.com/jkorf/CryptoClients.Net) library:
|
||||
https://github.com/JKorf/CryptoManager.Net
|
||||
|
||||
## Discord
|
||||
[](https://discord.gg/MSpeEtSY8t)
|
||||
A Discord server is available [here](https://discord.gg/MSpeEtSY8t). Feel free to join for discussion and/or questions around the CryptoExchange.Net and implementation libraries.
|
||||
@@ -59,6 +64,14 @@ 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 9.8.0 - 30 Sep 2025
|
||||
* Added ContractAddress to SharedAsset model
|
||||
* Added ITrackerFactory interface
|
||||
* Fixed UpdateSubscription still propagating connection events even though the specific listener is unsubscribed
|
||||
* Fixed ExchangeHelpers.AdjustValueStep high precision calculation
|
||||
* Fixed issue increasing the number of websocket connections increasing when sending a query when a previous connection was attempting to reconnect
|
||||
* Fixed EnumConverter to allow mapping empty string values
|
||||
|
||||
* Version 9.7.0 - 01 Sep 2025
|
||||
* Added LibraryHelpers.CreateHttpClientMessageHandle to standardize HttpMessageHandler creation
|
||||
* Added REST client option for selecting HTTP protocol version
|
||||
|
||||
Reference in New Issue
Block a user