diff --git a/src/Managing.Application/Bots/Grains/LiveTradingBotGrain.cs b/src/Managing.Application/Bots/Grains/LiveTradingBotGrain.cs
index 0938cb6c..35dc7959 100644
--- a/src/Managing.Application/Bots/Grains/LiveTradingBotGrain.cs
+++ b/src/Managing.Application/Bots/Grains/LiveTradingBotGrain.cs
@@ -316,15 +316,7 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
// Only check for open positions if this is not part of a restart operation
if (!isRestarting)
{
- var hasOpenPositions = await HasOpenPositionsInDatabaseAsync();
- if (hasOpenPositions)
- {
- _logger.LogWarning(
- "Stopping bot {Name} while it still has open positions in database. Trading loop will stop but positions remain managed by system.",
- _tradingBot?.Config.Name);
- throw new InvalidOperationException(
- "Cannot stop bot while it has open positions. Please close all positions first.");
- }
+ await CloseAllOpenPositionsAsync();
}
// The check is now against the registry status
@@ -1079,6 +1071,55 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
}
}
+ ///
+ /// Closes all open positions for this bot before stopping
+ ///
+ private async Task CloseAllOpenPositionsAsync()
+ {
+ try
+ {
+ var botId = this.GetPrimaryKey();
+ var positions = await ServiceScopeHelpers.WithScopedService>(
+ _scopeFactory,
+ async tradingService => await tradingService.GetPositionsByInitiatorIdentifierAsync(botId));
+
+ var openPositions = positions?.Where(p => p.IsOpen() || p.Status.Equals(PositionStatus.New)).ToList() ?? new List();
+
+ if (openPositions.Any())
+ {
+ _logger.LogInformation(
+ "Bot {GrainId} has {Count} open positions that will be closed before stopping: {Positions}",
+ botId, openPositions.Count, string.Join(", ", openPositions.Select(p => p.Identifier)));
+
+ foreach (var position in openPositions)
+ {
+ try
+ {
+ _logger.LogInformation("Closing position {PositionId} for bot {GrainId}", position.Identifier, botId);
+ await ClosePositionAsync(position.Identifier);
+ _logger.LogInformation("Successfully closed position {PositionId} for bot {GrainId}", position.Identifier, botId);
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Failed to close position {PositionId} for bot {GrainId}", position.Identifier, botId);
+ // Continue with other positions even if one fails
+ }
+ }
+
+ _logger.LogInformation("Finished closing all open positions for bot {GrainId}", botId);
+ }
+ else
+ {
+ _logger.LogDebug("Bot {GrainId} has no open positions to close", botId);
+ }
+ }
+ catch (Exception ex)
+ {
+ _logger.LogError(ex, "Error closing open positions for bot {GrainId}", this.GetPrimaryKey());
+ // Don't throw here - we want to continue with the stop process even if position closing fails
+ }
+ }
+
///
/// Checks for open positions in database by bot identifier (initiator identifier).
/// This is the source of truth for preventing bot stop when there are unfinished positions.