From f39d9f7cfb7be6deae10b01e899b0ea33c6e3179 Mon Sep 17 00:00:00 2001 From: Jkorf Date: Thu, 12 Feb 2026 11:43:20 +0100 Subject: [PATCH 1/6] Updated to version 10.5.4 --- CryptoExchange.Net/CryptoExchange.Net.csproj | 6 +++--- README.md | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CryptoExchange.Net/CryptoExchange.Net.csproj b/CryptoExchange.Net/CryptoExchange.Net.csproj index 5da4874..8e94af4 100644 --- a/CryptoExchange.Net/CryptoExchange.Net.csproj +++ b/CryptoExchange.Net/CryptoExchange.Net.csproj @@ -6,9 +6,9 @@ CryptoExchange.Net JKorf 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. - 10.5.3 - 10.5.3 - 10.5.3 + 10.5.4 + 10.5.4 + 10.5.4 false 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 git diff --git a/README.md b/README.md index 3009521..bb3742b 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,10 @@ 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.5.4 - 12 Feb 2026 + * Fixed type check ExchangeParameters GetValue + * Fixed bug in polling time filter for UserDataTracker item + * Version 10.5.3 - 11 Feb 2026 * Fixed orders getting incorrectly set to canceled state for UserDataTracker spot and futures orders * Added check EnumConverter to detect undefined int value parsing From 1471a4733fcef0d8956f44e60316cd69b9bd9005 Mon Sep 17 00:00:00 2001 From: Jkorf Date: Fri, 13 Feb 2026 10:12:43 +0100 Subject: [PATCH 2/6] Updated symbol tracking logic on UserDataTracker, added check for startTime filter for polling being to close to current time --- .../UserData/Interfaces/IUserDataTracker.cs | 7 --- .../Interfaces/IUserFuturesDataTracker.cs | 8 +++ .../Interfaces/IUserSpotDataTracker.cs | 8 +++ .../UserData/ItemTrackers/BalanceTracker.cs | 3 +- .../ItemTrackers/FuturesOrderTracker.cs | 16 +++-- .../ItemTrackers/FuturesUserTradeTracker.cs | 12 +++- .../UserData/ItemTrackers/PositionTracker.cs | 10 ++-- .../UserData/ItemTrackers/SpotOrderTracker.cs | 16 +++-- .../ItemTrackers/SpotUserTradeTracker.cs | 11 +++- .../ItemTrackers/UserDataItemTracker.cs | 57 +++++------------- .../UserData/Objects/UserDataSymbolTracker.cs | 60 +++++++++++++++++++ .../Trackers/UserData/UserDataTracker.cs | 12 ++++ .../UserData/UserFuturesDataTracker.cs | 8 +-- .../Trackers/UserData/UserSpotDataTracker.cs | 6 +- 14 files changed, 160 insertions(+), 74 deletions(-) create mode 100644 CryptoExchange.Net/Trackers/UserData/Objects/UserDataSymbolTracker.cs diff --git a/CryptoExchange.Net/Trackers/UserData/Interfaces/IUserDataTracker.cs b/CryptoExchange.Net/Trackers/UserData/Interfaces/IUserDataTracker.cs index 2cf7d00..e98282c 100644 --- a/CryptoExchange.Net/Trackers/UserData/Interfaces/IUserDataTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/Interfaces/IUserDataTracker.cs @@ -16,13 +16,6 @@ namespace CryptoExchange.Net.Trackers.UserData.Interfaces /// bool Connected { get; } - /// - /// Currently tracked symbols. Data for these symbols will be requested when polling. - /// Websocket updates will be available for all symbols regardless. - /// When new data is received for a symbol which is not yet being tracked it will be added to this list and polled in the future unless the `OnlyTrackProvidedSymbols` option is set in the configuration. - /// - IEnumerable TrackedSymbols { get; } - /// /// On connection status change. Might trigger multiple times with the same status depending on the underlying subscriptions. /// diff --git a/CryptoExchange.Net/Trackers/UserData/Interfaces/IUserFuturesDataTracker.cs b/CryptoExchange.Net/Trackers/UserData/Interfaces/IUserFuturesDataTracker.cs index 3c429eb..b762a05 100644 --- a/CryptoExchange.Net/Trackers/UserData/Interfaces/IUserFuturesDataTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/Interfaces/IUserFuturesDataTracker.cs @@ -2,6 +2,7 @@ using CryptoExchange.Net.SharedApis; using CryptoExchange.Net.Trackers.UserData.Objects; using System; +using System.Collections.Generic; using System.Threading.Tasks; namespace CryptoExchange.Net.Trackers.UserData.Interfaces @@ -26,6 +27,13 @@ namespace CryptoExchange.Net.Trackers.UserData.Interfaces /// public string Exchange { get; } + /// + /// Currently tracked symbols. Data for these symbols will be requested when polling. + /// Websocket updates will be available for all symbols regardless. + /// When new data is received for a symbol which is not yet being tracked it will be added to this list and polled in the future unless the `OnlyTrackProvidedSymbols` option is set in the configuration. + /// + IEnumerable TrackedSymbols { get; } + /// /// Balances tracker /// diff --git a/CryptoExchange.Net/Trackers/UserData/Interfaces/IUserSpotDataTracker.cs b/CryptoExchange.Net/Trackers/UserData/Interfaces/IUserSpotDataTracker.cs index 35612d0..6bc0242 100644 --- a/CryptoExchange.Net/Trackers/UserData/Interfaces/IUserSpotDataTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/Interfaces/IUserSpotDataTracker.cs @@ -2,6 +2,7 @@ using CryptoExchange.Net.SharedApis; using CryptoExchange.Net.Trackers.UserData.Objects; using System; +using System.Collections.Generic; using System.Threading.Tasks; namespace CryptoExchange.Net.Trackers.UserData.Interfaces @@ -26,6 +27,13 @@ namespace CryptoExchange.Net.Trackers.UserData.Interfaces /// public string Exchange { get; } + /// + /// Currently tracked symbols. Data for these symbols will be requested when polling. + /// Websocket updates will be available for all symbols regardless. + /// When new data is received for a symbol which is not yet being tracked it will be added to this list and polled in the future unless the `OnlyTrackProvidedSymbols` option is set in the configuration. + /// + IEnumerable TrackedSymbols { get; } + /// /// Balances tracker /// diff --git a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/BalanceTracker.cs b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/BalanceTracker.cs index 66f24fd..3895b87 100644 --- a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/BalanceTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/BalanceTracker.cs @@ -22,12 +22,13 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers /// public BalanceTracker( ILogger logger, + UserDataSymbolTracker symbolTracker, IBalanceRestClient restClient, IBalanceSocketClient? socketClient, SharedAccountType accountType, TrackerItemConfig config, ExchangeParameters? exchangeParameters = null - ) : base(logger, UserDataType.Balances, restClient.Exchange, config, false, null) + ) : base(logger, symbolTracker, UserDataType.Balances, restClient.Exchange, config) { if (_socketClient == null) config = config with { PollIntervalConnected = config.PollIntervalDisconnected }; diff --git a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/FuturesOrderTracker.cs b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/FuturesOrderTracker.cs index 3c52427..9cfdb97 100644 --- a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/FuturesOrderTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/FuturesOrderTracker.cs @@ -28,13 +28,14 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers /// public FuturesOrderTracker( ILogger logger, + UserDataSymbolTracker symbolTracker, IFuturesOrderRestClient restClient, IFuturesOrderSocketClient? socketClient, TrackerItemConfig config, IEnumerable symbols, bool onlyTrackProvidedSymbols, ExchangeParameters? exchangeParameters = null - ) : base(logger, UserDataType.Orders, restClient.Exchange, config, onlyTrackProvidedSymbols, symbols) + ) : base(logger, symbolTracker, UserDataType.Orders, restClient.Exchange, config) { if (_socketClient == null) config = config with { PollIntervalConnected = config.PollIntervalDisconnected }; @@ -234,7 +235,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers } else { - foreach (var symbol in _symbols.ToList()) + foreach (var symbol in _symbolTracker.GetTrackedSymbols()) { var openOrdersResult = await _restClient.GetOpenFuturesOrdersAsync(new GetOpenOrdersRequest(symbol, exchangeParameters: _exchangeParameters)).ConfigureAwait(false); if (!openOrdersResult.Success) @@ -272,7 +273,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers } var updatedPollTime = DateTime.UtcNow; - foreach (var symbol in _symbols.ToList()) + foreach (var symbol in _symbolTracker.GetTrackedSymbols()) { DateTime? fromTimeOrders = GetClosedOrdersRequestStartTime(symbol); @@ -373,7 +374,14 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers source = "StartTime"; } - _logger.LogTrace("{DataType} UserDataTracker poll startTime filter based on {Source}: {Time:yyyy-MM-dd HH:mm:ss.fff}", DataType, source, fromTime); + 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); + } + + _logger.LogTrace("{DataType}.{Symbol} UserDataTracker poll startTime filter based on {Source}: {Time:yyyy-MM-dd HH:mm:ss.fff}", + DataType, $"{symbol.BaseAsset}/{symbol.QuoteAsset}", source, fromTime); return fromTime!.Value; } } diff --git a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/FuturesUserTradeTracker.cs b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/FuturesUserTradeTracker.cs index a0f8d8f..b5028ae 100644 --- a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/FuturesUserTradeTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/FuturesUserTradeTracker.cs @@ -26,13 +26,14 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers /// public FuturesUserTradeTracker( ILogger logger, + UserDataSymbolTracker symbolTracker, IFuturesOrderRestClient restClient, IUserTradeSocketClient? socketClient, TrackerItemConfig config, IEnumerable symbols, bool onlyTrackProvidedSymbols, ExchangeParameters? exchangeParameters = null - ) : base(logger, UserDataType.Trades, restClient.Exchange, config, onlyTrackProvidedSymbols, symbols) + ) : base(logger, symbolTracker, UserDataType.Trades, restClient.Exchange, config) { if (_socketClient == null) config = config with { PollIntervalConnected = config.PollIntervalDisconnected }; @@ -57,7 +58,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers var anyError = false; var fromTimeTrades = GetTradesRequestStartTime(); var updatedPollTime = DateTime.UtcNow; - foreach (var symbol in _symbols) + foreach (var symbol in _symbolTracker.GetTrackedSymbols()) { var tradesResult = await _restClient.GetFuturesUserTradesAsync(new GetUserTradesRequest(symbol, startTime: fromTimeTrades, exchangeParameters: _exchangeParameters)).ConfigureAwait(false); if (!tradesResult.Success) @@ -116,6 +117,13 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers source = "StartTime"; } + 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); + } + _logger.LogTrace("{DataType} UserDataTracker poll startTime filter based on {Source}: {Time:yyyy-MM-dd HH:mm:ss.fff}", DataType, source, fromTime); return fromTime!.Value; } diff --git a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/PositionTracker.cs b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/PositionTracker.cs index 1701c69..eda6185 100644 --- a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/PositionTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/PositionTracker.cs @@ -29,6 +29,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers /// public PositionTracker( ILogger logger, + UserDataSymbolTracker symbolTracker, IFuturesOrderRestClient restClient, IPositionSocketClient? socketClient, TrackerItemConfig config, @@ -36,7 +37,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers bool onlyTrackProvidedSymbols, bool websocketPositionUpdatesAreFullSnapshots, ExchangeParameters? exchangeParameters = null - ) : base(logger, UserDataType.Positions, restClient.Exchange, config, onlyTrackProvidedSymbols, symbols) + ) : base(logger, symbolTracker, UserDataType.Positions, restClient.Exchange, config) { if (_socketClient == null) config = config with { PollIntervalConnected = config.PollIntervalDisconnected }; @@ -118,9 +119,9 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers { toRemove ??= new List(); toRemove.Add(item); + _logger.LogWarning("Ignoring {DataType} update for {Key}, no SharedSymbol set", DataType, GetKey(item)); } - else if (_onlyTrackProvidedSymbols - && !_symbols.Any(y => y.TradingMode == symbolModel.SharedSymbol!.TradingMode && y.BaseAsset == symbolModel.SharedSymbol.BaseAsset && y.QuoteAsset == symbolModel.SharedSymbol.QuoteAsset)) + else if (!_symbolTracker.ShouldProcess(symbolModel.SharedSymbol)) { toRemove ??= new List(); toRemove.Add(item); @@ -131,8 +132,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers if (toRemove != null) @event = @event.Except(toRemove).ToArray(); - if (!_onlyTrackProvidedSymbols) - UpdateSymbolsList(@event.Where(x => x.PositionSize > 0).OfType().Select(x => x.SharedSymbol!)); + _symbolTracker.UpdateTrackedSymbols(@event.Where(x => x.PositionSize > 0).OfType().Select(x => x.SharedSymbol!)); // Update local store diff --git a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/SpotOrderTracker.cs b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/SpotOrderTracker.cs index e16254f..c49090b 100644 --- a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/SpotOrderTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/SpotOrderTracker.cs @@ -28,13 +28,14 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers /// public SpotOrderTracker( ILogger logger, + UserDataSymbolTracker symbolTracker, ISpotOrderRestClient restClient, ISpotOrderSocketClient? socketClient, TrackerItemConfig config, IEnumerable symbols, bool onlyTrackProvidedSymbols, ExchangeParameters? exchangeParameters = null - ) : base(logger, UserDataType.Orders, restClient.Exchange, config, onlyTrackProvidedSymbols, symbols) + ) : base(logger, symbolTracker, UserDataType.Orders, restClient.Exchange, config) { if (_socketClient == null) config = config with { PollIntervalConnected = config.PollIntervalDisconnected }; @@ -245,7 +246,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers } else { - foreach (var symbol in _symbols.ToList()) + foreach (var symbol in _symbolTracker.GetTrackedSymbols()) { var openOrdersResult = await _restClient.GetOpenSpotOrdersAsync(new GetOpenOrdersRequest(symbol, exchangeParameters: _exchangeParameters)).ConfigureAwait(false); if (!openOrdersResult.Success) @@ -283,7 +284,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers } var updatedPollTime = DateTime.UtcNow; - foreach (var symbol in _symbols.ToList()) + foreach (var symbol in _symbolTracker.GetTrackedSymbols()) { DateTime? fromTimeOrders = GetClosedOrdersRequestStartTime(symbol); @@ -385,7 +386,14 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers source = "StartTime"; } - _logger.LogTrace("{DataType} UserDataTracker poll startTime filter based on {Source}: {Time:yyyy-MM-dd HH:mm:ss.fff}", DataType, source, fromTime); + 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); + } + + _logger.LogTrace("{DataType}.{Symbol} UserDataTracker poll startTime filter based on {Source}: {Time:yyyy-MM-dd HH:mm:ss.fff}", + DataType, $"{symbol.BaseAsset}/{symbol.QuoteAsset}", source, fromTime); return fromTime!.Value; } } diff --git a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/SpotUserTradeTracker.cs b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/SpotUserTradeTracker.cs index cf39fb8..2824574 100644 --- a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/SpotUserTradeTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/SpotUserTradeTracker.cs @@ -26,13 +26,14 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers /// public SpotUserTradeTracker( ILogger logger, + UserDataSymbolTracker symbolTracker, ISpotOrderRestClient restClient, IUserTradeSocketClient? socketClient, TrackerItemConfig config, IEnumerable symbols, bool onlyTrackProvidedSymbols, ExchangeParameters? exchangeParameters = null - ) : base(logger, UserDataType.Trades, restClient.Exchange, config, onlyTrackProvidedSymbols, symbols) + ) : base(logger, symbolTracker, UserDataType.Trades, restClient.Exchange, config) { if (_socketClient == null) config = config with { PollIntervalConnected = config.PollIntervalDisconnected }; @@ -57,7 +58,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers var anyError = false; var fromTimeTrades = GetTradesRequestStartTime(); var updatedPollTime = DateTime.UtcNow; - foreach (var symbol in _symbols) + foreach (var symbol in _symbolTracker.GetTrackedSymbols()) { var tradesResult = await _restClient.GetSpotUserTradesAsync(new GetUserTradesRequest(symbol, startTime: fromTimeTrades, exchangeParameters: _exchangeParameters)).ConfigureAwait(false); if (!tradesResult.Success) @@ -114,6 +115,12 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers source = "StartTime"; } + 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); + } + _logger.LogTrace("{DataType} UserDataTracker poll startTime filter based on {Source}: {Time:yyyy-MM-dd HH:mm:ss.fff}", DataType, source, fromTime); return fromTime!.Value; } diff --git a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/UserDataItemTracker.cs b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/UserDataItemTracker.cs index aa9ba0d..77f8de1 100644 --- a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/UserDataItemTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/UserDataItemTracker.cs @@ -203,21 +203,14 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers /// protected ConcurrentDictionary _store = new ConcurrentDictionary(StringComparer.InvariantCultureIgnoreCase); /// - /// Tracked symbols list - /// - protected readonly List _symbols; - /// - /// Symbol lock - /// - protected object _symbolLock = new object(); - /// - /// Only track provided symbols setting - /// - protected bool _onlyTrackProvidedSymbols; - /// /// Is SharedSymbol model /// protected bool _isSymbolModel; + /// + /// Symbol tracker + /// + + protected readonly UserDataSymbolTracker _symbolTracker; /// public T[] Values @@ -240,22 +233,23 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers /// public event Func, Task>? OnUpdate; - /// - public IEnumerable TrackedSymbols => _symbols; /// /// ctor /// - public UserDataItemTracker(ILogger logger, UserDataType dataType, string exchange, TrackerItemConfig config, bool onlyTrackProvidedSymbols, IEnumerable? symbols) : base(logger, dataType, exchange) + public UserDataItemTracker( + ILogger logger, + UserDataSymbolTracker symbolTracker, + UserDataType dataType, + string exchange, + TrackerItemConfig config) : base(logger, dataType, exchange) { - _onlyTrackProvidedSymbols = onlyTrackProvidedSymbols; - _symbols = symbols?.ToList() ?? []; - _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; } /// @@ -334,26 +328,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers /// Get the age of an item /// protected virtual TimeSpan GetAge(DateTime time, T item) => TimeSpan.Zero; - - /// - /// Update the tracked symbol list with potential new symbols - /// - /// - protected void UpdateSymbolsList(IEnumerable symbols) - { - lock (_symbolLock) - { - foreach (var symbol in symbols.Distinct()) - { - if (!_symbols.Any(x => x.TradingMode == symbol.TradingMode && x.BaseAsset == symbol.BaseAsset && x.QuoteAsset == symbol.QuoteAsset)) - { - _symbols.Add(symbol); - _logger.LogDebug("Adding {BaseAsset}/{QuoteAsset} to symbol tracking list", symbol.BaseAsset, symbol.QuoteAsset); - } - } - } - } - + /// /// Handle an update /// @@ -374,8 +349,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers toRemove.Add(item); _logger.LogWarning("Ignoring {DataType} update for {Key}, no SharedSymbol set", DataType, GetKey(item)); } - else if (_onlyTrackProvidedSymbols - && !_symbols.Any(y => y.TradingMode == symbolModel.SharedSymbol!.TradingMode && y.BaseAsset == symbolModel.SharedSymbol.BaseAsset && y.QuoteAsset == symbolModel.SharedSymbol.QuoteAsset)) + else if (!_symbolTracker.ShouldProcess(symbolModel.SharedSymbol)) { toRemove ??= new List(); toRemove.Add(item); @@ -386,8 +360,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers if (toRemove != null) @event = @event.Except(toRemove).ToArray(); - if (!_onlyTrackProvidedSymbols) - UpdateSymbolsList(@event.OfType().Select(x => x.SharedSymbol!)); + _symbolTracker.UpdateTrackedSymbols(@event.OfType().Select(x => x.SharedSymbol!)); } // Update local store diff --git a/CryptoExchange.Net/Trackers/UserData/Objects/UserDataSymbolTracker.cs b/CryptoExchange.Net/Trackers/UserData/Objects/UserDataSymbolTracker.cs new file mode 100644 index 0000000..adfdf59 --- /dev/null +++ b/CryptoExchange.Net/Trackers/UserData/Objects/UserDataSymbolTracker.cs @@ -0,0 +1,60 @@ +using CryptoExchange.Net.SharedApis; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace CryptoExchange.Net.Trackers.UserData.Objects +{ + public class UserDataSymbolTracker + { + private readonly ILogger _logger; + private readonly List _trackedSymbols; + private readonly bool _onlyTrackProvidedSymbols; + private readonly object _symbolLock = new object(); + + public UserDataSymbolTracker(ILogger logger, UserDataTrackerConfig config) + { + _logger = logger; + _trackedSymbols = config.TrackedSymbols?.ToList() ?? []; + _onlyTrackProvidedSymbols = config.OnlyTrackProvidedSymbols; + } + + public IEnumerable GetTrackedSymbols() + { + lock (_symbolLock) + return _trackedSymbols.ToList(); + } + + public bool ShouldProcess(SharedSymbol symbol) + { + if (!_onlyTrackProvidedSymbols) + return true; + + return _trackedSymbols.Any(y => y.TradingMode == symbol!.TradingMode && y.BaseAsset == symbol.BaseAsset && y.QuoteAsset == symbol.QuoteAsset); + } + + /// + /// Update the tracked symbol list with potential new symbols + /// + /// + public void UpdateTrackedSymbols(IEnumerable symbols) + { + if (_onlyTrackProvidedSymbols) + return; + + lock (_symbolLock) + { + foreach (var symbol in symbols.Distinct()) + { + if (!_trackedSymbols.Any(x => x.TradingMode == symbol.TradingMode && x.BaseAsset == symbol.BaseAsset && x.QuoteAsset == symbol.QuoteAsset)) + { + _trackedSymbols.Add(symbol); + _logger.LogDebug("Adding {TradingMode}.{BaseAsset}/{QuoteAsset} to symbol tracking list", symbol.TradingMode, symbol.BaseAsset, symbol.QuoteAsset); + } + } + } + } + } +} diff --git a/CryptoExchange.Net/Trackers/UserData/UserDataTracker.cs b/CryptoExchange.Net/Trackers/UserData/UserDataTracker.cs index 84131e0..b38bcb2 100644 --- a/CryptoExchange.Net/Trackers/UserData/UserDataTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/UserDataTracker.cs @@ -1,4 +1,5 @@ using CryptoExchange.Net.Objects; +using CryptoExchange.Net.SharedApis; using CryptoExchange.Net.Trackers.UserData.ItemTrackers; using CryptoExchange.Net.Trackers.UserData.Objects; using Microsoft.Extensions.Logging; @@ -33,6 +34,11 @@ namespace CryptoExchange.Net.Trackers.UserData /// protected abstract UserDataItemTracker[] DataTrackers { get; } + /// + /// Symbol tracker + /// + protected internal UserDataSymbolTracker SymbolTracker { get; } + /// public string? UserIdentifier { get; } @@ -51,6 +57,11 @@ namespace CryptoExchange.Net.Trackers.UserData /// public bool Connected => DataTrackers.All(x => x.Connected); + /// + /// Currently tracked symbols + /// + public IEnumerable TrackedSymbols => SymbolTracker.GetTrackedSymbols(); + /// /// ctor /// @@ -65,6 +76,7 @@ namespace CryptoExchange.Net.Trackers.UserData _logger = logger; + SymbolTracker = new UserDataSymbolTracker(logger, config); Exchange = exchange; UserIdentifier = userIdentifier; } diff --git a/CryptoExchange.Net/Trackers/UserData/UserFuturesDataTracker.cs b/CryptoExchange.Net/Trackers/UserData/UserFuturesDataTracker.cs index 355e281..3721447 100644 --- a/CryptoExchange.Net/Trackers/UserData/UserFuturesDataTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/UserFuturesDataTracker.cs @@ -77,21 +77,21 @@ namespace CryptoExchange.Net.Trackers.UserData var trackers = new List(); - var balanceTracker = new BalanceTracker(logger, balanceRestClient, balanceSocketClient, accountType ?? SharedAccountType.PerpetualLinearFutures, config.BalancesConfig, exchangeParameters); + var balanceTracker = new BalanceTracker(logger, SymbolTracker, balanceRestClient, balanceSocketClient, accountType ?? SharedAccountType.PerpetualLinearFutures, config.BalancesConfig, exchangeParameters); Balances = balanceTracker; trackers.Add(balanceTracker); - var orderTracker = new FuturesOrderTracker(logger, futuresOrderRestClient, futuresOrderSocketClient, config.OrdersConfig, config.TrackedSymbols, config.OnlyTrackProvidedSymbols, exchangeParameters); + var orderTracker = new FuturesOrderTracker(logger, SymbolTracker, futuresOrderRestClient, futuresOrderSocketClient, config.OrdersConfig, config.TrackedSymbols, config.OnlyTrackProvidedSymbols, exchangeParameters); Orders = orderTracker; trackers.Add(orderTracker); - var positionTracker = new PositionTracker(logger, futuresOrderRestClient, positionSocketClient, config.PositionConfig, config.TrackedSymbols, config.OnlyTrackProvidedSymbols, WebsocketPositionUpdatesAreFullSnapshots, exchangeParameters); + var positionTracker = new PositionTracker(logger, SymbolTracker, futuresOrderRestClient, positionSocketClient, config.PositionConfig, config.TrackedSymbols, config.OnlyTrackProvidedSymbols, WebsocketPositionUpdatesAreFullSnapshots, exchangeParameters); Positions = positionTracker; trackers.Add(positionTracker); if (config.TrackTrades) { - var tradeTracker = new FuturesUserTradeTracker(logger, futuresOrderRestClient, userTradeSocketClient, config.UserTradesConfig, config.TrackedSymbols, config.OnlyTrackProvidedSymbols, exchangeParameters); + var tradeTracker = new FuturesUserTradeTracker(logger, SymbolTracker, futuresOrderRestClient, userTradeSocketClient, config.UserTradesConfig, config.TrackedSymbols, config.OnlyTrackProvidedSymbols, exchangeParameters); Trades = tradeTracker; trackers.Add(tradeTracker); diff --git a/CryptoExchange.Net/Trackers/UserData/UserSpotDataTracker.cs b/CryptoExchange.Net/Trackers/UserData/UserSpotDataTracker.cs index 6856bca..43796ca 100644 --- a/CryptoExchange.Net/Trackers/UserData/UserSpotDataTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/UserSpotDataTracker.cs @@ -53,17 +53,17 @@ namespace CryptoExchange.Net.Trackers.UserData var trackers = new List(); - var balanceTracker = new BalanceTracker(logger, balanceRestClient, balanceSocketClient, SharedAccountType.Spot, config.BalancesConfig, exchangeParameters); + var balanceTracker = new BalanceTracker(logger, SymbolTracker, balanceRestClient, balanceSocketClient, SharedAccountType.Spot, config.BalancesConfig, exchangeParameters); Balances = balanceTracker; trackers.Add(balanceTracker); - var orderTracker = new SpotOrderTracker(logger, spotOrderRestClient, spotOrderSocketClient, config.OrdersConfig, config.TrackedSymbols, config.OnlyTrackProvidedSymbols, exchangeParameters); + var orderTracker = new SpotOrderTracker(logger, SymbolTracker, spotOrderRestClient, spotOrderSocketClient, config.OrdersConfig, config.TrackedSymbols, config.OnlyTrackProvidedSymbols, exchangeParameters); Orders = orderTracker; trackers.Add(orderTracker); if (config.TrackTrades) { - var tradeTracker = new SpotUserTradeTracker(logger, spotOrderRestClient, userTradeSocketClient, config.UserTradesConfig, config.TrackedSymbols, config.OnlyTrackProvidedSymbols, exchangeParameters); + var tradeTracker = new SpotUserTradeTracker(logger, SymbolTracker, spotOrderRestClient, userTradeSocketClient, config.UserTradesConfig, config.TrackedSymbols, config.OnlyTrackProvidedSymbols, exchangeParameters); Trades = tradeTracker; trackers.Add(tradeTracker); From 7dcf5cd6ea478bd3ff2d3229b0a4c756b0e31c40 Mon Sep 17 00:00:00 2001 From: Jkorf Date: Mon, 16 Feb 2026 09:11:01 +0100 Subject: [PATCH 3/6] Added AddTrackedSymbolsAsync and RemoveTrackedSymbolAsync methods to UserDataTracker, updated minimal fromTime filter in UserDataTracker to 5 seconds --- .../Interfaces/IUserFuturesDataTracker.cs | 13 ++++++++++ .../Interfaces/IUserSpotDataTracker.cs | 13 ++++++++++ .../ItemTrackers/FuturesOrderTracker.cs | 18 +++++++++++-- .../ItemTrackers/FuturesUserTradeTracker.cs | 18 +++++++++++-- .../UserData/ItemTrackers/PositionTracker.cs | 2 +- .../UserData/ItemTrackers/SpotOrderTracker.cs | 17 +++++++++++-- .../ItemTrackers/SpotUserTradeTracker.cs | 17 +++++++++++-- .../UserData/Objects/UserDataSymbolTracker.cs | 18 ++++++++++--- .../UserData/UserFuturesDataTracker.cs | 25 +++++++++++++++++++ .../Trackers/UserData/UserSpotDataTracker.cs | 25 +++++++++++++++++++ 10 files changed, 154 insertions(+), 12 deletions(-) diff --git a/CryptoExchange.Net/Trackers/UserData/Interfaces/IUserFuturesDataTracker.cs b/CryptoExchange.Net/Trackers/UserData/Interfaces/IUserFuturesDataTracker.cs index b762a05..fcc669e 100644 --- a/CryptoExchange.Net/Trackers/UserData/Interfaces/IUserFuturesDataTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/Interfaces/IUserFuturesDataTracker.cs @@ -65,5 +65,18 @@ namespace CryptoExchange.Net.Trackers.UserData.Interfaces /// /// Task StopAsync(); + + /// + /// Add symbols to the list of symbols for which data is being tracked + /// + /// Symbols to add + void AddTrackedSymbolsAsync(IEnumerable symbols); + + /// + /// 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. + /// + /// Symbol to remove + void RemoveTrackedSymbolAsync(SharedSymbol symbol); } } \ No newline at end of file diff --git a/CryptoExchange.Net/Trackers/UserData/Interfaces/IUserSpotDataTracker.cs b/CryptoExchange.Net/Trackers/UserData/Interfaces/IUserSpotDataTracker.cs index 6bc0242..de498bd 100644 --- a/CryptoExchange.Net/Trackers/UserData/Interfaces/IUserSpotDataTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/Interfaces/IUserSpotDataTracker.cs @@ -61,5 +61,18 @@ namespace CryptoExchange.Net.Trackers.UserData.Interfaces /// /// Task StopAsync(); + + /// + /// Add symbols to the list of symbols for which data is being tracked + /// + /// Symbols to add + void AddTrackedSymbolsAsync(IEnumerable symbols); + + /// + /// 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. + /// + /// Symbol to remove + void RemoveTrackedSymbolAsync(SharedSymbol symbol); } } \ No newline at end of file diff --git a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/FuturesOrderTracker.cs b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/FuturesOrderTracker.cs index 9cfdb97..72b443d 100644 --- a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/FuturesOrderTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/FuturesOrderTracker.cs @@ -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 _); + } + } + } + /// 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}", diff --git a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/FuturesUserTradeTracker.cs b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/FuturesUserTradeTracker.cs index b5028ae..208928b 100644 --- a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/FuturesUserTradeTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/FuturesUserTradeTracker.cs @@ -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 _); + } + } + } + /// protected override string GetKey(SharedUserTrade item) => item.Id; /// @@ -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); diff --git a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/PositionTracker.cs b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/PositionTracker.cs index eda6185..092c01f 100644 --- a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/PositionTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/PositionTracker.cs @@ -119,7 +119,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers { toRemove ??= new List(); 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)) { diff --git a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/SpotOrderTracker.cs b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/SpotOrderTracker.cs index c49090b..4264942 100644 --- a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/SpotOrderTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/SpotOrderTracker.cs @@ -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 _); + } + } + } + /// 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}", diff --git a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/SpotUserTradeTracker.cs b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/SpotUserTradeTracker.cs index 2824574..21eeae2 100644 --- a/CryptoExchange.Net/Trackers/UserData/ItemTrackers/SpotUserTradeTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/ItemTrackers/SpotUserTradeTracker.cs @@ -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 _); + } + } + } + /// protected override string GetKey(SharedUserTrade item) => item.Id; /// @@ -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); diff --git a/CryptoExchange.Net/Trackers/UserData/Objects/UserDataSymbolTracker.cs b/CryptoExchange.Net/Trackers/UserData/Objects/UserDataSymbolTracker.cs index adfdf59..36e3470 100644 --- a/CryptoExchange.Net/Trackers/UserData/Objects/UserDataSymbolTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/Objects/UserDataSymbolTracker.cs @@ -38,10 +38,9 @@ namespace CryptoExchange.Net.Trackers.UserData.Objects /// /// Update the tracked symbol list with potential new symbols /// - /// - public void UpdateTrackedSymbols(IEnumerable symbols) + public void UpdateTrackedSymbols(IEnumerable symbols, bool addByUser = false) { - if (_onlyTrackProvidedSymbols) + if (!addByUser && _onlyTrackProvidedSymbols) return; lock (_symbolLock) @@ -56,5 +55,18 @@ namespace CryptoExchange.Net.Trackers.UserData.Objects } } } + + /// + /// Remove a symbol from the list + /// + 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); + } + } } } diff --git a/CryptoExchange.Net/Trackers/UserData/UserFuturesDataTracker.cs b/CryptoExchange.Net/Trackers/UserData/UserFuturesDataTracker.cs index 3721447..e081f78 100644 --- a/CryptoExchange.Net/Trackers/UserData/UserFuturesDataTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/UserFuturesDataTracker.cs @@ -154,5 +154,30 @@ namespace CryptoExchange.Net.Trackers.UserData interval = result ? TimeSpan.FromMinutes(30) : TimeSpan.FromMinutes(5); } } + + /// + /// Add symbols to the list of symbols for which data is being tracked + /// + /// Symbols to add + public void AddTrackedSymbolsAsync(IEnumerable 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); + } + + /// + /// 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. + /// + /// Symbol to remove + public void RemoveTrackedSymbolAsync(SharedSymbol symbol) + { + SymbolTracker.RemoveTrackedSymbol(symbol); + + ((FuturesOrderTracker)Orders).ClearDataForSymbol(symbol); + ((FuturesUserTradeTracker)Trades).ClearDataForSymbol(symbol); + } } } diff --git a/CryptoExchange.Net/Trackers/UserData/UserSpotDataTracker.cs b/CryptoExchange.Net/Trackers/UserData/UserSpotDataTracker.cs index 43796ca..212ac46 100644 --- a/CryptoExchange.Net/Trackers/UserData/UserSpotDataTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/UserSpotDataTracker.cs @@ -127,5 +127,30 @@ namespace CryptoExchange.Net.Trackers.UserData interval = result ? TimeSpan.FromMinutes(30) : TimeSpan.FromMinutes(5); } } + + /// + /// Add symbols to the list of symbols for which data is being tracked + /// + /// Symbols to add + public void AddTrackedSymbolsAsync(IEnumerable 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); + } + + /// + /// 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. + /// + /// Symbol to remove + public void RemoveTrackedSymbolAsync(SharedSymbol symbol) + { + SymbolTracker.RemoveTrackedSymbol(symbol); + + ((SpotOrderTracker)Orders).ClearDataForSymbol(symbol); + ((SpotUserTradeTracker)Trades).ClearDataForSymbol(symbol); + } } } From 5e083811dfddb516d972daf865fdc8f2ef5c43a2 Mon Sep 17 00:00:00 2001 From: Jkorf Date: Mon, 16 Feb 2026 09:33:39 +0100 Subject: [PATCH 4/6] 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 --- CryptoExchange.Net/OrderBook/SymbolOrderBook.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/CryptoExchange.Net/OrderBook/SymbolOrderBook.cs b/CryptoExchange.Net/OrderBook/SymbolOrderBook.cs index 9b62eb2..766cf58 100644 --- a/CryptoExchange.Net/OrderBook/SymbolOrderBook.cs +++ b/CryptoExchange.Net/OrderBook/SymbolOrderBook.cs @@ -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 /// The sequence number of the message if it's a separate message with separate number 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(); } From b94085a27a97c8c67fb6e580cf2c5b1a0fcc8efd Mon Sep 17 00:00:00 2001 From: Jkorf Date: Mon, 16 Feb 2026 09:36:44 +0100 Subject: [PATCH 5/6] Added some code comments --- .../Sockets/Default/SocketConnection.cs | 6 ++++++ .../UserData/Objects/UserDataSymbolTracker.cs | 13 +++++++++++++ .../Trackers/UserData/UserFuturesDataTracker.cs | 2 +- .../Trackers/UserData/UserSpotDataTracker.cs | 2 +- 4 files changed, 21 insertions(+), 2 deletions(-) diff --git a/CryptoExchange.Net/Sockets/Default/SocketConnection.cs b/CryptoExchange.Net/Sockets/Default/SocketConnection.cs index d1e47a0..e2f4915 100644 --- a/CryptoExchange.Net/Sockets/Default/SocketConnection.cs +++ b/CryptoExchange.Net/Sockets/Default/SocketConnection.cs @@ -1103,6 +1103,12 @@ namespace CryptoExchange.Net.Sockets.Default return CallResult.SuccessResult; } + /// + /// Try to subscribe a new subscription by sending the subscribe query and wait for the result as needed + /// + /// The subscription + /// Whether this is a new subscription, or an existing subscription (resubscribing on reconnected socket) + /// Cancellation token protected internal async Task TrySubscribeAsync(Subscription subscription, bool newSubscription, CancellationToken subCancelToken) { subscription.ConnectionInvocations = 0; diff --git a/CryptoExchange.Net/Trackers/UserData/Objects/UserDataSymbolTracker.cs b/CryptoExchange.Net/Trackers/UserData/Objects/UserDataSymbolTracker.cs index 36e3470..9ff50f8 100644 --- a/CryptoExchange.Net/Trackers/UserData/Objects/UserDataSymbolTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/Objects/UserDataSymbolTracker.cs @@ -7,6 +7,9 @@ using System.Text; namespace CryptoExchange.Net.Trackers.UserData.Objects { + /// + /// Tracker for symbols used in UserDataTracker + /// 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(); + /// + /// ctor + /// public UserDataSymbolTracker(ILogger logger, UserDataTrackerConfig config) { _logger = logger; @@ -21,12 +27,19 @@ namespace CryptoExchange.Net.Trackers.UserData.Objects _onlyTrackProvidedSymbols = config.OnlyTrackProvidedSymbols; } + /// + /// Get currently tracked symbols + /// + /// public IEnumerable GetTrackedSymbols() { lock (_symbolLock) return _trackedSymbols.ToList(); } + /// + /// Check whether a symbol is in the tracked symbols list and should be processed + /// public bool ShouldProcess(SharedSymbol symbol) { if (!_onlyTrackProvidedSymbols) diff --git a/CryptoExchange.Net/Trackers/UserData/UserFuturesDataTracker.cs b/CryptoExchange.Net/Trackers/UserData/UserFuturesDataTracker.cs index e081f78..b7366c5 100644 --- a/CryptoExchange.Net/Trackers/UserData/UserFuturesDataTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/UserFuturesDataTracker.cs @@ -177,7 +177,7 @@ namespace CryptoExchange.Net.Trackers.UserData SymbolTracker.RemoveTrackedSymbol(symbol); ((FuturesOrderTracker)Orders).ClearDataForSymbol(symbol); - ((FuturesUserTradeTracker)Trades).ClearDataForSymbol(symbol); + ((FuturesUserTradeTracker?)Trades)?.ClearDataForSymbol(symbol); } } } diff --git a/CryptoExchange.Net/Trackers/UserData/UserSpotDataTracker.cs b/CryptoExchange.Net/Trackers/UserData/UserSpotDataTracker.cs index 212ac46..b71a700 100644 --- a/CryptoExchange.Net/Trackers/UserData/UserSpotDataTracker.cs +++ b/CryptoExchange.Net/Trackers/UserData/UserSpotDataTracker.cs @@ -150,7 +150,7 @@ namespace CryptoExchange.Net.Trackers.UserData SymbolTracker.RemoveTrackedSymbol(symbol); ((SpotOrderTracker)Orders).ClearDataForSymbol(symbol); - ((SpotUserTradeTracker)Trades).ClearDataForSymbol(symbol); + ((SpotUserTradeTracker?)Trades)?.ClearDataForSymbol(symbol); } } } From e40f2a15b6f3ac855d4fc692c54871cdc9826a83 Mon Sep 17 00:00:00 2001 From: Jkorf Date: Mon, 16 Feb 2026 09:38:55 +0100 Subject: [PATCH 6/6] Updated to version 10.6.0 --- CryptoExchange.Net/CryptoExchange.Net.csproj | 6 +++--- README.md | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CryptoExchange.Net/CryptoExchange.Net.csproj b/CryptoExchange.Net/CryptoExchange.Net.csproj index 8e94af4..dea73d4 100644 --- a/CryptoExchange.Net/CryptoExchange.Net.csproj +++ b/CryptoExchange.Net/CryptoExchange.Net.csproj @@ -6,9 +6,9 @@ CryptoExchange.Net JKorf 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. - 10.5.4 - 10.5.4 - 10.5.4 + 10.6.0 + 10.6.0 + 10.6.0 false 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 git diff --git a/README.md b/README.md index bb3742b..863d4f0 100644 --- a/README.md +++ b/README.md @@ -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