Implement force closing of remaining balance in SpotBot

- Added a new method to force close any remaining balance in a trading position if it was not fully closed.
- Enhanced logging to provide detailed information during the force close process, including current price checks and retry attempts.
- Implemented error handling to manage potential failures during the force close operation, ensuring manual intervention is flagged when necessary.
This commit is contained in:
2026-01-05 06:04:18 +07:00
parent d53b6eee20
commit 3c8242c88a

View File

@@ -1046,7 +1046,10 @@ public class SpotBot : TradingBotBase
$"Ticker: {Config.Ticker}\n" + $"Ticker: {Config.Ticker}\n" +
$"Remaining Token Balance: `{tokenBalance.Amount:F5}`\n" + $"Remaining Token Balance: `{tokenBalance.Amount:F5}`\n" +
$"Expected: `0` or less than `{maxDustAmount:F5}` (dust)\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 else
{ {
@@ -1064,4 +1067,81 @@ public class SpotBot : TradingBotBase
// Don't throw - this is just a verification step // 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<IExchangeService, decimal>(
_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<IExchangeService, IAccountService, ITradingService, Position>(
_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<IExchangeService, Balance?>(
_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");
}
}
} }