diff --git a/src/Managing.Api/Controllers/BacktestController.cs b/src/Managing.Api/Controllers/BacktestController.cs index 3e86fa2..64d15b7 100644 --- a/src/Managing.Api/Controllers/BacktestController.cs +++ b/src/Managing.Api/Controllers/BacktestController.cs @@ -30,6 +30,7 @@ public class BacktestController : BaseController private readonly IScenarioService _scenarioService; private readonly IAccountService _accountService; private readonly IMoneyManagementService _moneyManagementService; + private readonly IGeneticService _geneticService; /// /// Initializes a new instance of the class. @@ -39,12 +40,14 @@ public class BacktestController : BaseController /// The service for managing scenarios. /// The service for account management. /// The service for money management strategies. + /// The service for genetic algorithm operations. public BacktestController( IHubContext hubContext, IBacktester backtester, IScenarioService scenarioService, IAccountService accountService, IMoneyManagementService moneyManagementService, + IGeneticService geneticService, IUserService userService) : base(userService) { _hubContext = hubContext; @@ -52,6 +55,7 @@ public class BacktestController : BaseController _scenarioService = scenarioService; _accountService = accountService; _moneyManagementService = moneyManagementService; + _geneticService = geneticService; } /// @@ -266,8 +270,8 @@ public class BacktestController : BaseController { var user = await GetUser(); - // Create genetic request using the Backtester service - var geneticRequest = _backtester.CreateGeneticRequest( + // Create genetic request using the GeneticService directly + var geneticRequest = _geneticService.CreateGeneticRequest( user, request.Ticker, request.Timeframe, @@ -302,7 +306,7 @@ public class BacktestController : BaseController public async Task>> GetGeneticRequests() { var user = await GetUser(); - var geneticRequests = _backtester.GetGeneticRequestsByUser(user); + var geneticRequests = _geneticService.GetGeneticRequestsByUser(user); return Ok(geneticRequests); } @@ -316,7 +320,7 @@ public class BacktestController : BaseController public async Task> GetGeneticRequest(string id) { var user = await GetUser(); - var geneticRequest = _backtester.GetGeneticRequestByIdForUser(user, id); + var geneticRequest = _geneticService.GetGeneticRequestByIdForUser(user, id); if (geneticRequest == null) { @@ -336,7 +340,7 @@ public class BacktestController : BaseController public async Task DeleteGeneticRequest(string id) { var user = await GetUser(); - _backtester.DeleteGeneticRequestByIdForUser(user, id); + _geneticService.DeleteGeneticRequestByIdForUser(user, id); return Ok(); } diff --git a/src/Managing.Application.Abstractions/Services/IBacktester.cs b/src/Managing.Application.Abstractions/Services/IBacktester.cs index b046173..069dcf5 100644 --- a/src/Managing.Application.Abstractions/Services/IBacktester.cs +++ b/src/Managing.Application.Abstractions/Services/IBacktester.cs @@ -2,7 +2,6 @@ using Managing.Domain.Bots; using Managing.Domain.Candles; using Managing.Domain.Users; -using static Managing.Common.Enums; namespace Managing.Application.Abstractions.Services { @@ -50,25 +49,6 @@ namespace Managing.Application.Abstractions.Services 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 eligibleIndicators); - IEnumerable GetGeneticRequestsByUser(User user); - GeneticRequest GetGeneticRequestByIdForUser(User user, string id); - void UpdateGeneticRequest(GeneticRequest geneticRequest); - void DeleteGeneticRequestByIdForUser(User user, string id); } } \ No newline at end of file diff --git a/src/Managing.Application.Abstractions/Services/IGeneticService.cs b/src/Managing.Application.Abstractions/Services/IGeneticService.cs index d64170e..cf0a308 100644 --- a/src/Managing.Application.Abstractions/Services/IGeneticService.cs +++ b/src/Managing.Application.Abstractions/Services/IGeneticService.cs @@ -74,4 +74,11 @@ public interface IGeneticService /// /// Collection of pending genetic requests IEnumerable GetPendingGeneticRequests(); + + /// + /// Runs the genetic algorithm for a specific request + /// + /// The genetic request to process + /// The genetic algorithm result + Task RunGeneticAlgorithm(GeneticRequest request); } \ No newline at end of file diff --git a/src/Managing.Application.Workers/GeneticAlgorithmWorker.cs b/src/Managing.Application.Workers/GeneticAlgorithmWorker.cs index c906d62..7fc4968 100644 --- a/src/Managing.Application.Workers/GeneticAlgorithmWorker.cs +++ b/src/Managing.Application.Workers/GeneticAlgorithmWorker.cs @@ -69,8 +69,8 @@ public class GeneticAlgorithmWorker : BaseWorker request.Status = GeneticRequestStatus.Running; _geneticService.UpdateGeneticRequest(request); - // Run genetic algorithm - var results = await RunGeneticAlgorithm(request, cancellationToken); + // Run genetic algorithm using the service + var results = await _geneticService.RunGeneticAlgorithm(request); // Update request with results request.Status = GeneticRequestStatus.Completed; @@ -101,31 +101,5 @@ public class GeneticAlgorithmWorker : BaseWorker } } - private async Task 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}" - }; - } -} -/// -/// Result of a genetic algorithm run -/// -public class GeneticAlgorithmResult -{ - public double BestFitness { get; set; } - public string BestIndividual { get; set; } = string.Empty; - public string ProgressInfo { get; set; } = string.Empty; } \ No newline at end of file diff --git a/src/Managing.Application/Backtesting/Backtester.cs b/src/Managing.Application/Backtesting/Backtester.cs index a98ccb4..be9ab8a 100644 --- a/src/Managing.Application/Backtesting/Backtester.cs +++ b/src/Managing.Application/Backtesting/Backtester.cs @@ -27,7 +27,6 @@ namespace Managing.Application.Backtesting private readonly IScenarioService _scenarioService; private readonly IAccountService _accountService; private readonly IMessengerService _messengerService; - private readonly IGeneticService _geneticService; public Backtester( IExchangeService exchangeService, @@ -36,8 +35,7 @@ namespace Managing.Application.Backtesting ILogger logger, IScenarioService scenarioService, IAccountService accountService, - IMessengerService messengerService, - IGeneticService geneticService) + IMessengerService messengerService) { _exchangeService = exchangeService; _botFactory = botFactory; @@ -46,7 +44,6 @@ namespace Managing.Application.Backtesting _scenarioService = scenarioService; _accountService = accountService; _messengerService = messengerService; - _geneticService = geneticService; } public Backtest RunSimpleBotBacktest(Workflow workflow, bool save = false) @@ -460,82 +457,6 @@ namespace Managing.Application.Backtesting } } - /// - /// Creates a new genetic algorithm request - /// - /// The user creating the request - /// The ticker to optimize for - /// The timeframe to use - /// The start date for the backtest period - /// The end date for the backtest period - /// The starting balance - /// The population size for the genetic algorithm - /// The number of generations to evolve - /// The mutation rate (0.0 - 1.0) - /// The selection method to use - /// The percentage of elite individuals to preserve - /// The maximum take profit percentage - /// The list of eligible indicators - /// The created genetic request - 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 eligibleIndicators) - { - return _geneticService.CreateGeneticRequest( - user, ticker, timeframe, startDate, endDate, balance, - populationSize, generations, mutationRate, selectionMethod, - elitismPercentage, maxTakeProfit, eligibleIndicators); - } - /// - /// Gets all genetic requests for a user - /// - /// The user to get requests for - /// Collection of genetic requests - public IEnumerable GetGeneticRequestsByUser(User user) - { - return _geneticService.GetGeneticRequestsByUser(user); - } - - /// - /// Gets a specific genetic request by ID for a user - /// - /// The user - /// The request ID - /// The genetic request or null if not found - public GeneticRequest GetGeneticRequestByIdForUser(User user, string id) - { - return _geneticService.GetGeneticRequestByIdForUser(user, id); - } - - /// - /// Updates a genetic request - /// - /// The genetic request to update - public void UpdateGeneticRequest(GeneticRequest geneticRequest) - { - _geneticService.UpdateGeneticRequest(geneticRequest); - } - - /// - /// Deletes a genetic request by ID for a user - /// - /// The user - /// The request ID - public void DeleteGeneticRequestByIdForUser(User user, string id) - { - _geneticService.DeleteGeneticRequestByIdForUser(user, id); - } } } \ No newline at end of file diff --git a/src/Managing.Application/GeneticService.cs b/src/Managing.Application/GeneticService.cs index 0b2d7ad..e3a2b7e 100644 --- a/src/Managing.Application/GeneticService.cs +++ b/src/Managing.Application/GeneticService.cs @@ -1,7 +1,13 @@ +using System.Text.Json; +using GeneticSharp; using Managing.Application.Abstractions.Repositories; using Managing.Application.Abstractions.Services; using Managing.Domain.Backtests; +using Managing.Domain.Bots; +using Managing.Domain.MoneyManagements; +using Managing.Domain.Risk; using Managing.Domain.Users; +using Microsoft.Extensions.Logging; using static Managing.Common.Enums; namespace Managing.Application; @@ -12,10 +18,17 @@ namespace Managing.Application; public class GeneticService : IGeneticService { private readonly IGeneticRepository _geneticRepository; + private readonly IBacktester _backtester; + private readonly ILogger _logger; - public GeneticService(IGeneticRepository geneticRepository) + public GeneticService( + IGeneticRepository geneticRepository, + IBacktester backtester, + ILogger logger) { _geneticRepository = geneticRepository; + _backtester = backtester; + _logger = logger; } public GeneticRequest CreateGeneticRequest( @@ -79,4 +92,245 @@ public class GeneticService : IGeneticService { return _geneticRepository.GetPendingGeneticRequests(); } + + /// + /// Runs the genetic algorithm for a specific request + /// + /// The genetic request to process + /// The genetic algorithm result + public async Task RunGeneticAlgorithm(GeneticRequest request) + { + try + { + _logger.LogInformation("Starting genetic algorithm for request {RequestId}", request.RequestId); + + // Create chromosome for trading bot configuration + var chromosome = new TradingBotChromosome(request.EligibleIndicators); + + // Create fitness function + var fitness = new TradingBotFitness(_backtester, request); + + // Create genetic algorithm + var ga = new GeneticAlgorithm( + new Population(request.PopulationSize, request.PopulationSize, chromosome), + fitness, + GetSelection(request.SelectionMethod), + new UniformCrossover(), + GetMutation(request.MutationRate)) + { + Termination = new GenerationNumberTermination(request.Generations) + }; + + // Run the genetic algorithm + ga.Start(); + + // Get the best chromosome + var bestChromosome = ga.BestChromosome as TradingBotChromosome; + var bestFitness = ga.BestChromosome.Fitness.Value; + + _logger.LogInformation("Genetic algorithm completed for request {RequestId}. Best fitness: {Fitness}", + request.RequestId, bestFitness); + + return new GeneticAlgorithmResult + { + BestFitness = bestFitness, + BestIndividual = bestChromosome?.ToString() ?? "unknown", + ProgressInfo = JsonSerializer.Serialize(new + { + generation = ga.GenerationsNumber, + best_fitness = bestFitness, + population_size = request.PopulationSize, + generations = request.Generations + }) + }; + } + catch (Exception ex) + { + _logger.LogError(ex, "Error running genetic algorithm for request {RequestId}", request.RequestId); + throw; + } + } + + private ISelection GetSelection(string selectionMethod) + { + return selectionMethod.ToLower() switch + { + "tournament" => new TournamentSelection(), + "roulette" => new RouletteWheelSelection(), + "rank" => new RankSelection(), + _ => new TournamentSelection() + }; + } + + private IMutation GetMutation(double mutationRate) + { + return new UniformMutation(true); + } +} + +/// +/// Chromosome representing a trading bot configuration +/// +public class TradingBotChromosome : ChromosomeBase +{ + private readonly List _eligibleIndicators; + private readonly Random _random = new Random(); + + public TradingBotChromosome(List eligibleIndicators) : base(eligibleIndicators.Count + 5) + { + _eligibleIndicators = eligibleIndicators; + } + + public override Gene GenerateGene(int geneIndex) + { + if (geneIndex < _eligibleIndicators.Count) + { + // Gene represents whether an indicator is selected (0 or 1) + return new Gene(_random.Next(2)); + } + else + { + // Additional genes for other parameters + return geneIndex switch + { + var i when i == _eligibleIndicators.Count => new Gene(_random.Next(1, 11)), // Stop loss percentage + var i when i == _eligibleIndicators.Count + 1 => new Gene(_random.Next(1, 21)), // Take profit percentage + var i when i == _eligibleIndicators.Count + 2 => new Gene(_random.Next(1, 101)), // Position size percentage + var i when i == _eligibleIndicators.Count + 3 => new Gene(_random.Next(1, 51)), // Max positions + var i when i == _eligibleIndicators.Count + 4 => new Gene(_random.Next(1, 11)), // Risk level + _ => new Gene(0) + }; + } + } + + public override IChromosome CreateNew() + { + return new TradingBotChromosome(_eligibleIndicators); + } + + public override IChromosome Clone() + { + var clone = new TradingBotChromosome(_eligibleIndicators); + clone.ReplaceGenes(0, GetGenes()); + return clone; + } + + public List GetSelectedIndicators() + { + var selected = new List(); + for (int i = 0; i < _eligibleIndicators.Count; i++) + { + if (GetGene(i).Value.ToString() == "1") + { + selected.Add(_eligibleIndicators[i]); + } + } + return selected; + } + + public TradingBotConfig GetTradingBotConfig(GeneticRequest request) + { + var selectedIndicators = GetSelectedIndicators(); + var genes = GetGenes(); + + return new TradingBotConfig + { + Name = $"Genetic_{request.RequestId}", + AccountName = "genetic_account", + Ticker = request.Ticker, + Timeframe = request.Timeframe, + BotTradingBalance = request.Balance, + IsForBacktest = true, + IsForWatchingOnly = false, + CooldownPeriod = 0, + MaxLossStreak = 3, + FlipPosition = false, + FlipOnlyWhenInProfit = true, + MoneyManagement = new MoneyManagement + { + Name = $"Genetic_{request.RequestId}_MM", + Timeframe = request.Timeframe, + StopLoss = Convert.ToDecimal(genes[_eligibleIndicators.Count].Value), + TakeProfit = Convert.ToDecimal(genes[_eligibleIndicators.Count + 1].Value), + Leverage = 1.0m + }, + RiskManagement = new RiskManagement + { + RiskTolerance = (RiskToleranceLevel)Convert.ToInt32(genes[_eligibleIndicators.Count + 4].Value) + } + }; + } +} + +/// +/// Fitness function for trading bot optimization +/// +public class TradingBotFitness : IFitness +{ + private readonly IBacktester _backtester; + private readonly GeneticRequest _request; + + public TradingBotFitness(IBacktester backtester, GeneticRequest request) + { + _backtester = backtester; + _request = request; + } + + public double Evaluate(IChromosome chromosome) + { + try + { + var tradingBotChromosome = chromosome as TradingBotChromosome; + if (tradingBotChromosome == null) + return 0; + + var config = tradingBotChromosome.GetTradingBotConfig(_request); + + // Run backtest + var backtest = _backtester.RunTradingBotBacktest( + config, + _request.StartDate, + _request.EndDate, + _request.User, + false, // Don't save individual backtests + false // Don't include candles + ).Result; + + // Calculate fitness based on backtest results + var fitness = CalculateFitness(backtest); + + return fitness; + } + catch (Exception) + { + // Return low fitness for failed backtests + return 0.1; + } + } + + private double CalculateFitness(Backtest backtest) + { + if (backtest == null || backtest.Score == null) + return 0.1; + + // Use the backtest score as the primary fitness metric + var baseFitness = backtest.Score; + + // Apply additional factors + var tradeCount = backtest.Positions?.Count ?? 0; + var winRate = backtest.WinRate; + var finalPnl = backtest.FinalPnl; + + // Penalize if no trades were made + if (tradeCount == 0) + return 0.1; + + // Bonus for good win rate + var winRateBonus = winRate > 0.6 ? 10 : 0; + + // Bonus for positive PnL + var pnlBonus = finalPnl > 0 ? 20 : 0; + + return baseFitness + winRateBonus + pnlBonus; + } } \ No newline at end of file diff --git a/src/Managing.Application/Managing.Application.csproj b/src/Managing.Application/Managing.Application.csproj index 649ac50..34f6de5 100644 --- a/src/Managing.Application/Managing.Application.csproj +++ b/src/Managing.Application/Managing.Application.csproj @@ -14,6 +14,7 @@ + diff --git a/src/Managing.Domain/Backtests/GeneticAlgorithmResult.cs b/src/Managing.Domain/Backtests/GeneticAlgorithmResult.cs new file mode 100644 index 0000000..f044e96 --- /dev/null +++ b/src/Managing.Domain/Backtests/GeneticAlgorithmResult.cs @@ -0,0 +1,27 @@ +namespace Managing.Domain.Backtests; + +/// +/// Represents the result of a genetic algorithm run +/// +public class GeneticAlgorithmResult +{ + /// + /// The best fitness score achieved + /// + public double BestFitness { get; set; } + + /// + /// The best individual (chromosome) found + /// + public string BestIndividual { get; set; } = string.Empty; + + /// + /// Progress information as JSON string + /// + public string ProgressInfo { get; set; } = string.Empty; + + /// + /// When the algorithm completed + /// + public DateTime CompletedAt { get; set; } = DateTime.UtcNow; +} \ No newline at end of file