Update closing position on BotStop

This commit is contained in:
2025-11-23 23:31:34 +07:00
parent 6429501b70
commit 47bea1b9b7
2 changed files with 78 additions and 41 deletions

View File

@@ -317,44 +317,8 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
private async Task StopAsyncInternal(bool isRestarting, string? reason = null) private async Task StopAsyncInternal(bool isRestarting, string? reason = null)
{ {
// Only check for open positions if this is not part of a restart operation // Note: Position closing is now handled outside the grain in StopBotCommandHandler
if (!isRestarting) // to avoid Orleans timeout. This method only handles fast grain operations.
{
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
{
var botId = this.GetPrimaryKey();
var positions = await ServiceScopeHelpers.WithScopedService<ITradingService, IEnumerable<Position>>(
_scopeFactory,
async tradingService => await tradingService.GetPositionsByInitiatorIdentifierAsync(botId));
var stillOpenPositions =
positions?.Where(p => p.IsOpen() || p.Status.Equals(PositionStatus.New)).ToList() ??
new List<Position>();
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)));
}
else
{
_logger.LogInformation("Bot {GrainId} - all positions verified as closed", botId);
}
}
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",
this.GetPrimaryKey());
}
}
// The check is now against the registry status // The check is now against the registry status
var botRegistry = GrainFactory.GetGrain<ILiveBotRegistryGrain>(0); var botRegistry = GrainFactory.GetGrain<ILiveBotRegistryGrain>(0);

View File

@@ -4,8 +4,10 @@ using Managing.Application.Abstractions.Services;
using Managing.Application.ManageBot.Commands; using Managing.Application.ManageBot.Commands;
using Managing.Core; using Managing.Core;
using Managing.Domain.Accounts; using Managing.Domain.Accounts;
using Managing.Domain.Trades;
using MediatR; using MediatR;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums; using static Managing.Common.Enums;
namespace Managing.Application.ManageBot namespace Managing.Application.ManageBot
@@ -15,16 +17,27 @@ namespace Managing.Application.ManageBot
private readonly IBotService _botService; private readonly IBotService _botService;
private readonly IServiceScopeFactory _scopeFactory; private readonly IServiceScopeFactory _scopeFactory;
private readonly IGrainFactory _grainFactory; private readonly IGrainFactory _grainFactory;
private readonly ILogger<StopBotCommandHandler> _logger;
public StopBotCommandHandler(IBotService botService, IServiceScopeFactory scopeFactory, IGrainFactory grainFactory) public StopBotCommandHandler(
IBotService botService,
IServiceScopeFactory scopeFactory,
IGrainFactory grainFactory,
ILogger<StopBotCommandHandler> logger)
{ {
_botService = botService; _botService = botService;
_scopeFactory = scopeFactory; _scopeFactory = scopeFactory;
_grainFactory = grainFactory; _grainFactory = grainFactory;
_logger = logger;
} }
public async Task<BotStatus> Handle(StopBotCommand request, CancellationToken cancellationToken) public async Task<BotStatus> Handle(StopBotCommand request, CancellationToken cancellationToken)
{ {
// Close open positions BEFORE stopping the grain to avoid Orleans timeout
// This runs outside the grain context, so it won't cause grain timeouts
await CloseAllOpenPositionsAsync(request.Identifier);
// Now stop the grain (this is fast - just stops timer and updates registry)
var result = await _botService.StopBot(request.Identifier); var result = await _botService.StopBot(request.Identifier);
try try
@@ -49,10 +62,70 @@ namespace Managing.Application.ManageBot
{ {
// Log the error but don't fail the stop operation // Log the error but don't fail the stop operation
// The bot was successfully stopped, we just couldn't update the summary // The bot was successfully stopped, we just couldn't update the summary
Console.WriteLine($"Failed to update agent summary after stopping bot {request.Identifier}: {ex.Message}"); _logger.LogWarning(ex, "Failed to update agent summary after stopping bot {Identifier}", request.Identifier);
} }
return result; return result;
} }
/// <summary>
/// Closes all open positions for a bot. This runs outside the Orleans grain context
/// to avoid grain timeouts when closing positions takes a long time.
/// </summary>
private async Task CloseAllOpenPositionsAsync(Guid botId)
{
try
{
var positions = await ServiceScopeHelpers.WithScopedService<ITradingService, IEnumerable<Position>>(
_scopeFactory,
async tradingService => await tradingService.GetPositionsByInitiatorIdentifierAsync(botId));
var openPositions = positions?.Where(p => p.IsOpen() || p.Status.Equals(PositionStatus.New)).ToList() ??
new List<Position>();
if (!openPositions.Any())
{
_logger.LogDebug("Bot {BotId} has no open positions to close", botId);
return;
}
_logger.LogInformation(
"Closing {Count} open positions for bot {BotId}: {Positions}",
openPositions.Count, botId, string.Join(", ", openPositions.Select(p => p.Identifier)));
foreach (var position in openPositions)
{
try
{
_logger.LogInformation("Closing position {PositionId} for bot {BotId}", position.Identifier, botId);
await ClosePositionAsync(botId, position.Identifier);
_logger.LogInformation("Successfully closed position {PositionId} for bot {BotId}",
position.Identifier, botId);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to close position {PositionId} for bot {BotId}",
position.Identifier, botId);
// Continue with other positions even if one fails
}
}
_logger.LogInformation("Finished closing positions for bot {BotId}", botId);
}
catch (Exception ex)
{
_logger.LogError(ex, "Error closing open positions for bot {BotId}", botId);
// Don't throw - we want to continue with the stop process even if position closing fails
}
}
/// <summary>
/// Closes a single position using the grain's ClosePositionAsync method
/// </summary>
private async Task ClosePositionAsync(Guid botId, Guid positionId)
{
var grain = _grainFactory.GetGrain<ILiveTradingBotGrain>(botId);
await grain.ClosePositionAsync(positionId);
}
} }
} }