From ec88b124e62c45b98c495c0017b89bbdb55fa514 Mon Sep 17 00:00:00 2001 From: cryptooda Date: Sun, 16 Nov 2025 18:22:48 +0700 Subject: [PATCH] Refactor LiveTradingBotGrain to close all open positions before stopping the bot. Introduced CloseAllOpenPositionsAsync method to handle position closure and logging, ensuring a smoother stop process. Removed the previous check for open positions in the database. --- .../Bots/Grains/LiveTradingBotGrain.cs | 59 ++++++++++++++++--- 1 file changed, 50 insertions(+), 9 deletions(-) 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.