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 isDustAmount = tokenBalanceAmount <= dustThreshold;
|
||||||
var balanceIsVeryLow = tokenBalanceAmount < positionQuantity * 0.1m; // Less than 10% of expected
|
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 (isDustAmount || balanceIsVeryLow)
|
||||||
{
|
{
|
||||||
if (internalPosition.Status == PositionStatus.Filled)
|
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) =
|
var (positionFoundInHistory, hadWeb3ProxyError) =
|
||||||
await CheckSpotPositionInExchangeHistory(internalPosition);
|
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;
|
var previousPositionStatus = internalPosition.Status;
|
||||||
|
|
||||||
// Only check history if position is not already Filled
|
// Always verify the opening swap exists in history, even if position is already marked as Filled
|
||||||
if (internalPosition.Status != PositionStatus.Filled)
|
// This catches cases where the position was prematurely marked as Filled by the command handler
|
||||||
{
|
bool swapConfirmedInHistory = await VerifyOpeningSwapInHistory(internalPosition);
|
||||||
// Verify the opening swap actually executed successfully
|
|
||||||
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(
|
// Balance is very low and swap not in history - swap likely failed
|
||||||
$"⏳ Opening Swap Not Yet Confirmed in History\n" +
|
// 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" +
|
$"Position: `{internalPosition.Identifier}`\n" +
|
||||||
$"Token Balance: `{tokenBalanceAmount:F5}`\n" +
|
$"Signal: `{internalPosition.SignalIdentifier}`\n" +
|
||||||
$"Status: `{internalPosition.Status}`\n" +
|
$"Ticker: {Config.Ticker}\n" +
|
||||||
$"Waiting for swap to appear in exchange history...\n" +
|
$"Expected Quantity: `{positionQuantity:F5}`\n" +
|
||||||
$"Will retry on next cycle");
|
$"Token Balance: `{tokenBalanceAmount:F5}` (very low)\n" +
|
||||||
return; // Don't mark as Filled yet, wait for history confirmation
|
$"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