Add Genetic workers
This commit is contained in:
@@ -36,5 +36,6 @@
|
||||
"WorkerSpotlight": false,
|
||||
"WorkerTraderWatcher": false,
|
||||
"WorkerLeaderboard": false,
|
||||
"WorkerFundingRatesWatcher": false
|
||||
"WorkerFundingRatesWatcher": false,
|
||||
"WorkerGeneticAlgorithm": true
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
69
src/Managing.Api/Models/Requests/RunGeneticRequest.cs
Normal file
69
src/Managing.Api/Models/Requests/RunGeneticRequest.cs
Normal 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();
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
131
src/Managing.Application.Workers/GeneticAlgorithmWorker.cs
Normal file
131
src/Managing.Application.Workers/GeneticAlgorithmWorker.cs
Normal 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;
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
82
src/Managing.Application/GeneticService.cs
Normal file
82
src/Managing.Application/GeneticService.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
@@ -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>();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -382,7 +382,8 @@ public static class Enums
|
||||
Noobiesboard,
|
||||
BotManager,
|
||||
FundingRatesWatcher,
|
||||
BalanceTracking
|
||||
BalanceTracking,
|
||||
GeneticAlgorithm
|
||||
}
|
||||
|
||||
public enum WorkflowUsage
|
||||
|
||||
188
src/Managing.Domain/Backtests/GeneticRequest.cs
Normal file
188
src/Managing.Domain/Backtests/GeneticRequest.cs
Normal 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
|
||||
}
|
||||
90
src/Managing.Infrastructure.Database/GeneticRepository.cs
Normal file
90
src/Managing.Infrastructure.Database/GeneticRepository.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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 = () => {
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user