diff --git a/src/Managing.Application/Bots/Grains/LiveTradingBotGrain.cs b/src/Managing.Application/Bots/Grains/LiveTradingBotGrain.cs index 5c9c0060..f936f98b 100644 --- a/src/Managing.Application/Bots/Grains/LiveTradingBotGrain.cs +++ b/src/Managing.Application/Bots/Grains/LiveTradingBotGrain.cs @@ -4,6 +4,7 @@ using Managing.Application.Abstractions.Services; using Managing.Application.Orleans; using Managing.Application.Shared; using Managing.Core; +using Managing.Core.Exceptions; using Managing.Domain.Accounts; using Managing.Domain.Bots; using Managing.Domain.Indicators; @@ -320,7 +321,7 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable if (!isRestarting) { await CloseAllOpenPositionsAsync(); - + // Verify positions are actually closed (even if CloseAllOpenPositionsAsync had timeouts/exceptions) // This ensures we don't report failure if positions were successfully closed despite timeouts try @@ -330,14 +331,16 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable _scopeFactory, async tradingService => await tradingService.GetPositionsByInitiatorIdentifierAsync(botId)); - var stillOpenPositions = positions?.Where(p => p.IsOpen() || p.Status.Equals(PositionStatus.New)).ToList() ?? - new List(); + var stillOpenPositions = + positions?.Where(p => p.IsOpen() || p.Status.Equals(PositionStatus.New)).ToList() ?? + new List(); if (stillOpenPositions.Any()) { _logger.LogWarning( "Bot {GrainId} still has {Count} open positions after closure attempt: {Positions}", - botId, stillOpenPositions.Count, string.Join(", ", stillOpenPositions.Select(p => p.Identifier))); + botId, stillOpenPositions.Count, + string.Join(", ", stillOpenPositions.Select(p => p.Identifier))); } else { @@ -347,7 +350,8 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable catch (Exception ex) { // Don't fail the stop operation if we can't verify positions - _logger.LogWarning(ex, "Could not verify position closure status for bot {GrainId}, continuing with stop", + _logger.LogWarning(ex, + "Could not verify position closure status for bot {GrainId}, continuing with stop", this.GetPrimaryKey()); } } @@ -388,11 +392,11 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable // Sync state from the volatile TradingBotBase before destroying it SyncStateFromBase(); - + // Critical: Stop the bot and clear the trading bot instance await _tradingBot?.StopBot(reason)!; _tradingBot = null; - + // Mark critical operations as succeeded criticalOperationsSucceeded = true; @@ -403,7 +407,8 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable } catch (Exception ex) { - _logger.LogWarning(ex, "Failed to write state for bot {GrainId}, but bot is stopped", this.GetPrimaryKey()); + _logger.LogWarning(ex, "Failed to write state for bot {GrainId}, but bot is stopped", + this.GetPrimaryKey()); } try @@ -412,7 +417,8 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable } catch (Exception ex) { - _logger.LogWarning(ex, "Failed to save bot status for bot {GrainId}, but bot is stopped", this.GetPrimaryKey()); + _logger.LogWarning(ex, "Failed to save bot status for bot {GrainId}, but bot is stopped", + this.GetPrimaryKey()); } try @@ -421,7 +427,8 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable } catch (Exception ex) { - _logger.LogWarning(ex, "Failed to update bot registry for bot {GrainId}, but bot is stopped", this.GetPrimaryKey()); + _logger.LogWarning(ex, "Failed to update bot registry for bot {GrainId}, but bot is stopped", + this.GetPrimaryKey()); } _logger.LogInformation("LiveTradingBotGrain {GrainId} stopped successfully", this.GetPrimaryKey()); @@ -441,16 +448,16 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable } _logger.LogError(ex, "Failed to stop LiveTradingBotGrain {GrainId}", this.GetPrimaryKey()); - + // Log Orleans-specific exceptions with additional context for debugging - if (Managing.Core.Exceptions.OrleansExceptionHelper.IsOrleansException(ex)) + if (OrleansExceptionHelper.IsOrleansException(ex)) { _logger.LogWarning( "Orleans exception detected during bot stop: {ExceptionType} - {Message}. " + "This may indicate a timeout or deadlock when closing positions.", ex.GetType().Name, ex.Message); } - + throw; } } @@ -599,6 +606,8 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable // Check if copy trading authorization is still valid if (_state.State.Config.IsForCopyTrading && _state.State.Config.MasterBotIdentifier.HasValue) { + _logger.LogInformation("Checking copy trading authorization for bot {GrainId}", this.GetPrimaryKey()); + // Check if copy trading validation should be bypassed (for testing) var enableValidation = Environment.GetEnvironmentVariable("ENABLE_COPY_TRADING_VALIDATION")? .Equals("true", StringComparison.OrdinalIgnoreCase) ?? true; @@ -703,6 +712,7 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable // TODO : Turn off the bot if an error occurs _logger.LogError(ex, "Error during bot execution cycle for LiveTradingBotGrain {GrainId}", this.GetPrimaryKey()); + SentrySdk.CaptureException(ex); } } @@ -1269,16 +1279,20 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable "Closing position {PositionId} for bot {GrainId} timed out after {Timeout} seconds. " + "Verifying if position was actually closed...", position.Identifier, botId, perPositionTimeout.TotalSeconds); - + // Verify if position was actually closed despite timeout try { - var updatedPositions = await ServiceScopeHelpers.WithScopedService>( - _scopeFactory, - async tradingService => await tradingService.GetPositionsByInitiatorIdentifierAsync(botId)); - - var updatedPosition = updatedPositions?.FirstOrDefault(p => p.Identifier == position.Identifier); - if (updatedPosition != null && !updatedPosition.IsOpen() && updatedPosition.Status != PositionStatus.New) + var updatedPositions = await ServiceScopeHelpers + .WithScopedService>( + _scopeFactory, + async tradingService => + await tradingService.GetPositionsByInitiatorIdentifierAsync(botId)); + + var updatedPosition = + updatedPositions?.FirstOrDefault(p => p.Identifier == position.Identifier); + if (updatedPosition != null && !updatedPosition.IsOpen() && + updatedPosition.Status != PositionStatus.New) { _logger.LogInformation( "Position {PositionId} was actually closed despite timeout. Status: {Status}", @@ -1289,7 +1303,8 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable { _logger.LogWarning( "Position {PositionId} may not be closed. Status: {Status}", - position.Identifier, updatedPosition != null ? updatedPosition.Status.ToString() : "null"); + position.Identifier, + updatedPosition != null ? updatedPosition.Status.ToString() : "null"); // Continue with other positions - we'll verify all positions at the end } } @@ -1299,7 +1314,7 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable "Could not verify position {PositionId} closure status after timeout", position.Identifier); } - + // Continue with other positions continue; } diff --git a/src/Managing.Application/Bots/TradingBotBase.cs b/src/Managing.Application/Bots/TradingBotBase.cs index 03f5c6b8..b66a5f60 100644 --- a/src/Managing.Application/Bots/TradingBotBase.cs +++ b/src/Managing.Application/Bots/TradingBotBase.cs @@ -254,10 +254,10 @@ public class TradingBotBase : ITradingBot ExecutionCount++; Logger.LogInformation( - "Bot Status {Name} - ServerDate: {ServerDate}, LastCandleDate: {LastCandleDate}, Signals: {SignalCount}, Executions: {ExecutionCount}, Positions: {PositionCount}", - Config.Name, DateTime.UtcNow, LastCandle?.Date, Signals.Count, ExecutionCount, Positions.Count); + "[{CopyTrading}][{AgentName}] Bot Status {Name} - ServerDate: {ServerDate}, LastCandleDate: {LastCandleDate}, Signals: {SignalCount}, Executions: {ExecutionCount}, Positions: {PositionCount}", + Config.IsForCopyTrading ? "CopyTrading" : "LiveTrading", Account.User.AgentName, Config.Name, DateTime.UtcNow, LastCandle?.Date, Signals.Count, ExecutionCount, Positions.Count); - Logger.LogInformation("[{Name}] Internal Positions : {Position}", Config.Name, + Logger.LogInformation("[{AgentName}] Internal Positions : {Position}", Account.User.AgentName, string.Join(", ", Positions.Values.Select(p => $"{p.SignalIdentifier} - Status: {p.Status}"))); }