Enhance SpotBot to verify opening swaps and handle failed transactions

- Added logic to confirm the existence of opening swaps in exchange history before marking positions as filled, addressing potential failures in on-chain transactions.
- Implemented checks for very low token balances and adjusted position statuses accordingly, ensuring accurate tracking and management of positions.
- Improved logging for failed swaps to provide clearer insights into transaction issues and position management.
This commit is contained in:
2026-01-08 05:40:06 +07:00
parent efb1f2edce
commit 3f1d102452

View File

@@ -463,11 +463,43 @@ public class SpotBot : TradingBotBase
var isDustAmount = tokenBalanceAmount <= dustThreshold;
var balanceIsVeryLow = tokenBalanceAmount < positionQuantity * 0.1m; // Less than 10% of expected
// If balance is dust or very low, check if position was closed in history
// If balance is dust or very low, check if opening swap failed or position was closed
if (isDustAmount || balanceIsVeryLow)
{
if (internalPosition.Status == PositionStatus.Filled)
{
// First, check if the opening swap actually succeeded (exists in history)
// If not, the swap likely failed and position should be marked as Canceled
var openingSwapConfirmed = await VerifyOpeningSwapInHistory(internalPosition);
if (!openingSwapConfirmed)
{
// Opening swap not found in history - likely failed
// Mark position as Canceled since it never actually opened
var previousStatus = internalPosition.Status;
internalPosition.Status = PositionStatus.Canceled;
internalPosition.Open.SetStatus(TradeStatus.Cancelled);
positionForSignal.Open.SetStatus(TradeStatus.Cancelled);
await SetPositionStatus(internalPosition.SignalIdentifier, PositionStatus.Canceled);
await UpdatePositionInDatabaseAsync(internalPosition);
await LogWarningAsync(
$"❌ Position Opening Failed - Swap Not Found in History\n" +
$"Position: `{internalPosition.Identifier}`\n" +
$"Signal: `{internalPosition.SignalIdentifier}`\n" +
$"Ticker: {Config.Ticker}\n" +
$"Expected Quantity: `{positionQuantity:F5}`\n" +
$"Token Balance: `{tokenBalanceAmount:F5}` (very low)\n" +
$"Status Changed: `{previousStatus}` → `Canceled`\n" +
$"The opening swap (USDC → {Config.Ticker}) was not found in exchange history\n" +
$"This indicates the swap failed and position never opened\n" +
$"Position will not be tracked or managed");
await NotifyAgentAndPlatformAsync(NotificationEventType.PositionClosed, internalPosition);
return; // Exit - position failed to open
}
// Opening swap exists, so position did open - check if it was closed
var (positionFoundInHistory, hadWeb3ProxyError) =
await CheckSpotPositionInExchangeHistory(internalPosition);
@@ -531,25 +563,68 @@ public class SpotBot : TradingBotBase
}
}
// Token balance exists - verify the opening swap actually succeeded in history before marking as Filled
// Token balance exists - ALWAYS verify the opening swap actually succeeded in history
// This is critical because Web3Proxy may return success when transaction is sent,
// but the swap might fail on-chain. We must verify it actually executed.
var previousPositionStatus = internalPosition.Status;
// 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);
// Always verify the opening swap exists in history, even if position is already marked as Filled
// This catches cases where the position was prematurely marked as Filled by the command handler
bool swapConfirmedInHistory = await VerifyOpeningSwapInHistory(internalPosition);
if (!swapConfirmedInHistory)
if (!swapConfirmedInHistory)
{
// Swap not found in history - check if this is a failed swap or just delayed
// If balance is very low, the swap likely failed
var dustThreshold = Config.Ticker == Ticker.ETH
? 0.01m // ETH: 0.01 ETH is likely gas reserve or dust
: 0.0001m; // Other tokens: very small amount is dust
var isDustAmount = tokenBalanceAmount <= dustThreshold;
var balanceIsVeryLow = tokenBalanceAmount < positionQuantity * 0.1m; // Less than 10% of expected
if (isDustAmount || balanceIsVeryLow)
{
await LogDebugAsync(
$"⏳ Opening Swap Not Yet Confirmed in History\n" +
// Balance is very low and swap not in history - swap likely failed
// Mark position as Canceled since swap failed on-chain
var previousStatus = internalPosition.Status;
internalPosition.Status = PositionStatus.Canceled;
internalPosition.Open.SetStatus(TradeStatus.Cancelled);
positionForSignal.Open.SetStatus(TradeStatus.Cancelled);
await SetPositionStatus(internalPosition.SignalIdentifier, PositionStatus.Canceled);
await UpdatePositionInDatabaseAsync(internalPosition);
await LogWarningAsync(
$"❌ Position Opening Failed - Swap Not Found 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
$"Signal: `{internalPosition.SignalIdentifier}`\n" +
$"Ticker: {Config.Ticker}\n" +
$"Expected Quantity: `{positionQuantity:F5}`\n" +
$"Token Balance: `{tokenBalanceAmount:F5}` (very low)\n" +
$"Status Changed: `{previousStatus}` → `Canceled`\n" +
$"The opening swap (USDC → {Config.Ticker}) was not found in exchange history\n" +
$"This indicates the swap transaction failed on-chain even though it was sent\n" +
$"Position will not be tracked or managed");
await NotifyAgentAndPlatformAsync(NotificationEventType.PositionClosed, internalPosition);
return; // Exit - swap failed
}
else
{
// Balance exists but swap not yet in history - might be delayed
// Only wait if position is not already Filled (to avoid repeated checks)
if (internalPosition.Status != PositionStatus.Filled)
{
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
}
// If already Filled, continue - might be a timing issue with history indexing
}
}