diff --git a/src/Managing.Application/Bots/SpotBot.cs b/src/Managing.Application/Bots/SpotBot.cs index f89131ca..0e92b929 100644 --- a/src/Managing.Application/Bots/SpotBot.cs +++ b/src/Managing.Application/Bots/SpotBot.cs @@ -180,13 +180,30 @@ public class SpotBot : TradingBotBase return false; // Don't allow opening new position until resolved } - if (tokenBalance is { Value: > 1m }) + if (tokenBalance is { Amount: > 0 }) { - // We have a token balance but no internal position - attempt to recover orphaned position + // Check if this is a meaningful balance or just gas reserves / dust + // Minimum threshold: $10 USD value to be considered an orphaned position + const decimal minOrphanedBalanceValue = 10m; + + if (tokenBalance.Value < minOrphanedBalanceValue) + { + await LogDebugAsync( + $"â„šī¸ Small Token Balance Detected (Likely Gas Reserve or Dust)\n" + + $"Ticker: {Config.Ticker}\n" + + $"Token balance: `{tokenBalance.Amount:F8}`\n" + + $"USD Value: `${tokenBalance.Value:F2}`\n" + + $"Below orphaned threshold of `${minOrphanedBalanceValue:F2}`\n" + + $"Ignoring - safe to open new position"); + return true; // Safe to open new position - this is just dust/gas reserve + } + + // We have a significant token balance but no internal position - attempt to recover orphaned position await LogWarningAsync( $"âš ī¸ Orphaned Token Balance Detected\n" + $"Ticker: {Config.Ticker}\n" + - $"Token balance: `{tokenBalance.Amount:F5}`\n" + + $"Token balance: `{tokenBalance.Amount:F5}` (Value: ${tokenBalance.Value:F2})\n" + + $"Above orphaned threshold of `${minOrphanedBalanceValue:F2}`\n" + $"But no internal position tracked\n" + $"Attempting to recover position from database..."); @@ -198,7 +215,7 @@ public class SpotBot : TradingBotBase $"Could not recover orphaned position\n" + $"Manual cleanup may be required"); } - + return false; // Don't allow opening new position until next cycle } @@ -413,12 +430,17 @@ public class SpotBot : TradingBotBase // Update quantity to match actual token balance var actualTokenBalance = tokenBalance.Amount; - if (Math.Abs(internalPosition.Open.Quantity - actualTokenBalance) > 0.0001m) + var quantityTolerance = internalPosition.Open.Quantity * 0.006m; // 0.6% tolerance for slippage + var quantityDifference = Math.Abs(internalPosition.Open.Quantity - actualTokenBalance); + + if (quantityDifference > quantityTolerance) { await LogDebugAsync( $"🔄 Token Balance Mismatch\n" + $"Internal Quantity: `{internalPosition.Open.Quantity:F5}`\n" + $"Broker Balance: `{actualTokenBalance:F5}`\n" + + $"Difference: `{quantityDifference:F5}`\n" + + $"Tolerance (0.6%): `{quantityTolerance:F5}`\n" + $"Updating to match broker balance"); internalPosition.Open.Quantity = actualTokenBalance; positionForSignal.Open.Quantity = actualTokenBalance; @@ -478,10 +500,15 @@ public class SpotBot : TradingBotBase return; } - await LogDebugAsync( - $"âš ī¸ Position Status Check\n" + - $"Internal position `{internalPosition.Identifier}` shows Filled\n" + - $"But no token balance found on broker or in history\n" + + // Position is Filled but no token balance and not found in history + // This could be a zombie position - check if token balance exists via direct exchange call + await LogWarningAsync( + $"âš ī¸ Potential Zombie Position Detected\n" + + $"Position: `{internalPosition.Identifier}`\n" + + $"Status: Filled\n" + + $"Token balance: 0\n" + + $"Not found in exchange history\n" + + $"This position may have been closed externally or data is delayed\n" + $"Will retry verification on next cycle"); } } @@ -1089,8 +1116,10 @@ public class SpotBot : TradingBotBase { try { + // Prevent infinite retry loop - only attempt force close once + // The next bot cycle will handle verification await LogInformationAsync( - $"🔄 Force Closing Remaining Balance\n" + + $"🔄 Force Closing Remaining Balance (One-Time Attempt)\n" + $"Position: `{position.Identifier}`\n" + $"Ticker: {Config.Ticker}\n" + $"Remaining Balance: `{remainingBalance:F5}`"); @@ -1127,7 +1156,7 @@ public class SpotBot : TradingBotBase $"Cleared Balance: `{remainingBalance:F5}`\n" + $"Close Price: `${currentPrice:F2}`"); - // Verify one more time that balance is now cleared + // Verify one more time that balance is now cleared (without triggering another force close) await Task.Delay(2000); // Wait for swap to complete var finalBalance = await ServiceScopeHelpers.WithScopedService( @@ -1137,15 +1166,16 @@ public class SpotBot : TradingBotBase if (finalBalance is { Amount: > 0.0001m }) { await LogWarningAsync( - $"âš ī¸ Balance Still Remaining After Retry\n" + + $"âš ī¸ Balance Still Remaining After Force Close Attempt\n" + $"Position: `{position.Identifier}`\n" + $"Remaining: `{finalBalance.Amount:F5}`\n" + - $"Manual intervention may be required"); + $"This will be handled on the next bot cycle\n" + + $"Manual intervention may be required if issue persists"); } else { await LogInformationAsync( - $"✅ Balance Fully Cleared After Retry\n" + + $"✅ Balance Fully Cleared After Force Close\n" + $"Position: `{position.Identifier}`\n" + $"Final Balance: `{finalBalance?.Amount ?? 0:F5}`"); }