Add genetic backtest to worker

This commit is contained in:
2025-11-09 03:32:08 +07:00
parent 7dba29c66f
commit 7e08e63dd1
30 changed files with 5056 additions and 232 deletions

View File

@@ -0,0 +1,149 @@
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;
}
}
}