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.
This commit is contained in:
2026-01-06 01:23:58 +07:00
parent 94f86c8937
commit 09a6a13eb1

View File

@@ -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<bool> 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<IExchangeService, List<Position>>(
_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<bool> CheckIfOpeningSwapWasCanceled(Position position)
{
try