Fix genetic backend
This commit is contained in:
@@ -80,8 +80,7 @@ namespace Managing.Application.Backtesting
|
||||
bool withCandles = false,
|
||||
string requestId = null)
|
||||
{
|
||||
var account = await GetAccountFromConfig(config);
|
||||
var candles = GetCandles(account, config.Ticker, config.Timeframe, startDate, endDate);
|
||||
var candles = GetCandles(config.Ticker, config.Timeframe, startDate, endDate);
|
||||
|
||||
var result = await RunBacktestWithCandles(config, candles, user, withCandles, requestId);
|
||||
|
||||
@@ -165,7 +164,7 @@ namespace Managing.Application.Backtesting
|
||||
};
|
||||
}
|
||||
|
||||
private List<Candle> GetCandles(Account account, Ticker ticker, Timeframe timeframe,
|
||||
private List<Candle> GetCandles(Ticker ticker, Timeframe timeframe,
|
||||
DateTime startDate, DateTime endDate)
|
||||
{
|
||||
var candles = _exchangeService.GetCandlesInflux(TradingExchanges.Evm, ticker,
|
||||
@@ -400,6 +399,12 @@ namespace Managing.Application.Backtesting
|
||||
return backtests;
|
||||
}
|
||||
|
||||
public IEnumerable<Backtest> GetBacktestsByRequestId(string requestId)
|
||||
{
|
||||
var backtests = _backtestRepository.GetBacktestsByRequestId(requestId).ToList();
|
||||
return backtests;
|
||||
}
|
||||
|
||||
public Backtest GetBacktestByIdForUser(User user, string id)
|
||||
{
|
||||
var backtest = _backtestRepository.GetBacktestByIdForUser(user, id);
|
||||
@@ -462,7 +467,5 @@ namespace Managing.Application.Backtesting
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@@ -42,104 +42,127 @@ public class GeneticService : IGeneticService
|
||||
[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.MacdCross] = new()
|
||||
{
|
||||
["fastPeriods"] = 12.0,
|
||||
["slowPeriods"] = 26.0,
|
||||
["signalPeriods"] = 9.0
|
||||
},
|
||||
[IndicatorType.DualEmaCross] = new() {
|
||||
["fastPeriods"] = 12.0,
|
||||
["slowPeriods"] = 26.0
|
||||
[IndicatorType.DualEmaCross] = new()
|
||||
{
|
||||
["fastPeriods"] = 12.0,
|
||||
["slowPeriods"] = 26.0
|
||||
},
|
||||
[IndicatorType.SuperTrend] = new() {
|
||||
["period"] = 14.0,
|
||||
["multiplier"] = 3.0
|
||||
[IndicatorType.SuperTrend] = new()
|
||||
{
|
||||
["period"] = 14.0,
|
||||
["multiplier"] = 3.0
|
||||
},
|
||||
[IndicatorType.SuperTrendCrossEma] = 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.ChandelierExit] = new()
|
||||
{
|
||||
["period"] = 14.0,
|
||||
["multiplier"] = 3.0
|
||||
},
|
||||
[IndicatorType.StochRsiTrend] = new() {
|
||||
["period"] = 14.0,
|
||||
["stochPeriods"] = 14.0,
|
||||
["signalPeriods"] = 9.0,
|
||||
["smoothPeriods"] = 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.Stc] = new()
|
||||
{
|
||||
["cyclePeriods"] = 10.0,
|
||||
["fastPeriods"] = 12.0,
|
||||
["slowPeriods"] = 26.0
|
||||
},
|
||||
[IndicatorType.LaggingStc] = 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)
|
||||
}
|
||||
};
|
||||
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()
|
||||
@@ -236,8 +259,10 @@ public class GeneticService : IGeneticService
|
||||
/// Runs the genetic algorithm for a specific request
|
||||
/// </summary>
|
||||
/// <param name="request">The genetic request to process</param>
|
||||
/// <param name="cancellationToken">Cancellation token to stop the algorithm</param>
|
||||
/// <returns>The genetic algorithm result</returns>
|
||||
public async Task<GeneticAlgorithmResult> RunGeneticAlgorithm(GeneticRequest request)
|
||||
public async Task<GeneticAlgorithmResult> RunGeneticAlgorithm(GeneticRequest request,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
try
|
||||
{
|
||||
@@ -247,15 +272,39 @@ public class GeneticService : IGeneticService
|
||||
request.Status = GeneticRequestStatus.Running;
|
||||
UpdateGeneticRequest(request);
|
||||
|
||||
// Create chromosome for trading bot configuration
|
||||
var chromosome = new TradingBotChromosome(request.EligibleIndicators, request.MaxTakeProfit);
|
||||
|
||||
// Create or resume chromosome for trading bot configuration
|
||||
TradingBotChromosome chromosome;
|
||||
Population population;
|
||||
|
||||
if (!string.IsNullOrEmpty(request.BestChromosome) && request.CurrentGeneration > 0)
|
||||
{
|
||||
// Resume from previous state (best chromosome only)
|
||||
chromosome = new TradingBotChromosome(request.EligibleIndicators, request.MaxTakeProfit);
|
||||
var savedChromosome = JsonSerializer.Deserialize<double[]>(request.BestChromosome);
|
||||
if (savedChromosome != null)
|
||||
{
|
||||
chromosome.ReplaceGenes(0, savedChromosome.Select(g => new Gene(g)).ToArray());
|
||||
}
|
||||
|
||||
population = new Population(request.PopulationSize, request.PopulationSize, chromosome);
|
||||
_logger.LogInformation(
|
||||
"Resuming genetic algorithm for request {RequestId} from generation {Generation} with best chromosome",
|
||||
request.RequestId, request.CurrentGeneration);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Start fresh
|
||||
chromosome = new TradingBotChromosome(request.EligibleIndicators, request.MaxTakeProfit);
|
||||
population = new Population(request.PopulationSize, request.PopulationSize, chromosome);
|
||||
_logger.LogInformation("Starting fresh genetic algorithm for request {RequestId}", request.RequestId);
|
||||
}
|
||||
|
||||
// Create fitness function
|
||||
var fitness = new TradingBotFitness(_backtester, request);
|
||||
|
||||
|
||||
// Create genetic algorithm with better configuration
|
||||
var ga = new GeneticAlgorithm(
|
||||
new Population(request.PopulationSize, request.PopulationSize, chromosome),
|
||||
population,
|
||||
fitness,
|
||||
GetSelection(request.SelectionMethod),
|
||||
new UniformCrossover(),
|
||||
@@ -266,14 +315,73 @@ public class GeneticService : IGeneticService
|
||||
CrossoverProbability = 0.7f // Fixed crossover rate as in frontend
|
||||
};
|
||||
|
||||
// Custom termination condition that checks for cancellation
|
||||
var originalTermination = ga.Termination;
|
||||
ga.Termination = new GenerationNumberTermination(request.Generations);
|
||||
|
||||
// Add cancellation check in the generation event
|
||||
|
||||
// Run the genetic algorithm with periodic checks for cancellation
|
||||
var generationCount = 0;
|
||||
ga.GenerationRan += (sender, e) =>
|
||||
{
|
||||
generationCount = ga.GenerationsNumber;
|
||||
|
||||
// Update progress every 5 generations
|
||||
if (generationCount % 5 == 0)
|
||||
{
|
||||
var bestFitness = ga.BestChromosome?.Fitness ?? 0;
|
||||
request.CurrentGeneration = generationCount;
|
||||
request.BestFitnessSoFar = bestFitness;
|
||||
|
||||
if (ga.BestChromosome is TradingBotChromosome bestChromosome)
|
||||
{
|
||||
var genes = bestChromosome.GetGenes();
|
||||
var geneValues = genes.Select(g =>
|
||||
{
|
||||
if (g.Value is double doubleValue) return doubleValue;
|
||||
if (g.Value is int intValue) return (double)intValue;
|
||||
return Convert.ToDouble(g.Value.ToString());
|
||||
}).ToArray();
|
||||
request.BestChromosome = JsonSerializer.Serialize(geneValues);
|
||||
}
|
||||
|
||||
UpdateGeneticRequest(request);
|
||||
}
|
||||
|
||||
// Check for cancellation
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
ga.Stop();
|
||||
}
|
||||
};
|
||||
|
||||
// Run the genetic algorithm
|
||||
ga.Start();
|
||||
|
||||
// Check if the algorithm was cancelled
|
||||
if (cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
_logger.LogInformation("Genetic algorithm cancelled for request {RequestId}", request.RequestId);
|
||||
|
||||
// Update request status to pending so it can be resumed
|
||||
request.Status = GeneticRequestStatus.Pending;
|
||||
UpdateGeneticRequest(request);
|
||||
|
||||
return new GeneticAlgorithmResult
|
||||
{
|
||||
BestFitness = request.BestFitnessSoFar ?? 0,
|
||||
BestIndividual = request.BestIndividual ?? "unknown",
|
||||
ProgressInfo = request.ProgressInfo,
|
||||
CompletedAt = DateTime.UtcNow
|
||||
};
|
||||
}
|
||||
|
||||
// Get the best chromosome
|
||||
var bestChromosome = ga.BestChromosome as TradingBotChromosome;
|
||||
var bestFitness = ga.BestChromosome.Fitness.Value;
|
||||
var bestFitness = ga.BestChromosome?.Fitness ?? 0;
|
||||
|
||||
_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);
|
||||
|
||||
// Update request with results
|
||||
@@ -289,7 +397,7 @@ public class GeneticService : IGeneticService
|
||||
generations = request.Generations,
|
||||
completed_at = DateTime.UtcNow
|
||||
});
|
||||
|
||||
|
||||
UpdateGeneticRequest(request);
|
||||
|
||||
return new GeneticAlgorithmResult
|
||||
@@ -303,13 +411,13 @@ public class GeneticService : IGeneticService
|
||||
catch (Exception ex)
|
||||
{
|
||||
_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;
|
||||
}
|
||||
}
|
||||
@@ -345,11 +453,18 @@ public class TradingBotChromosome : ChromosomeBase
|
||||
// 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
|
||||
public TradingBotChromosome(List<IndicatorType> eligibleIndicators, double maxTakeProfit)
|
||||
: base(4 + 1 + 4 +
|
||||
eligibleIndicators.Count * 8) // Trading params + loopback + indicator selection + indicator params
|
||||
{
|
||||
_eligibleIndicators = eligibleIndicators;
|
||||
_maxTakeProfit = maxTakeProfit;
|
||||
|
||||
// Initialize all genes
|
||||
for (int i = 0; i < Length; i++)
|
||||
{
|
||||
ReplaceGene(i, GenerateGene(i));
|
||||
}
|
||||
}
|
||||
|
||||
public override Gene GenerateGene(int geneIndex)
|
||||
@@ -366,7 +481,12 @@ public class TradingBotChromosome : ChromosomeBase
|
||||
_ => new Gene(0)
|
||||
};
|
||||
}
|
||||
else if (geneIndex < 8)
|
||||
else if (geneIndex == 4)
|
||||
{
|
||||
// LoopbackPeriod gene (always between 5 and 20)
|
||||
return new Gene(GetRandomIntInRange((5, 20)));
|
||||
}
|
||||
else if (geneIndex < 9)
|
||||
{
|
||||
// Indicator selection (0 = not selected, 1 = selected)
|
||||
return new Gene(_random.Next(2));
|
||||
@@ -374,46 +494,46 @@ public class TradingBotChromosome : ChromosomeBase
|
||||
else
|
||||
{
|
||||
// Indicator parameters
|
||||
var indicatorIndex = (geneIndex - 8) / 8;
|
||||
var paramIndex = (geneIndex - 8) % 8;
|
||||
|
||||
var indicatorIndex = (geneIndex - 9) / 8;
|
||||
var paramIndex = (geneIndex - 9) % 8;
|
||||
|
||||
if (indicatorIndex < _eligibleIndicators.Count)
|
||||
{
|
||||
var indicator = _eligibleIndicators[indicatorIndex];
|
||||
var paramName = GetParameterName(paramIndex);
|
||||
|
||||
if (paramName != null && GeneticService.IndicatorParamMapping.ContainsKey(indicator))
|
||||
{
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (paramName != null && GeneticService.IndicatorParamMapping.ContainsKey(indicator))
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -434,70 +554,70 @@ public class TradingBotChromosome : ChromosomeBase
|
||||
{
|
||||
var selected = new List<GeneticIndicator>();
|
||||
var genes = GetGenes();
|
||||
|
||||
|
||||
for (int i = 0; i < 4; i++) // Check first 4 indicator slots
|
||||
{
|
||||
if (genes[4 + i].Value.ToString() == "1" && i < _eligibleIndicators.Count)
|
||||
if (genes[5 + i].Value.ToString() == "1" && i < _eligibleIndicators.Count)
|
||||
{
|
||||
var indicator = new GeneticIndicator
|
||||
{
|
||||
Type = _eligibleIndicators[i]
|
||||
};
|
||||
|
||||
|
||||
// Add parameters for this indicator
|
||||
var baseIndex = 8 + i * 8;
|
||||
var baseIndex = 9 + 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;
|
||||
}
|
||||
|
||||
@@ -505,7 +625,7 @@ public class TradingBotChromosome : ChromosomeBase
|
||||
{
|
||||
var genes = GetGenes();
|
||||
var selectedIndicators = GetSelectedIndicators();
|
||||
|
||||
|
||||
// Ensure we have at least one indicator
|
||||
if (!selectedIndicators.Any())
|
||||
{
|
||||
@@ -514,25 +634,28 @@ public class TradingBotChromosome : ChromosomeBase
|
||||
|
||||
// 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));
|
||||
}
|
||||
|
||||
// Get loopback period from gene 4
|
||||
var loopbackPeriod = Convert.ToInt32(genes[4].Value);
|
||||
|
||||
// Build scenario using selected indicators
|
||||
var scenario = new Scenario($"Genetic_{request.RequestId}_Scenario", 1);
|
||||
|
||||
var scenario = new Scenario($"Genetic_{request.RequestId}_Scenario", loopbackPeriod);
|
||||
|
||||
foreach (var geneticIndicator in selectedIndicators)
|
||||
{
|
||||
var indicator = ScenarioHelpers.BuildIndicator(
|
||||
@@ -547,14 +670,14 @@ public class TradingBotChromosome : ChromosomeBase
|
||||
smoothPeriods: geneticIndicator.SmoothPeriods,
|
||||
cyclePeriods: geneticIndicator.CyclePeriods
|
||||
);
|
||||
|
||||
|
||||
scenario.AddIndicator(indicator);
|
||||
}
|
||||
|
||||
return new TradingBotConfig
|
||||
{
|
||||
Name = $"Genetic_{request.RequestId}",
|
||||
AccountName = "genetic_account",
|
||||
AccountName = "Oda-embedded",
|
||||
Ticker = request.Ticker,
|
||||
Timeframe = request.Timeframe,
|
||||
BotTradingBalance = request.Balance,
|
||||
@@ -612,11 +735,11 @@ public class TradingBotChromosome : ChromosomeBase
|
||||
};
|
||||
}
|
||||
|
||||
private bool HasParameter(IndicatorType indicator, string paramName)
|
||||
{
|
||||
return GeneticService.IndicatorParamMapping.ContainsKey(indicator) &&
|
||||
GeneticService.IndicatorParamMapping[indicator].Contains(paramName);
|
||||
}
|
||||
private bool HasParameter(IndicatorType indicator, string paramName)
|
||||
{
|
||||
return GeneticService.IndicatorParamMapping.ContainsKey(indicator) &&
|
||||
GeneticService.IndicatorParamMapping[indicator].Contains(paramName);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -658,21 +781,21 @@ public class TradingBotFitness : IFitness
|
||||
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
|
||||
true, // Don't save individual backtests
|
||||
false, // Don't include candles
|
||||
_request.RequestId
|
||||
).Result;
|
||||
|
||||
// Calculate multi-objective fitness based on backtest results
|
||||
var fitness = CalculateMultiObjectiveFitness(backtest, config);
|
||||
|
||||
|
||||
return fitness;
|
||||
}
|
||||
catch (Exception)
|
||||
@@ -688,30 +811,32 @@ public class TradingBotFitness : IFitness
|
||||
return 0.1;
|
||||
|
||||
var stats = backtest.Statistics;
|
||||
|
||||
// Multi-objective fitness function (matching frontend)
|
||||
var pnlScore = Math.Max(0, (double)stats.TotalPnL / 1000); // Normalize PnL
|
||||
var winRateScore = backtest.WinRate / 100.0; // Normalize win rate
|
||||
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;
|
||||
|
||||
|
||||
// Multi-objective fitness function (matching frontend)
|
||||
var pnlScore = Math.Max(0, (double)stats.TotalPnL / 1000); // Normalize PnL
|
||||
var winRateScore = backtest.WinRate / 100.0; // Normalize win rate
|
||||
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