Update closing position on BotStop
This commit is contained in:
@@ -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);
|
||||||
@@ -607,7 +571,7 @@ public class LiveTradingBotGrain : Grain, ILiveTradingBotGrain, IRemindable
|
|||||||
if (_state.State.Config.IsForCopyTrading && _state.State.Config.MasterBotIdentifier.HasValue)
|
if (_state.State.Config.IsForCopyTrading && _state.State.Config.MasterBotIdentifier.HasValue)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("Checking copy trading authorization for bot {GrainId}", this.GetPrimaryKey());
|
_logger.LogInformation("Checking copy trading authorization for bot {GrainId}", this.GetPrimaryKey());
|
||||||
|
|
||||||
// Check if copy trading validation should be bypassed (for testing)
|
// Check if copy trading validation should be bypassed (for testing)
|
||||||
var enableValidation = Environment.GetEnvironmentVariable("ENABLE_COPY_TRADING_VALIDATION")?
|
var enableValidation = Environment.GetEnvironmentVariable("ENABLE_COPY_TRADING_VALIDATION")?
|
||||||
.Equals("true", StringComparison.OrdinalIgnoreCase) ?? true;
|
.Equals("true", StringComparison.OrdinalIgnoreCase) ?? true;
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user