Add Genetic workers

This commit is contained in:
2025-07-10 19:15:57 +07:00
parent c2c181e417
commit 0b4f2173e0
20 changed files with 1752 additions and 3 deletions

View File

@@ -36,5 +36,6 @@
"WorkerSpotlight": false,
"WorkerTraderWatcher": false,
"WorkerLeaderboard": false,
"WorkerFundingRatesWatcher": false
"WorkerFundingRatesWatcher": false,
"WorkerGeneticAlgorithm": true
}

View File

@@ -226,6 +226,120 @@ public class BacktestController : BaseController
}
}
/// <summary>
/// Runs a genetic algorithm optimization with the specified configuration.
/// This endpoint saves the genetic request to the database and returns the request ID.
/// The actual genetic algorithm execution will be handled by a background service.
/// </summary>
/// <param name="request">The genetic algorithm request containing configuration and parameters.</param>
/// <returns>The genetic request with ID for tracking progress.</returns>
[HttpPost]
[Route("Genetic")]
public async Task<ActionResult<GeneticRequest>> RunGenetic([FromBody] RunGeneticRequest request)
{
if (request == null)
{
return BadRequest("Genetic request is required");
}
if (request.EligibleIndicators == null || !request.EligibleIndicators.Any())
{
return BadRequest("At least one eligible indicator is required");
}
if (request.StartDate >= request.EndDate)
{
return BadRequest("Start date must be before end date");
}
if (request.PopulationSize <= 0 || request.Generations <= 0)
{
return BadRequest("Population size and generations must be greater than 0");
}
if (request.MutationRate < 0 || request.MutationRate > 1)
{
return BadRequest("Mutation rate must be between 0 and 1");
}
try
{
var user = await GetUser();
// Create genetic request using the Backtester service
var geneticRequest = _backtester.CreateGeneticRequest(
user,
request.Ticker,
request.Timeframe,
request.StartDate,
request.EndDate,
request.Balance,
request.PopulationSize,
request.Generations,
request.MutationRate,
request.SelectionMethod,
request.ElitismPercentage,
request.MaxTakeProfit,
request.EligibleIndicators);
// TODO: Trigger background genetic algorithm execution
// This will be implemented in the next step with a background service
return Ok(geneticRequest);
}
catch (Exception ex)
{
return StatusCode(500, $"Error creating genetic request: {ex.Message}");
}
}
/// <summary>
/// Retrieves all genetic requests for the authenticated user.
/// </summary>
/// <returns>A list of genetic requests with their current status.</returns>
[HttpGet]
[Route("Genetic")]
public async Task<ActionResult<IEnumerable<GeneticRequest>>> GetGeneticRequests()
{
var user = await GetUser();
var geneticRequests = _backtester.GetGeneticRequestsByUser(user);
return Ok(geneticRequests);
}
/// <summary>
/// Retrieves a specific genetic request by ID for the authenticated user.
/// </summary>
/// <param name="id">The ID of the genetic request to retrieve.</param>
/// <returns>The requested genetic request with current status and results.</returns>
[HttpGet]
[Route("Genetic/{id}")]
public async Task<ActionResult<GeneticRequest>> GetGeneticRequest(string id)
{
var user = await GetUser();
var geneticRequest = _backtester.GetGeneticRequestByIdForUser(user, id);
if (geneticRequest == null)
{
return NotFound($"Genetic request with ID {id} not found or doesn't belong to the current user.");
}
return Ok(geneticRequest);
}
/// <summary>
/// Deletes a specific genetic request by ID for the authenticated user.
/// </summary>
/// <param name="id">The ID of the genetic request to delete.</param>
/// <returns>An ActionResult indicating the outcome of the operation.</returns>
[HttpDelete]
[Route("Genetic/{id}")]
public async Task<ActionResult> DeleteGeneticRequest(string id)
{
var user = await GetUser();
_backtester.DeleteGeneticRequestByIdForUser(user, id);
return Ok();
}
/// <summary>
/// Notifies subscribers about the backtesting results via SignalR.
/// </summary>

View File

@@ -0,0 +1,69 @@
using static Managing.Common.Enums;
namespace Managing.Api.Models.Requests;
/// <summary>
/// Request model for running a genetic algorithm optimization
/// </summary>
public class RunGeneticRequest
{
/// <summary>
/// The ticker to optimize for
/// </summary>
public Ticker Ticker { get; set; }
/// <summary>
/// The timeframe to use for optimization
/// </summary>
public Timeframe Timeframe { get; set; }
/// <summary>
/// The start date for the backtest period
/// </summary>
public DateTime StartDate { get; set; }
/// <summary>
/// The end date for the backtest period
/// </summary>
public DateTime EndDate { get; set; }
/// <summary>
/// The starting balance for the backtest
/// </summary>
public decimal Balance { get; set; }
/// <summary>
/// The population size for the genetic algorithm
/// </summary>
public int PopulationSize { get; set; }
/// <summary>
/// The number of generations to evolve
/// </summary>
public int Generations { get; set; }
/// <summary>
/// The mutation rate (0.0 - 1.0)
/// </summary>
public double MutationRate { get; set; }
/// <summary>
/// The selection method to use
/// </summary>
public string SelectionMethod { get; set; } = "tournament";
/// <summary>
/// The percentage of elite individuals to preserve (1-50)
/// </summary>
public int ElitismPercentage { get; set; } = 15;
/// <summary>
/// The maximum take profit percentage
/// </summary>
public double MaxTakeProfit { get; set; } = 4.0;
/// <summary>
/// The list of eligible indicators to include in optimization
/// </summary>
public List<IndicatorType> EligibleIndicators { get; set; } = new();
}

View File

@@ -0,0 +1,57 @@
using Managing.Domain.Backtests;
using Managing.Domain.Users;
namespace Managing.Application.Abstractions.Repositories;
/// <summary>
/// Repository interface for genetic algorithm requests
/// </summary>
public interface IGeneticRepository
{
/// <summary>
/// Inserts a genetic request for a user
/// </summary>
/// <param name="user">The user</param>
/// <param name="geneticRequest">The genetic request to insert</param>
void InsertGeneticRequestForUser(User user, GeneticRequest geneticRequest);
/// <summary>
/// Gets all genetic requests for a user
/// </summary>
/// <param name="user">The user to get requests for</param>
/// <returns>Collection of genetic requests</returns>
IEnumerable<GeneticRequest> GetGeneticRequestsByUser(User user);
/// <summary>
/// Gets a specific genetic request by ID for a user
/// </summary>
/// <param name="user">The user</param>
/// <param name="id">The request ID</param>
/// <returns>The genetic request or null if not found</returns>
GeneticRequest GetGeneticRequestByIdForUser(User user, string id);
/// <summary>
/// Updates a genetic request
/// </summary>
/// <param name="geneticRequest">The genetic request to update</param>
void UpdateGeneticRequest(GeneticRequest geneticRequest);
/// <summary>
/// Deletes a genetic request by ID for a user
/// </summary>
/// <param name="user">The user</param>
/// <param name="id">The request ID</param>
void DeleteGeneticRequestByIdForUser(User user, string id);
/// <summary>
/// Deletes all genetic requests for a user
/// </summary>
/// <param name="user">The user</param>
void DeleteAllGeneticRequestsForUser(User user);
/// <summary>
/// Gets all pending genetic requests across all users
/// </summary>
/// <returns>Collection of pending genetic requests</returns>
IEnumerable<GeneticRequest> GetPendingGeneticRequests();
}

View File

@@ -2,6 +2,7 @@
using Managing.Domain.Bots;
using Managing.Domain.Candles;
using Managing.Domain.Users;
using static Managing.Common.Enums;
namespace Managing.Application.Abstractions.Services
{
@@ -48,5 +49,26 @@ namespace Managing.Application.Abstractions.Services
Backtest GetBacktestByIdForUser(User user, string id);
bool DeleteBacktestByUser(User user, string id);
bool DeleteBacktestsByUser(User user);
// Genetic algorithm request methods
GeneticRequest CreateGeneticRequest(
User user,
Ticker ticker,
Timeframe timeframe,
DateTime startDate,
DateTime endDate,
decimal balance,
int populationSize,
int generations,
double mutationRate,
string selectionMethod,
int elitismPercentage,
double maxTakeProfit,
List<IndicatorType> eligibleIndicators);
IEnumerable<GeneticRequest> GetGeneticRequestsByUser(User user);
GeneticRequest GetGeneticRequestByIdForUser(User user, string id);
void UpdateGeneticRequest(GeneticRequest geneticRequest);
void DeleteGeneticRequestByIdForUser(User user, string id);
}
}

View File

@@ -0,0 +1,77 @@
using Managing.Domain.Backtests;
using Managing.Domain.Users;
using static Managing.Common.Enums;
namespace Managing.Application.Abstractions.Services;
/// <summary>
/// Service for managing genetic algorithm requests
/// </summary>
public interface IGeneticService
{
/// <summary>
/// Creates a new genetic request for a user
/// </summary>
/// <param name="user">The user creating the request</param>
/// <param name="ticker">The ticker to optimize for</param>
/// <param name="timeframe">The timeframe to use</param>
/// <param name="startDate">The start date for the backtest period</param>
/// <param name="endDate">The end date for the backtest period</param>
/// <param name="balance">The starting balance</param>
/// <param name="populationSize">The population size for the genetic algorithm</param>
/// <param name="generations">The number of generations to evolve</param>
/// <param name="mutationRate">The mutation rate (0.0 - 1.0)</param>
/// <param name="selectionMethod">The selection method to use</param>
/// <param name="elitismPercentage">The percentage of elite individuals to preserve</param>
/// <param name="maxTakeProfit">The maximum take profit percentage</param>
/// <param name="eligibleIndicators">The list of eligible indicators</param>
/// <returns>The created genetic request</returns>
GeneticRequest CreateGeneticRequest(
User user,
Ticker ticker,
Timeframe timeframe,
DateTime startDate,
DateTime endDate,
decimal balance,
int populationSize,
int generations,
double mutationRate,
string selectionMethod,
int elitismPercentage,
double maxTakeProfit,
List<IndicatorType> eligibleIndicators);
/// <summary>
/// Gets all genetic requests for a user
/// </summary>
/// <param name="user">The user to get requests for</param>
/// <returns>Collection of genetic requests</returns>
IEnumerable<GeneticRequest> GetGeneticRequestsByUser(User user);
/// <summary>
/// Gets a specific genetic request by ID for a user
/// </summary>
/// <param name="user">The user</param>
/// <param name="id">The request ID</param>
/// <returns>The genetic request or null if not found</returns>
GeneticRequest GetGeneticRequestByIdForUser(User user, string id);
/// <summary>
/// Updates a genetic request
/// </summary>
/// <param name="geneticRequest">The genetic request to update</param>
void UpdateGeneticRequest(GeneticRequest geneticRequest);
/// <summary>
/// Deletes a genetic request by ID for a user
/// </summary>
/// <param name="user">The user</param>
/// <param name="id">The request ID</param>
void DeleteGeneticRequestByIdForUser(User user, string id);
/// <summary>
/// Gets all pending genetic requests across all users
/// </summary>
/// <returns>Collection of pending genetic requests</returns>
IEnumerable<GeneticRequest> GetPendingGeneticRequests();
}

View File

@@ -0,0 +1,131 @@
using Managing.Application.Abstractions.Services;
using Managing.Application.Workers.Abstractions;
using Managing.Domain.Backtests;
using Microsoft.Extensions.Logging;
using static Managing.Common.Enums;
namespace Managing.Application.Workers;
/// <summary>
/// Worker that processes genetic algorithm requests
/// </summary>
public class GeneticAlgorithmWorker : BaseWorker<GeneticAlgorithmWorker>
{
private readonly IGeneticService _geneticService;
private readonly IBacktester _backtester;
public GeneticAlgorithmWorker(
ILogger<GeneticAlgorithmWorker> logger,
IWorkerService workerService,
IGeneticService geneticService,
IBacktester backtester)
: base(WorkerType.GeneticAlgorithm, logger, TimeSpan.FromMinutes(5), workerService)
{
_geneticService = geneticService;
_backtester = backtester;
}
protected override async Task Run(CancellationToken cancellationToken)
{
try
{
_logger.LogInformation("[GeneticAlgorithm] Starting genetic algorithm processing");
// TODO: Implement method to get pending genetic requests
// For now, we'll create a placeholder for the genetic algorithm logic
await ProcessPendingGeneticRequests(cancellationToken);
_logger.LogInformation("[GeneticAlgorithm] Completed genetic algorithm processing");
}
catch (Exception ex)
{
_logger.LogError(ex, "[GeneticAlgorithm] Error during genetic algorithm processing");
throw;
}
}
private async Task ProcessPendingGeneticRequests(CancellationToken cancellationToken)
{
try
{
// Get pending genetic requests from the repository
var pendingRequests = _geneticService.GetPendingGeneticRequests();
if (!pendingRequests.Any())
{
_logger.LogInformation("[GeneticAlgorithm] No pending genetic requests found");
return;
}
_logger.LogInformation("[GeneticAlgorithm] Found {Count} pending genetic requests", pendingRequests.Count());
foreach (var request in pendingRequests)
{
try
{
_logger.LogInformation("[GeneticAlgorithm] Processing request {RequestId}", request.RequestId);
// Update status to Running
request.Status = GeneticRequestStatus.Running;
_geneticService.UpdateGeneticRequest(request);
// Run genetic algorithm
var results = await RunGeneticAlgorithm(request, cancellationToken);
// Update request with results
request.Status = GeneticRequestStatus.Completed;
request.CompletedAt = DateTime.UtcNow;
request.BestFitness = results.BestFitness;
request.BestIndividual = results.BestIndividual;
request.ProgressInfo = results.ProgressInfo;
_geneticService.UpdateGeneticRequest(request);
_logger.LogInformation("[GeneticAlgorithm] Successfully completed request {RequestId}", request.RequestId);
}
catch (Exception ex)
{
request.Status = GeneticRequestStatus.Failed;
request.ErrorMessage = ex.Message;
request.CompletedAt = DateTime.UtcNow;
_geneticService.UpdateGeneticRequest(request);
_logger.LogError(ex, "[GeneticAlgorithm] Error processing request {RequestId}", request.RequestId);
}
}
}
catch (Exception ex)
{
_logger.LogError(ex, "[GeneticAlgorithm] Error retrieving pending genetic requests");
throw;
}
}
private async Task<GeneticAlgorithmResult> RunGeneticAlgorithm(GeneticRequest request, CancellationToken cancellationToken)
{
// TODO: Implement the actual genetic algorithm
// This is where the genetic algorithm logic will be implemented
_logger.LogInformation("[GeneticAlgorithm] Placeholder: Would run genetic algorithm for request {RequestId}", request.RequestId);
// Simulate some processing time
await Task.Delay(1000, cancellationToken);
return new GeneticAlgorithmResult
{
BestFitness = 0.85,
BestIndividual = "placeholder_individual",
ProgressInfo = "{\"generation\": 10, \"best_fitness\": 0.85}"
};
}
}
/// <summary>
/// Result of a genetic algorithm run
/// </summary>
public class GeneticAlgorithmResult
{
public double BestFitness { get; set; }
public string BestIndividual { get; set; } = string.Empty;
public string ProgressInfo { get; set; } = string.Empty;
}

View File

@@ -27,6 +27,7 @@ namespace Managing.Application.Backtesting
private readonly IScenarioService _scenarioService;
private readonly IAccountService _accountService;
private readonly IMessengerService _messengerService;
private readonly IGeneticService _geneticService;
public Backtester(
IExchangeService exchangeService,
@@ -35,7 +36,8 @@ namespace Managing.Application.Backtesting
ILogger<Backtester> logger,
IScenarioService scenarioService,
IAccountService accountService,
IMessengerService messengerService)
IMessengerService messengerService,
IGeneticService geneticService)
{
_exchangeService = exchangeService;
_botFactory = botFactory;
@@ -44,6 +46,7 @@ namespace Managing.Application.Backtesting
_scenarioService = scenarioService;
_accountService = accountService;
_messengerService = messengerService;
_geneticService = geneticService;
}
public Backtest RunSimpleBotBacktest(Workflow workflow, bool save = false)
@@ -456,5 +459,83 @@ namespace Managing.Application.Backtesting
return false;
}
}
/// <summary>
/// Creates a new genetic algorithm request
/// </summary>
/// <param name="user">The user creating the request</param>
/// <param name="ticker">The ticker to optimize for</param>
/// <param name="timeframe">The timeframe to use</param>
/// <param name="startDate">The start date for the backtest period</param>
/// <param name="endDate">The end date for the backtest period</param>
/// <param name="balance">The starting balance</param>
/// <param name="populationSize">The population size for the genetic algorithm</param>
/// <param name="generations">The number of generations to evolve</param>
/// <param name="mutationRate">The mutation rate (0.0 - 1.0)</param>
/// <param name="selectionMethod">The selection method to use</param>
/// <param name="elitismPercentage">The percentage of elite individuals to preserve</param>
/// <param name="maxTakeProfit">The maximum take profit percentage</param>
/// <param name="eligibleIndicators">The list of eligible indicators</param>
/// <returns>The created genetic request</returns>
public GeneticRequest CreateGeneticRequest(
User user,
Ticker ticker,
Timeframe timeframe,
DateTime startDate,
DateTime endDate,
decimal balance,
int populationSize,
int generations,
double mutationRate,
string selectionMethod,
int elitismPercentage,
double maxTakeProfit,
List<IndicatorType> eligibleIndicators)
{
return _geneticService.CreateGeneticRequest(
user, ticker, timeframe, startDate, endDate, balance,
populationSize, generations, mutationRate, selectionMethod,
elitismPercentage, maxTakeProfit, eligibleIndicators);
}
/// <summary>
/// Gets all genetic requests for a user
/// </summary>
/// <param name="user">The user to get requests for</param>
/// <returns>Collection of genetic requests</returns>
public IEnumerable<GeneticRequest> GetGeneticRequestsByUser(User user)
{
return _geneticService.GetGeneticRequestsByUser(user);
}
/// <summary>
/// Gets a specific genetic request by ID for a user
/// </summary>
/// <param name="user">The user</param>
/// <param name="id">The request ID</param>
/// <returns>The genetic request or null if not found</returns>
public GeneticRequest GetGeneticRequestByIdForUser(User user, string id)
{
return _geneticService.GetGeneticRequestByIdForUser(user, id);
}
/// <summary>
/// Updates a genetic request
/// </summary>
/// <param name="geneticRequest">The genetic request to update</param>
public void UpdateGeneticRequest(GeneticRequest geneticRequest)
{
_geneticService.UpdateGeneticRequest(geneticRequest);
}
/// <summary>
/// Deletes a genetic request by ID for a user
/// </summary>
/// <param name="user">The user</param>
/// <param name="id">The request ID</param>
public void DeleteGeneticRequestByIdForUser(User user, string id)
{
_geneticService.DeleteGeneticRequestByIdForUser(user, id);
}
}
}

View File

@@ -0,0 +1,82 @@
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
using Managing.Domain.Backtests;
using Managing.Domain.Users;
using static Managing.Common.Enums;
namespace Managing.Application;
/// <summary>
/// Service implementation for managing genetic algorithm requests
/// </summary>
public class GeneticService : IGeneticService
{
private readonly IGeneticRepository _geneticRepository;
public GeneticService(IGeneticRepository geneticRepository)
{
_geneticRepository = geneticRepository;
}
public GeneticRequest CreateGeneticRequest(
User user,
Ticker ticker,
Timeframe timeframe,
DateTime startDate,
DateTime endDate,
decimal balance,
int populationSize,
int generations,
double mutationRate,
string selectionMethod,
int elitismPercentage,
double maxTakeProfit,
List<IndicatorType> eligibleIndicators)
{
var id = Guid.NewGuid().ToString(); // Generate unique GUID
var geneticRequest = new GeneticRequest(id)
{
Ticker = ticker,
Timeframe = timeframe,
StartDate = startDate,
EndDate = endDate,
Balance = balance,
PopulationSize = populationSize,
Generations = generations,
MutationRate = mutationRate,
SelectionMethod = selectionMethod,
ElitismPercentage = elitismPercentage,
MaxTakeProfit = maxTakeProfit,
EligibleIndicators = eligibleIndicators,
Status = GeneticRequestStatus.Pending
};
_geneticRepository.InsertGeneticRequestForUser(user, geneticRequest);
return geneticRequest;
}
public IEnumerable<GeneticRequest> GetGeneticRequestsByUser(User user)
{
return _geneticRepository.GetGeneticRequestsByUser(user);
}
public GeneticRequest GetGeneticRequestByIdForUser(User user, string id)
{
return _geneticRepository.GetGeneticRequestByIdForUser(user, id);
}
public void UpdateGeneticRequest(GeneticRequest geneticRequest)
{
_geneticRepository.UpdateGeneticRequest(geneticRequest);
}
public void DeleteGeneticRequestByIdForUser(User user, string id)
{
_geneticRepository.DeleteGeneticRequestByIdForUser(user, id);
}
public IEnumerable<GeneticRequest> GetPendingGeneticRequests()
{
return _geneticRepository.GetPendingGeneticRequests();
}
}

View File

@@ -2,6 +2,7 @@
using Discord.Commands;
using Discord.WebSocket;
using FluentValidation;
using Managing.Application;
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
@@ -77,6 +78,7 @@ public static class ApiBootstrap
services.AddSingleton<IUserService, UserService>();
services.AddSingleton<IWorkflowService, WorkflowService>();
services.AddSingleton<IFlowFactory, FlowFactory>();
services.AddSingleton<IGeneticService, GeneticService>();
services.AddTransient<ICommandHandler<OpenPositionRequest, Position>, OpenPositionCommandHandler>();
services.AddTransient<ICommandHandler<ClosePositionCommand, Position>, ClosePositionCommandHandler>();
@@ -128,6 +130,7 @@ public static class ApiBootstrap
services.AddTransient<ICandleRepository, CandleRepository>();
services.AddTransient<IAccountRepository, AccountRepository>();
services.AddTransient<IBacktestRepository, BacktestRepository>();
services.AddTransient<IGeneticRepository, GeneticRepository>();
services.AddTransient<ITradingRepository, TradingRepository>();
services.AddTransient<ISettingsRepository, SettingsRepository>();
services.AddTransient<IUserRepository, UserRepository>();

View File

@@ -2,6 +2,7 @@
using Binance.Net.Interfaces.Clients;
using Kraken.Net.Clients;
using Kraken.Net.Interfaces.Clients;
using Managing.Application;
using Managing.Application.Abstractions;
using Managing.Application.Abstractions.Repositories;
using Managing.Application.Abstractions.Services;
@@ -69,6 +70,7 @@ public static class WorkersBootstrap
services.AddSingleton<IBacktester, Backtester>();
services.AddSingleton<IBotService, BotService>();
services.AddSingleton<ISynthPredictionService, SynthPredictionService>();
services.AddSingleton<IGeneticService, GeneticService>();
services.AddTransient<ICommandHandler<OpenPositionRequest, Position>, OpenPositionCommandHandler>();
services.AddTransient<ICommandHandler<ClosePositionCommand, Position>, ClosePositionCommandHandler>();
@@ -129,6 +131,11 @@ public static class WorkersBootstrap
services.AddHostedService<FundingRatesWatcher>();
}
if (configuration.GetValue<bool>("WorkerGeneticAlgorithm", false))
{
services.AddHostedService<GeneticAlgorithmWorker>();
}
return services;
}
@@ -167,6 +174,7 @@ public static class WorkersBootstrap
services.AddTransient<IBotRepository, BotRepository>();
services.AddTransient<IUserRepository, UserRepository>();
services.AddTransient<ISynthRepository, SynthRepository>();
services.AddTransient<IGeneticRepository, GeneticRepository>();
// Cache
services.AddDistributedMemoryCache();

View File

@@ -382,7 +382,8 @@ public static class Enums
Noobiesboard,
BotManager,
FundingRatesWatcher,
BalanceTracking
BalanceTracking,
GeneticAlgorithm
}
public enum WorkflowUsage

View File

@@ -0,0 +1,188 @@
using System.ComponentModel.DataAnnotations;
using Managing.Domain.Users;
using static Managing.Common.Enums;
namespace Managing.Domain.Backtests;
/// <summary>
/// Domain model for genetic algorithm optimization requests
/// </summary>
public class GeneticRequest
{
public GeneticRequest()
{
RequestId = Guid.NewGuid().ToString();
CreatedAt = DateTime.UtcNow;
Status = GeneticRequestStatus.Pending;
Results = new List<Backtest>();
}
/// <summary>
/// Constructor that allows setting a specific ID
/// </summary>
/// <param name="requestId">The specific ID to use</param>
public GeneticRequest(string requestId)
{
RequestId = requestId;
CreatedAt = DateTime.UtcNow;
Status = GeneticRequestStatus.Pending;
Results = new List<Backtest>();
}
/// <summary>
/// Unique identifier for the genetic request
/// </summary>
[Required]
public string RequestId { get; set; }
/// <summary>
/// The user who created this request
/// </summary>
[Required]
public User User { get; set; }
/// <summary>
/// When the request was created
/// </summary>
[Required]
public DateTime CreatedAt { get; set; }
/// <summary>
/// When the request was completed (if completed)
/// </summary>
public DateTime? CompletedAt { get; set; }
/// <summary>
/// Current status of the genetic request
/// </summary>
[Required]
public GeneticRequestStatus Status { get; set; }
/// <summary>
/// The ticker to optimize for
/// </summary>
[Required]
public Ticker Ticker { get; set; }
/// <summary>
/// The timeframe to use for optimization
/// </summary>
[Required]
public Timeframe Timeframe { get; set; }
/// <summary>
/// The start date for the backtest period
/// </summary>
[Required]
public DateTime StartDate { get; set; }
/// <summary>
/// The end date for the backtest period
/// </summary>
[Required]
public DateTime EndDate { get; set; }
/// <summary>
/// The starting balance for the backtest
/// </summary>
[Required]
public decimal Balance { get; set; }
/// <summary>
/// The population size for the genetic algorithm
/// </summary>
[Required]
public int PopulationSize { get; set; }
/// <summary>
/// The number of generations to evolve
/// </summary>
[Required]
public int Generations { get; set; }
/// <summary>
/// The mutation rate (0.0 - 1.0)
/// </summary>
[Required]
public double MutationRate { get; set; }
/// <summary>
/// The selection method to use
/// </summary>
[Required]
public string SelectionMethod { get; set; } = "tournament";
/// <summary>
/// The percentage of elite individuals to preserve (1-50)
/// </summary>
[Required]
public int ElitismPercentage { get; set; } = 15;
/// <summary>
/// The maximum take profit percentage
/// </summary>
[Required]
public double MaxTakeProfit { get; set; } = 4.0;
/// <summary>
/// The list of eligible indicators to include in optimization
/// </summary>
[Required]
public List<IndicatorType> EligibleIndicators { get; set; } = new();
/// <summary>
/// The results of the genetic algorithm optimization
/// </summary>
public List<Backtest> Results { get; set; } = new();
/// <summary>
/// The best fitness score achieved
/// </summary>
public double? BestFitness { get; set; }
/// <summary>
/// The best individual found
/// </summary>
public string? BestIndividual { get; set; }
/// <summary>
/// Error message if the request failed
/// </summary>
public string? ErrorMessage { get; set; }
/// <summary>
/// Progress information (JSON serialized)
/// </summary>
public string? ProgressInfo { get; set; }
}
/// <summary>
/// Status of a genetic request
/// </summary>
public enum GeneticRequestStatus
{
/// <summary>
/// Request is pending execution
/// </summary>
Pending,
/// <summary>
/// Request is currently being processed
/// </summary>
Running,
/// <summary>
/// Request completed successfully
/// </summary>
Completed,
/// <summary>
/// Request failed with an error
/// </summary>
Failed,
/// <summary>
/// Request was cancelled
/// </summary>
Cancelled
}

View File

@@ -0,0 +1,90 @@
using Managing.Application.Abstractions.Repositories;
using Managing.Domain.Backtests;
using Managing.Domain.Users;
using Managing.Infrastructure.Databases.MongoDb;
using Managing.Infrastructure.Databases.MongoDb.Abstractions;
using Managing.Infrastructure.Databases.MongoDb.Collections;
namespace Managing.Infrastructure.Databases;
public class GeneticRepository : IGeneticRepository
{
private readonly IMongoRepository<GeneticRequestDto> _geneticRequestRepository;
public GeneticRepository(IMongoRepository<GeneticRequestDto> geneticRequestRepository)
{
_geneticRequestRepository = geneticRequestRepository;
}
public void InsertGeneticRequestForUser(User user, GeneticRequest geneticRequest)
{
geneticRequest.User = user;
_geneticRequestRepository.InsertOne(MongoMappers.Map(geneticRequest));
}
public IEnumerable<GeneticRequest> GetGeneticRequestsByUser(User user)
{
var geneticRequests = _geneticRequestRepository.AsQueryable()
.Where(gr => gr.User.Name == user.Name)
.OrderByDescending(gr => gr.CreatedAt)
.ToList();
return geneticRequests.Select(gr => MongoMappers.Map(gr));
}
public GeneticRequest GetGeneticRequestByIdForUser(User user, string id)
{
var geneticRequest = _geneticRequestRepository.FindById(id);
// Check if genetic request exists and belongs to the user
if (geneticRequest != null && geneticRequest.User != null && geneticRequest.User.Name == user.Name)
{
return MongoMappers.Map(geneticRequest);
}
return null;
}
public void UpdateGeneticRequest(GeneticRequest geneticRequest)
{
var existingRequest = _geneticRequestRepository.FindOne(gr => gr.RequestId == geneticRequest.RequestId);
if (existingRequest != null)
{
var updatedDto = MongoMappers.Map(geneticRequest);
updatedDto.Id = existingRequest.Id; // Preserve the MongoDB ObjectId
_geneticRequestRepository.ReplaceOne(updatedDto);
}
}
public void DeleteGeneticRequestByIdForUser(User user, string id)
{
var geneticRequest = _geneticRequestRepository.FindOne(gr => gr.RequestId == id);
if (geneticRequest != null && geneticRequest.User != null && geneticRequest.User.Name == user.Name)
{
_geneticRequestRepository.DeleteById(geneticRequest.Id.ToString());
}
}
public void DeleteAllGeneticRequestsForUser(User user)
{
var geneticRequests = _geneticRequestRepository.AsQueryable()
.Where(gr => gr.User != null && gr.User.Name == user.Name)
.ToList();
foreach (var geneticRequest in geneticRequests)
{
_geneticRequestRepository.DeleteById(geneticRequest.Id.ToString());
}
}
public IEnumerable<GeneticRequest> GetPendingGeneticRequests()
{
var pendingRequests = _geneticRequestRepository.AsQueryable()
.Where(gr => gr.Status == "Pending")
.OrderBy(gr => gr.CreatedAt)
.ToList();
return pendingRequests.Select(gr => MongoMappers.Map(gr));
}
}

View File

@@ -0,0 +1,31 @@
using Managing.Infrastructure.Databases.MongoDb.Attributes;
using Managing.Infrastructure.Databases.MongoDb.Configurations;
using static Managing.Common.Enums;
namespace Managing.Infrastructure.Databases.MongoDb.Collections
{
[BsonCollection("GeneticRequests")]
public class GeneticRequestDto : Document
{
public string RequestId { get; set; }
public UserDto User { get; set; }
public DateTime? CompletedAt { get; set; }
public string Status { get; set; }
public Ticker Ticker { get; set; }
public Timeframe Timeframe { get; set; }
public DateTime StartDate { get; set; }
public DateTime EndDate { get; set; }
public decimal Balance { get; set; }
public int PopulationSize { get; set; }
public int Generations { get; set; }
public double MutationRate { get; set; }
public string SelectionMethod { get; set; }
public int ElitismPercentage { get; set; }
public double MaxTakeProfit { get; set; }
public List<IndicatorType> EligibleIndicators { get; set; } = new();
public double? BestFitness { get; set; }
public string? BestIndividual { get; set; }
public string? ErrorMessage { get; set; }
public string? ProgressInfo { get; set; }
}
}

View File

@@ -182,6 +182,71 @@ public static class MongoMappers
#endregion
#region Genetic Requests
internal static GeneticRequest Map(GeneticRequestDto dto)
{
if (dto == null)
return null;
return new GeneticRequest
{
RequestId = dto.RequestId,
User = Map(dto.User),
CreatedAt = dto.CreatedAt,
CompletedAt = dto.CompletedAt,
Status = Enum.Parse<GeneticRequestStatus>(dto.Status),
Ticker = dto.Ticker,
Timeframe = dto.Timeframe,
StartDate = dto.StartDate,
EndDate = dto.EndDate,
Balance = dto.Balance,
PopulationSize = dto.PopulationSize,
Generations = dto.Generations,
MutationRate = dto.MutationRate,
SelectionMethod = dto.SelectionMethod,
ElitismPercentage = dto.ElitismPercentage,
MaxTakeProfit = dto.MaxTakeProfit,
EligibleIndicators = dto.EligibleIndicators,
BestFitness = dto.BestFitness,
BestIndividual = dto.BestIndividual,
ErrorMessage = dto.ErrorMessage,
ProgressInfo = dto.ProgressInfo
};
}
internal static GeneticRequestDto Map(GeneticRequest geneticRequest)
{
if (geneticRequest == null)
return null;
return new GeneticRequestDto
{
RequestId = geneticRequest.RequestId,
User = Map(geneticRequest.User),
CompletedAt = geneticRequest.CompletedAt,
Status = geneticRequest.Status.ToString(),
Ticker = geneticRequest.Ticker,
Timeframe = geneticRequest.Timeframe,
StartDate = geneticRequest.StartDate,
EndDate = geneticRequest.EndDate,
Balance = geneticRequest.Balance,
PopulationSize = geneticRequest.PopulationSize,
Generations = geneticRequest.Generations,
MutationRate = geneticRequest.MutationRate,
SelectionMethod = geneticRequest.SelectionMethod,
ElitismPercentage = geneticRequest.ElitismPercentage,
MaxTakeProfit = geneticRequest.MaxTakeProfit,
EligibleIndicators = geneticRequest.EligibleIndicators,
BestFitness = geneticRequest.BestFitness,
BestIndividual = geneticRequest.BestIndividual,
ErrorMessage = geneticRequest.ErrorMessage,
ProgressInfo = geneticRequest.ProgressInfo
};
}
#endregion
#region Candles
public static Candle Map(CandleDto candle)

View File

@@ -536,6 +536,161 @@ export class BacktestClient extends AuthorizedApiBase {
}
return Promise.resolve<Backtest>(null as any);
}
backtest_RunGenetic(request: RunGeneticRequest): Promise<GeneticRequest> {
let url_ = this.baseUrl + "/Backtest/Genetic";
url_ = url_.replace(/[?&]$/, "");
const content_ = JSON.stringify(request);
let options_: RequestInit = {
body: content_,
method: "POST",
headers: {
"Content-Type": "application/json",
"Accept": "application/json"
}
};
return this.transformOptions(options_).then(transformedOptions_ => {
return this.http.fetch(url_, transformedOptions_);
}).then((_response: Response) => {
return this.processBacktest_RunGenetic(_response);
});
}
protected processBacktest_RunGenetic(response: Response): Promise<GeneticRequest> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200) {
return response.text().then((_responseText) => {
let result200: any = null;
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as GeneticRequest;
return result200;
});
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<GeneticRequest>(null as any);
}
backtest_GetGeneticRequests(): Promise<GeneticRequest[]> {
let url_ = this.baseUrl + "/Backtest/Genetic";
url_ = url_.replace(/[?&]$/, "");
let options_: RequestInit = {
method: "GET",
headers: {
"Accept": "application/json"
}
};
return this.transformOptions(options_).then(transformedOptions_ => {
return this.http.fetch(url_, transformedOptions_);
}).then((_response: Response) => {
return this.processBacktest_GetGeneticRequests(_response);
});
}
protected processBacktest_GetGeneticRequests(response: Response): Promise<GeneticRequest[]> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200) {
return response.text().then((_responseText) => {
let result200: any = null;
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as GeneticRequest[];
return result200;
});
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<GeneticRequest[]>(null as any);
}
backtest_GetGeneticRequest(id: string): Promise<GeneticRequest> {
let url_ = this.baseUrl + "/Backtest/Genetic/{id}";
if (id === undefined || id === null)
throw new Error("The parameter 'id' must be defined.");
url_ = url_.replace("{id}", encodeURIComponent("" + id));
url_ = url_.replace(/[?&]$/, "");
let options_: RequestInit = {
method: "GET",
headers: {
"Accept": "application/json"
}
};
return this.transformOptions(options_).then(transformedOptions_ => {
return this.http.fetch(url_, transformedOptions_);
}).then((_response: Response) => {
return this.processBacktest_GetGeneticRequest(_response);
});
}
protected processBacktest_GetGeneticRequest(response: Response): Promise<GeneticRequest> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200) {
return response.text().then((_responseText) => {
let result200: any = null;
result200 = _responseText === "" ? null : JSON.parse(_responseText, this.jsonParseReviver) as GeneticRequest;
return result200;
});
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<GeneticRequest>(null as any);
}
backtest_DeleteGeneticRequest(id: string): Promise<FileResponse> {
let url_ = this.baseUrl + "/Backtest/Genetic/{id}";
if (id === undefined || id === null)
throw new Error("The parameter 'id' must be defined.");
url_ = url_.replace("{id}", encodeURIComponent("" + id));
url_ = url_.replace(/[?&]$/, "");
let options_: RequestInit = {
method: "DELETE",
headers: {
"Accept": "application/octet-stream"
}
};
return this.transformOptions(options_).then(transformedOptions_ => {
return this.http.fetch(url_, transformedOptions_);
}).then((_response: Response) => {
return this.processBacktest_DeleteGeneticRequest(_response);
});
}
protected processBacktest_DeleteGeneticRequest(response: Response): Promise<FileResponse> {
const status = response.status;
let _headers: any = {}; if (response.headers && response.headers.forEach) { response.headers.forEach((v: any, k: any) => _headers[k] = v); };
if (status === 200 || status === 206) {
const contentDisposition = response.headers ? response.headers.get("content-disposition") : undefined;
let fileNameMatch = contentDisposition ? /filename\*=(?:(\\?['"])(.*?)\1|(?:[^\s]+'.*?')?([^;\n]*))/g.exec(contentDisposition) : undefined;
let fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[3] || fileNameMatch[2] : undefined;
if (fileName) {
fileName = decodeURIComponent(fileName);
} else {
fileNameMatch = contentDisposition ? /filename="?([^"]*?)"?(;|$)/g.exec(contentDisposition) : undefined;
fileName = fileNameMatch && fileNameMatch.length > 1 ? fileNameMatch[1] : undefined;
}
return response.blob().then(blob => { return { fileName: fileName, data: blob, status: status, headers: _headers }; });
} else if (status !== 200 && status !== 204) {
return response.text().then((_responseText) => {
return throwException("An unexpected server error occurred.", status, _responseText, _headers);
});
}
return Promise.resolve<FileResponse>(null as any);
}
}
export class BotClient extends AuthorizedApiBase {
@@ -3513,6 +3668,54 @@ export interface MoneyManagementRequest {
leverage: number;
}
export interface GeneticRequest {
requestId: string;
user: User;
createdAt: Date;
completedAt?: Date | null;
status: GeneticRequestStatus;
ticker: Ticker;
timeframe: Timeframe;
startDate: Date;
endDate: Date;
balance: number;
populationSize: number;
generations: number;
mutationRate: number;
selectionMethod: string;
elitismPercentage: number;
maxTakeProfit: number;
eligibleIndicators: IndicatorType[];
results?: Backtest[] | null;
bestFitness?: number | null;
bestIndividual?: string | null;
errorMessage?: string | null;
progressInfo?: string | null;
}
export enum GeneticRequestStatus {
Pending = "Pending",
Running = "Running",
Completed = "Completed",
Failed = "Failed",
Cancelled = "Cancelled",
}
export interface RunGeneticRequest {
ticker?: Ticker;
timeframe?: Timeframe;
startDate?: Date;
endDate?: Date;
balance?: number;
populationSize?: number;
generations?: number;
mutationRate?: number;
selectionMethod?: string | null;
elitismPercentage?: number;
maxTakeProfit?: number;
eligibleIndicators?: IndicatorType[] | null;
}
export interface StartBotRequest {
config?: TradingBotConfigRequest | null;
}

View File

@@ -653,6 +653,54 @@ export interface MoneyManagementRequest {
leverage: number;
}
export interface GeneticRequest {
requestId: string;
user: User;
createdAt: Date;
completedAt?: Date | null;
status: GeneticRequestStatus;
ticker: Ticker;
timeframe: Timeframe;
startDate: Date;
endDate: Date;
balance: number;
populationSize: number;
generations: number;
mutationRate: number;
selectionMethod: string;
elitismPercentage: number;
maxTakeProfit: number;
eligibleIndicators: IndicatorType[];
results?: Backtest[] | null;
bestFitness?: number | null;
bestIndividual?: string | null;
errorMessage?: string | null;
progressInfo?: string | null;
}
export enum GeneticRequestStatus {
Pending = "Pending",
Running = "Running",
Completed = "Completed",
Failed = "Failed",
Cancelled = "Cancelled",
}
export interface RunGeneticRequest {
ticker?: Ticker;
timeframe?: Timeframe;
startDate?: Date;
endDate?: Date;
balance?: number;
populationSize?: number;
generations?: number;
mutationRate?: number;
selectionMethod?: string | null;
elitismPercentage?: number;
maxTakeProfit?: number;
eligibleIndicators?: IndicatorType[] | null;
}
export interface StartBotRequest {
config?: TradingBotConfigRequest | null;
}

View File

@@ -6,6 +6,7 @@ import BacktestPlayground from './backtestPlayground'
import BacktestScanner from './backtestScanner'
import BacktestUpload from './backtestUpload'
import BacktestGenetic from './backtestGenetic'
import BacktestGeneticBundle from './backtestGeneticBundle'
import type {TabsType} from '../../global/type.tsx'
// Tabs Array
@@ -30,6 +31,11 @@ const tabs: TabsType = [
index: 4,
label: 'Genetic',
},
{
Component: BacktestGeneticBundle,
index: 5,
label: 'GeneticBundle',
},
]
const Backtest: React.FC = () => {

View File

@@ -0,0 +1,472 @@
import React, {useState} from 'react'
import {useForm} from 'react-hook-form'
import {useQuery} from '@tanstack/react-query'
import useApiUrlStore from '../../app/store/apiStore'
import {
BacktestClient,
type GeneticRequest,
IndicatorType,
type RunGeneticRequest,
Ticker,
Timeframe,
} from '../../generated/ManagingApi'
import {Toast} from '../../components/mollecules'
// Available Indicator Types
const ALL_INDICATORS = [
IndicatorType.RsiDivergence,
IndicatorType.RsiDivergenceConfirm,
IndicatorType.MacdCross,
IndicatorType.EmaCross,
IndicatorType.ThreeWhiteSoldiers,
IndicatorType.SuperTrend,
IndicatorType.ChandelierExit,
IndicatorType.EmaTrend,
IndicatorType.StochRsiTrend,
IndicatorType.Stc,
IndicatorType.StDev,
IndicatorType.LaggingStc,
IndicatorType.SuperTrendCrossEma,
IndicatorType.DualEmaCross,
]
// Form Interface
interface GeneticBundleFormData {
ticker: Ticker
timeframe: Timeframe
startDate: string
endDate: string
balance: number
populationSize: number
generations: number
mutationRate: number
selectionMethod: string
elitismPercentage: number
maxTakeProfit: number
eligibleIndicators: IndicatorType[]
}
const BacktestGeneticBundle: React.FC = () => {
const {apiUrl} = useApiUrlStore()
const backtestClient = new BacktestClient({}, apiUrl)
// State
const [isSubmitting, setIsSubmitting] = useState(false)
const [selectedIndicators, setSelectedIndicators] = useState<IndicatorType[]>(ALL_INDICATORS)
const [geneticRequests, setGeneticRequests] = useState<GeneticRequest[]>([])
// Form setup
const {register, handleSubmit, watch, setValue, formState: {errors}} = useForm<GeneticBundleFormData>({
defaultValues: {
ticker: Ticker.BTC,
timeframe: Timeframe.OneHour,
startDate: getDefaultDateRange().startDate,
endDate: getDefaultDateRange().endDate,
balance: 10000,
populationSize: 10,
generations: 5,
mutationRate: 0.3,
selectionMethod: 'tournament',
elitismPercentage: 10,
maxTakeProfit: 2.0,
eligibleIndicators: ALL_INDICATORS,
}
})
const formValues = watch()
// Get default date range (last 30 days)
function getDefaultDateRange() {
const endDate = new Date()
const startDate = new Date()
startDate.setDate(startDate.getDate() - 30)
return {
startDate: startDate.toISOString().split('T')[0],
endDate: endDate.toISOString().split('T')[0],
}
}
// Fetch existing genetic requests
const {data: existingRequests, refetch: refetchRequests} = useQuery({
queryKey: ['geneticRequests'],
queryFn: async () => {
try {
const requests = await backtestClient.backtest_GetGeneticRequests()
setGeneticRequests(requests)
return requests
} catch (error) {
console.error('Error fetching genetic requests:', error)
return []
}
},
})
// Handle form submission
const onSubmit = async (data: GeneticBundleFormData) => {
if (selectedIndicators.length === 0) {
new Toast('Please select at least one indicator', false)
return
}
setIsSubmitting(true)
const t = new Toast('Creating genetic request...')
try {
const request: RunGeneticRequest = {
ticker: data.ticker,
timeframe: data.timeframe,
startDate: new Date(data.startDate),
endDate: new Date(data.endDate),
balance: data.balance,
populationSize: data.populationSize,
generations: data.generations,
mutationRate: data.mutationRate,
selectionMethod: data.selectionMethod,
elitismPercentage: data.elitismPercentage,
maxTakeProfit: data.maxTakeProfit,
eligibleIndicators: selectedIndicators,
}
const geneticRequest = await backtestClient.backtest_RunGenetic(request)
t.update('success', `Genetic request created successfully! ID: ${geneticRequest.requestId}`)
// Refresh the list of genetic requests
await refetchRequests()
// Reset form
setValue('ticker', Ticker.BTC)
setValue('timeframe', Timeframe.OneHour)
setValue('startDate', getDefaultDateRange().startDate)
setValue('endDate', getDefaultDateRange().endDate)
setValue('balance', 10000)
setValue('populationSize', 10)
setValue('generations', 5)
setValue('mutationRate', 0.3)
setValue('selectionMethod', 'tournament')
setValue('elitismPercentage', 10)
setValue('maxTakeProfit', 2.0)
setSelectedIndicators(ALL_INDICATORS)
} catch (error) {
console.error('Error creating genetic request:', error)
t.update('error', 'Failed to create genetic request. Please try again.')
} finally {
setIsSubmitting(false)
}
}
// Handle indicator selection
const handleIndicatorToggle = (indicator: IndicatorType) => {
setSelectedIndicators(prev => {
if (prev.includes(indicator)) {
return prev.filter(i => i !== indicator)
} else {
return [...prev, indicator]
}
})
}
// Get status badge color
const getStatusBadgeColor = (status: string) => {
switch (status) {
case 'Pending':
return 'badge-warning'
case 'Running':
return 'badge-info'
case 'Completed':
return 'badge-success'
case 'Failed':
return 'badge-error'
case 'Cancelled':
return 'badge-neutral'
default:
return 'badge-neutral'
}
}
return (
<div className="space-y-6">
<div className="card bg-base-100 shadow-xl">
<div className="card-body">
<h2 className="card-title">Genetic Algorithm Bundle</h2>
<p className="text-sm text-gray-600 mb-4">
Create a genetic algorithm request that will be processed in the background.
The algorithm will optimize trading parameters and indicator combinations.
</p>
<form onSubmit={handleSubmit(onSubmit)} className="space-y-4">
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="form-control">
<label className="label">
<span className="label-text">Ticker</span>
</label>
<select
className="select select-bordered w-full"
{...register('ticker')}
>
{Object.values(Ticker).map(ticker => (
<option key={ticker} value={ticker}>{ticker}</option>
))}
</select>
</div>
<div className="form-control">
<label className="label">
<span className="label-text">Timeframe</span>
</label>
<select
className="select select-bordered w-full"
{...register('timeframe')}
>
{Object.values(Timeframe).map(tf => (
<option key={tf} value={tf}>{tf}</option>
))}
</select>
</div>
<div className="form-control">
<label className="label">
<span className="label-text">Start Date</span>
</label>
<input
type="date"
className="input input-bordered w-full"
{...register('startDate')}
/>
</div>
<div className="form-control">
<label className="label">
<span className="label-text">End Date</span>
</label>
<input
type="date"
className="input input-bordered w-full"
{...register('endDate')}
/>
</div>
<div className="form-control">
<label className="label">
<span className="label-text">Balance</span>
</label>
<input
type="number"
className="input input-bordered w-full"
{...register('balance', {valueAsNumber: true})}
/>
</div>
<div className="form-control">
<label className="label">
<span className="label-text">Population Size</span>
</label>
<input
type="number"
min="5"
max="100"
className="input input-bordered w-full"
{...register('populationSize', {valueAsNumber: true})}
/>
</div>
<div className="form-control">
<label className="label">
<span className="label-text">Generations</span>
</label>
<input
type="number"
min="1"
max="50"
className="input input-bordered w-full"
{...register('generations', {valueAsNumber: true})}
/>
</div>
<div className="form-control">
<label className="label">
<span className="label-text">Mutation Rate</span>
</label>
<input
type="number"
step="0.1"
min="0"
max="1"
className="input input-bordered w-full"
{...register('mutationRate', {valueAsNumber: true})}
/>
</div>
<div className="form-control">
<label className="label">
<span className="label-text">Selection Method</span>
</label>
<select
className="select select-bordered w-full"
{...register('selectionMethod')}
>
<option value="tournament">Tournament Selection</option>
<option value="roulette">Roulette Wheel</option>
<option value="fitness-weighted">Fitness Weighted</option>
</select>
</div>
<div className="form-control">
<label className="label">
<span className="label-text">Elitism Percentage</span>
</label>
<input
type="number"
min="1"
max="50"
step="1"
className="input input-bordered w-full"
{...register('elitismPercentage', {valueAsNumber: true})}
/>
</div>
<div className="form-control">
<label className="label">
<span className="label-text">Max Take Profit (%)</span>
</label>
<input
type="number"
min="0.9"
max="8"
step="0.1"
className="input input-bordered w-full"
{...register('maxTakeProfit', {valueAsNumber: true})}
/>
</div>
</div>
<div className="form-control">
<label className="label">
<span className="label-text">Eligible Indicators</span>
</label>
<div className="grid grid-cols-2 md:grid-cols-3 gap-2 max-h-40 overflow-y-auto border rounded-lg p-4">
{ALL_INDICATORS.map(indicator => (
<label key={indicator} className="flex items-center gap-2 cursor-pointer">
<input
type="checkbox"
checked={selectedIndicators.includes(indicator)}
onChange={() => handleIndicatorToggle(indicator)}
className="checkbox checkbox-sm"
/>
<span className="text-sm">{indicator}</span>
</label>
))}
</div>
<div className="text-xs text-gray-500 mt-1">
{selectedIndicators.length} of {ALL_INDICATORS.length} indicators selected
</div>
</div>
<div className="flex gap-4">
<button
type="submit"
disabled={isSubmitting || selectedIndicators.length === 0}
className="btn btn-primary"
>
{isSubmitting ? 'Creating...' : 'Create Genetic Request'}
</button>
<button
type="button"
onClick={() => setSelectedIndicators(ALL_INDICATORS)}
className="btn btn-secondary"
>
Select All Indicators
</button>
<button
type="button"
onClick={() => setSelectedIndicators([])}
className="btn btn-secondary"
>
Clear All
</button>
</div>
</form>
</div>
</div>
{/* Existing Genetic Requests */}
{geneticRequests.length > 0 && (
<div className="card bg-base-100 shadow-xl">
<div className="card-body">
<h3 className="card-title">Existing Genetic Requests</h3>
<div className="overflow-x-auto">
<table className="table table-zebra">
<thead>
<tr>
<th>ID</th>
<th>Ticker</th>
<th>Timeframe</th>
<th>Status</th>
<th>Created</th>
<th>Completed</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{geneticRequests.map((request) => (
<tr key={request.requestId}>
<td className="font-mono text-xs">{request.requestId.slice(0, 8)}...</td>
<td>{request.ticker}</td>
<td>{request.timeframe}</td>
<td>
<span className={`badge ${getStatusBadgeColor(request.status)}`}>
{request.status}
</span>
</td>
<td>{new Date(request.createdAt).toLocaleDateString()}</td>
<td>
{request.completedAt
? new Date(request.completedAt).toLocaleDateString()
: '-'
}
</td>
<td>
<div className="flex gap-2">
<button
onClick={() => {
// TODO: Implement view details
new Toast('View details not implemented yet', false)
}}
className="btn btn-sm btn-outline"
>
View
</button>
<button
onClick={async () => {
try {
await backtestClient.backtest_DeleteGeneticRequest(request.requestId)
new Toast('Request deleted successfully', false)
await refetchRequests()
} catch (error) {
new Toast('Failed to delete request', false)
}
}}
className="btn btn-sm btn-error"
>
Delete
</button>
</div>
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</div>
)}
</div>
)
}
export default BacktestGeneticBundle