From 15eba0fc3cce728eb30a4f891c8d36cc2d9e604d Mon Sep 17 00:00:00 2001 From: cryptooda Date: Sat, 4 Oct 2025 17:43:43 +0700 Subject: [PATCH] Prevent bot from stopping if position is open --- .../Bots/Grains/LiveTradingBotGrain.cs | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) diff --git a/src/Managing.Application/Bots/Grains/LiveTradingBotGrain.cs b/src/Managing.Application/Bots/Grains/LiveTradingBotGrain.cs index 509e3d50..8345e15a 100644 --- a/src/Managing.Application/Bots/Grains/LiveTradingBotGrain.cs +++ b/src/Managing.Application/Bots/Grains/LiveTradingBotGrain.cs @@ -248,6 +248,15 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable public async Task StopAsync() { + // Check if bot has open positions in database before allowing stop + var hasOpenPositions = await HasOpenPositionsInDatabaseAsync(); + if (hasOpenPositions) + { + _logger.LogWarning("Cannot stop LiveTradingBotGrain {GrainId} - bot has open positions in database", + this.GetPrimaryKey()); + throw new InvalidOperationException("Cannot stop bot while it has open positions. Please close all positions first."); + } + // The check is now against the registry status var botRegistry = GrainFactory.GetGrain(0); var botStatus = await botRegistry.GetBotStatus(this.GetPrimaryKey()); @@ -569,6 +578,15 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable { _logger.LogInformation("Restarting LiveTradingBotGrain {GrainId}", this.GetPrimaryKey()); + // Check if bot has open positions in database before allowing restart + var hasOpenPositions = await HasOpenPositionsInDatabaseAsync(); + if (hasOpenPositions) + { + _logger.LogWarning("Cannot restart LiveTradingBotGrain {GrainId} - bot has open positions in database", + this.GetPrimaryKey()); + throw new InvalidOperationException("Cannot restart bot while it has open positions. Please close all positions first."); + } + try { await StopAsync(); @@ -594,6 +612,15 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable public async Task DeleteAsync() { + // Check if bot has open positions in database before allowing deletion + var hasOpenPositions = await HasOpenPositionsInDatabaseAsync(); + if (hasOpenPositions) + { + _logger.LogWarning("Cannot delete LiveTradingBotGrain {GrainId} - bot has open positions in database", + this.GetPrimaryKey()); + throw new InvalidOperationException("Cannot delete bot while it has open positions. Please close all positions first."); + } + try { // Stop the bot first if it's running @@ -616,6 +643,11 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable _logger.LogInformation("LiveTradingBotGrain {GrainId} deleted successfully", this.GetPrimaryKey()); } + catch (InvalidOperationException) + { + // Re-throw InvalidOperationException from StopAsync (open positions check) + throw; + } catch (Exception ex) { _logger.LogError(ex, "Failed to delete LiveTradingBotGrain {GrainId}", this.GetPrimaryKey()); @@ -865,4 +897,38 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable return Task.FromResult(false); // Default to false on error to avoid blocking autoswap } } + + /// + /// 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. + /// + private async Task HasOpenPositionsInDatabaseAsync() + { + try + { + var botId = this.GetPrimaryKey(); + var positions = await ServiceScopeHelpers.WithScopedService>( + _scopeFactory, + async tradingService => await tradingService.GetPositionsByInitiatorIdentifierAsync(botId)); + + var hasOpenPositions = positions?.Any(p => !p.IsFinished()) ?? false; + _logger.LogDebug("Bot {GrainId} has open positions in database: {HasOpenPositions}", + botId, hasOpenPositions); + + if (hasOpenPositions) + { + var openPositions = positions?.Where(p => !p.IsFinished()).ToList() ?? new List(); + _logger.LogWarning("Bot {GrainId} cannot be stopped - has {Count} open positions in database: {Positions}", + botId, openPositions.Count, string.Join(", ", openPositions.Select(p => p.Identifier))); + } + + return hasOpenPositions; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error checking database positions for bot {GrainId}", this.GetPrimaryKey()); + // Default to true on error to err on the side of caution - don't stop bot if we can't verify + return true; + } + } } \ No newline at end of file