From 09a6a13eb1fa0d4bf63ca51f95b995f6865b331d Mon Sep 17 00:00:00 2001 From: cryptooda Date: Tue, 6 Jan 2026 01:23:58 +0700 Subject: [PATCH] Enhance SpotBot position verification and logging - Introduced a new method to verify the execution of opening swaps in history, ensuring positions are only marked as filled after confirming successful swaps. - Improved logging to provide detailed feedback on swap confirmation status, including retries for pending swaps and error handling for verification failures. - Adjusted position status update logic to enhance robustness in managing filled positions, preventing premature status changes. --- src/Managing.Application/Bots/SpotBot.cs | 113 ++++++++++++++++++++++- 1 file changed, 111 insertions(+), 2 deletions(-) diff --git a/src/Managing.Application/Bots/SpotBot.cs b/src/Managing.Application/Bots/SpotBot.cs index 6088c0e7..33cfa1fe 100644 --- a/src/Managing.Application/Bots/SpotBot.cs +++ b/src/Managing.Application/Bots/SpotBot.cs @@ -488,10 +488,29 @@ public class SpotBot : TradingBotBase } } - // Token balance exists and matches position - verify position is filled + // Token balance exists - verify the opening swap actually succeeded in history before marking as Filled var previousPositionStatus = internalPosition.Status; - // Position found on broker (token balance exists), means the position is filled + // Only check history if position is not already Filled + if (internalPosition.Status != PositionStatus.Filled) + { + // Verify the opening swap actually executed successfully + bool swapConfirmedInHistory = await VerifyOpeningSwapInHistory(internalPosition); + + if (!swapConfirmedInHistory) + { + await LogDebugAsync( + $"âŗ Opening Swap Not Yet Confirmed in History\n" + + $"Position: `{internalPosition.Identifier}`\n" + + $"Token Balance: `{tokenBalanceAmount:F5}`\n" + + $"Status: `{internalPosition.Status}`\n" + + $"Waiting for swap to appear in exchange history...\n" + + $"Will retry on next cycle"); + return; // Don't mark as Filled yet, wait for history confirmation + } + } + + // Position confirmed on broker (token balance exists AND swap confirmed in history) // Update position status internalPosition.Status = PositionStatus.Filled; await SetPositionStatus(internalPosition.SignalIdentifier, PositionStatus.Filled); @@ -669,6 +688,96 @@ public class SpotBot : TradingBotBase } } + private async Task VerifyOpeningSwapInHistory(Position position) + { + try + { + await LogDebugAsync( + $"🔍 Verifying Opening Swap in History\n" + + $"Position: `{position.Identifier}`\n" + + $"Ticker: `{Config.Ticker}`\n" + + $"Expected Quantity: `{position.Open.Quantity:F5}`"); + + // Get swap history from exchange + var positionHistory = await ServiceScopeHelpers.WithScopedService>( + _scopeFactory, + async exchangeService => + { + // Check swaps from 1 hour before position date to now + var fromDate = position.Date.AddHours(-1); + var toDate = DateTime.UtcNow; + return await exchangeService.GetSpotPositionHistory(Account, Config.Ticker, fromDate, toDate); + }); + + if (positionHistory == null || positionHistory.Count == 0) + { + await LogDebugAsync( + $"â„šī¸ No History Found Yet\n" + + $"Position: `{position.Identifier}`\n" + + $"Swap may still be pending or history not yet indexed"); + return false; + } + + // For a LONG position, the opening swap should be LONG (USDC → Token) + // Find a matching swap in the history + var openingSwaps = positionHistory + .Where(p => p.OriginDirection == TradeDirection.Long && + Math.Abs((p.Date - position.Date).TotalMinutes) < 10) // Within 10 minutes of position creation + .OrderBy(p => Math.Abs((p.Date - position.Date).TotalSeconds)) + .ToList(); + + if (!openingSwaps.Any()) + { + await LogDebugAsync( + $"â„šī¸ No Matching Opening Swap Found in History\n" + + $"Position: `{position.Identifier}`\n" + + $"Position Date: `{position.Date}`\n" + + $"Searched {positionHistory.Count} history entries\n" + + $"No LONG swaps found within 10 minutes of position creation"); + return false; + } + + // Check if any of the swaps match our position quantity (with tolerance) + var matchingSwap = openingSwaps.FirstOrDefault(swap => + { + if (position.Open.Quantity == 0) return false; + var quantityDifference = Math.Abs(swap.Open.Quantity - position.Open.Quantity) / position.Open.Quantity; + return quantityDifference < 0.02m; // Within 2% tolerance + }); + + if (matchingSwap != null) + { + await LogDebugAsync( + $"✅ Opening Swap Confirmed in History\n" + + $"Position: `{position.Identifier}`\n" + + $"Swap Date: `{matchingSwap.Date}`\n" + + $"Swap Quantity: `{matchingSwap.Open.Quantity:F5}`\n" + + $"Expected Quantity: `{position.Open.Quantity:F5}`\n" + + $"Swap successfully executed"); + return true; + } + + // Found swaps around the time, but none match the quantity + await LogDebugAsync( + $"âš ī¸ Found Swaps But None Match Position Quantity\n" + + $"Position: `{position.Identifier}`\n" + + $"Expected Quantity: `{position.Open.Quantity:F5}`\n" + + $"Found {openingSwaps.Count} LONG swaps around position creation time\n" + + $"But none match the quantity (within 2% tolerance)"); + return false; + } + catch (Exception ex) + { + Logger.LogError(ex, "Error verifying opening swap in history for position {PositionId}", position.Identifier); + await LogWarningAsync( + $"âš ī¸ Error Verifying Opening Swap\n" + + $"Position: `{position.Identifier}`\n" + + $"Error: {ex.Message}\n" + + $"Assuming swap not yet confirmed, will retry on next cycle"); + return false; // On error, don't assume swap succeeded + } + } + private async Task CheckIfOpeningSwapWasCanceled(Position position) { try