1
0
mirror of https://github.com/JKorf/CryptoExchange.Net.git synced 2026-10-30 06:47:46 +00:00

Compare commits

..

11 Commits

22 changed files with 217 additions and 96 deletions
+71 -10
View File
@@ -286,7 +286,7 @@ namespace CryptoExchange.Net.Clients
while (true)
{
// Get a new or existing socket connection
var socketResult = await GetSocketConnection(url, subscription.Authenticated, false, ct, subscription.Topic, subscription.IndividualSubscriptionCount).ConfigureAwait(false);
var socketResult = await GetSocketConnection(url, subscription.Authenticated, false, ct, subscription, subscription.Topic, subscription.IndividualSubscriptionCount).ConfigureAwait(false);
if (!socketResult.Success)
return WebSocketResult.Fail<UpdateSubscription>(Exchange, socketResult.Error);
@@ -309,7 +309,7 @@ namespace CryptoExchange.Net.Clients
var needsConnecting = !socketConnection.Connected;
var connectResult = await ConnectIfNeededAsync(socketConnection, subscription.Authenticated, ct).ConfigureAwait(false);
var connectResult = await ConnectIfNeededAsync(socketConnection, subscription.Authenticated, ct, subscription).ConfigureAwait(false);
if (!connectResult.Success)
return WebSocketResult.Fail<UpdateSubscription>(Exchange, connectResult.Error!);
@@ -482,7 +482,7 @@ namespace CryptoExchange.Net.Clients
await semaphoreSlim.WaitAsync().ConfigureAwait(false);
try
{
var socketResult = await GetSocketConnection(url, query.Authenticated, true, ct).ConfigureAwait(false);
var socketResult = await GetSocketConnection(url, query.Authenticated, true, ct, processor: query).ConfigureAwait(false);
if (!socketResult.Success)
return QueryResult.Fail<THandlerResponse>(Exchange, socketResult.Error);
@@ -495,7 +495,7 @@ namespace CryptoExchange.Net.Clients
released = true;
}
var connectResult = await ConnectIfNeededAsync(socketConnection, query.Authenticated, ct).ConfigureAwait(false);
var connectResult = await ConnectIfNeededAsync(socketConnection, query.Authenticated, ct, query).ConfigureAwait(false);
if (!connectResult.Success)
return QueryResult.Fail<THandlerResponse>(Exchange, connectResult.Error!);
}
@@ -524,7 +524,18 @@ namespace CryptoExchange.Net.Clients
/// <param name="authenticated">Whether the socket should authenticated</param>
/// <param name="ct">Cancellation token</param>
/// <returns></returns>
protected virtual async Task<CallResult> ConnectIfNeededAsync(ISocketConnection socket, bool authenticated, CancellationToken ct)
protected virtual Task<CallResult> ConnectIfNeededAsync(ISocketConnection socket, bool authenticated, CancellationToken ct)
=> ConnectIfNeededAsync(socket, authenticated, ct, null);
/// <summary>
/// Checks if a socket needs to be connected and does so if needed. Also authenticates on the socket if needed
/// </summary>
/// <param name="socket">The connection to check</param>
/// <param name="authenticated">Whether the socket should authenticated</param>
/// <param name="processor">The message processor requesting a connection</param>
/// <param name="ct">Cancellation token</param>
/// <returns></returns>
protected virtual async Task<CallResult> ConnectIfNeededAsync(ISocketConnection socket, bool authenticated, CancellationToken ct, IMessageProcessor? processor)
{
if (socket.Connected)
return CallResult.Ok();
@@ -539,7 +550,7 @@ namespace CryptoExchange.Net.Clients
if (socket is not SocketConnection sc)
throw new InvalidOperationException("HighPerfSocketConnection not supported for authentication");
var result = await AuthenticateSocketAsync(sc).ConfigureAwait(false);
var result = await AuthenticateSocketAsync(sc, processor).ConfigureAwait(false);
if (!result.Success)
await socket.CloseAsync().ConfigureAwait(false);
@@ -551,13 +562,22 @@ namespace CryptoExchange.Net.Clients
/// </summary>
/// <param name="socket">Socket to authenticate</param>
/// <returns></returns>
public virtual async Task<CallResult> AuthenticateSocketAsync(SocketConnection socket)
public virtual Task<CallResult> AuthenticateSocketAsync(SocketConnection socket)
=> AuthenticateSocketAsync(socket, null);
/// <summary>
/// Authenticate a socket connection
/// </summary>
/// <param name="socket">Socket to authenticate</param>
/// <param name="processor">The message processor requesting authentication</param>
/// <returns></returns>
public virtual async Task<CallResult> AuthenticateSocketAsync(SocketConnection socket, IMessageProcessor? processor)
{
if (GetAuthenticationProvider() == null)
return CallResult.Fail(new NoApiCredentialsError());
_logger.AttemptingToAuthenticate(socket.SocketId);
var authRequest = await GetAuthenticationRequestAsync(socket).ConfigureAwait(false);
var authRequest = await GetAuthenticationRequestAsync(socket, processor).ConfigureAwait(false);
if (authRequest != null)
{
var result = await socket.SendAndWaitQueryAsync(authRequest).ConfigureAwait(false);
@@ -578,6 +598,7 @@ namespace CryptoExchange.Net.Clients
return CallResult.Ok();
}
/// <summary>
/// Should return the request which can be used to authenticate a socket connection
/// </summary>
@@ -585,6 +606,13 @@ namespace CryptoExchange.Net.Clients
protected internal virtual Task<Query?> GetAuthenticationRequestAsync(SocketConnection connection) =>
Task.FromResult(GetAuthenticationProvider()!.GetAuthenticationQuery(this, connection));
/// <summary>
/// Should return the request which can be used to authenticate a socket connection
/// </summary>
/// <returns></returns>
protected internal virtual Task<Query?> GetAuthenticationRequestAsync(SocketConnection connection, IMessageProcessor? processor) =>
GetAuthenticationRequestAsync(connection);
/// <summary>
/// Adds a system subscription. Used for example to reply to ping requests
/// </summary>
@@ -642,6 +670,18 @@ namespace CryptoExchange.Net.Clients
&& (AllowTopicsOnTheSameConnection || !connection.Topics.Contains(topic));
}
/// <summary>
/// Whether the connection can be used for a new subscription or query with the provided parameters
/// </summary>
/// <param name="connection">The connection to check</param>
/// <param name="address">The address set by the request</param>
/// <param name="authenticated">Whether the request needs an authenticated connection</param>
/// <param name="processor">The processor requesting a connection</param>
/// <param name="topic">Topic of the request</param>
/// <returns>True if connection can be used</returns>
protected virtual bool ConnectionCanBeUsedFor(SocketConnection connection, string address, bool authenticated, IMessageProcessor? processor, string? topic = null)
=> ConnectionCanBeUsedFor(connection, address, authenticated, topic);
/// <summary>
/// Gets a connection for a new subscription or query. Can be an existing if there are open position or a new one.
/// </summary>
@@ -652,15 +692,36 @@ namespace CryptoExchange.Net.Clients
/// <param name="topic">The subscription topic, can be provided when multiple of the same topics are not allowed on a connection</param>
/// <param name="individualSubscriptionCount">The number of individual subscriptions in this subscribe request</param>
/// <returns></returns>
protected virtual async Task<CallResult<SocketConnection>> GetSocketConnection(
protected virtual Task<CallResult<SocketConnection>> GetSocketConnection(
string address,
bool authenticated,
bool dedicatedRequestConnection,
CancellationToken ct,
string? topic = null,
int individualSubscriptionCount = 1)
=> GetSocketConnection(address, authenticated, dedicatedRequestConnection, ct, null, topic, individualSubscriptionCount);
/// <summary>
/// Gets a connection for a new subscription or query. Can be an existing if there are open position or a new one.
/// </summary>
/// <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>
/// <param name="individualSubscriptionCount">The number of individual subscriptions in this subscribe request</param>
/// <param name="processor">The processor requesting a connection</param>
/// <returns></returns>
protected virtual async Task<CallResult<SocketConnection>> GetSocketConnection(
string address,
bool authenticated,
bool dedicatedRequestConnection,
CancellationToken ct,
IMessageProcessor? processor,
string? topic = null,
int individualSubscriptionCount = 1)
{
var socketQuery = _socketConnections.Where(s => ConnectionCanBeUsedFor(s.Value, address, authenticated, topic)).Select(x => x.Value); // Don't ToList this so the query is executed again when called
var socketQuery = _socketConnections.Where(s => ConnectionCanBeUsedFor(s.Value, address, authenticated, processor, topic)).Select(x => x.Value); // Don't ToList this so the query is executed again when called
// 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;
+3 -3
View File
@@ -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>13.1.0</PackageVersion>
<AssemblyVersion>13.1.0</AssemblyVersion>
<FileVersion>13.1.0</FileVersion>
<PackageVersion>13.2.2</PackageVersion>
<AssemblyVersion>13.2.2</AssemblyVersion>
<FileVersion>13.2.2</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>
@@ -1076,19 +1076,23 @@ namespace CryptoExchange.Net.Sockets.Default
=> _listeners.OfType<Subscription>().Any(
x => x != subscription
&& x.Active
&& x.MessageRouter.Routes.All(l => subscription.MessageRouter.ContainsCheck(l)));
&& subscription.MessageRouter.Routes.All(l => x.MessageRouter.ContainsCheck(l)));
/// <summary>
/// Whether there is a subscription earlier in the list covering all the routes the subscription lists
/// </summary>
protected bool IsCoveredByEarlierSubscription(Subscription subscription)
{
if (subscription.MessageRouter.Routes.Length == 0)
// Can be subscription which only knows what to listen to after receiving the subscribe response, for example Bitfinex
return false;
foreach (var other in _listeners.OfType<Subscription>())
{
if (other == subscription)
return false;
if (other.Active && other.MessageRouter.Routes.All(x => subscription.MessageRouter.ContainsCheck(x)))
if (other.Active && subscription.MessageRouter.Routes.All(x => other.MessageRouter.ContainsCheck(x)))
return true;
}
@@ -198,8 +198,13 @@ namespace CryptoExchange.Net.Sockets.HighPerf
{
var result = await _socket.ConnectAsync(ct).ConfigureAwait(false);
if (result.Success)
{
_processTask = ProcessAsync(_cts.Token);
if ((ApiClient.ApiOptions.DelayAfterConnect ?? ApiClient.ClientOptions.DelayAfterConnect) != TimeSpan.Zero)
await Task.Delay(ApiClient.ApiOptions.DelayAfterConnect ?? ApiClient.ClientOptions.DelayAfterConnect).ConfigureAwait(false);
}
return result;
}
@@ -168,12 +168,18 @@ namespace CryptoExchange.Net.Testing
{
// Validate request body
Dictionary<string, object> bodyParameters;
if (result.RequestBody!.StartsWith("{") || result.RequestBody.StartsWith("["))
if (string.IsNullOrEmpty(result.RequestBody))
{
bodyParameters = new Dictionary<string, object>();
}
else if (result.RequestBody!.StartsWith("{") || result.RequestBody.StartsWith("["))
{
// Json
bodyParameters = JsonSerializer.Deserialize<Dictionary<string, object>>(result.RequestBody!)!;
}
else
{
// Form data
var splitKvp = result.RequestBody.Split('&');
bodyParameters = splitKvp.Select(x => x.Split('=')).ToDictionary(x => x[0], x => (object)x[1]);
}
@@ -28,10 +28,11 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
SharedAccountType accountType,
TrackerItemConfig config,
ExchangeParameters? exchangeParameters = null
) : base(logger, symbolTracker, UserDataType.Balances, restClient.Exchange, config)
) : base(logger, symbolTracker, UserDataType.Balances, restClient.Exchange)
{
if (_socketClient == null)
if (socketClient == null)
config = config with { PollIntervalConnected = config.PollIntervalDisconnected };
SetConfig(config);
_restClient = restClient;
_socketClient = socketClient;
@@ -24,6 +24,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
private readonly Dictionary<string, int> _openOrderNotReturnedTimes = new();
private readonly TimeSpan _pollOverlapPeriod = TimeSpan.FromSeconds(3);
private bool _openOrderWarningDone = false;
internal event Func<UpdateSource, SharedUserTrade[], Task>? OnTradeUpdate;
/// <summary>
@@ -39,10 +40,11 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
IEnumerable<SharedSymbol> symbols,
bool onlyTrackProvidedSymbols,
ExchangeParameters? exchangeParameters = null
) : base(logger, symbolTracker, UserDataType.Orders, openRestClient.Exchange, config)
) : base(logger, symbolTracker, UserDataType.Orders, openRestClient.Exchange)
{
if (_socketClient == null)
if (socketClient == null)
config = config with { PollIntervalConnected = config.PollIntervalDisconnected };
SetConfig(config);
_openOrdersClient = openRestClient;
_closedOrdersClient = closedRestClient;
@@ -234,7 +236,14 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
}
else
{
foreach (var symbol in _symbolTracker.GetTrackedSymbols())
var trackedSymbols = _symbolTracker.GetTrackedSymbols();
if (!_openOrderWarningDone && trackedSymbols.Count() == 0)
{
_openOrderWarningDone = true;
_logger.LogDebug("No tracked symbols specified while symbol is required to retrieve open orders. Tracked symbols should be updated before orders may be returned");
}
foreach (var symbol in trackedSymbols)
{
var openOrdersResult = await _openOrdersClient.GetOpenFuturesOrdersAsync(new GetOpenOrdersRequest(symbol, exchangeParameters: _exchangeParameters)).ConfigureAwait(false);
if (!openOrdersResult.Success)
@@ -34,10 +34,11 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
IEnumerable<SharedSymbol> symbols,
bool onlyTrackProvidedSymbols,
ExchangeParameters? exchangeParameters = null
) : base(logger, symbolTracker, UserDataType.Trades, restClient.Exchange, config)
) : base(logger, symbolTracker, UserDataType.Trades, restClient.Exchange)
{
if (_socketClient == null)
if (socketClient == null)
config = config with { PollIntervalConnected = config.PollIntervalDisconnected };
SetConfig(config);
_restClient = restClient;
_socketClient = socketClient;
@@ -37,10 +37,11 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
bool onlyTrackProvidedSymbols,
bool websocketPositionUpdatesAreFullSnapshots,
ExchangeParameters? exchangeParameters = null
) : base(logger, symbolTracker, UserDataType.Positions, restClient.Exchange, config)
) : base(logger, symbolTracker, UserDataType.Positions, restClient.Exchange)
{
if (_socketClient == null)
if (socketClient == null)
config = config with { PollIntervalConnected = config.PollIntervalDisconnected };
SetConfig(config);
_restClient = restClient;
_socketClient = socketClient;
@@ -23,6 +23,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
private readonly bool _timeFilterSupportedClosedOrders;
private readonly Dictionary<string, int> _openOrderNotReturnedTimes = new();
private readonly TimeSpan _pollOverlapPeriod = TimeSpan.FromSeconds(3);
private bool _openOrderWarningDone = false;
internal event Func<UpdateSource, SharedUserTrade[], Task>? OnTradeUpdate;
@@ -39,10 +40,11 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
IEnumerable<SharedSymbol> symbols,
bool onlyTrackProvidedSymbols,
ExchangeParameters? exchangeParameters = null
) : base(logger, symbolTracker, UserDataType.Orders, openOrderClient.Exchange, config)
) : base(logger, symbolTracker, UserDataType.Orders, openOrderClient.Exchange)
{
if (_socketClient == null)
if (socketClient == null)
config = config with { PollIntervalConnected = config.PollIntervalDisconnected };
SetConfig(config);
_openOrderClient = openOrderClient;
_closedOrderClient = closedOrderClient;
@@ -244,7 +246,14 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
}
else
{
foreach (var symbol in _symbolTracker.GetTrackedSymbols())
var trackedSymbols = _symbolTracker.GetTrackedSymbols();
if (!_openOrderWarningDone && trackedSymbols.Count() == 0)
{
_openOrderWarningDone = true;
_logger.LogDebug("No tracked symbols specified while symbol is required to retrieve open orders. Tracked symbols should be updated before orders may be returned");
}
foreach (var symbol in trackedSymbols)
{
var openOrdersResult = await _openOrderClient.GetOpenSpotOrdersAsync(new GetOpenOrdersRequest(symbol, exchangeParameters: _exchangeParameters)).ConfigureAwait(false);
if (!openOrdersResult.Success)
@@ -34,10 +34,11 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
IEnumerable<SharedSymbol> symbols,
bool onlyTrackProvidedSymbols,
ExchangeParameters? exchangeParameters = null
) : base(logger, symbolTracker, UserDataType.Trades, restClient.Exchange, config)
) : base(logger, symbolTracker, UserDataType.Trades, restClient.Exchange)
{
if (_socketClient == null)
if (socketClient == null)
config = config with { PollIntervalConnected = config.PollIntervalDisconnected };
SetConfig(config);
_restClient = restClient;
_socketClient = socketClient;
@@ -242,15 +242,21 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
ILogger logger,
UserDataSymbolTracker symbolTracker,
UserDataType dataType,
string exchange,
TrackerItemConfig config) : base(logger, dataType, exchange)
string exchange) : base(logger, dataType, exchange)
{
_isSymbolModel = typeof(T).IsSubclassOf(typeof(SharedSymbolModel));
_symbolTracker = symbolTracker;
}
/// <summary>
/// Set the tracker item config
/// </summary>
protected void SetConfig(TrackerItemConfig config)
{
_pollIntervalDisconnected = config.PollIntervalDisconnected;
_pollIntervalConnected = config.PollIntervalConnected;
_pollAtStart = config.PollAtStart;
_retentionTime = config is TrackerTimedItemConfig timeConfig ? timeConfig.RetentionTime : TimeSpan.MaxValue;
_isSymbolModel = typeof(T).IsSubclassOf(typeof(SharedSymbolModel));
_symbolTracker = symbolTracker;
}
/// <summary>
+29 -29
View File
@@ -5,36 +5,36 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Binance.Net" Version="13.6.0" />
<PackageReference Include="Bitfinex.Net" Version="11.6.0" />
<PackageReference Include="BitMart.Net" Version="4.5.0" />
<PackageReference Include="BloFin.Net" Version="3.5.0" />
<PackageReference Include="Bybit.Net" Version="7.6.0" />
<PackageReference Include="CoinEx.Net" Version="11.5.0" />
<PackageReference Include="CoinW.Net" Version="3.5.0" />
<PackageReference Include="CryptoCom.Net" Version="4.5.0" />
<PackageReference Include="DeepCoin.Net" Version="4.5.0" />
<PackageReference Include="GateIo.Net" Version="4.7.0" />
<PackageReference Include="HyperLiquid.Net" Version="5.8.0" />
<PackageReference Include="JK.BingX.Net" Version="4.6.0" />
<PackageReference Include="JK.Bitget.Net" Version="4.6.0" />
<PackageReference Include="JK.Mexc.Net" Version="6.7.0" />
<PackageReference Include="JK.OKX.Net" Version="5.5.0" />
<PackageReference Include="Jkorf.Aster.Net" Version="4.5.0" />
<PackageReference Include="JKorf.Coinbase.Net" Version="4.6.0" />
<PackageReference Include="JKorf.HTX.Net" Version="9.6.0" />
<PackageReference Include="JKorf.Lighter.Net" Version="1.6.0" />
<PackageReference Include="JKorf.Upbit.Net" Version="3.5.0" />
<PackageReference Include="KrakenExchange.Net" Version="8.5.0" />
<PackageReference Include="Kucoin.Net" Version="9.6.0" />
<PackageReference Include="LBank.Net" Version="1.2.0" />
<PackageReference Include="Pionex.Net" Version="1.3.0" />
<PackageReference Include="Binance.Net" Version="13.7.0" />
<PackageReference Include="Bitfinex.Net" Version="11.7.1" />
<PackageReference Include="Bitstamp.Net" Version="2.6.0" />
<PackageReference Include="Bitunix.Net" Version="1.0.1" />
<PackageReference Include="BloFin.Net" Version="3.6.0" />
<PackageReference Include="Bybit.Net" Version="7.7.0" />
<PackageReference Include="CoinW.Net" Version="3.6.2" />
<PackageReference Include="CryptoCom.Net" Version="4.6.0" />
<PackageReference Include="DeepCoin.Net" Version="4.6.1" />
<PackageReference Include="GateIo.Net" Version="4.8.0" />
<PackageReference Include="HyperLiquid.Net" Version="5.9.1" />
<PackageReference Include="JK.BingX.Net" Version="4.7.0" />
<PackageReference Include="JK.Bitget.Net" Version="4.9.0" />
<PackageReference Include="JK.Mexc.Net" Version="6.8.0" />
<PackageReference Include="JK.OKX.Net" Version="5.7.0" />
<PackageReference Include="Jkorf.Aster.Net" Version="4.6.0" />
<PackageReference Include="JKorf.Coinbase.Net" Version="4.7.0" />
<PackageReference Include="JKorf.HTX.Net" Version="9.7.0" />
<PackageReference Include="JKorf.Lighter.Net" Version="1.7.0" />
<PackageReference Include="JKorf.Upbit.Net" Version="3.6.0" />
<PackageReference Include="KrakenExchange.Net" Version="8.6.0" />
<PackageReference Include="Kucoin.Net" Version="9.8.0" />
<PackageReference Include="LBank.Net" Version="1.3.0" />
<PackageReference Include="Pionex.Net" Version="1.4.0" />
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
<PackageReference Include="Tapbit.Net" Version="1.1.0" />
<PackageReference Include="Toobit.Net" Version="4.5.0" />
<PackageReference Include="Weex.Net" Version="2.5.0" />
<PackageReference Include="WhiteBit.Net" Version="4.5.0" />
<PackageReference Include="XT.Net" Version="4.6.0" />
<PackageReference Include="Tapbit.Net" Version="1.2.0" />
<PackageReference Include="Toobit.Net" Version="4.6.0" />
<PackageReference Include="Weex.Net" Version="2.6.0" />
<PackageReference Include="WhiteBit.Net" Version="4.7.0" />
<PackageReference Include="XT.Net" Version="4.7.0" />
</ItemGroup>
</Project>
+12 -10
View File
@@ -1,14 +1,16 @@
@page "/"
@using Bitstamp.Net.Interfaces.Clients
@using Bitunix.Net.Interfaces.Clients
@inject IAsterRestClient asterClient
@inject IBinanceRestClient binanceClient
@inject IBingXRestClient bingXClient
@inject IBitfinexRestClient bitfinexClient
@inject IBitgetRestClient bitgetClient
@inject IBitMartRestClient bitmartClient
@inject IBitstampRestClient bitstampClient
@inject IBitunixRestClient bitunixClient
@inject IBloFinRestClient bloFinClient
@inject IBybitRestClient bybitClient
@inject ICoinbaseRestClient coinbaseClient
@inject ICoinExRestClient coinexClient
@inject ICoinWRestClient coinWClient
@inject ICryptoComRestClient cryptocomClient
@inject IDeepCoinRestClient deepCoinClient
@@ -45,11 +47,11 @@
var bingXTask = bingXClient.SpotApi.ExchangeData.GetTickersAsync("BTC-USDT");
var bitfinexTask = bitfinexClient.ExchangeApi.ExchangeData.GetTickerAsync("tBTCUSD");
var bitgetTask = bitgetClient.SpotApiV2.ExchangeData.GetTickersAsync("BTCUSDT");
var bitmartTask = bitmartClient.SpotApi.ExchangeData.GetTickerAsync("BTC_USDT");
var bitstampTask = bitstampClient.ExchangeApi.ExchangeData.GetTickerAsync("BTCUSDT");
var bitunixTask = bitunixClient.SpotApi.ExchangeData.GetLastPriceAsync("BTCUSDT");
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"]);
var coinWTask = coinWClient.SpotApi.ExchangeData.GetTickersAsync();
var cryptocomTask = cryptocomClient.ExchangeApi.ExchangeData.GetTickersAsync("BTC_USDT");
var deepCoinTask = deepCoinClient.ExchangeApi.ExchangeData.GetTickersAsync(DeepCoin.Net.Enums.SymbolType.Spot);
@@ -69,7 +71,7 @@
var whitebitTask = whitebitClient.V4Api.ExchangeData.GetTickersAsync();
var xtTask = xtClient.SpotApi.ExchangeData.GetTickersAsync("btc_usdt");
await Task.WhenAll(asterTask, binanceTask, bingXTask, bitfinexTask, bitgetTask, bitmartTask, bloFinTask, bybitTask, coinexTask, coinWTask, deepCoinTask, gateioTask,
await Task.WhenAll(asterTask, binanceTask, bingXTask, bitfinexTask, bitgetTask, bitstampTask, bitunixTask, bloFinTask, bybitTask, coinWTask, deepCoinTask, gateioTask,
htxTask, krakenTask, kucoinTask, lBankTask, mexcTask, okxTask, pionexTask, toobitTask, upbitTask, weexTask, whitebitTask, xtTask);
if (asterTask.Result.Success)
@@ -87,8 +89,11 @@
if (bitgetTask.Result.Success)
_prices.Add("Bitget", bitgetTask.Result.Data.Single().LastPrice);
if (bitmartTask.Result.Success)
_prices.Add("BitMart", bitmartTask.Result.Data.LastPrice);
if (bitstampTask.Result.Success)
_prices.Add("Bitstamp", bitstampTask.Result.Data.LastPrice);
if (bitunixTask.Result.Success)
_prices.Add("Bitunix", bitunixTask.Result.Data);
if (bloFinTask.Result.Success)
_prices.Add("BloFin", bloFinTask.Result.Data.First().LastPrice);
@@ -99,9 +104,6 @@
if (coinbaseTask.Result.Success)
_prices.Add("Coinbase", coinbaseTask.Result.Data.LastPrice ?? 0);
if (coinexTask.Result.Success)
_prices.Add("CoinEx", coinexTask.Result.Data.Single().LastPrice);
if (coinWTask.Result.Success)
_prices.Add("CoinW", coinWTask.Result.Data.Single(x => x.Symbol == "BTC_USDT").LastPrice);
+4 -4
View File
@@ -4,11 +4,11 @@
@inject IBingXSocketClient bingXSocketClient
@inject IBitfinexSocketClient bitfinexSocketClient
@inject IBitgetSocketClient bitgetSocketClient
@inject IBitMartSocketClient bitmartSocketClient
@inject IBitstampSocketClient bitstampSocketClient
@inject IBitunixSocketClient bitunixSocketClient
@inject IBloFinSocketClient bloFinSocketClient
@inject IBybitSocketClient bybitSocketClient
@inject ICoinbaseSocketClient coinbaseSocketClient
@inject ICoinExSocketClient coinExSocketClient
@inject ICoinWSocketClient coinWSocketClient
@inject ICryptoComSocketClient cryptocomSocketClient
@inject IDeepCoinSocketClient deepCoinSocketClient
@@ -28,6 +28,8 @@
@inject IWhiteBitSocketClient whitebitSocketClient
@inject IXTSocketClient xtSocketClient
@using System.Collections.Concurrent
@using Bitstamp.Net.Interfaces.Clients
@using Bitunix.Net.Interfaces.Clients
@using CryptoExchange.Net.Objects
@using CryptoExchange.Net.Objects.Sockets;
@using CryptoExchange.Net.Sockets
@@ -54,11 +56,9 @@
bingXSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH-BTC", data => UpdateData("BingX", data.Data.LastPrice)),
bitfinexSocketClient.ExchangeApi.SubscribeToTickerUpdatesAsync("tETHBTC", data => UpdateData("Bitfinex", data.Data.LastPrice)),
bitgetSocketClient.SpotApiV2.SubscribeToTickerUpdatesAsync("ETHBTC", data => UpdateData("Bitget", data.Data.First().LastPrice)),
bitmartSocketClient.SpotApi.SubscribeToTickerUpdatesAsync("ETH_BTC", data => UpdateData("BitMart", data.Data.LastPrice)),
// 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)),
coinbaseSocketClient.AdvancedTradeApi.SubscribeToTickerUpdatesAsync("ETH-BTC", data => UpdateData("Coinbase", data.Data.LastPrice ?? 0)),
cryptocomSocketClient.ExchangeApi.SubscribeToTickerUpdatesAsync("ETH_BTC", data => UpdateData("CryptoCom", data.Data.LastPrice ?? 0)),
+6 -6
View File
@@ -6,10 +6,10 @@
@using BingX.Net.Interfaces
@using Bitfinex.Net.Interfaces
@using Bitget.Net.Interfaces;
@using BitMart.Net.Interfaces;
@using Bitstamp.Net.Interfaces;
@using Bitunix.Net.Interfaces;
@using BloFin.Net.Interfaces;
@using Bybit.Net.Interfaces
@using CoinEx.Net.Interfaces
@using CoinW.Net.Interfaces
@using Coinbase.Net.Interfaces
@using CryptoExchange.Net.Authentication
@@ -38,11 +38,11 @@
@inject IBingXOrderBookFactory bingXFactory
@inject IBitfinexOrderBookFactory bitfinexFactory
@inject IBitgetOrderBookFactory bitgetFactory
@inject IBitMartOrderBookFactory bitmartFactory
@inject IBitstampOrderBookFactory bitstampFactory
@inject IBitunixOrderBookFactory bitunixFactory
@inject IBloFinOrderBookFactory bloFinFactory
@inject IBybitOrderBookFactory bybitFactory
@inject ICoinbaseOrderBookFactory coinbaseFactory
@inject ICoinExOrderBookFactory coinExFactory
@inject ICoinWOrderBookFactory coinWFactory
@inject ICryptoComOrderBookFactory cryptocomFactory
@inject IDeepCoinOrderBookFactory deepCoinFactory
@@ -100,11 +100,11 @@
{ "BingX", bingXFactory.CreateSpot("ETH-BTC") },
{ "Bitfinex", bitfinexFactory.Create("tETHBTC") },
{ "Bitget", bitgetFactory.CreateSpot("ETHBTC") },
{ "BitMart", bitmartFactory.CreateSpot("ETH_BTC", null) },
{ "Bitstamp", bitstampFactory.Create("ETHUSDT", null) },
{ "Bitunix", bitunixFactory.CreateFutures("ETHUSDT", null) },
{ "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") },
+5 -6
View File
@@ -6,10 +6,10 @@
@using BingX.Net.Interfaces
@using Bitfinex.Net.Interfaces
@using Bitget.Net.Interfaces;
@using BitMart.Net.Interfaces;
@using Bitstamp.Net.Interfaces;
@using Bitunix.Net.Interfaces;
@using BloFin.Net.Interfaces
@using Bybit.Net.Interfaces
@using CoinEx.Net.Interfaces
@using CoinW.Net.Interfaces
@using Coinbase.Net.Interfaces
@using CryptoExchange.Net.Interfaces
@@ -38,11 +38,11 @@
@inject IBingXTrackerFactory bingXFactory
@inject IBitfinexTrackerFactory bitfinexFactory
@inject IBitgetTrackerFactory bitgetFactory
@inject IBitMartTrackerFactory bitmartFactory
@inject IBitstampTrackerFactory bitstampFactory
@inject IBitunixTrackerFactory bitunixFactory
@inject IBloFinTrackerFactory bloFinFactory
@inject IBybitTrackerFactory bybitFactory
@inject ICoinbaseTrackerFactory coinbaseFactory
@inject ICoinExTrackerFactory coinExFactory
@inject ICoinWTrackerFactory coinWFactory
@inject ICryptoComTrackerFactory cryptocomFactory
@inject IDeepCoinTrackerFactory deepCoinFactory
@@ -93,11 +93,10 @@
{ 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)) },
{ bitstampFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ bloFinFactory.CreateTradeTracker(futuresSymbol, 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)) },
{ coinWFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ cryptocomFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
{ deepCoinFactory.CreateTradeTracker(symbol, period: TimeSpan.FromMinutes(5)) },
+2 -2
View File
@@ -38,11 +38,11 @@ namespace BlazorClient
services.AddBingX();
services.AddBitfinex();
services.AddBitget();
services.AddBitMart();
services.AddBitstamp();
services.AddBitunix();
services.AddBloFin();
services.AddBybit();
services.AddCoinbase();
services.AddCoinEx();
services.AddCoinW();
services.AddCryptoCom();
services.AddDeepCoin();
-2
View File
@@ -13,11 +13,9 @@
@using BingX.Net.Interfaces.Clients;
@using Bitfinex.Net.Interfaces.Clients;
@using Bitget.Net.Interfaces.Clients;
@using BitMart.Net.Interfaces.Clients;
@using BloFin.Net.Interfaces.Clients;
@using Bybit.Net.Interfaces.Clients;
@using Coinbase.Net.Interfaces.Clients;
@using CoinEx.Net.Interfaces.Clients;
@using CoinW.Net.Interfaces.Clients;
@using CryptoCom.Net.Interfaces.Clients;
@using DeepCoin.Net.Interfaces.Clients;
+18
View File
@@ -39,6 +39,7 @@ Full list of all libraries part of the CryptoExchange.Net ecosystem. Consider us
|![Bitget](https://raw.githubusercontent.com/JKorf/Bitget.Net/refs/heads/main/Bitget.Net/Icon/icon.png)|Bitget|CEX|[JKorf/Bitget.Net](https://github.com/JKorf/Bitget.Net)|[![Nuget version](https://img.shields.io/nuget/v/JK.Bitget.net.svg?style=flat-square)](https://www.nuget.org/packages/JK.Bitget.Net)|[Link](https://partner.bitget.com/bg/1qlf6pj1)|20%|
|![BitMart](https://raw.githubusercontent.com/JKorf/BitMart.Net/refs/heads/main/BitMart.Net/Icon/icon.png)|BitMart|CEX|[JKorf/BitMart.Net](https://github.com/JKorf/BitMart.Net)|[![Nuget version](https://img.shields.io/nuget/v/BitMart.net.svg?style=flat-square)](https://www.nuget.org/packages/BitMart.Net)|[Link](https://www.bitmart.com/invite/JKorfAPI/en-US)|30%|
|![Bitstamp](https://raw.githubusercontent.com/JKorf/Bitstamp.Net/refs/heads/main/Bitstamp.Net/Icon/icon.png)|Bitstamp|CEX|[JKorf/Bitstamp.Net](https://github.com/JKorf/Bitstamp.Net)|[![Nuget version](https://img.shields.io/nuget/v/Bitstamp.net.svg?style=flat-square)](https://www.nuget.org/packages/Bitstamp.Net)|-|-|
|![Bitunix](https://raw.githubusercontent.com/JKorf/Bitunix.Net/refs/heads/main/Bitunix.Net/Icon/icon.png)|Bitunix|CEX|[JKorf/Bitunix.Net](https://github.com/JKorf/Bitunix.Net)|[![Nuget version](https://img.shields.io/nuget/v/Bitunix.net.svg?style=flat-square)](https://www.nuget.org/packages/Bitunix.Net)|-|-|
|![BloFin](https://raw.githubusercontent.com/JKorf/BloFin.Net/refs/heads/main/BloFin.Net/Icon/icon.png)|BloFin|CEX|[JKorf/BloFin.Net](https://github.com/JKorf/BloFin.Net)|[![Nuget version](https://img.shields.io/nuget/v/BloFin.net.svg?style=flat-square)](https://www.nuget.org/packages/BloFin.Net)|-|-|
|![Bybit](https://raw.githubusercontent.com/JKorf/Bybit.Net/refs/heads/main/ByBit.Net/Icon/icon.png)|Bybit|CEX|[JKorf/Bybit.Net](https://github.com/JKorf/Bybit.Net)|[![Nuget version](https://img.shields.io/nuget/v/Bybit.net.svg?style=flat-square)](https://www.nuget.org/packages/Bybit.Net)|[Link](https://partner.bybit.com/b/jkorf)|-|
|![Coinbase](https://raw.githubusercontent.com/JKorf/Coinbase.Net/refs/heads/main/Coinbase.Net/Icon/icon.png)|Coinbase|CEX|[JKorf/Coinbase.Net](https://github.com/JKorf/Coinbase.Net)|[![Nuget version](https://img.shields.io/nuget/v/JKorf.Coinbase.Net.svg?style=flat-square)](https://www.nuget.org/packages/JKorf.Coinbase.Net)|[Link](https://advanced.coinbase.com/join/T6H54H8)|-|
@@ -127,6 +128,23 @@ Various:
* PlatformInfo now required support environment names in the constructor
## Release notes
* Version 13.2.2 - 22 Oct 2026
* Fixed UserDataTracker config not getting set correctly
* Added UserDataTracker warning for missing tracked symbols if open orders request requires it
* Version 13.2.1 - 19 Oct 2026
* Fixed subscription without any predefined routes failing to subscribe
* Fixed test validation failing with empty body string
* Version 13.2.0 - 13 Oct 2026
* Added Shared API ISubscribeFundingInfoSocket
* Added DelayAfterConnect to SocketApiOptions
* Added processor parameter for some methods in SocketApiClient to allow specific overriding behavior
* Updated WebSocket duplicate subscription handling
* Updated Shared APIs ISubscribeIncrementalOrderBook
* Fixed incorrect status keeping of WebSocket subscriptions which don't expect a response
* Fixed DelayAfterConnect not being respected on reconnection
* Version 13.1.0 - 28 Sep 2026
* Added UnsubscribeAllAsync method to Shared APIs ISharedSubscription and ISharedApiClientBase
* Added WaitAllAsync extension method for waiting on an IAsyncEnumerable to finish
+2 -2
View File
@@ -1,8 +1,8 @@
# CryptoExchange.Net — full AI context
Library: CryptoExchange.Net
Library version: 13.1.0
CryptoExchange.Net package version: 13.1.0
Library version: 13.2.2
CryptoExchange.Net package version: 13.2.2
Language: C#/.NET
Targets: netstandard2.0, netstandard2.1, net8.0, net9.0, net10.0
+1 -1
View File
@@ -2,7 +2,7 @@
> Base C#/.NET library used by 28+ cryptocurrency exchange clients. It standardizes REST, WebSocket, authentication, rate limiting, results, and exchange-agnostic Shared APIs.
CryptoExchange.Net is not an exchange client by itself. Install exchange-specific packages such as Binance.Net, Bybit.Net, JK.OKX.Net, Kraken.Net, or Coinbase.Net, or install `CryptoClients.Net` for the bundle. Current release: 13.1.0. Targets netstandard2.0, netstandard2.1, net8.0, net9.0, and net10.0; Native AOT is supported.
CryptoExchange.Net is not an exchange client by itself. Install exchange-specific packages such as Binance.Net, Bybit.Net, JK.OKX.Net, Kraken.Net, or Coinbase.Net, or install `CryptoClients.Net` for the bundle. Current release: 13.2.2. Targets netstandard2.0, netstandard2.1, net8.0, net9.0, and net10.0; Native AOT is supported.
## Shared API V2