150 lines
6.3 KiB
C#
150 lines
6.3 KiB
C#
using Managing.Application.Abstractions.Services;
|
|
using Managing.Domain.Backtests;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace Managing.Application.Backtests;
|
|
|
|
/// <summary>
|
|
/// Service for executing genetic algorithm requests without Orleans dependencies.
|
|
/// Extracted from GeneticBacktestGrain to be reusable in compute workers.
|
|
/// </summary>
|
|
public class GeneticExecutor
|
|
{
|
|
private readonly ILogger<GeneticExecutor> _logger;
|
|
private readonly IGeneticService _geneticService;
|
|
private readonly IAccountService _accountService;
|
|
private readonly IWebhookService _webhookService;
|
|
|
|
public GeneticExecutor(
|
|
ILogger<GeneticExecutor> logger,
|
|
IGeneticService geneticService,
|
|
IAccountService accountService,
|
|
IWebhookService webhookService)
|
|
{
|
|
_logger = logger;
|
|
_geneticService = geneticService;
|
|
_accountService = accountService;
|
|
_webhookService = webhookService;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Executes a genetic algorithm request.
|
|
/// </summary>
|
|
/// <param name="geneticRequestId">The genetic request ID to process</param>
|
|
/// <param name="progressCallback">Optional callback for progress updates (0-100)</param>
|
|
/// <param name="cancellationToken">Cancellation token</param>
|
|
/// <returns>The genetic algorithm result</returns>
|
|
public async Task<GeneticAlgorithmResult> ExecuteAsync(
|
|
string geneticRequestId,
|
|
Func<int, Task> 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<int, Task> 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;
|
|
}
|
|
}
|
|
}
|
|
|