1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2026-02-16 14:13:46 +00:00

Compare commits

...

4 Commits

14 changed files with 197 additions and 15 deletions

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>10.5.4</PackageVersion>
<AssemblyVersion>10.5.4</AssemblyVersion>
<FileVersion>10.5.4</FileVersion>
<PackageVersion>10.6.0</PackageVersion>
<AssemblyVersion>10.6.0</AssemblyVersion>
<FileVersion>10.6.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>

View File

@ -448,6 +448,9 @@ namespace CryptoExchange.Net.OrderBook
DateTime? serverDataTime = null,
DateTime? localDataTime = null)
{
if (Status == OrderBookStatus.Disposed || Status == OrderBookStatus.Disconnected)
throw new InvalidOperationException("Trying to set snapshot while book is not working");
_processQueue.Enqueue(
new OrderBookSnapshot
{
@ -475,6 +478,9 @@ namespace CryptoExchange.Net.OrderBook
DateTime? serverDataTime = null,
DateTime? localDataTime = null)
{
if (Status == OrderBookStatus.Disposed || Status == OrderBookStatus.Disconnected)
throw new InvalidOperationException("Trying to update order book while book is not working");
_processQueue.Enqueue(
new OrderBookUpdate
{
@ -505,6 +511,9 @@ namespace CryptoExchange.Net.OrderBook
DateTime? serverDataTime = null,
DateTime? localDataTime = null)
{
if (Status == OrderBookStatus.Disposed || Status == OrderBookStatus.Disconnected)
throw new InvalidOperationException("Trying to update order book while book is not working");
_processQueue.Enqueue(
new OrderBookUpdate
{
@ -531,6 +540,9 @@ namespace CryptoExchange.Net.OrderBook
DateTime? serverDataTime = null,
DateTime? localDataTime = null)
{
if (Status == OrderBookStatus.Disposed || Status == OrderBookStatus.Disconnected)
throw new InvalidOperationException("Trying to update order book while book is not working");
var highest = Math.Max(bids.Any() ? bids.Max(b => b.Sequence) : 0, asks.Any() ? asks.Max(a => a.Sequence) : 0);
var lowest = Math.Min(bids.Any() ? bids.Min(b => b.Sequence) : long.MaxValue, asks.Any() ? asks.Min(a => a.Sequence) : long.MaxValue);
@ -554,6 +566,9 @@ namespace CryptoExchange.Net.OrderBook
/// <param name="sequenceNumber">The sequence number of the message if it's a separate message with separate number</param>
protected void AddChecksum(int checksum, long? sequenceNumber = null)
{
if (Status == OrderBookStatus.Disposed || Status == OrderBookStatus.Disconnected)
throw new InvalidOperationException("Trying to add checksum while book is not working");
_processQueue.Enqueue(new OrderBookChecksum() { Checksum = checksum, SequenceNumber = sequenceNumber });
_queueEvent.Set();
}

View File

@ -1103,6 +1103,12 @@ namespace CryptoExchange.Net.Sockets.Default
return CallResult.SuccessResult;
}
/// <summary>
/// Try to subscribe a new subscription by sending the subscribe query and wait for the result as needed
/// </summary>
/// <param name="subscription">The subscription</param>
/// <param name="newSubscription">Whether this is a new subscription, or an existing subscription (resubscribing on reconnected socket)</param>
/// <param name="subCancelToken">Cancellation token</param>
protected internal async Task<CallResult> TrySubscribeAsync(Subscription subscription, bool newSubscription, CancellationToken subCancelToken)
{
subscription.ConnectionInvocations = 0;

View File

@ -65,5 +65,18 @@ namespace CryptoExchange.Net.Trackers.UserData.Interfaces
/// </summary>
/// <returns></returns>
Task StopAsync();
/// <summary>
/// Add symbols to the list of symbols for which data is being tracked
/// </summary>
/// <param name="symbols">Symbols to add</param>
void AddTrackedSymbolsAsync(IEnumerable<SharedSymbol> symbols);
/// <summary>
/// Remove a symbol from the list of symbols for which data is being tracked.
/// Note that the symbol will be added again if new data for that symbol is received, unless the OnlyTrackProvidedSymbols option has been set to true.
/// </summary>
/// <param name="symbol">Symbol to remove</param>
void RemoveTrackedSymbolAsync(SharedSymbol symbol);
}
}

View File

@ -61,5 +61,18 @@ namespace CryptoExchange.Net.Trackers.UserData.Interfaces
/// </summary>
/// <returns></returns>
Task StopAsync();
/// <summary>
/// Add symbols to the list of symbols for which data is being tracked
/// </summary>
/// <param name="symbols">Symbols to add</param>
void AddTrackedSymbolsAsync(IEnumerable<SharedSymbol> symbols);
/// <summary>
/// Remove a symbol from the list of symbols for which data is being tracked.
/// Note that the symbol will be added again if new data for that symbol is received, unless the OnlyTrackProvidedSymbols option has been set to true.
/// </summary>
/// <param name="symbol">Symbol to remove</param>
void RemoveTrackedSymbolAsync(SharedSymbol symbol);
}
}

View File

@ -47,6 +47,20 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
_requiresSymbolParameterOpenOrders = restClient.GetOpenFuturesOrdersOptions.RequiredOptionalParameters.Any(x => x.Name == "Symbol");
}
internal void ClearDataForSymbol(SharedSymbol symbol)
{
foreach (var order in _store)
{
if (order.Value.SharedSymbol!.TradingMode == symbol.TradingMode
&& order.Value.SharedSymbol.BaseAsset == symbol.BaseAsset
&& order.Value.SharedSymbol.QuoteAsset == symbol.QuoteAsset
&& order.Value.SharedSymbol.DeliverTime == symbol.DeliverTime)
{
_store.TryRemove(order.Key, out _);
}
}
}
/// <inheritdoc />
protected override bool Update(SharedFuturesOrder existingItem, SharedFuturesOrder updateItem)
{
@ -376,8 +390,8 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
if (DateTime.UtcNow - fromTime < TimeSpan.FromSeconds(1))
{
// Set it to at least a seconds in the past to prevent issues
fromTime = DateTime.UtcNow.AddSeconds(-1);
// Set it to at least 5 seconds in the past to prevent issues when local time isn't in sync
fromTime = DateTime.UtcNow.AddSeconds(-5);
}
_logger.LogTrace("{DataType}.{Symbol} UserDataTracker poll startTime filter based on {Source}: {Time:yyyy-MM-dd HH:mm:ss.fff}",

View File

@ -43,6 +43,20 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
_exchangeParameters = exchangeParameters;
}
internal void ClearDataForSymbol(SharedSymbol symbol)
{
foreach (var order in _store)
{
if (order.Value.SharedSymbol!.TradingMode == symbol.TradingMode
&& order.Value.SharedSymbol.BaseAsset == symbol.BaseAsset
&& order.Value.SharedSymbol.QuoteAsset == symbol.QuoteAsset
&& order.Value.SharedSymbol.DeliverTime == symbol.DeliverTime)
{
_store.TryRemove(order.Key, out _);
}
}
}
/// <inheritdoc />
protected override string GetKey(SharedUserTrade item) => item.Id;
/// <inheritdoc />
@ -120,8 +134,8 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
var now = DateTime.UtcNow;
if (now - fromTime < TimeSpan.FromSeconds(1))
{
// Set it to at least a seconds in the past to prevent issues
fromTime = now.AddSeconds(-1);
// Set it to at least 5 seconds in the past to prevent issues when local time isn't in sync
fromTime = DateTime.UtcNow.AddSeconds(-5);
}
_logger.LogTrace("{DataType} UserDataTracker poll startTime filter based on {Source}: {Time:yyyy-MM-dd HH:mm:ss.fff}", DataType, source, fromTime);

View File

@ -119,7 +119,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
{
toRemove ??= new List<SharedPosition>();
toRemove.Add(item);
_logger.LogWarning("Ignoring {DataType} update for {Key}, no SharedSymbol set", DataType, GetKey(item));
_logger.LogTrace("Ignoring {DataType} update for {Key}, no SharedSymbol set", DataType, item.Symbol);
}
else if (!_symbolTracker.ShouldProcess(symbolModel.SharedSymbol))
{

View File

@ -47,6 +47,19 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
_requiresSymbolParameterOpenOrders = restClient.GetOpenSpotOrdersOptions.RequiredOptionalParameters.Any(x => x.Name == "Symbol");
}
internal void ClearDataForSymbol(SharedSymbol symbol)
{
foreach(var order in _store)
{
if (order.Value.SharedSymbol!.TradingMode == symbol.TradingMode
&& order.Value.SharedSymbol.BaseAsset == symbol.BaseAsset
&& order.Value.SharedSymbol.QuoteAsset == symbol.QuoteAsset)
{
_store.TryRemove(order.Key, out _);
}
}
}
/// <inheritdoc />
protected override bool Update(SharedSpotOrder existingItem, SharedSpotOrder updateItem)
{
@ -388,8 +401,8 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
if (DateTime.UtcNow - fromTime < TimeSpan.FromSeconds(1))
{
// Set it to at least a seconds in the past to prevent issues
fromTime = DateTime.UtcNow.AddSeconds(-1);
// Set it to at least 5 seconds in the past to prevent issues when local time isn't in sync
fromTime = DateTime.UtcNow.AddSeconds(-5);
}
_logger.LogTrace("{DataType}.{Symbol} UserDataTracker poll startTime filter based on {Source}: {Time:yyyy-MM-dd HH:mm:ss.fff}",

View File

@ -43,6 +43,19 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
_exchangeParameters = exchangeParameters;
}
internal void ClearDataForSymbol(SharedSymbol symbol)
{
foreach (var trade in _store)
{
if (trade.Value.SharedSymbol!.TradingMode == symbol.TradingMode
&& trade.Value.SharedSymbol.BaseAsset == symbol.BaseAsset
&& trade.Value.SharedSymbol.QuoteAsset == symbol.QuoteAsset)
{
_store.TryRemove(trade.Key, out _);
}
}
}
/// <inheritdoc />
protected override string GetKey(SharedUserTrade item) => item.Id;
/// <inheritdoc />
@ -117,8 +130,8 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
if (DateTime.UtcNow - fromTime < TimeSpan.FromSeconds(1))
{
// Set it to at least a seconds in the past to prevent issues
fromTime = DateTime.UtcNow.AddSeconds(-1);
// Set it to at least 5 seconds in the past to prevent issues when local time isn't in sync
fromTime = DateTime.UtcNow.AddSeconds(-5);
}
_logger.LogTrace("{DataType} UserDataTracker poll startTime filter based on {Source}: {Time:yyyy-MM-dd HH:mm:ss.fff}", DataType, source, fromTime);

View File

@ -7,6 +7,9 @@ using System.Text;
namespace CryptoExchange.Net.Trackers.UserData.Objects
{
/// <summary>
/// Tracker for symbols used in UserDataTracker
/// </summary>
public class UserDataSymbolTracker
{
private readonly ILogger _logger;
@ -14,6 +17,9 @@ namespace CryptoExchange.Net.Trackers.UserData.Objects
private readonly bool _onlyTrackProvidedSymbols;
private readonly object _symbolLock = new object();
/// <summary>
/// ctor
/// </summary>
public UserDataSymbolTracker(ILogger logger, UserDataTrackerConfig config)
{
_logger = logger;
@ -21,12 +27,19 @@ namespace CryptoExchange.Net.Trackers.UserData.Objects
_onlyTrackProvidedSymbols = config.OnlyTrackProvidedSymbols;
}
/// <summary>
/// Get currently tracked symbols
/// </summary>
/// <returns></returns>
public IEnumerable<SharedSymbol> GetTrackedSymbols()
{
lock (_symbolLock)
return _trackedSymbols.ToList();
}
/// <summary>
/// Check whether a symbol is in the tracked symbols list and should be processed
/// </summary>
public bool ShouldProcess(SharedSymbol symbol)
{
if (!_onlyTrackProvidedSymbols)
@ -38,10 +51,9 @@ namespace CryptoExchange.Net.Trackers.UserData.Objects
/// <summary>
/// Update the tracked symbol list with potential new symbols
/// </summary>
/// <param name="symbols"></param>
public void UpdateTrackedSymbols(IEnumerable<SharedSymbol> symbols)
public void UpdateTrackedSymbols(IEnumerable<SharedSymbol> symbols, bool addByUser = false)
{
if (_onlyTrackProvidedSymbols)
if (!addByUser && _onlyTrackProvidedSymbols)
return;
lock (_symbolLock)
@ -56,5 +68,18 @@ namespace CryptoExchange.Net.Trackers.UserData.Objects
}
}
}
/// <summary>
/// Remove a symbol from the list
/// </summary>
public void RemoveTrackedSymbol(SharedSymbol symbol)
{
lock (_symbolLock)
{
var symbolToRemove = _trackedSymbols.SingleOrDefault(x => x.TradingMode == symbol.TradingMode && x.BaseAsset == symbol.BaseAsset && x.QuoteAsset == symbol.QuoteAsset);
if (symbolToRemove != null)
_trackedSymbols.Remove(symbolToRemove);
}
}
}
}

View File

@ -154,5 +154,30 @@ namespace CryptoExchange.Net.Trackers.UserData
interval = result ? TimeSpan.FromMinutes(30) : TimeSpan.FromMinutes(5);
}
}
/// <summary>
/// Add symbols to the list of symbols for which data is being tracked
/// </summary>
/// <param name="symbols">Symbols to add</param>
public void AddTrackedSymbolsAsync(IEnumerable<SharedSymbol> symbols)
{
if (symbols.Any(x => x.TradingMode == TradingMode.Spot))
throw new ArgumentException("Spot symbol not allowed in futures tracker", nameof(symbols));
SymbolTracker.UpdateTrackedSymbols(symbols, true);
}
/// <summary>
/// Remove a symbol from the list of symbols for which data is being tracked.
/// Note that the symbol will be added again if new data for that symbol is received, unless the OnlyTrackProvidedSymbols option has been set to true.
/// </summary>
/// <param name="symbol">Symbol to remove</param>
public void RemoveTrackedSymbolAsync(SharedSymbol symbol)
{
SymbolTracker.RemoveTrackedSymbol(symbol);
((FuturesOrderTracker)Orders).ClearDataForSymbol(symbol);
((FuturesUserTradeTracker?)Trades)?.ClearDataForSymbol(symbol);
}
}
}

View File

@ -127,5 +127,30 @@ namespace CryptoExchange.Net.Trackers.UserData
interval = result ? TimeSpan.FromMinutes(30) : TimeSpan.FromMinutes(5);
}
}
/// <summary>
/// Add symbols to the list of symbols for which data is being tracked
/// </summary>
/// <param name="symbols">Symbols to add</param>
public void AddTrackedSymbolsAsync(IEnumerable<SharedSymbol> symbols)
{
if (symbols.Any(x => x.TradingMode != TradingMode.Spot))
throw new ArgumentException("Futures symbol not allowed in spot tracker", nameof(symbols));
SymbolTracker.UpdateTrackedSymbols(symbols, true);
}
/// <summary>
/// Remove a symbol from the list of symbols for which data is being tracked. Also removes stored data for that symbol.
/// Note that the symbol will be added again if new data for that symbol is received, unless the OnlyTrackProvidedSymbols option has been set to true.
/// </summary>
/// <param name="symbol">Symbol to remove</param>
public void RemoveTrackedSymbolAsync(SharedSymbol symbol)
{
SymbolTracker.RemoveTrackedSymbol(symbol);
((SpotOrderTracker)Orders).ClearDataForSymbol(symbol);
((SpotUserTradeTracker?)Trades)?.ClearDataForSymbol(symbol);
}
}
}

View File

@ -67,6 +67,12 @@ 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 10.6.0 - 16 Feb 2026
* Updated symbol tracking logic on UserDataTracker, now is per UserDataTracker instead of per topic
* Added check for startTime filter for polling being to close to current time which can cause issues if time isn't in sync with server
* Added AddTrackedSymbolsAsync and RemoveTrackedSymbolAsync methods to UserDataTracker
* Added check SymbolOrderBook is still alive when trying to add updates to prevent unnoticed growing in the background when subscription isn't closed while book is
* Version 10.5.4 - 12 Feb 2026
* Fixed type check ExchangeParameters GetValue
* Fixed bug in polling time filter for UserDataTracker item