Update genetic
This commit is contained in:
@@ -6,6 +6,7 @@ using Managing.Domain.Backtests;
|
|||||||
using Managing.Domain.Bots;
|
using Managing.Domain.Bots;
|
||||||
using Managing.Domain.MoneyManagements;
|
using Managing.Domain.MoneyManagements;
|
||||||
using Managing.Domain.Risk;
|
using Managing.Domain.Risk;
|
||||||
|
using Managing.Domain.Scenarios;
|
||||||
using Managing.Domain.Users;
|
using Managing.Domain.Users;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using static Managing.Common.Enums;
|
using static Managing.Common.Enums;
|
||||||
@@ -21,6 +22,144 @@ public class GeneticService : IGeneticService
|
|||||||
private readonly IBacktester _backtester;
|
private readonly IBacktester _backtester;
|
||||||
private readonly ILogger<GeneticService> _logger;
|
private readonly ILogger<GeneticService> _logger;
|
||||||
|
|
||||||
|
// Predefined parameter ranges for each indicator (matching backtestGenetic.tsx)
|
||||||
|
public static readonly Dictionary<string, (double min, double max)> ParameterRanges = new()
|
||||||
|
{
|
||||||
|
// Trading Parameters only - indicator parameters are now handled by IndicatorParameterRanges
|
||||||
|
["stopLoss"] = (0.2, 50.0), // Minimum 0.2% to cover fees, no upper limit (set to 50% as practical max)
|
||||||
|
["leverage"] = (1.0, 10.0),
|
||||||
|
["cooldownPeriod"] = (5.0, 25.0),
|
||||||
|
["maxLossStreak"] = (0.0, 4.0),
|
||||||
|
["maxPositionTimeHours"] = (0, 48.0)
|
||||||
|
};
|
||||||
|
|
||||||
|
// Default indicator values per indicator type (matching CustomScenario.tsx)
|
||||||
|
public static readonly Dictionary<IndicatorType, Dictionary<string, double>> DefaultIndicatorValues = new()
|
||||||
|
{
|
||||||
|
[IndicatorType.RsiDivergence] = new() { ["period"] = 14.0 },
|
||||||
|
[IndicatorType.RsiDivergenceConfirm] = new() { ["period"] = 14.0 },
|
||||||
|
[IndicatorType.EmaCross] = new() { ["period"] = 14.0 },
|
||||||
|
[IndicatorType.EmaTrend] = new() { ["period"] = 14.0 },
|
||||||
|
[IndicatorType.StDev] = new() { ["period"] = 14.0 },
|
||||||
|
[IndicatorType.ThreeWhiteSoldiers] = new() { ["period"] = 14.0 },
|
||||||
|
[IndicatorType.MacdCross] = new() {
|
||||||
|
["fastPeriods"] = 12.0,
|
||||||
|
["slowPeriods"] = 26.0,
|
||||||
|
["signalPeriods"] = 9.0
|
||||||
|
},
|
||||||
|
[IndicatorType.DualEmaCross] = new() {
|
||||||
|
["fastPeriods"] = 12.0,
|
||||||
|
["slowPeriods"] = 26.0
|
||||||
|
},
|
||||||
|
[IndicatorType.SuperTrend] = new() {
|
||||||
|
["period"] = 14.0,
|
||||||
|
["multiplier"] = 3.0
|
||||||
|
},
|
||||||
|
[IndicatorType.SuperTrendCrossEma] = new() {
|
||||||
|
["period"] = 14.0,
|
||||||
|
["multiplier"] = 3.0
|
||||||
|
},
|
||||||
|
[IndicatorType.ChandelierExit] = new() {
|
||||||
|
["period"] = 14.0,
|
||||||
|
["multiplier"] = 3.0
|
||||||
|
},
|
||||||
|
[IndicatorType.StochRsiTrend] = new() {
|
||||||
|
["period"] = 14.0,
|
||||||
|
["stochPeriods"] = 14.0,
|
||||||
|
["signalPeriods"] = 9.0,
|
||||||
|
["smoothPeriods"] = 3.0
|
||||||
|
},
|
||||||
|
[IndicatorType.Stc] = new() {
|
||||||
|
["cyclePeriods"] = 10.0,
|
||||||
|
["fastPeriods"] = 12.0,
|
||||||
|
["slowPeriods"] = 26.0
|
||||||
|
},
|
||||||
|
[IndicatorType.LaggingStc] = new() {
|
||||||
|
["cyclePeriods"] = 10.0,
|
||||||
|
["fastPeriods"] = 12.0,
|
||||||
|
["slowPeriods"] = 26.0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Indicator-specific parameter ranges
|
||||||
|
public static readonly Dictionary<IndicatorType, Dictionary<string, (double min, double max)>> IndicatorParameterRanges = new()
|
||||||
|
{
|
||||||
|
[IndicatorType.RsiDivergence] = new() {
|
||||||
|
["period"] = (5.0, 50.0)
|
||||||
|
},
|
||||||
|
[IndicatorType.RsiDivergenceConfirm] = new() {
|
||||||
|
["period"] = (5.0, 50.0)
|
||||||
|
},
|
||||||
|
[IndicatorType.EmaCross] = new() {
|
||||||
|
["period"] = (5.0, 200.0)
|
||||||
|
},
|
||||||
|
[IndicatorType.EmaTrend] = new() {
|
||||||
|
["period"] = (5.0, 200.0)
|
||||||
|
},
|
||||||
|
[IndicatorType.StDev] = new() {
|
||||||
|
["period"] = (5.0, 50.0)
|
||||||
|
},
|
||||||
|
[IndicatorType.ThreeWhiteSoldiers] = new() {
|
||||||
|
["period"] = (5.0, 50.0)
|
||||||
|
},
|
||||||
|
[IndicatorType.MacdCross] = new() {
|
||||||
|
["fastPeriods"] = (10.0, 50.0),
|
||||||
|
["slowPeriods"] = (20.0, 100.0),
|
||||||
|
["signalPeriods"] = (5.0, 20.0)
|
||||||
|
},
|
||||||
|
[IndicatorType.DualEmaCross] = new() {
|
||||||
|
["fastPeriods"] = (5.0, 300.0),
|
||||||
|
["slowPeriods"] = (5.0, 300.0)
|
||||||
|
},
|
||||||
|
[IndicatorType.SuperTrend] = new() {
|
||||||
|
["period"] = (5.0, 50.0),
|
||||||
|
["multiplier"] = (1.0, 10.0)
|
||||||
|
},
|
||||||
|
[IndicatorType.SuperTrendCrossEma] = new() {
|
||||||
|
["period"] = (5.0, 50.0),
|
||||||
|
["multiplier"] = (1.0, 10.0)
|
||||||
|
},
|
||||||
|
[IndicatorType.ChandelierExit] = new() {
|
||||||
|
["period"] = (5.0, 50.0),
|
||||||
|
["multiplier"] = (1.0, 10.0)
|
||||||
|
},
|
||||||
|
[IndicatorType.StochRsiTrend] = new() {
|
||||||
|
["period"] = (5.0, 50.0),
|
||||||
|
["stochPeriods"] = (5.0, 30.0),
|
||||||
|
["signalPeriods"] = (3.0, 15.0),
|
||||||
|
["smoothPeriods"] = (1.0, 10.0)
|
||||||
|
},
|
||||||
|
[IndicatorType.Stc] = new() {
|
||||||
|
["cyclePeriods"] = (5.0, 30.0),
|
||||||
|
["fastPeriods"] = (5.0, 50.0),
|
||||||
|
["slowPeriods"] = (10.0, 100.0)
|
||||||
|
},
|
||||||
|
[IndicatorType.LaggingStc] = new() {
|
||||||
|
["cyclePeriods"] = (5.0, 30.0),
|
||||||
|
["fastPeriods"] = (5.0, 50.0),
|
||||||
|
["slowPeriods"] = (10.0, 100.0)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Indicator type to parameter mapping
|
||||||
|
public static readonly Dictionary<IndicatorType, string[]> IndicatorParamMapping = new()
|
||||||
|
{
|
||||||
|
[IndicatorType.RsiDivergence] = ["period"],
|
||||||
|
[IndicatorType.RsiDivergenceConfirm] = ["period"],
|
||||||
|
[IndicatorType.EmaCross] = ["period"],
|
||||||
|
[IndicatorType.EmaTrend] = ["period"],
|
||||||
|
[IndicatorType.StDev] = ["period"],
|
||||||
|
[IndicatorType.ThreeWhiteSoldiers] = ["period"],
|
||||||
|
[IndicatorType.MacdCross] = ["fastPeriods", "slowPeriods", "signalPeriods"],
|
||||||
|
[IndicatorType.DualEmaCross] = ["fastPeriods", "slowPeriods"],
|
||||||
|
[IndicatorType.SuperTrend] = ["period", "multiplier"],
|
||||||
|
[IndicatorType.SuperTrendCrossEma] = ["period", "multiplier"],
|
||||||
|
[IndicatorType.ChandelierExit] = ["period", "multiplier"],
|
||||||
|
[IndicatorType.StochRsiTrend] = ["period", "stochPeriods", "signalPeriods", "smoothPeriods"],
|
||||||
|
[IndicatorType.Stc] = ["cyclePeriods", "fastPeriods", "slowPeriods"],
|
||||||
|
[IndicatorType.LaggingStc] = ["cyclePeriods", "fastPeriods", "slowPeriods"]
|
||||||
|
};
|
||||||
|
|
||||||
public GeneticService(
|
public GeneticService(
|
||||||
IGeneticRepository geneticRepository,
|
IGeneticRepository geneticRepository,
|
||||||
IBacktester backtester,
|
IBacktester backtester,
|
||||||
@@ -46,7 +185,7 @@ public class GeneticService : IGeneticService
|
|||||||
double maxTakeProfit,
|
double maxTakeProfit,
|
||||||
List<IndicatorType> eligibleIndicators)
|
List<IndicatorType> eligibleIndicators)
|
||||||
{
|
{
|
||||||
var id = Guid.NewGuid().ToString(); // Generate unique GUID
|
var id = Guid.NewGuid().ToString();
|
||||||
var geneticRequest = new GeneticRequest(id)
|
var geneticRequest = new GeneticRequest(id)
|
||||||
{
|
{
|
||||||
Ticker = ticker,
|
Ticker = ticker,
|
||||||
@@ -104,13 +243,17 @@ public class GeneticService : IGeneticService
|
|||||||
{
|
{
|
||||||
_logger.LogInformation("Starting genetic algorithm for request {RequestId}", request.RequestId);
|
_logger.LogInformation("Starting genetic algorithm for request {RequestId}", request.RequestId);
|
||||||
|
|
||||||
|
// Update status to running
|
||||||
|
request.Status = GeneticRequestStatus.Running;
|
||||||
|
UpdateGeneticRequest(request);
|
||||||
|
|
||||||
// Create chromosome for trading bot configuration
|
// Create chromosome for trading bot configuration
|
||||||
var chromosome = new TradingBotChromosome(request.EligibleIndicators);
|
var chromosome = new TradingBotChromosome(request.EligibleIndicators, request.MaxTakeProfit);
|
||||||
|
|
||||||
// Create fitness function
|
// Create fitness function
|
||||||
var fitness = new TradingBotFitness(_backtester, request);
|
var fitness = new TradingBotFitness(_backtester, request);
|
||||||
|
|
||||||
// Create genetic algorithm
|
// Create genetic algorithm with better configuration
|
||||||
var ga = new GeneticAlgorithm(
|
var ga = new GeneticAlgorithm(
|
||||||
new Population(request.PopulationSize, request.PopulationSize, chromosome),
|
new Population(request.PopulationSize, request.PopulationSize, chromosome),
|
||||||
fitness,
|
fitness,
|
||||||
@@ -118,7 +261,9 @@ public class GeneticService : IGeneticService
|
|||||||
new UniformCrossover(),
|
new UniformCrossover(),
|
||||||
GetMutation(request.MutationRate))
|
GetMutation(request.MutationRate))
|
||||||
{
|
{
|
||||||
Termination = new GenerationNumberTermination(request.Generations)
|
Termination = new GenerationNumberTermination(request.Generations),
|
||||||
|
MutationProbability = (float)request.MutationRate,
|
||||||
|
CrossoverProbability = 0.7f // Fixed crossover rate as in frontend
|
||||||
};
|
};
|
||||||
|
|
||||||
// Run the genetic algorithm
|
// Run the genetic algorithm
|
||||||
@@ -131,22 +276,40 @@ public class GeneticService : IGeneticService
|
|||||||
_logger.LogInformation("Genetic algorithm completed for request {RequestId}. Best fitness: {Fitness}",
|
_logger.LogInformation("Genetic algorithm completed for request {RequestId}. Best fitness: {Fitness}",
|
||||||
request.RequestId, bestFitness);
|
request.RequestId, bestFitness);
|
||||||
|
|
||||||
|
// Update request with results
|
||||||
|
request.Status = GeneticRequestStatus.Completed;
|
||||||
|
request.CompletedAt = DateTime.UtcNow;
|
||||||
|
request.BestFitness = bestFitness;
|
||||||
|
request.BestIndividual = bestChromosome?.ToString() ?? "unknown";
|
||||||
|
request.ProgressInfo = JsonSerializer.Serialize(new
|
||||||
|
{
|
||||||
|
generation = ga.GenerationsNumber,
|
||||||
|
best_fitness = bestFitness,
|
||||||
|
population_size = request.PopulationSize,
|
||||||
|
generations = request.Generations,
|
||||||
|
completed_at = DateTime.UtcNow
|
||||||
|
});
|
||||||
|
|
||||||
|
UpdateGeneticRequest(request);
|
||||||
|
|
||||||
return new GeneticAlgorithmResult
|
return new GeneticAlgorithmResult
|
||||||
{
|
{
|
||||||
BestFitness = bestFitness,
|
BestFitness = bestFitness,
|
||||||
BestIndividual = bestChromosome?.ToString() ?? "unknown",
|
BestIndividual = bestChromosome?.ToString() ?? "unknown",
|
||||||
ProgressInfo = JsonSerializer.Serialize(new
|
ProgressInfo = request.ProgressInfo,
|
||||||
{
|
CompletedAt = DateTime.UtcNow
|
||||||
generation = ga.GenerationsNumber,
|
|
||||||
best_fitness = bestFitness,
|
|
||||||
population_size = request.PopulationSize,
|
|
||||||
generations = request.Generations
|
|
||||||
})
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "Error running genetic algorithm for request {RequestId}", request.RequestId);
|
_logger.LogError(ex, "Error running genetic algorithm for request {RequestId}", request.RequestId);
|
||||||
|
|
||||||
|
// Update request with error
|
||||||
|
request.Status = GeneticRequestStatus.Failed;
|
||||||
|
request.ErrorMessage = ex.Message;
|
||||||
|
request.CompletedAt = DateTime.UtcNow;
|
||||||
|
UpdateGeneticRequest(request);
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -157,7 +320,7 @@ public class GeneticService : IGeneticService
|
|||||||
{
|
{
|
||||||
"tournament" => new TournamentSelection(),
|
"tournament" => new TournamentSelection(),
|
||||||
"roulette" => new RouletteWheelSelection(),
|
"roulette" => new RouletteWheelSelection(),
|
||||||
"rank" => new RankSelection(),
|
"fitness-weighted" => new RankSelection(), // Use rank selection as approximation
|
||||||
_ => new TournamentSelection()
|
_ => new TournamentSelection()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -169,69 +332,224 @@ public class GeneticService : IGeneticService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Chromosome representing a trading bot configuration
|
/// Chromosome representing a trading bot configuration with predefined parameter ranges
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TradingBotChromosome : ChromosomeBase
|
public class TradingBotChromosome : ChromosomeBase
|
||||||
{
|
{
|
||||||
private readonly List<IndicatorType> _eligibleIndicators;
|
private readonly List<IndicatorType> _eligibleIndicators;
|
||||||
|
private readonly double _maxTakeProfit;
|
||||||
private readonly Random _random = new Random();
|
private readonly Random _random = new Random();
|
||||||
|
|
||||||
public TradingBotChromosome(List<IndicatorType> eligibleIndicators) : base(eligibleIndicators.Count + 5)
|
// Gene structure:
|
||||||
|
// 0-2: Trading parameters (takeProfit, stopLoss, cooldownPeriod, maxLossStreak)
|
||||||
|
// 3-6: Indicator selection (up to 4 indicators)
|
||||||
|
// 7+: Indicator parameters (period, fastPeriods, etc.)
|
||||||
|
|
||||||
|
public TradingBotChromosome(List<IndicatorType> eligibleIndicators, double maxTakeProfit)
|
||||||
|
: base(4 + 4 + eligibleIndicators.Count * 8) // Trading params + indicator selection + indicator params
|
||||||
{
|
{
|
||||||
_eligibleIndicators = eligibleIndicators;
|
_eligibleIndicators = eligibleIndicators;
|
||||||
|
_maxTakeProfit = maxTakeProfit;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Gene GenerateGene(int geneIndex)
|
public override Gene GenerateGene(int geneIndex)
|
||||||
{
|
{
|
||||||
if (geneIndex < _eligibleIndicators.Count)
|
if (geneIndex < 4)
|
||||||
{
|
{
|
||||||
// Gene represents whether an indicator is selected (0 or 1)
|
// Trading parameters
|
||||||
|
return geneIndex switch
|
||||||
|
{
|
||||||
|
0 => new Gene(GetRandomInRange((0.9, _maxTakeProfit))), // Take profit (0.9% to max TP)
|
||||||
|
1 => new Gene(GetRandomInRange(GeneticService.ParameterRanges["stopLoss"])), // Stop loss
|
||||||
|
2 => new Gene(GetRandomIntInRange(GeneticService.ParameterRanges["cooldownPeriod"])), // Cooldown period
|
||||||
|
3 => new Gene(GetRandomIntInRange(GeneticService.ParameterRanges["maxLossStreak"])), // Max loss streak
|
||||||
|
_ => new Gene(0)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else if (geneIndex < 8)
|
||||||
|
{
|
||||||
|
// Indicator selection (0 = not selected, 1 = selected)
|
||||||
return new Gene(_random.Next(2));
|
return new Gene(_random.Next(2));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Additional genes for other parameters
|
// Indicator parameters
|
||||||
return geneIndex switch
|
var indicatorIndex = (geneIndex - 8) / 8;
|
||||||
|
var paramIndex = (geneIndex - 8) % 8;
|
||||||
|
|
||||||
|
if (indicatorIndex < _eligibleIndicators.Count)
|
||||||
{
|
{
|
||||||
var i when i == _eligibleIndicators.Count => new Gene(_random.Next(1, 11)), // Stop loss percentage
|
var indicator = _eligibleIndicators[indicatorIndex];
|
||||||
var i when i == _eligibleIndicators.Count + 1 => new Gene(_random.Next(1, 21)), // Take profit percentage
|
var paramName = GetParameterName(paramIndex);
|
||||||
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
|
if (paramName != null && GeneticService.IndicatorParamMapping.ContainsKey(indicator))
|
||||||
var i when i == _eligibleIndicators.Count + 4 => new Gene(_random.Next(1, 11)), // Risk level
|
{
|
||||||
_ => new Gene(0)
|
var requiredParams = GeneticService.IndicatorParamMapping[indicator];
|
||||||
};
|
if (requiredParams.Contains(paramName))
|
||||||
|
{
|
||||||
|
// Use indicator-specific ranges only
|
||||||
|
if (GeneticService.IndicatorParameterRanges.ContainsKey(indicator) &&
|
||||||
|
GeneticService.IndicatorParameterRanges[indicator].ContainsKey(paramName))
|
||||||
|
{
|
||||||
|
var indicatorRange = GeneticService.IndicatorParameterRanges[indicator][paramName];
|
||||||
|
|
||||||
|
// 70% chance to use default value, 30% chance to use random value within indicator-specific range
|
||||||
|
if (_random.NextDouble() < 0.7)
|
||||||
|
{
|
||||||
|
var defaultValues = GeneticService.DefaultIndicatorValues[indicator];
|
||||||
|
return new Gene(defaultValues[paramName]);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new Gene(GetRandomInRange(indicatorRange));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// If no indicator-specific range is found, use default value only
|
||||||
|
var defaultValues = GeneticService.DefaultIndicatorValues[indicator];
|
||||||
|
return new Gene(defaultValues[paramName]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Gene(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IChromosome CreateNew()
|
public override IChromosome CreateNew()
|
||||||
{
|
{
|
||||||
return new TradingBotChromosome(_eligibleIndicators);
|
return new TradingBotChromosome(_eligibleIndicators, _maxTakeProfit);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override IChromosome Clone()
|
public override IChromosome Clone()
|
||||||
{
|
{
|
||||||
var clone = new TradingBotChromosome(_eligibleIndicators);
|
var clone = new TradingBotChromosome(_eligibleIndicators, _maxTakeProfit);
|
||||||
clone.ReplaceGenes(0, GetGenes());
|
clone.ReplaceGenes(0, GetGenes());
|
||||||
return clone;
|
return clone;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<IndicatorType> GetSelectedIndicators()
|
public List<GeneticIndicator> GetSelectedIndicators()
|
||||||
{
|
{
|
||||||
var selected = new List<IndicatorType>();
|
var selected = new List<GeneticIndicator>();
|
||||||
for (int i = 0; i < _eligibleIndicators.Count; i++)
|
var genes = GetGenes();
|
||||||
|
|
||||||
|
for (int i = 0; i < 4; i++) // Check first 4 indicator slots
|
||||||
{
|
{
|
||||||
if (GetGene(i).Value.ToString() == "1")
|
if (genes[4 + i].Value.ToString() == "1" && i < _eligibleIndicators.Count)
|
||||||
{
|
{
|
||||||
selected.Add(_eligibleIndicators[i]);
|
var indicator = new GeneticIndicator
|
||||||
|
{
|
||||||
|
Type = _eligibleIndicators[i]
|
||||||
|
};
|
||||||
|
|
||||||
|
// Add parameters for this indicator
|
||||||
|
var baseIndex = 8 + i * 8;
|
||||||
|
var paramName = GetParameterName(0); // period
|
||||||
|
if (paramName != null && HasParameter(_eligibleIndicators[i], paramName))
|
||||||
|
{
|
||||||
|
indicator.Period = Convert.ToInt32(genes[baseIndex].Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
paramName = GetParameterName(1); // fastPeriods
|
||||||
|
if (paramName != null && HasParameter(_eligibleIndicators[i], paramName))
|
||||||
|
{
|
||||||
|
indicator.FastPeriods = Convert.ToInt32(genes[baseIndex + 1].Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
paramName = GetParameterName(2); // slowPeriods
|
||||||
|
if (paramName != null && HasParameter(_eligibleIndicators[i], paramName))
|
||||||
|
{
|
||||||
|
indicator.SlowPeriods = Convert.ToInt32(genes[baseIndex + 2].Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
paramName = GetParameterName(3); // signalPeriods
|
||||||
|
if (paramName != null && HasParameter(_eligibleIndicators[i], paramName))
|
||||||
|
{
|
||||||
|
indicator.SignalPeriods = Convert.ToInt32(genes[baseIndex + 3].Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
paramName = GetParameterName(4); // multiplier
|
||||||
|
if (paramName != null && HasParameter(_eligibleIndicators[i], paramName))
|
||||||
|
{
|
||||||
|
indicator.Multiplier = Convert.ToDouble(genes[baseIndex + 4].Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
paramName = GetParameterName(5); // stochPeriods
|
||||||
|
if (paramName != null && HasParameter(_eligibleIndicators[i], paramName))
|
||||||
|
{
|
||||||
|
indicator.StochPeriods = Convert.ToInt32(genes[baseIndex + 5].Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
paramName = GetParameterName(6); // smoothPeriods
|
||||||
|
if (paramName != null && HasParameter(_eligibleIndicators[i], paramName))
|
||||||
|
{
|
||||||
|
indicator.SmoothPeriods = Convert.ToInt32(genes[baseIndex + 6].Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
paramName = GetParameterName(7); // cyclePeriods
|
||||||
|
if (paramName != null && HasParameter(_eligibleIndicators[i], paramName))
|
||||||
|
{
|
||||||
|
indicator.CyclePeriods = Convert.ToInt32(genes[baseIndex + 7].Value);
|
||||||
|
}
|
||||||
|
|
||||||
|
selected.Add(indicator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return selected;
|
return selected;
|
||||||
}
|
}
|
||||||
|
|
||||||
public TradingBotConfig GetTradingBotConfig(GeneticRequest request)
|
public TradingBotConfig GetTradingBotConfig(GeneticRequest request)
|
||||||
{
|
{
|
||||||
var selectedIndicators = GetSelectedIndicators();
|
|
||||||
var genes = GetGenes();
|
var genes = GetGenes();
|
||||||
|
var selectedIndicators = GetSelectedIndicators();
|
||||||
|
|
||||||
|
// Ensure we have at least one indicator
|
||||||
|
if (!selectedIndicators.Any())
|
||||||
|
{
|
||||||
|
selectedIndicators.Add(new GeneticIndicator { Type = _eligibleIndicators[0] });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get take profit from chromosome (gene 0)
|
||||||
|
var takeProfit = Convert.ToDouble(genes[0].Value);
|
||||||
|
|
||||||
|
// Calculate stop loss based on 1.1:1 risk-reward ratio (gene 1)
|
||||||
|
var stopLoss = Convert.ToDouble(genes[1].Value);
|
||||||
|
|
||||||
|
// Ensure minimum 1.1:1 risk-reward ratio and minimum 0.2% to cover fees
|
||||||
|
var minStopLossForRR = takeProfit / 1.1;
|
||||||
|
var minStopLossForFees = 0.2; // Minimum 0.2% to cover trading fees
|
||||||
|
var minStopLoss = Math.Max(minStopLossForRR, minStopLossForFees);
|
||||||
|
var maxStopLoss = takeProfit - 0.1; // Ensure SL is less than TP with some buffer
|
||||||
|
|
||||||
|
// Adjust stop loss if it doesn't meet the constraints
|
||||||
|
if (stopLoss > maxStopLoss || stopLoss < minStopLoss)
|
||||||
|
{
|
||||||
|
stopLoss = GetRandomInRange((minStopLoss, maxStopLoss));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build scenario using selected indicators
|
||||||
|
var scenario = new Scenario($"Genetic_{request.RequestId}_Scenario", 1);
|
||||||
|
|
||||||
|
foreach (var geneticIndicator in selectedIndicators)
|
||||||
|
{
|
||||||
|
var indicator = ScenarioHelpers.BuildIndicator(
|
||||||
|
type: geneticIndicator.Type,
|
||||||
|
name: $"Genetic_{geneticIndicator.Type}_{Guid.NewGuid():N}",
|
||||||
|
period: geneticIndicator.Period,
|
||||||
|
fastPeriods: geneticIndicator.FastPeriods,
|
||||||
|
slowPeriods: geneticIndicator.SlowPeriods,
|
||||||
|
signalPeriods: geneticIndicator.SignalPeriods,
|
||||||
|
multiplier: geneticIndicator.Multiplier,
|
||||||
|
stochPeriods: geneticIndicator.StochPeriods,
|
||||||
|
smoothPeriods: geneticIndicator.SmoothPeriods,
|
||||||
|
cyclePeriods: geneticIndicator.CyclePeriods
|
||||||
|
);
|
||||||
|
|
||||||
|
scenario.AddIndicator(indicator);
|
||||||
|
}
|
||||||
|
|
||||||
return new TradingBotConfig
|
return new TradingBotConfig
|
||||||
{
|
{
|
||||||
@@ -242,28 +560,83 @@ public class TradingBotChromosome : ChromosomeBase
|
|||||||
BotTradingBalance = request.Balance,
|
BotTradingBalance = request.Balance,
|
||||||
IsForBacktest = true,
|
IsForBacktest = true,
|
||||||
IsForWatchingOnly = false,
|
IsForWatchingOnly = false,
|
||||||
CooldownPeriod = 0,
|
CooldownPeriod = Convert.ToInt32(genes[2].Value),
|
||||||
MaxLossStreak = 3,
|
MaxLossStreak = Convert.ToInt32(genes[3].Value),
|
||||||
FlipPosition = false,
|
FlipPosition = false,
|
||||||
FlipOnlyWhenInProfit = true,
|
FlipOnlyWhenInProfit = true,
|
||||||
|
CloseEarlyWhenProfitable = true,
|
||||||
|
MaxPositionTimeHours = 0, // Always 0 to prevent early position cutting
|
||||||
|
UseSynthApi = false,
|
||||||
|
UseForPositionSizing = false,
|
||||||
|
UseForSignalFiltering = false,
|
||||||
|
UseForDynamicStopLoss = false,
|
||||||
|
Scenario = scenario,
|
||||||
MoneyManagement = new MoneyManagement
|
MoneyManagement = new MoneyManagement
|
||||||
{
|
{
|
||||||
Name = $"Genetic_{request.RequestId}_MM",
|
Name = $"Genetic_{request.RequestId}_MM",
|
||||||
Timeframe = request.Timeframe,
|
Timeframe = request.Timeframe,
|
||||||
StopLoss = Convert.ToDecimal(genes[_eligibleIndicators.Count].Value),
|
StopLoss = Convert.ToDecimal(stopLoss),
|
||||||
TakeProfit = Convert.ToDecimal(genes[_eligibleIndicators.Count + 1].Value),
|
TakeProfit = Convert.ToDecimal(takeProfit),
|
||||||
Leverage = 1.0m
|
Leverage = 1.0m
|
||||||
},
|
},
|
||||||
RiskManagement = new RiskManagement
|
RiskManagement = new RiskManagement
|
||||||
{
|
{
|
||||||
RiskTolerance = (RiskToleranceLevel)Convert.ToInt32(genes[_eligibleIndicators.Count + 4].Value)
|
RiskTolerance = RiskToleranceLevel.Moderate
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private double GetRandomInRange((double min, double max) range)
|
||||||
|
{
|
||||||
|
return _random.NextDouble() * (range.max - range.min) + range.min;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int GetRandomIntInRange((double min, double max) range)
|
||||||
|
{
|
||||||
|
return _random.Next((int)range.min, (int)range.max + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private string? GetParameterName(int index)
|
||||||
|
{
|
||||||
|
return index switch
|
||||||
|
{
|
||||||
|
0 => "period",
|
||||||
|
1 => "fastPeriods",
|
||||||
|
2 => "slowPeriods",
|
||||||
|
3 => "signalPeriods",
|
||||||
|
4 => "multiplier",
|
||||||
|
5 => "stochPeriods",
|
||||||
|
6 => "smoothPeriods",
|
||||||
|
7 => "cyclePeriods",
|
||||||
|
_ => null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private bool HasParameter(IndicatorType indicator, string paramName)
|
||||||
|
{
|
||||||
|
return GeneticService.IndicatorParamMapping.ContainsKey(indicator) &&
|
||||||
|
GeneticService.IndicatorParamMapping[indicator].Contains(paramName);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Fitness function for trading bot optimization
|
/// Genetic indicator with parameters
|
||||||
|
/// </summary>
|
||||||
|
public class GeneticIndicator
|
||||||
|
{
|
||||||
|
public IndicatorType Type { get; set; }
|
||||||
|
public int? Period { get; set; }
|
||||||
|
public int? FastPeriods { get; set; }
|
||||||
|
public int? SlowPeriods { get; set; }
|
||||||
|
public int? SignalPeriods { get; set; }
|
||||||
|
public double? Multiplier { get; set; }
|
||||||
|
public int? StochPeriods { get; set; }
|
||||||
|
public int? SmoothPeriods { get; set; }
|
||||||
|
public int? CyclePeriods { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Multi-objective fitness function for trading bot optimization
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class TradingBotFitness : IFitness
|
public class TradingBotFitness : IFitness
|
||||||
{
|
{
|
||||||
@@ -297,8 +670,8 @@ public class TradingBotFitness : IFitness
|
|||||||
_request.RequestId
|
_request.RequestId
|
||||||
).Result;
|
).Result;
|
||||||
|
|
||||||
// Calculate fitness based on backtest results
|
// Calculate multi-objective fitness based on backtest results
|
||||||
var fitness = CalculateFitness(backtest);
|
var fitness = CalculateMultiObjectiveFitness(backtest, config);
|
||||||
|
|
||||||
return fitness;
|
return fitness;
|
||||||
}
|
}
|
||||||
@@ -309,29 +682,36 @@ public class TradingBotFitness : IFitness
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private double CalculateFitness(Backtest backtest)
|
private double CalculateMultiObjectiveFitness(Backtest backtest, TradingBotConfig config)
|
||||||
{
|
{
|
||||||
if (backtest == null || backtest.Score == null)
|
if (backtest == null || backtest.Statistics == null)
|
||||||
return 0.1;
|
return 0.1;
|
||||||
|
|
||||||
// Use the backtest score as the primary fitness metric
|
var stats = backtest.Statistics;
|
||||||
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
|
// Multi-objective fitness function (matching frontend)
|
||||||
var pnlBonus = finalPnl > 0 ? 20 : 0;
|
var pnlScore = Math.Max(0, (double)stats.TotalPnL / 1000); // Normalize PnL
|
||||||
|
var winRateScore = backtest.WinRate / 100.0; // Normalize win rate
|
||||||
return baseFitness + winRateBonus + pnlBonus;
|
var riskRewardScore = Math.Min(2, (double)stats.WinningTrades / Math.Max(1, Math.Abs((double)stats.LoosingTrades)));
|
||||||
|
var consistencyScore = 1 - Math.Abs((double)stats.TotalPnL - (double)backtest.FinalPnl) / Math.Max(1, Math.Abs((double)stats.TotalPnL));
|
||||||
|
|
||||||
|
// Risk-reward ratio bonus
|
||||||
|
var riskRewardRatio = (double)(config.MoneyManagement.TakeProfit / config.MoneyManagement.StopLoss);
|
||||||
|
var riskRewardBonus = Math.Min(0.2, (riskRewardRatio - 1.1) * 0.1);
|
||||||
|
|
||||||
|
// Drawdown score (normalized to 0-1, where lower drawdown is better)
|
||||||
|
var maxDrawdownPc = Math.Abs((double)stats.MaxDrawdownPc);
|
||||||
|
var drawdownScore = Math.Max(0, 1 - (maxDrawdownPc / 50));
|
||||||
|
|
||||||
|
// Weighted combination
|
||||||
|
var fitness =
|
||||||
|
pnlScore * 0.3 +
|
||||||
|
winRateScore * 0.2 +
|
||||||
|
riskRewardScore * 0.2 +
|
||||||
|
consistencyScore * 0.1 +
|
||||||
|
riskRewardBonus * 0.1 +
|
||||||
|
drawdownScore * 0.1;
|
||||||
|
|
||||||
|
return Math.Max(0, fitness);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user