Start impl for genetic

This commit is contained in:
2025-07-10 21:33:24 +07:00
parent 0b4f2173e0
commit 2fc7a1d4bb
8 changed files with 302 additions and 134 deletions

View File

@@ -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<Backtester> 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
}
}
/// <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

@@ -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<GeneticService> _logger;
public GeneticService(IGeneticRepository geneticRepository)
public GeneticService(
IGeneticRepository geneticRepository,
IBacktester backtester,
ILogger<GeneticService> logger)
{
_geneticRepository = geneticRepository;
_backtester = backtester;
_logger = logger;
}
public GeneticRequest CreateGeneticRequest(
@@ -79,4 +92,245 @@ public class GeneticService : IGeneticService
{
return _geneticRepository.GetPendingGeneticRequests();
}
/// <summary>
/// Runs the genetic algorithm for a specific request
/// </summary>
/// <param name="request">The genetic request to process</param>
/// <returns>The genetic algorithm result</returns>
public async Task<GeneticAlgorithmResult> 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);
}
}
/// <summary>
/// Chromosome representing a trading bot configuration
/// </summary>
public class TradingBotChromosome : ChromosomeBase
{
private readonly List<IndicatorType> _eligibleIndicators;
private readonly Random _random = new Random();
public TradingBotChromosome(List<IndicatorType> 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<IndicatorType> GetSelectedIndicators()
{
var selected = new List<IndicatorType>();
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)
}
};
}
}
/// <summary>
/// Fitness function for trading bot optimization
/// </summary>
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;
}
}

View File

@@ -14,6 +14,7 @@
<ItemGroup>
<PackageReference Include="FluentValidation" Version="11.9.1" />
<PackageReference Include="GeneticSharp" Version="3.1.4" />
<PackageReference Include="MediatR" Version="12.2.0" />
<PackageReference Include="MediatR.Extensions.Microsoft.DependencyInjection" Version="11.1.0" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Core" Version="1.1.0" />