mirror of
https://github.com/JKorf/CryptoExchange.Net
synced 2026-04-12 16:13:12 +00:00
Compare commits
8 Commits
2281b8d7b3
...
2bc29b9cc0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2bc29b9cc0 | ||
|
|
5309de3a21 | ||
|
|
e40f2a15b6 | ||
|
|
b94085a27a | ||
|
|
5e083811df | ||
|
|
7dcf5cd6ea | ||
|
|
1471a4733f | ||
|
|
f39d9f7cfb |
@ -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.3</PackageVersion>
|
||||
<AssemblyVersion>10.5.3</AssemblyVersion>
|
||||
<FileVersion>10.5.3</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>
|
||||
|
||||
@ -450,20 +450,6 @@ namespace CryptoExchange.Net
|
||||
};
|
||||
}
|
||||
|
||||
// Without date/time filter
|
||||
public static PageRequest? GetNextPageRequestPageSimple(
|
||||
PageRequest? currentPageRequest,
|
||||
bool hasNextPage,
|
||||
IEnumerable<DateTime> timeSelector,
|
||||
DateTime? requestStartTime,
|
||||
DateTime? requestEndTime
|
||||
)
|
||||
{
|
||||
if (CheckForNextPage(timeSelector, requestStartTime, requestEndTime, limit, direction))
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public static PageRequest? GetNextPageRequest(
|
||||
Func<PageRequest> normalNextPageCallback,
|
||||
int responseLength,
|
||||
|
||||
@ -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();
|
||||
}
|
||||
|
||||
@ -14,6 +14,7 @@ namespace CryptoExchange.Net.SharedApis
|
||||
|
||||
public record PaginationParameters
|
||||
{
|
||||
public DataDirection Direction { get; set; }
|
||||
public DateTime? StartTime { get; set; }
|
||||
public DateTime? EndTime { get; set; }
|
||||
public string? FromId { get; set; }
|
||||
@ -39,5 +40,190 @@ namespace CryptoExchange.Net.SharedApis
|
||||
public static PageRequest NextStartTimeAsc(IEnumerable<DateTime> timestampSelector) => new PageRequest { StartTime = timestampSelector.Max().AddMilliseconds(1) };
|
||||
public static PageRequest NextEndTimeDesc(IEnumerable<DateTime> timestampSelector) => new PageRequest { EndTime = timestampSelector.Min().AddMilliseconds(-1) };
|
||||
}
|
||||
|
||||
public static class Pagination
|
||||
{
|
||||
public static PaginationParameters GetPaginationParameters(
|
||||
DataDirection direction,
|
||||
DateTime? requestStartTime,
|
||||
DateTime requestEndTime,
|
||||
PageRequest? paginationRequest,
|
||||
bool setOtherTimeLimiter = true,
|
||||
TimeSpan? maxPeriod = null
|
||||
)
|
||||
{
|
||||
var startTime = paginationRequest?.StartTime ?? requestStartTime;
|
||||
var endTime = paginationRequest?.EndTime ?? requestEndTime;
|
||||
if (maxPeriod != null)
|
||||
{
|
||||
if (direction == DataDirection.Ascending)
|
||||
{
|
||||
if (startTime == null)
|
||||
startTime = endTime.Add(-maxPeriod.Value);
|
||||
else
|
||||
endTime = startTime.Value.Add(maxPeriod.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
startTime = endTime.Add(-maxPeriod.Value);
|
||||
}
|
||||
}
|
||||
|
||||
return new PaginationParameters
|
||||
{
|
||||
StartTime = direction == DataDirection.Ascending || setOtherTimeLimiter ? startTime : null,
|
||||
EndTime = direction == DataDirection.Descending || setOtherTimeLimiter ? endTime : null,
|
||||
Direction = direction,
|
||||
FromId = paginationRequest?.FromId,
|
||||
Offset = paginationRequest?.Offset,
|
||||
Page = paginationRequest?.Page
|
||||
};
|
||||
}
|
||||
|
||||
public static PageRequest? GetNextPageRequest(
|
||||
Func<PageRequest> nextPageRequest,
|
||||
int resultCount,
|
||||
IEnumerable<DateTime> timestamps,
|
||||
DateTime? requestStartTime,
|
||||
DateTime requestEndTime,
|
||||
int limit,
|
||||
DataDirection direction,
|
||||
PaginationParameters lastPaginationData,
|
||||
TimeSpan? maxTimespan = null
|
||||
)
|
||||
{
|
||||
if (HasNextPage(resultCount, timestamps, requestStartTime, requestEndTime, limit, direction))
|
||||
return nextPageRequest();
|
||||
|
||||
if (maxTimespan != null)
|
||||
{
|
||||
if (HasNextPeriod(requestStartTime, requestEndTime, direction, lastPaginationData, maxTimespan.Value))
|
||||
{
|
||||
var (startTime, endTime) = GetNextPeriod(requestStartTime, requestEndTime, direction, lastPaginationData, maxTimespan.Value);
|
||||
return new PageRequest
|
||||
{
|
||||
StartTime = startTime,
|
||||
EndTime = endTime
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public static bool HasNextPage(
|
||||
int resultCount,
|
||||
IEnumerable<DateTime> timestamps,
|
||||
DateTime? requestStartTime,
|
||||
DateTime requestEndTime,
|
||||
int limit,
|
||||
DataDirection direction)
|
||||
{
|
||||
if (resultCount < limit)
|
||||
return false;
|
||||
|
||||
if (!timestamps.Any()) //?
|
||||
return false;
|
||||
|
||||
if (direction == DataDirection.Ascending)
|
||||
{
|
||||
if (timestamps.Max() >= requestEndTime)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (timestamps.Min() < requestStartTime)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
public static PageRequest NextPageFromPage(PaginationParameters lastPaginationData)
|
||||
{
|
||||
return new PageRequest { Page = lastPaginationData.Page + 1 };
|
||||
}
|
||||
public static PageRequest NextPageFromOffset(PaginationParameters lastPaginationData, int resultCount)
|
||||
{
|
||||
return new PageRequest { Offset = lastPaginationData.Offset + resultCount };
|
||||
}
|
||||
public static PageRequest NextPageFromCursor(string nextCursor)
|
||||
{
|
||||
return new PageRequest { Cursor = nextCursor };
|
||||
}
|
||||
public static PageRequest NextPageFromId(long nextFromId)
|
||||
{
|
||||
return new PageRequest { FromId = nextFromId.ToString() };
|
||||
}
|
||||
public static PageRequest NextPageFromId(string nextFromId)
|
||||
{
|
||||
return new PageRequest { FromId = nextFromId };
|
||||
}
|
||||
public static PageRequest NextPageFromTime(PaginationParameters lastPaginationData, DateTime lastTimestamp, bool setOtherTimeLimiter)
|
||||
{
|
||||
if (lastPaginationData.Direction == DataDirection.Ascending)
|
||||
return new PageRequest { StartTime = lastTimestamp.AddMilliseconds(1), EndTime = setOtherTimeLimiter ? lastPaginationData.EndTime : null };
|
||||
else
|
||||
return new PageRequest { EndTime = lastTimestamp.AddMilliseconds(-1), StartTime = setOtherTimeLimiter ? lastPaginationData.StartTime : null };
|
||||
}
|
||||
|
||||
public static bool HasNextPeriod(
|
||||
DateTime? requestStartTime,
|
||||
DateTime requestEndTime,
|
||||
DataDirection direction,
|
||||
PaginationParameters lastPaginationParameters,
|
||||
TimeSpan period)
|
||||
{
|
||||
if (direction == DataDirection.Ascending && lastPaginationParameters.StartTime == null)
|
||||
throw new Exception();
|
||||
|
||||
if (direction == DataDirection.Ascending)
|
||||
{
|
||||
return (requestEndTime - lastPaginationParameters.EndTime!.Value).TotalSeconds > 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (requestStartTime != null)
|
||||
return (lastPaginationParameters.StartTime!.Value - requestStartTime.Value).TotalSeconds > 1;
|
||||
else
|
||||
return (lastPaginationParameters.StartTime!.Value - (lastPaginationParameters.EndTime!.Value - period)).TotalSeconds > 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static (DateTime? startTime, DateTime? endTime) GetNextPeriod(
|
||||
DateTime? requestStartTime,
|
||||
DateTime requestEndTime,
|
||||
DataDirection direction,
|
||||
PaginationParameters lastPaginationParameters,
|
||||
TimeSpan period
|
||||
)
|
||||
{
|
||||
DateTime? nextStartTime = null;
|
||||
DateTime? nextEndTime = null;
|
||||
if (direction == DataDirection.Ascending)
|
||||
{
|
||||
if (lastPaginationParameters.StartTime != null)
|
||||
nextStartTime = lastPaginationParameters.StartTime.Value.Add(period);
|
||||
if (lastPaginationParameters.EndTime != null)
|
||||
nextEndTime = lastPaginationParameters.EndTime.Value.Add(period);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (lastPaginationParameters.StartTime != null)
|
||||
nextStartTime = lastPaginationParameters.StartTime.Value.Add(-period);
|
||||
if (lastPaginationParameters.EndTime != null)
|
||||
nextEndTime = lastPaginationParameters.EndTime.Value.Add(-period);
|
||||
}
|
||||
|
||||
if (nextStartTime != null && nextStartTime < requestStartTime)
|
||||
nextStartTime = requestStartTime;
|
||||
|
||||
if (nextEndTime != null && nextEndTime > requestEndTime)
|
||||
nextEndTime = requestEndTime;
|
||||
|
||||
return (nextStartTime, nextEndTime);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -16,13 +16,6 @@ namespace CryptoExchange.Net.Trackers.UserData.Interfaces
|
||||
/// </summary>
|
||||
bool Connected { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
IEnumerable<SharedSymbol> TrackedSymbols { get; }
|
||||
|
||||
/// <summary>
|
||||
/// On connection status change. Might trigger multiple times with the same status depending on the underlying subscriptions.
|
||||
/// </summary>
|
||||
|
||||
@ -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
|
||||
/// </summary>
|
||||
public string Exchange { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
IEnumerable<SharedSymbol> TrackedSymbols { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Balances tracker
|
||||
/// </summary>
|
||||
@ -57,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);
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
/// </summary>
|
||||
public string Exchange { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
IEnumerable<SharedSymbol> TrackedSymbols { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Balances tracker
|
||||
/// </summary>
|
||||
@ -53,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);
|
||||
}
|
||||
}
|
||||
@ -22,12 +22,13 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
|
||||
/// </summary>
|
||||
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 };
|
||||
|
||||
@ -28,13 +28,14 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
|
||||
/// </summary>
|
||||
public FuturesOrderTracker(
|
||||
ILogger logger,
|
||||
UserDataSymbolTracker symbolTracker,
|
||||
IFuturesOrderRestClient restClient,
|
||||
IFuturesOrderSocketClient? socketClient,
|
||||
TrackerItemConfig config,
|
||||
IEnumerable<SharedSymbol> 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 };
|
||||
@ -46,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)
|
||||
{
|
||||
@ -234,7 +249,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 +287,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 +388,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 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}",
|
||||
DataType, $"{symbol.BaseAsset}/{symbol.QuoteAsset}", source, fromTime);
|
||||
return fromTime!.Value;
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,13 +26,14 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
|
||||
/// </summary>
|
||||
public FuturesUserTradeTracker(
|
||||
ILogger logger,
|
||||
UserDataSymbolTracker symbolTracker,
|
||||
IFuturesOrderRestClient restClient,
|
||||
IUserTradeSocketClient? socketClient,
|
||||
TrackerItemConfig config,
|
||||
IEnumerable<SharedSymbol> 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 };
|
||||
@ -42,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 />
|
||||
@ -57,7 +72,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 +131,13 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
|
||||
source = "StartTime";
|
||||
}
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
if (now - fromTime < TimeSpan.FromSeconds(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);
|
||||
return fromTime!.Value;
|
||||
}
|
||||
|
||||
@ -29,6 +29,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
|
||||
/// </summary>
|
||||
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<SharedPosition>();
|
||||
toRemove.Add(item);
|
||||
_logger.LogTrace("Ignoring {DataType} update for {Key}, no SharedSymbol set", DataType, item.Symbol);
|
||||
}
|
||||
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<SharedPosition>();
|
||||
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<SharedSymbolModel>().Select(x => x.SharedSymbol!));
|
||||
_symbolTracker.UpdateTrackedSymbols(@event.Where(x => x.PositionSize > 0).OfType<SharedSymbolModel>().Select(x => x.SharedSymbol!));
|
||||
|
||||
|
||||
// Update local store
|
||||
|
||||
@ -28,13 +28,14 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
|
||||
/// </summary>
|
||||
public SpotOrderTracker(
|
||||
ILogger logger,
|
||||
UserDataSymbolTracker symbolTracker,
|
||||
ISpotOrderRestClient restClient,
|
||||
ISpotOrderSocketClient? socketClient,
|
||||
TrackerItemConfig config,
|
||||
IEnumerable<SharedSymbol> 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 };
|
||||
@ -46,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)
|
||||
{
|
||||
@ -245,7 +259,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 +297,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 +399,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 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}",
|
||||
DataType, $"{symbol.BaseAsset}/{symbol.QuoteAsset}", source, fromTime);
|
||||
return fromTime!.Value;
|
||||
}
|
||||
}
|
||||
|
||||
@ -26,13 +26,14 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
|
||||
/// </summary>
|
||||
public SpotUserTradeTracker(
|
||||
ILogger logger,
|
||||
UserDataSymbolTracker symbolTracker,
|
||||
ISpotOrderRestClient restClient,
|
||||
IUserTradeSocketClient? socketClient,
|
||||
TrackerItemConfig config,
|
||||
IEnumerable<SharedSymbol> 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 };
|
||||
@ -42,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 />
|
||||
@ -57,7 +71,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 +128,12 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
|
||||
source = "StartTime";
|
||||
}
|
||||
|
||||
if (DateTime.UtcNow - fromTime < TimeSpan.FromSeconds(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);
|
||||
return fromTime!.Value;
|
||||
}
|
||||
|
||||
@ -203,21 +203,14 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
|
||||
/// </summary>
|
||||
protected ConcurrentDictionary<string, T> _store = new ConcurrentDictionary<string, T>(StringComparer.InvariantCultureIgnoreCase);
|
||||
/// <summary>
|
||||
/// Tracked symbols list
|
||||
/// </summary>
|
||||
protected readonly List<SharedSymbol> _symbols;
|
||||
/// <summary>
|
||||
/// Symbol lock
|
||||
/// </summary>
|
||||
protected object _symbolLock = new object();
|
||||
/// <summary>
|
||||
/// Only track provided symbols setting
|
||||
/// </summary>
|
||||
protected bool _onlyTrackProvidedSymbols;
|
||||
/// <summary>
|
||||
/// Is SharedSymbol model
|
||||
/// </summary>
|
||||
protected bool _isSymbolModel;
|
||||
/// <summary>
|
||||
/// Symbol tracker
|
||||
/// </summary>
|
||||
|
||||
protected readonly UserDataSymbolTracker _symbolTracker;
|
||||
|
||||
/// <inheritdoc />
|
||||
public T[] Values
|
||||
@ -240,22 +233,23 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
|
||||
|
||||
/// <inheritdoc />
|
||||
public event Func<UserDataUpdate<T[]>, Task>? OnUpdate;
|
||||
/// <inheritdoc />
|
||||
public IEnumerable<SharedSymbol> TrackedSymbols => _symbols;
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
public UserDataItemTracker(ILogger logger, UserDataType dataType, string exchange, TrackerItemConfig config, bool onlyTrackProvidedSymbols, IEnumerable<SharedSymbol>? 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -334,26 +328,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
|
||||
/// Get the age of an item
|
||||
/// </summary>
|
||||
protected virtual TimeSpan GetAge(DateTime time, T item) => TimeSpan.Zero;
|
||||
|
||||
/// <summary>
|
||||
/// Update the tracked symbol list with potential new symbols
|
||||
/// </summary>
|
||||
/// <param name="symbols"></param>
|
||||
protected void UpdateSymbolsList(IEnumerable<SharedSymbol> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Handle an update
|
||||
/// </summary>
|
||||
@ -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<T>();
|
||||
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<SharedSymbolModel>().Select(x => x.SharedSymbol!));
|
||||
_symbolTracker.UpdateTrackedSymbols(@event.OfType<SharedSymbolModel>().Select(x => x.SharedSymbol!));
|
||||
}
|
||||
|
||||
// Update local store
|
||||
|
||||
@ -0,0 +1,85 @@
|
||||
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
|
||||
{
|
||||
/// <summary>
|
||||
/// Tracker for symbols used in UserDataTracker
|
||||
/// </summary>
|
||||
public class UserDataSymbolTracker
|
||||
{
|
||||
private readonly ILogger _logger;
|
||||
private readonly List<SharedSymbol> _trackedSymbols;
|
||||
private readonly bool _onlyTrackProvidedSymbols;
|
||||
private readonly object _symbolLock = new object();
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
public UserDataSymbolTracker(ILogger logger, UserDataTrackerConfig config)
|
||||
{
|
||||
_logger = logger;
|
||||
_trackedSymbols = config.TrackedSymbols?.ToList() ?? [];
|
||||
_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)
|
||||
return true;
|
||||
|
||||
return _trackedSymbols.Any(y => y.TradingMode == symbol!.TradingMode && y.BaseAsset == symbol.BaseAsset && y.QuoteAsset == symbol.QuoteAsset);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Update the tracked symbol list with potential new symbols
|
||||
/// </summary>
|
||||
public void UpdateTrackedSymbols(IEnumerable<SharedSymbol> symbols, bool addByUser = false)
|
||||
{
|
||||
if (!addByUser && _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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
/// </summary>
|
||||
protected abstract UserDataItemTracker[] DataTrackers { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Symbol tracker
|
||||
/// </summary>
|
||||
protected internal UserDataSymbolTracker SymbolTracker { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? UserIdentifier { get; }
|
||||
|
||||
@ -51,6 +57,11 @@ namespace CryptoExchange.Net.Trackers.UserData
|
||||
/// </summary>
|
||||
public bool Connected => DataTrackers.All(x => x.Connected);
|
||||
|
||||
/// <summary>
|
||||
/// Currently tracked symbols
|
||||
/// </summary>
|
||||
public IEnumerable<SharedSymbol> TrackedSymbols => SymbolTracker.GetTrackedSymbols();
|
||||
|
||||
/// <summary>
|
||||
/// ctor
|
||||
/// </summary>
|
||||
@ -65,6 +76,7 @@ namespace CryptoExchange.Net.Trackers.UserData
|
||||
|
||||
_logger = logger;
|
||||
|
||||
SymbolTracker = new UserDataSymbolTracker(logger, config);
|
||||
Exchange = exchange;
|
||||
UserIdentifier = userIdentifier;
|
||||
}
|
||||
|
||||
@ -77,21 +77,21 @@ namespace CryptoExchange.Net.Trackers.UserData
|
||||
|
||||
var trackers = new List<UserDataItemTracker>();
|
||||
|
||||
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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,17 +53,17 @@ namespace CryptoExchange.Net.Trackers.UserData
|
||||
|
||||
var trackers = new List<UserDataItemTracker>();
|
||||
|
||||
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);
|
||||
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
10
README.md
10
README.md
@ -67,6 +67,16 @@ 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
|
||||
|
||||
* 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
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user