using Managing.Application.Abstractions.Services; using Managing.Domain.Backtests; using Microsoft.Extensions.Logging; namespace Managing.Application.Backtests; /// /// Service for executing genetic algorithm requests without Orleans dependencies. /// Extracted from GeneticBacktestGrain to be reusable in compute workers. /// public class GeneticExecutor { private readonly ILogger _logger; private readonly IGeneticService _geneticService; private readonly IAccountService _accountService; private readonly IWebhookService _webhookService; public GeneticExecutor( ILogger logger, IGeneticService geneticService, IAccountService accountService, IWebhookService webhookService) { _logger = logger; _geneticService = geneticService; _accountService = accountService; _webhookService = webhookService; } /// /// Executes a genetic algorithm request. /// /// The genetic request ID to process /// Optional callback for progress updates (0-100) /// Cancellation token /// The genetic algorithm result public async Task ExecuteAsync( string geneticRequestId, Func progressCallback = null, CancellationToken cancellationToken = default) { try { // Load the request by status lists and filter by ID (Pending first, then Failed for retries) var pending = await _geneticService.GetGeneticRequestsAsync(GeneticRequestStatus.Pending); var failed = await _geneticService.GetGeneticRequestsAsync(GeneticRequestStatus.Failed); var request = pending.Concat(failed).FirstOrDefault(r => r.RequestId == geneticRequestId); if (request == null) { _logger.LogWarning("[GeneticExecutor] Request {RequestId} not found among pending/failed.", geneticRequestId); throw new InvalidOperationException($"Genetic request {geneticRequestId} not found"); } _logger.LogInformation("[GeneticExecutor] Processing genetic request {RequestId} for user {UserId}", request.RequestId, request.User.Id); // Mark running request.Status = GeneticRequestStatus.Running; await _geneticService.UpdateGeneticRequestAsync(request); // Load user accounts if not already loaded if (request.User.Accounts == null || !request.User.Accounts.Any()) { request.User.Accounts = (await _accountService.GetAccountsByUserAsync(request.User)).ToList(); } // Create progress wrapper if callback provided Func wrappedProgressCallback = null; if (progressCallback != null) { wrappedProgressCallback = async (percentage) => { try { await progressCallback(percentage); } catch (Exception ex) { _logger.LogWarning(ex, "Error in progress callback for genetic request {RequestId}", geneticRequestId); } }; } // Run GA var result = await _geneticService.RunGeneticAlgorithm(request, cancellationToken); // Update final state request.Status = GeneticRequestStatus.Completed; request.CompletedAt = DateTime.UtcNow; request.BestFitness = result.BestFitness; request.BestIndividual = result.BestIndividual; request.ProgressInfo = result.ProgressInfo; await _geneticService.UpdateGeneticRequestAsync(request); _logger.LogInformation("[GeneticExecutor] Completed genetic request {RequestId}. Best Fitness: {BestFitness}", request.RequestId, result.BestFitness); // Send webhook notification if user has telegram channel if (!string.IsNullOrEmpty(request.User?.TelegramChannel)) { var message = $"✅ Genetic algorithm optimization completed for {request.Ticker} on {request.Timeframe}. " + $"Request ID: {request.RequestId}. " + $"Best Fitness: {result.BestFitness:F4}. " + $"Generations: {request.Generations}, Population: {request.PopulationSize}."; await _webhookService.SendMessage(message, request.User.TelegramChannel); } return result; } catch (Exception ex) { _logger.LogError(ex, "[GeneticExecutor] Error processing genetic request {RequestId}", geneticRequestId); // Try to mark as failed try { var running = await _geneticService.GetGeneticRequestsAsync(GeneticRequestStatus.Running); var req = running.FirstOrDefault(r => r.RequestId == geneticRequestId); if (req != null) { req.Status = GeneticRequestStatus.Failed; req.ErrorMessage = ex.Message; req.CompletedAt = DateTime.UtcNow; await _geneticService.UpdateGeneticRequestAsync(req); // Send webhook notification for failed genetic request if (!string.IsNullOrEmpty(req.User?.TelegramChannel)) { var message = $"❌ Genetic algorithm optimization failed for {req.Ticker} on {req.Timeframe}. " + $"Request ID: {req.RequestId}. " + $"Error: {ex.Message}"; await _webhookService.SendMessage(message, req.User.TelegramChannel); } } } catch (Exception updateEx) { _logger.LogError(updateEx, "[GeneticExecutor] Failed to update request status to Failed for {RequestId}", geneticRequestId); } throw; } } }