Add genetic grain
This commit is contained in:
@@ -1,4 +1,5 @@
|
|||||||
using Orleans;
|
using Orleans;
|
||||||
|
using Orleans.Concurrency;
|
||||||
|
|
||||||
namespace Managing.Application.Abstractions.Grains;
|
namespace Managing.Application.Abstractions.Grains;
|
||||||
|
|
||||||
@@ -15,5 +16,6 @@ public interface IBundleBacktestGrain : IGrainWithGuidKey
|
|||||||
/// The RequestId is determined by the grain's primary key
|
/// The RequestId is determined by the grain's primary key
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns>Task representing the async operation</returns>
|
/// <returns>Task representing the async operation</returns>
|
||||||
|
[OneWay]
|
||||||
Task ProcessBundleRequestAsync();
|
Task ProcessBundleRequestAsync();
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using Orleans;
|
||||||
|
using Orleans.Concurrency;
|
||||||
|
|
||||||
|
namespace Managing.Application.Abstractions.Grains;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Orleans grain interface for Genetic Backtest operations.
|
||||||
|
/// Uses the genetic request ID as the primary key (string).
|
||||||
|
/// The grain processes a single genetic request identified by its primary key.
|
||||||
|
/// </summary>
|
||||||
|
public interface IGeneticBacktestGrain : IGrainWithStringKey
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Processes the genetic backtest request for this grain's RequestId.
|
||||||
|
/// The RequestId is determined by the grain's primary key.
|
||||||
|
/// </summary>
|
||||||
|
[OneWay]
|
||||||
|
Task ProcessGeneticRequestAsync();
|
||||||
|
}
|
||||||
@@ -64,7 +64,7 @@ public class BacktestTradingBotGrain : Grain, IBacktestTradingBotGrain
|
|||||||
|
|
||||||
// Create a fresh TradingBotBase instance for this backtest
|
// Create a fresh TradingBotBase instance for this backtest
|
||||||
var tradingBot = await CreateTradingBotInstance(config);
|
var tradingBot = await CreateTradingBotInstance(config);
|
||||||
tradingBot.Account = user.Accounts.First(a => a.Name == config.AccountName);
|
tradingBot.Account = user.Accounts.First();
|
||||||
|
|
||||||
var totalCandles = candles.Count;
|
var totalCandles = candles.Count;
|
||||||
var currentCandle = 0;
|
var currentCandle = 0;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using GeneticSharp;
|
using GeneticSharp;
|
||||||
|
using Managing.Application.Abstractions.Grains;
|
||||||
using Managing.Application.Abstractions.Repositories;
|
using Managing.Application.Abstractions.Repositories;
|
||||||
using Managing.Application.Abstractions.Services;
|
using Managing.Application.Abstractions.Services;
|
||||||
using Managing.Core;
|
using Managing.Core;
|
||||||
@@ -26,6 +27,7 @@ public class GeneticService : IGeneticService
|
|||||||
private readonly ILogger<GeneticService> _logger;
|
private readonly ILogger<GeneticService> _logger;
|
||||||
private readonly IMessengerService _messengerService;
|
private readonly IMessengerService _messengerService;
|
||||||
private readonly IServiceScopeFactory _serviceScopeFactory;
|
private readonly IServiceScopeFactory _serviceScopeFactory;
|
||||||
|
private readonly IGrainFactory _grainFactory;
|
||||||
|
|
||||||
// Predefined parameter ranges for each indicator (matching backtestGenetic.tsx)
|
// Predefined parameter ranges for each indicator (matching backtestGenetic.tsx)
|
||||||
public static readonly Dictionary<string, (double min, double max)> ParameterRanges = new()
|
public static readonly Dictionary<string, (double min, double max)> ParameterRanges = new()
|
||||||
@@ -193,13 +195,15 @@ public class GeneticService : IGeneticService
|
|||||||
IBacktester backtester,
|
IBacktester backtester,
|
||||||
ILogger<GeneticService> logger,
|
ILogger<GeneticService> logger,
|
||||||
IMessengerService messengerService,
|
IMessengerService messengerService,
|
||||||
IServiceScopeFactory serviceScopeFactory)
|
IServiceScopeFactory serviceScopeFactory,
|
||||||
|
IGrainFactory grainFactory)
|
||||||
{
|
{
|
||||||
_geneticRepository = geneticRepository;
|
_geneticRepository = geneticRepository;
|
||||||
_backtester = backtester;
|
_backtester = backtester;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
_messengerService = messengerService;
|
_messengerService = messengerService;
|
||||||
_serviceScopeFactory = serviceScopeFactory;
|
_serviceScopeFactory = serviceScopeFactory;
|
||||||
|
_grainFactory = grainFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public GeneticRequest CreateGeneticRequest(
|
public GeneticRequest CreateGeneticRequest(
|
||||||
@@ -240,6 +244,18 @@ public class GeneticService : IGeneticService
|
|||||||
};
|
};
|
||||||
|
|
||||||
_geneticRepository.InsertGeneticRequestForUser(user, geneticRequest);
|
_geneticRepository.InsertGeneticRequestForUser(user, geneticRequest);
|
||||||
|
|
||||||
|
// Trigger Orleans grain to process this request asynchronously
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var grain = _grainFactory.GetGrain<IGeneticBacktestGrain>(id);
|
||||||
|
_ = grain.ProcessGeneticRequestAsync();
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogWarning(ex, "Failed to trigger GeneticBacktestGrain for request {RequestId}", id);
|
||||||
|
}
|
||||||
|
|
||||||
return geneticRequest;
|
return geneticRequest;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -888,7 +904,7 @@ public class TradingBotFitness : IFitness
|
|||||||
// Run backtest using scoped service to avoid DbContext concurrency issues
|
// Run backtest using scoped service to avoid DbContext concurrency issues
|
||||||
var lightBacktest = ServiceScopeHelpers.WithScopedService<IBacktester, LightBacktest>(
|
var lightBacktest = ServiceScopeHelpers.WithScopedService<IBacktester, LightBacktest>(
|
||||||
_serviceScopeFactory,
|
_serviceScopeFactory,
|
||||||
backtester => backtester.RunTradingBotBacktest(
|
async backtester => await backtester.RunTradingBotBacktest(
|
||||||
config,
|
config,
|
||||||
_request.StartDate,
|
_request.StartDate,
|
||||||
_request.EndDate,
|
_request.EndDate,
|
||||||
@@ -896,12 +912,9 @@ public class TradingBotFitness : IFitness
|
|||||||
true,
|
true,
|
||||||
false, // Don't include candles
|
false, // Don't include candles
|
||||||
_request.RequestId,
|
_request.RequestId,
|
||||||
new
|
new GeneticBacktestMetadata(currentGeneration, _request.RequestId)
|
||||||
{
|
|
||||||
generation = currentGeneration
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
).Result;
|
).GetAwaiter().GetResult();
|
||||||
|
|
||||||
// Calculate multi-objective fitness based on backtest results
|
// Calculate multi-objective fitness based on backtest results
|
||||||
var fitness = CalculateFitness(lightBacktest, config);
|
var fitness = CalculateFitness(lightBacktest, config);
|
||||||
@@ -912,7 +925,7 @@ public class TradingBotFitness : IFitness
|
|||||||
{
|
{
|
||||||
_logger.LogWarning("Fitness evaluation failed for chromosome: {Message}", ex.Message);
|
_logger.LogWarning("Fitness evaluation failed for chromosome: {Message}", ex.Message);
|
||||||
// Return low fitness for failed backtests
|
// Return low fitness for failed backtests
|
||||||
return 0.1;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
90
src/Managing.Application/Grains/GeneticBacktestGrain.cs
Normal file
90
src/Managing.Application/Grains/GeneticBacktestGrain.cs
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
using Managing.Application.Abstractions.Grains;
|
||||||
|
using Managing.Application.Abstractions.Services;
|
||||||
|
using Managing.Core;
|
||||||
|
using Managing.Domain.Accounts;
|
||||||
|
using Managing.Domain.Backtests;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Orleans.Concurrency;
|
||||||
|
|
||||||
|
namespace Managing.Application.Grains;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Stateless worker grain for processing genetic backtest requests.
|
||||||
|
/// Uses the genetic request ID (string) as the primary key.
|
||||||
|
/// </summary>
|
||||||
|
[StatelessWorker]
|
||||||
|
public class GeneticBacktestGrain : Grain, IGeneticBacktestGrain
|
||||||
|
{
|
||||||
|
private readonly ILogger<GeneticBacktestGrain> _logger;
|
||||||
|
private readonly IServiceScopeFactory _scopeFactory;
|
||||||
|
|
||||||
|
public GeneticBacktestGrain(
|
||||||
|
ILogger<GeneticBacktestGrain> logger,
|
||||||
|
IServiceScopeFactory scopeFactory)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_scopeFactory = scopeFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ProcessGeneticRequestAsync()
|
||||||
|
{
|
||||||
|
var requestId = this.GetPrimaryKeyString();
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var scope = _scopeFactory.CreateScope();
|
||||||
|
var geneticService = scope.ServiceProvider.GetRequiredService<IGeneticService>();
|
||||||
|
|
||||||
|
// 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 == requestId);
|
||||||
|
|
||||||
|
if (request == null)
|
||||||
|
{
|
||||||
|
_logger.LogWarning("[GeneticBacktestGrain] Request {RequestId} not found among pending/failed.",
|
||||||
|
requestId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark running
|
||||||
|
request.Status = GeneticRequestStatus.Running;
|
||||||
|
await geneticService.UpdateGeneticRequestAsync(request);
|
||||||
|
|
||||||
|
request.User.Accounts = await ServiceScopeHelpers.WithScopedService<IAccountService, List<Account>>(_scopeFactory,
|
||||||
|
async accountService => (await accountService.GetAccountsByUserAsync(request.User)).ToList());
|
||||||
|
|
||||||
|
// Run GA
|
||||||
|
var result = await geneticService.RunGeneticAlgorithm(request, CancellationToken.None);
|
||||||
|
|
||||||
|
// 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("[GeneticBacktestGrain] Completed request {RequestId}", requestId);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
_logger.LogError(ex, "[GeneticBacktestGrain] Error processing request {RequestId}", requestId);
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using var scope = _scopeFactory.CreateScope();
|
||||||
|
var geneticService = scope.ServiceProvider.GetRequiredService<IGeneticService>();
|
||||||
|
var running = await geneticService.GetGeneticRequestsAsync(GeneticRequestStatus.Running);
|
||||||
|
var req = running.FirstOrDefault(r => r.RequestId == requestId) ?? new GeneticRequest(requestId);
|
||||||
|
req.Status = GeneticRequestStatus.Failed;
|
||||||
|
req.ErrorMessage = ex.Message;
|
||||||
|
req.CompletedAt = DateTime.UtcNow;
|
||||||
|
await geneticService.UpdateGeneticRequestAsync(req);
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
26
src/Managing.Domain/Backtests/GeneticBacktestMetadata.cs
Normal file
26
src/Managing.Domain/Backtests/GeneticBacktestMetadata.cs
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
using Orleans;
|
||||||
|
|
||||||
|
namespace Managing.Domain.Backtests;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Metadata class for genetic algorithm backtests.
|
||||||
|
/// This class is designed to be Orleans-serializable.
|
||||||
|
/// </summary>
|
||||||
|
[GenerateSerializer]
|
||||||
|
public class GeneticBacktestMetadata
|
||||||
|
{
|
||||||
|
[Id(0)] public int Generation { get; set; }
|
||||||
|
[Id(1)] public string RequestId { get; set; } = string.Empty;
|
||||||
|
[Id(2)] public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
|
public GeneticBacktestMetadata()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public GeneticBacktestMetadata(int generation, string requestId = null)
|
||||||
|
{
|
||||||
|
Generation = generation;
|
||||||
|
RequestId = requestId ?? string.Empty;
|
||||||
|
CreatedAt = DateTime.UtcNow;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,4 +23,5 @@ public class LightBacktest
|
|||||||
[Id(10)] public double? SharpeRatio { get; set; }
|
[Id(10)] public double? SharpeRatio { get; set; }
|
||||||
[Id(11)] public double Score { get; set; }
|
[Id(11)] public double Score { get; set; }
|
||||||
[Id(12)] public string ScoreMessage { get; set; } = string.Empty;
|
[Id(12)] public string ScoreMessage { get; set; } = string.Empty;
|
||||||
|
[Id(13)] public object Metadata { get; set; }
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user