diff --git a/src/Managing.Application/Bots/SpotBot.cs b/src/Managing.Application/Bots/SpotBot.cs index 995be313..5a581f91 100644 --- a/src/Managing.Application/Bots/SpotBot.cs +++ b/src/Managing.Application/Bots/SpotBot.cs @@ -1046,7 +1046,10 @@ public class SpotBot : TradingBotBase $"Ticker: {Config.Ticker}\n" + $"Remaining Token Balance: `{tokenBalance.Amount:F5}`\n" + $"Expected: `0` or less than `{maxDustAmount:F5}` (dust)\n" + - $"Position may not have been fully closed on exchange"); + $"Attempting to force close remaining balance..."); + + // Attempt to force close the remaining balance + await ForceCloseRemainingBalance(closedPosition, tokenBalance.Amount); } else { @@ -1064,4 +1067,81 @@ public class SpotBot : TradingBotBase // Don't throw - this is just a verification step } } + + private async Task ForceCloseRemainingBalance(Position position, decimal remainingBalance) + { + try + { + await LogInformationAsync( + $"🔄 Force Closing Remaining Balance\n" + + $"Position: `{position.Identifier}`\n" + + $"Ticker: {Config.Ticker}\n" + + $"Remaining Balance: `{remainingBalance:F5}`"); + + // Get current price for closing + var currentPrice = await ServiceScopeHelpers.WithScopedService( + _scopeFactory, + async exchangeService => await exchangeService.GetCurrentPrice(Account, Config.Ticker)); + + if (currentPrice <= 0) + { + await LogWarningAsync( + $"❌ Cannot Force Close\n" + + $"Current price is invalid: `{currentPrice}`\n" + + $"Will retry on next cycle"); + return; + } + + // Create a new command to close the remaining balance + var retryCommand = new CloseSpotPositionCommand(position, position.AccountId, currentPrice); + + Position retryClosedPosition = await ServiceScopeHelpers + .WithScopedServices( + _scopeFactory, async (exchangeService, accountService, tradingService) => + await new CloseSpotPositionCommandHandler(exchangeService, accountService, tradingService) + .Handle(retryCommand)); + + if (retryClosedPosition != null && + (retryClosedPosition.Status == PositionStatus.Finished || retryClosedPosition.Status == PositionStatus.Flipped)) + { + await LogInformationAsync( + $"✅ Remaining Balance Force Closed Successfully\n" + + $"Position: `{position.Identifier}`\n" + + $"Cleared Balance: `{remainingBalance:F5}`\n" + + $"Close Price: `${currentPrice:F2}`"); + + // Verify one more time that balance is now cleared + await Task.Delay(2000); // Wait for swap to complete + + var finalBalance = await ServiceScopeHelpers.WithScopedService( + _scopeFactory, + async exchangeService => await exchangeService.GetBalance(Account, Config.Ticker)); + + if (finalBalance is { Amount: > 0.0001m }) + { + await LogWarningAsync( + $"⚠️ Balance Still Remaining After Retry\n" + + $"Position: `{position.Identifier}`\n" + + $"Remaining: `{finalBalance.Amount:F5}`\n" + + $"Manual intervention may be required"); + } + else + { + await LogInformationAsync( + $"✅ Balance Fully Cleared After Retry\n" + + $"Position: `{position.Identifier}`\n" + + $"Final Balance: `{finalBalance?.Amount ?? 0:F5}`"); + } + } + } + catch (Exception ex) + { + Logger.LogError(ex, "Error force closing remaining balance for position {PositionId}", position.Identifier); + await LogWarningAsync( + $"❌ Force Close Failed\n" + + $"Position: `{position.Identifier}`\n" + + $"Error: {ex.Message}\n" + + $"Manual intervention may be required"); + } + } } \ No newline at end of file