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:
@@ -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)
|
||||
// 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)
|
||||
{
|
||||
// Verify the opening swap actually executed successfully
|
||||
bool swapConfirmedInHistory = await VerifyOpeningSwapInHistory(internalPosition);
|
||||
// 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
|
||||
|
||||
if (!swapConfirmedInHistory)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user