1
0
mirror of https://github.com/JKorf/CryptoExchange.Net synced 2026-04-13 00:22:22 +00:00
This commit is contained in:
Jkorf 2026-02-04 16:31:52 +01:00
parent fdc8b452b1
commit 9d43d358cf
11 changed files with 107 additions and 17 deletions

View File

@ -21,6 +21,11 @@ namespace CryptoExchange.Net.SharedApis
/// </summary>
public decimal? QuantityInContracts { get; set; }
/// <summary>
/// Whether all values are null or zero
/// </summary>
public bool IsZero => !(QuantityInBaseAsset > 0) && !(QuantityInQuoteAsset > 0) && !(QuantityInContracts > 0);
/// <summary>
/// ctor
/// </summary>

View File

@ -295,6 +295,9 @@ namespace CryptoExchange.Net.Sockets.Default
await (OnReconnecting?.Invoke() ?? Task.CompletedTask).ConfigureAwait(false);
}
#warning debug
await Task.Delay(10000).ConfigureAwait(false);
// Delay here to prevent very rapid looping when a connection to the server is accepted and immediately disconnected
var initialDelay = GetReconnectDelay();
await Task.Delay(initialDelay).ConfigureAwait(false);

View File

@ -46,7 +46,8 @@ namespace CryptoExchange.Net.Sockets.Default
return;
_status = value;
Task.Run(() => StatusChanged?.Invoke(value));
StatusChanged?.Invoke(value);
//Task.Run(() => StatusChanged?.Invoke(value));
}
}

View File

@ -15,6 +15,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
private readonly IBalanceRestClient _restClient;
private readonly IBalanceSocketClient? _socketClient;
private readonly ExchangeParameters? _exchangeParameters;
private readonly SharedAccountType _accountType;
/// <summary>
/// ctor
@ -23,6 +24,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
ILogger logger,
IBalanceRestClient restClient,
IBalanceSocketClient? socketClient,
SharedAccountType accountType,
TrackerItemConfig config,
ExchangeParameters? exchangeParameters = null
) : base(logger, UserDataType.Balances, config, false, null)
@ -30,6 +32,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
_restClient = restClient;
_socketClient = socketClient;
_exchangeParameters = exchangeParameters;
_accountType = accountType;
}
/// <inheritdoc />
@ -52,7 +55,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
}
/// <inheritdoc />
protected override string GetKey(SharedBalance item) => item.Asset;
protected override string GetKey(SharedBalance item) => item.Asset + item.IsolatedMarginSymbol;
/// <inheritdoc />
protected override bool? CheckIfUpdateShouldBeApplied(SharedBalance existingItem, SharedBalance updateItem) => true;
@ -63,15 +66,20 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
if (_socketClient == null)
return Task.FromResult(new CallResult<UpdateSubscription?>(data: null));
var accountType = _accountType == SharedAccountType.Spot ? TradingMode.Spot :
_accountType == SharedAccountType.PerpetualInverseFutures ? TradingMode.PerpetualInverse :
_accountType == SharedAccountType.DeliveryLinearFutures ? TradingMode.DeliveryLinear :
_accountType == SharedAccountType.DeliveryInverseFutures ? TradingMode.DeliveryInverse :
TradingMode.PerpetualLinear;
return ExchangeHelpers.ProcessQueuedAsync<SharedBalance[]>(
async handler => await _socketClient.SubscribeToBalanceUpdatesAsync(new SubscribeBalancesRequest(listenKey, exchangeParameters: _exchangeParameters), handler, ct: _cts!.Token).ConfigureAwait(false),
async handler => await _socketClient.SubscribeToBalanceUpdatesAsync(new SubscribeBalancesRequest(listenKey, accountType, exchangeParameters: _exchangeParameters), handler, ct: _cts!.Token).ConfigureAwait(false),
x => HandleUpdateAsync(UpdateSource.Push, x.Data))!;
}
/// <inheritdoc />
protected override async Task<bool> DoPollAsync()
{
var balances = await _restClient.GetBalancesAsync(new GetBalancesRequest(exchangeParameters: _exchangeParameters)).ConfigureAwait(false);
var balances = await _restClient.GetBalancesAsync(new GetBalancesRequest(accountType: _accountType, exchangeParameters: _exchangeParameters)).ConfigureAwait(false);
if (balances.Success)
await HandleUpdateAsync(UpdateSource.Poll, balances.Data).ConfigureAwait(false);

View File

@ -232,6 +232,24 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
|| x.CreateTime == null && x.UpdateTime == null // Unknown time
).ToArray();
// Check for orders which are no longer returned in either open/closed and assume they're canceled without fill
var openOrdersNotReturned = Values.Where(x =>
x.SharedSymbol!.BaseAsset == symbol.BaseAsset && x.SharedSymbol.QuoteAsset == symbol.QuoteAsset // Orders for the same symbol
&& x.QuantityFilled?.IsZero == true // With no filled value
&& !openOrdersResult.Data.Any(r => r.OrderId == x.OrderId) // Not returned in open orders
&& !relevantOrders.Any(r => r.OrderId == x.OrderId) // Not return in closed orders
).ToList();
var additionalUpdates = new List<SharedFuturesOrder>();
foreach (var order in openOrdersNotReturned)
{
additionalUpdates.Add(order with
{
Status = SharedOrderStatus.Canceled
});
}
relevantOrders = relevantOrders.Concat(additionalUpdates).ToArray();
if (relevantOrders.Length > 0)
await HandleUpdateAsync(UpdateSource.Poll, relevantOrders).ConfigureAwait(false);
}

View File

@ -200,7 +200,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
/// <inheritdoc />
protected override string GetKey(SharedPosition item) =>
item.Id ?? item.Symbol + item.PositionMode + (item.PositionMode != SharedPositionMode.OneWay ? item.PositionSide.ToString() : "");
item.SharedSymbol!.TradingMode + item.SharedSymbol.BaseAsset + item.SharedSymbol.QuoteAsset + item.PositionMode + (item.PositionMode != SharedPositionMode.OneWay ? item.PositionSide.ToString() : "");
/// <inheritdoc />
protected override bool? CheckIfUpdateShouldBeApplied(SharedPosition existingItem, SharedPosition updateItem) => true;

View File

@ -69,14 +69,42 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
if (updateItem.OrderQuantity != null && updateItem.OrderQuantity != existingItem.OrderQuantity)
{
existingItem.OrderQuantity = updateItem.OrderQuantity;
changed = true;
existingItem.OrderQuantity ??= new SharedOrderQuantity();
if (updateItem.OrderQuantity.QuantityInBaseAsset != null)
{
existingItem.OrderQuantity.QuantityInBaseAsset = updateItem.OrderQuantity.QuantityInBaseAsset;
changed = true;
}
if (updateItem.OrderQuantity.QuantityInQuoteAsset != null)
{
existingItem.OrderQuantity.QuantityInQuoteAsset = updateItem.OrderQuantity.QuantityInQuoteAsset;
changed = true;
}
if (updateItem.OrderQuantity.QuantityInContracts != null)
{
existingItem.OrderQuantity.QuantityInContracts = updateItem.OrderQuantity.QuantityInContracts;
changed = true;
}
}
if (updateItem.QuantityFilled != null && updateItem.QuantityFilled != existingItem.QuantityFilled)
{
existingItem.QuantityFilled = updateItem.QuantityFilled;
changed = true;
existingItem.QuantityFilled ??= new SharedOrderQuantity();
if (updateItem.QuantityFilled.QuantityInBaseAsset != null)
{
existingItem.QuantityFilled.QuantityInBaseAsset = updateItem.QuantityFilled.QuantityInBaseAsset;
changed = true;
}
if (updateItem.QuantityFilled.QuantityInQuoteAsset != null)
{
existingItem.QuantityFilled.QuantityInQuoteAsset = updateItem.QuantityFilled.QuantityInQuoteAsset;
changed = true;
}
if (updateItem.QuantityFilled.QuantityInContracts != null)
{
existingItem.QuantityFilled.QuantityInContracts = updateItem.QuantityFilled.QuantityInContracts;
changed = true;
}
}
if (updateItem.Status != existingItem.Status)
@ -214,6 +242,24 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
|| x.CreateTime == null && x.UpdateTime == null // Unknown time
).ToArray();
// Check for orders which are no longer returned in either open/closed and assume they're canceled without fill
var openOrdersNotReturned = Values.Where(x =>
x.SharedSymbol!.BaseAsset == symbol.BaseAsset && x.SharedSymbol.QuoteAsset == symbol.QuoteAsset // Orders for the same symbol
&& x.QuantityFilled?.IsZero == true // With no filled value
&& !openOrdersResult.Data.Any(r => r.OrderId == x.OrderId) // Not returned in open orders
&& !relevantOrders.Any(r => r.OrderId == x.OrderId) // Not return in closed orders
).ToList();
var additionalUpdates = new List<SharedSpotOrder>();
foreach (var order in openOrdersNotReturned)
{
additionalUpdates.Add(order with
{
Status = SharedOrderStatus.Canceled
});
}
relevantOrders = relevantOrders.Concat(additionalUpdates).ToArray();
if (relevantOrders.Length > 0)
await HandleUpdateAsync(UpdateSource.Poll, relevantOrders).ConfigureAwait(false);
}

View File

@ -183,7 +183,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
/// <summary>
/// Data store
/// </summary>
protected ConcurrentDictionary<string, T> _store = new ConcurrentDictionary<string, T>();
protected ConcurrentDictionary<string, T> _store = new ConcurrentDictionary<string, T>(StringComparer.InvariantCultureIgnoreCase);
/// <summary>
/// Tracked symbols list
/// </summary>
@ -409,6 +409,7 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
catch { }
}
var currentlyFirstPoll = !_firstPollDone;
_firstPollDone = true;
if (_cts.IsCancellationRequested)
break;
@ -433,6 +434,12 @@ namespace CryptoExchange.Net.Trackers.UserData.ItemTrackers
_lastPollAttempt = DateTime.UtcNow;
_lastPollSuccess = !anyError;
if (anyError && currentlyFirstPoll && _pollAtStart)
{
// This is the initial polling at start and it failed, should this be a start error?
}
}
catch (Exception ex)
{

View File

@ -7,7 +7,7 @@ namespace CryptoExchange.Net.Trackers.UserData.Objects
/// <summary>
/// User data tracker configuration
/// </summary>
public record UserDataTrackerConfig
public abstract record UserDataTrackerConfig
{
/// <summary>
/// Symbols to initially track, used when polling data. Other symbols will get tracked when updates are received for orders or trades on a new symbol and when there are open orders or positions on a new symbol. To only track the symbols specified here set `OnlyTrackProvidedSymbols` to true.

View File

@ -60,6 +60,7 @@ namespace CryptoExchange.Net.Trackers.UserData
IPositionSocketClient? positionSocketClient,
string? userIdentifier,
FuturesUserDataTrackerConfig config,
SharedAccountType? accountType = null,
ExchangeParameters? exchangeParameters = null) : base(logger, config, userIdentifier)
{
// create trackers
@ -69,7 +70,8 @@ namespace CryptoExchange.Net.Trackers.UserData
var trackers = new List<UserDataItemTracker>();
var balanceTracker = new BalanceTracker(logger, balanceRestClient, balanceSocketClient, config.BalancesConfig, exchangeParameters);
var balanceAccountType = accountType ?? SharedAccountType.PerpetualLinearFutures;
var balanceTracker = new BalanceTracker(logger, balanceRestClient, balanceSocketClient, balanceAccountType, config.BalancesConfig, exchangeParameters);
Balances = balanceTracker;
trackers.Add(balanceTracker);
@ -100,7 +102,7 @@ namespace CryptoExchange.Net.Trackers.UserData
var symbolResult = await _symbolClient.GetFuturesSymbolsAsync(new GetSymbolsRequest(exchangeParameters: _exchangeParameters)).ConfigureAwait(false);
if (!symbolResult)
{
_logger.LogWarning("Failed to start UserFuturesDataTracker; symbols request failed: {Error}", symbolResult.Error!.Message);
_logger.LogWarning("Failed to start UserFuturesDataTracker; symbols request failed: {Error}", symbolResult.Error);
return symbolResult;
}
@ -109,7 +111,7 @@ namespace CryptoExchange.Net.Trackers.UserData
var lkResult = await _listenKeyClient.StartListenKeyAsync(new StartListenKeyRequest(exchangeParameters: _exchangeParameters)).ConfigureAwait(false);
if (!lkResult)
{
_logger.LogWarning("Failed to start UserFuturesDataTracker; listen key request failed: {Error}", lkResult.Error!.Message);
_logger.LogWarning("Failed to start UserFuturesDataTracker; listen key request failed: {Error}", lkResult.Error);
return lkResult;
}

View File

@ -51,7 +51,7 @@ namespace CryptoExchange.Net.Trackers.UserData
var trackers = new List<UserDataItemTracker>();
var balanceTracker = new BalanceTracker(logger, balanceRestClient, balanceSocketClient, config.BalancesConfig, exchangeParameters);
var balanceTracker = new BalanceTracker(logger, balanceRestClient, balanceSocketClient, SharedAccountType.Spot, config.BalancesConfig, exchangeParameters);
Balances = balanceTracker;
trackers.Add(balanceTracker);
@ -78,7 +78,7 @@ namespace CryptoExchange.Net.Trackers.UserData
var symbolResult = await _symbolClient.GetSpotSymbolsAsync(new GetSymbolsRequest(exchangeParameters: _exchangeParameters)).ConfigureAwait(false);
if (!symbolResult)
{
_logger.LogWarning("Failed to start UserSpotDataTracker; symbols request failed: {Error}", symbolResult.Error!.Message);
_logger.LogWarning("Failed to start UserSpotDataTracker; symbols request failed: {Error}", symbolResult.Error);
return symbolResult;
}
@ -87,7 +87,7 @@ namespace CryptoExchange.Net.Trackers.UserData
var lkResult = await _listenKeyClient.StartListenKeyAsync(new StartListenKeyRequest(exchangeParameters: _exchangeParameters)).ConfigureAwait(false);
if (!lkResult)
{
_logger.LogWarning("Failed to start UserSpotDataTracker; listen key request failed: {Error}", lkResult.Error!.Message);
_logger.LogWarning("Failed to start UserSpotDataTracker; listen key request failed: {Error}", lkResult.Error);
return lkResult;
}