Fix genetic backend
This commit is contained in:
@@ -105,5 +105,6 @@ Key Principles
|
|||||||
- Do not update ManagingApi.ts, once you made a change on the backend endpoint, execute the command to regenerate ManagingApi.ts on the frontend; cd src/Managing.Nswag && dotnet build
|
- Do not update ManagingApi.ts, once you made a change on the backend endpoint, execute the command to regenerate ManagingApi.ts on the frontend; cd src/Managing.Nswag && dotnet build
|
||||||
- Do not reference new react library if a component already exist in mollecules or atoms
|
- Do not reference new react library if a component already exist in mollecules or atoms
|
||||||
- After finishing the editing, build the project
|
- After finishing the editing, build the project
|
||||||
|
- you have to pass from controller -> application -> repository, do not inject repository inside controllers
|
||||||
|
|
||||||
Follow the official Microsoft documentation and ASP.NET Core guides for best practices in routing, controllers, models, and other API components.
|
Follow the official Microsoft documentation and ASP.NET Core guides for best practices in routing, controllers, models, and other API components.
|
||||||
|
|||||||
@@ -25,10 +25,10 @@
|
|||||||
"Uri": "http://localhost:9200"
|
"Uri": "http://localhost:9200"
|
||||||
},
|
},
|
||||||
"AllowedHosts": "*",
|
"AllowedHosts": "*",
|
||||||
"WorkerPricesFifteenMinutes": true,
|
"WorkerPricesFifteenMinutes": false,
|
||||||
"WorkerPricesOneHour": true,
|
"WorkerPricesOneHour": false,
|
||||||
"WorkerPricesFourHours": true,
|
"WorkerPricesFourHours": false,
|
||||||
"WorkerPricesOneDay": true,
|
"WorkerPricesOneDay": false,
|
||||||
"WorkerPricesFiveMinutes": false,
|
"WorkerPricesFiveMinutes": false,
|
||||||
"WorkerFee": false,
|
"WorkerFee": false,
|
||||||
"WorkerPositionManager": false,
|
"WorkerPositionManager": false,
|
||||||
|
|||||||
@@ -103,6 +103,25 @@ public class BacktestController : BaseController
|
|||||||
return Ok(_backtester.DeleteBacktestByUser(user, id));
|
return Ok(_backtester.DeleteBacktestByUser(user, id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves all backtests for a specific genetic request ID.
|
||||||
|
/// This endpoint is used to view the results of a genetic algorithm optimization.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="requestId">The request ID to filter backtests by.</param>
|
||||||
|
/// <returns>A list of backtests associated with the specified request ID.</returns>
|
||||||
|
[HttpGet]
|
||||||
|
[Route("ByRequestId/{requestId}")]
|
||||||
|
public async Task<ActionResult<IEnumerable<Backtest>>> GetBacktestsByRequestId(string requestId)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(requestId))
|
||||||
|
{
|
||||||
|
return BadRequest("Request ID is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
var backtests = _backtester.GetBacktestsByRequestId(requestId);
|
||||||
|
return Ok(backtests);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Runs a backtest with the specified configuration.
|
/// Runs a backtest with the specified configuration.
|
||||||
/// The returned backtest includes a complete TradingBotConfig that preserves all
|
/// The returned backtest includes a complete TradingBotConfig that preserves all
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ public interface IBacktestRepository
|
|||||||
{
|
{
|
||||||
void InsertBacktestForUser(User user, Backtest result);
|
void InsertBacktestForUser(User user, Backtest result);
|
||||||
IEnumerable<Backtest> GetBacktestsByUser(User user);
|
IEnumerable<Backtest> GetBacktestsByUser(User user);
|
||||||
|
IEnumerable<Backtest> GetBacktestsByRequestId(string requestId);
|
||||||
Backtest GetBacktestByIdForUser(User user, string id);
|
Backtest GetBacktestByIdForUser(User user, string id);
|
||||||
void DeleteBacktestByIdForUser(User user, string id);
|
void DeleteBacktestByIdForUser(User user, string id);
|
||||||
void DeleteAllBacktestsForUser(User user);
|
void DeleteAllBacktestsForUser(User user);
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ namespace Managing.Application.Abstractions.Services
|
|||||||
bool DeleteBacktest(string id);
|
bool DeleteBacktest(string id);
|
||||||
bool DeleteBacktests();
|
bool DeleteBacktests();
|
||||||
IEnumerable<Backtest> GetBacktestsByUser(User user);
|
IEnumerable<Backtest> GetBacktestsByUser(User user);
|
||||||
|
IEnumerable<Backtest> GetBacktestsByRequestId(string requestId);
|
||||||
Backtest GetBacktestByIdForUser(User user, string id);
|
Backtest GetBacktestByIdForUser(User user, string id);
|
||||||
bool DeleteBacktestByUser(User user, string id);
|
bool DeleteBacktestByUser(User user, string id);
|
||||||
bool DeleteBacktestsByUser(User user);
|
bool DeleteBacktestsByUser(User user);
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ public interface IGeneticService
|
|||||||
/// Runs the genetic algorithm for a specific request
|
/// Runs the genetic algorithm for a specific request
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request">The genetic request to process</param>
|
/// <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>
|
/// <returns>The genetic algorithm result</returns>
|
||||||
Task<GeneticAlgorithmResult> RunGeneticAlgorithm(GeneticRequest request);
|
Task<GeneticAlgorithmResult> RunGeneticAlgorithm(GeneticRequest request, CancellationToken cancellationToken = default);
|
||||||
}
|
}
|
||||||
@@ -70,7 +70,7 @@ public class GeneticAlgorithmWorker : BaseWorker<GeneticAlgorithmWorker>
|
|||||||
_geneticService.UpdateGeneticRequest(request);
|
_geneticService.UpdateGeneticRequest(request);
|
||||||
|
|
||||||
// Run genetic algorithm using the service
|
// Run genetic algorithm using the service
|
||||||
var results = await _geneticService.RunGeneticAlgorithm(request);
|
var results = await _geneticService.RunGeneticAlgorithm(request, cancellationToken);
|
||||||
|
|
||||||
// Update request with results
|
// Update request with results
|
||||||
request.Status = GeneticRequestStatus.Completed;
|
request.Status = GeneticRequestStatus.Completed;
|
||||||
|
|||||||
@@ -80,8 +80,7 @@ namespace Managing.Application.Backtesting
|
|||||||
bool withCandles = false,
|
bool withCandles = false,
|
||||||
string requestId = null)
|
string requestId = null)
|
||||||
{
|
{
|
||||||
var account = await GetAccountFromConfig(config);
|
var candles = GetCandles(config.Ticker, config.Timeframe, startDate, endDate);
|
||||||
var candles = GetCandles(account, config.Ticker, config.Timeframe, startDate, endDate);
|
|
||||||
|
|
||||||
var result = await RunBacktestWithCandles(config, candles, user, withCandles, requestId);
|
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)
|
DateTime startDate, DateTime endDate)
|
||||||
{
|
{
|
||||||
var candles = _exchangeService.GetCandlesInflux(TradingExchanges.Evm, ticker,
|
var candles = _exchangeService.GetCandlesInflux(TradingExchanges.Evm, ticker,
|
||||||
@@ -400,6 +399,12 @@ namespace Managing.Application.Backtesting
|
|||||||
return backtests;
|
return backtests;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Backtest> GetBacktestsByRequestId(string requestId)
|
||||||
|
{
|
||||||
|
var backtests = _backtestRepository.GetBacktestsByRequestId(requestId).ToList();
|
||||||
|
return backtests;
|
||||||
|
}
|
||||||
|
|
||||||
public Backtest GetBacktestByIdForUser(User user, string id)
|
public Backtest GetBacktestByIdForUser(User user, string id)
|
||||||
{
|
{
|
||||||
var backtest = _backtestRepository.GetBacktestByIdForUser(user, id);
|
var backtest = _backtestRepository.GetBacktestByIdForUser(user, id);
|
||||||
@@ -462,7 +467,5 @@ namespace Managing.Application.Backtesting
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -42,104 +42,127 @@ public class GeneticService : IGeneticService
|
|||||||
[IndicatorType.EmaTrend] = new() { ["period"] = 14.0 },
|
[IndicatorType.EmaTrend] = new() { ["period"] = 14.0 },
|
||||||
[IndicatorType.StDev] = new() { ["period"] = 14.0 },
|
[IndicatorType.StDev] = new() { ["period"] = 14.0 },
|
||||||
[IndicatorType.ThreeWhiteSoldiers] = new() { ["period"] = 14.0 },
|
[IndicatorType.ThreeWhiteSoldiers] = new() { ["period"] = 14.0 },
|
||||||
[IndicatorType.MacdCross] = new() {
|
[IndicatorType.MacdCross] = new()
|
||||||
["fastPeriods"] = 12.0,
|
{
|
||||||
["slowPeriods"] = 26.0,
|
["fastPeriods"] = 12.0,
|
||||||
["signalPeriods"] = 9.0
|
["slowPeriods"] = 26.0,
|
||||||
|
["signalPeriods"] = 9.0
|
||||||
},
|
},
|
||||||
[IndicatorType.DualEmaCross] = new() {
|
[IndicatorType.DualEmaCross] = new()
|
||||||
["fastPeriods"] = 12.0,
|
{
|
||||||
["slowPeriods"] = 26.0
|
["fastPeriods"] = 12.0,
|
||||||
|
["slowPeriods"] = 26.0
|
||||||
},
|
},
|
||||||
[IndicatorType.SuperTrend] = new() {
|
[IndicatorType.SuperTrend] = new()
|
||||||
["period"] = 14.0,
|
{
|
||||||
["multiplier"] = 3.0
|
["period"] = 14.0,
|
||||||
|
["multiplier"] = 3.0
|
||||||
},
|
},
|
||||||
[IndicatorType.SuperTrendCrossEma] = new() {
|
[IndicatorType.SuperTrendCrossEma] = new()
|
||||||
["period"] = 14.0,
|
{
|
||||||
["multiplier"] = 3.0
|
["period"] = 14.0,
|
||||||
|
["multiplier"] = 3.0
|
||||||
},
|
},
|
||||||
[IndicatorType.ChandelierExit] = new() {
|
[IndicatorType.ChandelierExit] = new()
|
||||||
["period"] = 14.0,
|
{
|
||||||
["multiplier"] = 3.0
|
["period"] = 14.0,
|
||||||
|
["multiplier"] = 3.0
|
||||||
},
|
},
|
||||||
[IndicatorType.StochRsiTrend] = new() {
|
[IndicatorType.StochRsiTrend] = new()
|
||||||
["period"] = 14.0,
|
{
|
||||||
["stochPeriods"] = 14.0,
|
["period"] = 14.0,
|
||||||
["signalPeriods"] = 9.0,
|
["stochPeriods"] = 14.0,
|
||||||
["smoothPeriods"] = 3.0
|
["signalPeriods"] = 9.0,
|
||||||
|
["smoothPeriods"] = 3.0
|
||||||
},
|
},
|
||||||
[IndicatorType.Stc] = new() {
|
[IndicatorType.Stc] = new()
|
||||||
["cyclePeriods"] = 10.0,
|
{
|
||||||
["fastPeriods"] = 12.0,
|
["cyclePeriods"] = 10.0,
|
||||||
["slowPeriods"] = 26.0
|
["fastPeriods"] = 12.0,
|
||||||
|
["slowPeriods"] = 26.0
|
||||||
},
|
},
|
||||||
[IndicatorType.LaggingStc] = new() {
|
[IndicatorType.LaggingStc] = new()
|
||||||
["cyclePeriods"] = 10.0,
|
{
|
||||||
["fastPeriods"] = 12.0,
|
["cyclePeriods"] = 10.0,
|
||||||
["slowPeriods"] = 26.0
|
["fastPeriods"] = 12.0,
|
||||||
|
["slowPeriods"] = 26.0
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Indicator-specific parameter ranges
|
// Indicator-specific parameter ranges
|
||||||
public static readonly Dictionary<IndicatorType, Dictionary<string, (double min, double max)>> IndicatorParameterRanges = new()
|
public static readonly Dictionary<IndicatorType, Dictionary<string, (double min, double max)>>
|
||||||
{
|
IndicatorParameterRanges = new()
|
||||||
[IndicatorType.RsiDivergence] = new() {
|
{
|
||||||
["period"] = (5.0, 50.0)
|
[IndicatorType.RsiDivergence] = new()
|
||||||
},
|
{
|
||||||
[IndicatorType.RsiDivergenceConfirm] = new() {
|
["period"] = (5.0, 50.0)
|
||||||
["period"] = (5.0, 50.0)
|
},
|
||||||
},
|
[IndicatorType.RsiDivergenceConfirm] = new()
|
||||||
[IndicatorType.EmaCross] = new() {
|
{
|
||||||
["period"] = (5.0, 200.0)
|
["period"] = (5.0, 50.0)
|
||||||
},
|
},
|
||||||
[IndicatorType.EmaTrend] = new() {
|
[IndicatorType.EmaCross] = new()
|
||||||
["period"] = (5.0, 200.0)
|
{
|
||||||
},
|
["period"] = (5.0, 200.0)
|
||||||
[IndicatorType.StDev] = new() {
|
},
|
||||||
["period"] = (5.0, 50.0)
|
[IndicatorType.EmaTrend] = new()
|
||||||
},
|
{
|
||||||
[IndicatorType.ThreeWhiteSoldiers] = new() {
|
["period"] = (5.0, 200.0)
|
||||||
["period"] = (5.0, 50.0)
|
},
|
||||||
},
|
[IndicatorType.StDev] = new()
|
||||||
[IndicatorType.MacdCross] = new() {
|
{
|
||||||
["fastPeriods"] = (10.0, 50.0),
|
["period"] = (5.0, 50.0)
|
||||||
["slowPeriods"] = (20.0, 100.0),
|
},
|
||||||
["signalPeriods"] = (5.0, 20.0)
|
[IndicatorType.ThreeWhiteSoldiers] = new()
|
||||||
},
|
{
|
||||||
[IndicatorType.DualEmaCross] = new() {
|
["period"] = (5.0, 50.0)
|
||||||
["fastPeriods"] = (5.0, 300.0),
|
},
|
||||||
["slowPeriods"] = (5.0, 300.0)
|
[IndicatorType.MacdCross] = new()
|
||||||
},
|
{
|
||||||
[IndicatorType.SuperTrend] = new() {
|
["fastPeriods"] = (10.0, 50.0),
|
||||||
["period"] = (5.0, 50.0),
|
["slowPeriods"] = (20.0, 100.0),
|
||||||
["multiplier"] = (1.0, 10.0)
|
["signalPeriods"] = (5.0, 20.0)
|
||||||
},
|
},
|
||||||
[IndicatorType.SuperTrendCrossEma] = new() {
|
[IndicatorType.DualEmaCross] = new()
|
||||||
["period"] = (5.0, 50.0),
|
{
|
||||||
["multiplier"] = (1.0, 10.0)
|
["fastPeriods"] = (5.0, 300.0),
|
||||||
},
|
["slowPeriods"] = (5.0, 300.0)
|
||||||
[IndicatorType.ChandelierExit] = new() {
|
},
|
||||||
["period"] = (5.0, 50.0),
|
[IndicatorType.SuperTrend] = new()
|
||||||
["multiplier"] = (1.0, 10.0)
|
{
|
||||||
},
|
["period"] = (5.0, 50.0),
|
||||||
[IndicatorType.StochRsiTrend] = new() {
|
["multiplier"] = (1.0, 10.0)
|
||||||
["period"] = (5.0, 50.0),
|
},
|
||||||
["stochPeriods"] = (5.0, 30.0),
|
[IndicatorType.SuperTrendCrossEma] = new()
|
||||||
["signalPeriods"] = (3.0, 15.0),
|
{
|
||||||
["smoothPeriods"] = (1.0, 10.0)
|
["period"] = (5.0, 50.0),
|
||||||
},
|
["multiplier"] = (1.0, 10.0)
|
||||||
[IndicatorType.Stc] = new() {
|
},
|
||||||
["cyclePeriods"] = (5.0, 30.0),
|
[IndicatorType.ChandelierExit] = new()
|
||||||
["fastPeriods"] = (5.0, 50.0),
|
{
|
||||||
["slowPeriods"] = (10.0, 100.0)
|
["period"] = (5.0, 50.0),
|
||||||
},
|
["multiplier"] = (1.0, 10.0)
|
||||||
[IndicatorType.LaggingStc] = new() {
|
},
|
||||||
["cyclePeriods"] = (5.0, 30.0),
|
[IndicatorType.StochRsiTrend] = new()
|
||||||
["fastPeriods"] = (5.0, 50.0),
|
{
|
||||||
["slowPeriods"] = (10.0, 100.0)
|
["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
|
// Indicator type to parameter mapping
|
||||||
public static readonly Dictionary<IndicatorType, string[]> IndicatorParamMapping = new()
|
public static readonly Dictionary<IndicatorType, string[]> IndicatorParamMapping = new()
|
||||||
@@ -236,8 +259,10 @@ public class GeneticService : IGeneticService
|
|||||||
/// Runs the genetic algorithm for a specific request
|
/// Runs the genetic algorithm for a specific request
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="request">The genetic request to process</param>
|
/// <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>
|
/// <returns>The genetic algorithm result</returns>
|
||||||
public async Task<GeneticAlgorithmResult> RunGeneticAlgorithm(GeneticRequest request)
|
public async Task<GeneticAlgorithmResult> RunGeneticAlgorithm(GeneticRequest request,
|
||||||
|
CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -247,15 +272,39 @@ public class GeneticService : IGeneticService
|
|||||||
request.Status = GeneticRequestStatus.Running;
|
request.Status = GeneticRequestStatus.Running;
|
||||||
UpdateGeneticRequest(request);
|
UpdateGeneticRequest(request);
|
||||||
|
|
||||||
// Create chromosome for trading bot configuration
|
// Create or resume chromosome for trading bot configuration
|
||||||
var chromosome = new TradingBotChromosome(request.EligibleIndicators, request.MaxTakeProfit);
|
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
|
// Create fitness function
|
||||||
var fitness = new TradingBotFitness(_backtester, request);
|
var fitness = new TradingBotFitness(_backtester, request);
|
||||||
|
|
||||||
// Create genetic algorithm with better configuration
|
// Create genetic algorithm with better configuration
|
||||||
var ga = new GeneticAlgorithm(
|
var ga = new GeneticAlgorithm(
|
||||||
new Population(request.PopulationSize, request.PopulationSize, chromosome),
|
population,
|
||||||
fitness,
|
fitness,
|
||||||
GetSelection(request.SelectionMethod),
|
GetSelection(request.SelectionMethod),
|
||||||
new UniformCrossover(),
|
new UniformCrossover(),
|
||||||
@@ -266,14 +315,73 @@ public class GeneticService : IGeneticService
|
|||||||
CrossoverProbability = 0.7f // Fixed crossover rate as in frontend
|
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
|
// Run the genetic algorithm
|
||||||
ga.Start();
|
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
|
// Get the best chromosome
|
||||||
var bestChromosome = ga.BestChromosome as TradingBotChromosome;
|
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);
|
request.RequestId, bestFitness);
|
||||||
|
|
||||||
// Update request with results
|
// Update request with results
|
||||||
@@ -289,7 +397,7 @@ public class GeneticService : IGeneticService
|
|||||||
generations = request.Generations,
|
generations = request.Generations,
|
||||||
completed_at = DateTime.UtcNow
|
completed_at = DateTime.UtcNow
|
||||||
});
|
});
|
||||||
|
|
||||||
UpdateGeneticRequest(request);
|
UpdateGeneticRequest(request);
|
||||||
|
|
||||||
return new GeneticAlgorithmResult
|
return new GeneticAlgorithmResult
|
||||||
@@ -303,13 +411,13 @@ public class GeneticService : IGeneticService
|
|||||||
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
|
// Update request with error
|
||||||
request.Status = GeneticRequestStatus.Failed;
|
request.Status = GeneticRequestStatus.Failed;
|
||||||
request.ErrorMessage = ex.Message;
|
request.ErrorMessage = ex.Message;
|
||||||
request.CompletedAt = DateTime.UtcNow;
|
request.CompletedAt = DateTime.UtcNow;
|
||||||
UpdateGeneticRequest(request);
|
UpdateGeneticRequest(request);
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -345,11 +453,18 @@ public class TradingBotChromosome : ChromosomeBase
|
|||||||
// 3-6: Indicator selection (up to 4 indicators)
|
// 3-6: Indicator selection (up to 4 indicators)
|
||||||
// 7+: Indicator parameters (period, fastPeriods, etc.)
|
// 7+: Indicator parameters (period, fastPeriods, etc.)
|
||||||
|
|
||||||
public TradingBotChromosome(List<IndicatorType> eligibleIndicators, double maxTakeProfit)
|
public TradingBotChromosome(List<IndicatorType> eligibleIndicators, double maxTakeProfit)
|
||||||
: base(4 + 4 + eligibleIndicators.Count * 8) // Trading params + indicator selection + indicator params
|
: base(4 + 1 + 4 +
|
||||||
|
eligibleIndicators.Count * 8) // Trading params + loopback + indicator selection + indicator params
|
||||||
{
|
{
|
||||||
_eligibleIndicators = eligibleIndicators;
|
_eligibleIndicators = eligibleIndicators;
|
||||||
_maxTakeProfit = maxTakeProfit;
|
_maxTakeProfit = maxTakeProfit;
|
||||||
|
|
||||||
|
// Initialize all genes
|
||||||
|
for (int i = 0; i < Length; i++)
|
||||||
|
{
|
||||||
|
ReplaceGene(i, GenerateGene(i));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Gene GenerateGene(int geneIndex)
|
public override Gene GenerateGene(int geneIndex)
|
||||||
@@ -366,7 +481,12 @@ public class TradingBotChromosome : ChromosomeBase
|
|||||||
_ => new Gene(0)
|
_ => 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)
|
// Indicator selection (0 = not selected, 1 = selected)
|
||||||
return new Gene(_random.Next(2));
|
return new Gene(_random.Next(2));
|
||||||
@@ -374,46 +494,46 @@ public class TradingBotChromosome : ChromosomeBase
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
// Indicator parameters
|
// Indicator parameters
|
||||||
var indicatorIndex = (geneIndex - 8) / 8;
|
var indicatorIndex = (geneIndex - 9) / 8;
|
||||||
var paramIndex = (geneIndex - 8) % 8;
|
var paramIndex = (geneIndex - 9) % 8;
|
||||||
|
|
||||||
if (indicatorIndex < _eligibleIndicators.Count)
|
if (indicatorIndex < _eligibleIndicators.Count)
|
||||||
{
|
{
|
||||||
var indicator = _eligibleIndicators[indicatorIndex];
|
var indicator = _eligibleIndicators[indicatorIndex];
|
||||||
var paramName = GetParameterName(paramIndex);
|
var paramName = GetParameterName(paramIndex);
|
||||||
|
|
||||||
if (paramName != null && GeneticService.IndicatorParamMapping.ContainsKey(indicator))
|
if (paramName != null && GeneticService.IndicatorParamMapping.ContainsKey(indicator))
|
||||||
{
|
{
|
||||||
var requiredParams = GeneticService.IndicatorParamMapping[indicator];
|
var requiredParams = GeneticService.IndicatorParamMapping[indicator];
|
||||||
if (requiredParams.Contains(paramName))
|
if (requiredParams.Contains(paramName))
|
||||||
{
|
{
|
||||||
// Use indicator-specific ranges only
|
// Use indicator-specific ranges only
|
||||||
if (GeneticService.IndicatorParameterRanges.ContainsKey(indicator) &&
|
if (GeneticService.IndicatorParameterRanges.ContainsKey(indicator) &&
|
||||||
GeneticService.IndicatorParameterRanges[indicator].ContainsKey(paramName))
|
GeneticService.IndicatorParameterRanges[indicator].ContainsKey(paramName))
|
||||||
{
|
{
|
||||||
var indicatorRange = GeneticService.IndicatorParameterRanges[indicator][paramName];
|
var indicatorRange = GeneticService.IndicatorParameterRanges[indicator][paramName];
|
||||||
|
|
||||||
// 70% chance to use default value, 30% chance to use random value within indicator-specific range
|
// 70% chance to use default value, 30% chance to use random value within indicator-specific range
|
||||||
if (_random.NextDouble() < 0.7)
|
if (_random.NextDouble() < 0.7)
|
||||||
{
|
{
|
||||||
var defaultValues = GeneticService.DefaultIndicatorValues[indicator];
|
var defaultValues = GeneticService.DefaultIndicatorValues[indicator];
|
||||||
return new Gene(defaultValues[paramName]);
|
return new Gene(defaultValues[paramName]);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
return new Gene(GetRandomInRange(indicatorRange));
|
return new Gene(GetRandomInRange(indicatorRange));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// If no indicator-specific range is found, use default value only
|
// If no indicator-specific range is found, use default value only
|
||||||
var defaultValues = GeneticService.DefaultIndicatorValues[indicator];
|
var defaultValues = GeneticService.DefaultIndicatorValues[indicator];
|
||||||
return new Gene(defaultValues[paramName]);
|
return new Gene(defaultValues[paramName]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Gene(0);
|
return new Gene(0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -434,70 +554,70 @@ public class TradingBotChromosome : ChromosomeBase
|
|||||||
{
|
{
|
||||||
var selected = new List<GeneticIndicator>();
|
var selected = new List<GeneticIndicator>();
|
||||||
var genes = GetGenes();
|
var genes = GetGenes();
|
||||||
|
|
||||||
for (int i = 0; i < 4; i++) // Check first 4 indicator slots
|
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
|
var indicator = new GeneticIndicator
|
||||||
{
|
{
|
||||||
Type = _eligibleIndicators[i]
|
Type = _eligibleIndicators[i]
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add parameters for this indicator
|
// Add parameters for this indicator
|
||||||
var baseIndex = 8 + i * 8;
|
var baseIndex = 9 + i * 8;
|
||||||
var paramName = GetParameterName(0); // period
|
var paramName = GetParameterName(0); // period
|
||||||
if (paramName != null && HasParameter(_eligibleIndicators[i], paramName))
|
if (paramName != null && HasParameter(_eligibleIndicators[i], paramName))
|
||||||
{
|
{
|
||||||
indicator.Period = Convert.ToInt32(genes[baseIndex].Value);
|
indicator.Period = Convert.ToInt32(genes[baseIndex].Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
paramName = GetParameterName(1); // fastPeriods
|
paramName = GetParameterName(1); // fastPeriods
|
||||||
if (paramName != null && HasParameter(_eligibleIndicators[i], paramName))
|
if (paramName != null && HasParameter(_eligibleIndicators[i], paramName))
|
||||||
{
|
{
|
||||||
indicator.FastPeriods = Convert.ToInt32(genes[baseIndex + 1].Value);
|
indicator.FastPeriods = Convert.ToInt32(genes[baseIndex + 1].Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
paramName = GetParameterName(2); // slowPeriods
|
paramName = GetParameterName(2); // slowPeriods
|
||||||
if (paramName != null && HasParameter(_eligibleIndicators[i], paramName))
|
if (paramName != null && HasParameter(_eligibleIndicators[i], paramName))
|
||||||
{
|
{
|
||||||
indicator.SlowPeriods = Convert.ToInt32(genes[baseIndex + 2].Value);
|
indicator.SlowPeriods = Convert.ToInt32(genes[baseIndex + 2].Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
paramName = GetParameterName(3); // signalPeriods
|
paramName = GetParameterName(3); // signalPeriods
|
||||||
if (paramName != null && HasParameter(_eligibleIndicators[i], paramName))
|
if (paramName != null && HasParameter(_eligibleIndicators[i], paramName))
|
||||||
{
|
{
|
||||||
indicator.SignalPeriods = Convert.ToInt32(genes[baseIndex + 3].Value);
|
indicator.SignalPeriods = Convert.ToInt32(genes[baseIndex + 3].Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
paramName = GetParameterName(4); // multiplier
|
paramName = GetParameterName(4); // multiplier
|
||||||
if (paramName != null && HasParameter(_eligibleIndicators[i], paramName))
|
if (paramName != null && HasParameter(_eligibleIndicators[i], paramName))
|
||||||
{
|
{
|
||||||
indicator.Multiplier = Convert.ToDouble(genes[baseIndex + 4].Value);
|
indicator.Multiplier = Convert.ToDouble(genes[baseIndex + 4].Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
paramName = GetParameterName(5); // stochPeriods
|
paramName = GetParameterName(5); // stochPeriods
|
||||||
if (paramName != null && HasParameter(_eligibleIndicators[i], paramName))
|
if (paramName != null && HasParameter(_eligibleIndicators[i], paramName))
|
||||||
{
|
{
|
||||||
indicator.StochPeriods = Convert.ToInt32(genes[baseIndex + 5].Value);
|
indicator.StochPeriods = Convert.ToInt32(genes[baseIndex + 5].Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
paramName = GetParameterName(6); // smoothPeriods
|
paramName = GetParameterName(6); // smoothPeriods
|
||||||
if (paramName != null && HasParameter(_eligibleIndicators[i], paramName))
|
if (paramName != null && HasParameter(_eligibleIndicators[i], paramName))
|
||||||
{
|
{
|
||||||
indicator.SmoothPeriods = Convert.ToInt32(genes[baseIndex + 6].Value);
|
indicator.SmoothPeriods = Convert.ToInt32(genes[baseIndex + 6].Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
paramName = GetParameterName(7); // cyclePeriods
|
paramName = GetParameterName(7); // cyclePeriods
|
||||||
if (paramName != null && HasParameter(_eligibleIndicators[i], paramName))
|
if (paramName != null && HasParameter(_eligibleIndicators[i], paramName))
|
||||||
{
|
{
|
||||||
indicator.CyclePeriods = Convert.ToInt32(genes[baseIndex + 7].Value);
|
indicator.CyclePeriods = Convert.ToInt32(genes[baseIndex + 7].Value);
|
||||||
}
|
}
|
||||||
|
|
||||||
selected.Add(indicator);
|
selected.Add(indicator);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return selected;
|
return selected;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -505,7 +625,7 @@ public class TradingBotChromosome : ChromosomeBase
|
|||||||
{
|
{
|
||||||
var genes = GetGenes();
|
var genes = GetGenes();
|
||||||
var selectedIndicators = GetSelectedIndicators();
|
var selectedIndicators = GetSelectedIndicators();
|
||||||
|
|
||||||
// Ensure we have at least one indicator
|
// Ensure we have at least one indicator
|
||||||
if (!selectedIndicators.Any())
|
if (!selectedIndicators.Any())
|
||||||
{
|
{
|
||||||
@@ -514,25 +634,28 @@ public class TradingBotChromosome : ChromosomeBase
|
|||||||
|
|
||||||
// Get take profit from chromosome (gene 0)
|
// Get take profit from chromosome (gene 0)
|
||||||
var takeProfit = Convert.ToDouble(genes[0].Value);
|
var takeProfit = Convert.ToDouble(genes[0].Value);
|
||||||
|
|
||||||
// Calculate stop loss based on 1.1:1 risk-reward ratio (gene 1)
|
// Calculate stop loss based on 1.1:1 risk-reward ratio (gene 1)
|
||||||
var stopLoss = Convert.ToDouble(genes[1].Value);
|
var stopLoss = Convert.ToDouble(genes[1].Value);
|
||||||
|
|
||||||
// Ensure minimum 1.1:1 risk-reward ratio and minimum 0.2% to cover fees
|
// Ensure minimum 1.1:1 risk-reward ratio and minimum 0.2% to cover fees
|
||||||
var minStopLossForRR = takeProfit / 1.1;
|
var minStopLossForRR = takeProfit / 1.1;
|
||||||
var minStopLossForFees = 0.2; // Minimum 0.2% to cover trading fees
|
var minStopLossForFees = 0.2; // Minimum 0.2% to cover trading fees
|
||||||
var minStopLoss = Math.Max(minStopLossForRR, minStopLossForFees);
|
var minStopLoss = Math.Max(minStopLossForRR, minStopLossForFees);
|
||||||
var maxStopLoss = takeProfit - 0.1; // Ensure SL is less than TP with some buffer
|
var maxStopLoss = takeProfit - 0.1; // Ensure SL is less than TP with some buffer
|
||||||
|
|
||||||
// Adjust stop loss if it doesn't meet the constraints
|
// Adjust stop loss if it doesn't meet the constraints
|
||||||
if (stopLoss > maxStopLoss || stopLoss < minStopLoss)
|
if (stopLoss > maxStopLoss || stopLoss < minStopLoss)
|
||||||
{
|
{
|
||||||
stopLoss = GetRandomInRange((minStopLoss, maxStopLoss));
|
stopLoss = GetRandomInRange((minStopLoss, maxStopLoss));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get loopback period from gene 4
|
||||||
|
var loopbackPeriod = Convert.ToInt32(genes[4].Value);
|
||||||
|
|
||||||
// Build scenario using selected indicators
|
// 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)
|
foreach (var geneticIndicator in selectedIndicators)
|
||||||
{
|
{
|
||||||
var indicator = ScenarioHelpers.BuildIndicator(
|
var indicator = ScenarioHelpers.BuildIndicator(
|
||||||
@@ -547,14 +670,14 @@ public class TradingBotChromosome : ChromosomeBase
|
|||||||
smoothPeriods: geneticIndicator.SmoothPeriods,
|
smoothPeriods: geneticIndicator.SmoothPeriods,
|
||||||
cyclePeriods: geneticIndicator.CyclePeriods
|
cyclePeriods: geneticIndicator.CyclePeriods
|
||||||
);
|
);
|
||||||
|
|
||||||
scenario.AddIndicator(indicator);
|
scenario.AddIndicator(indicator);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new TradingBotConfig
|
return new TradingBotConfig
|
||||||
{
|
{
|
||||||
Name = $"Genetic_{request.RequestId}",
|
Name = $"Genetic_{request.RequestId}",
|
||||||
AccountName = "genetic_account",
|
AccountName = "Oda-embedded",
|
||||||
Ticker = request.Ticker,
|
Ticker = request.Ticker,
|
||||||
Timeframe = request.Timeframe,
|
Timeframe = request.Timeframe,
|
||||||
BotTradingBalance = request.Balance,
|
BotTradingBalance = request.Balance,
|
||||||
@@ -612,11 +735,11 @@ public class TradingBotChromosome : ChromosomeBase
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private bool HasParameter(IndicatorType indicator, string paramName)
|
private bool HasParameter(IndicatorType indicator, string paramName)
|
||||||
{
|
{
|
||||||
return GeneticService.IndicatorParamMapping.ContainsKey(indicator) &&
|
return GeneticService.IndicatorParamMapping.ContainsKey(indicator) &&
|
||||||
GeneticService.IndicatorParamMapping[indicator].Contains(paramName);
|
GeneticService.IndicatorParamMapping[indicator].Contains(paramName);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -658,21 +781,21 @@ public class TradingBotFitness : IFitness
|
|||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
var config = tradingBotChromosome.GetTradingBotConfig(_request);
|
var config = tradingBotChromosome.GetTradingBotConfig(_request);
|
||||||
|
|
||||||
// Run backtest
|
// Run backtest
|
||||||
var backtest = _backtester.RunTradingBotBacktest(
|
var backtest = _backtester.RunTradingBotBacktest(
|
||||||
config,
|
config,
|
||||||
_request.StartDate,
|
_request.StartDate,
|
||||||
_request.EndDate,
|
_request.EndDate,
|
||||||
_request.User,
|
_request.User,
|
||||||
false, // Don't save individual backtests
|
true, // Don't save individual backtests
|
||||||
false, // Don't include candles
|
false, // Don't include candles
|
||||||
_request.RequestId
|
_request.RequestId
|
||||||
).Result;
|
).Result;
|
||||||
|
|
||||||
// Calculate multi-objective fitness based on backtest results
|
// Calculate multi-objective fitness based on backtest results
|
||||||
var fitness = CalculateMultiObjectiveFitness(backtest, config);
|
var fitness = CalculateMultiObjectiveFitness(backtest, config);
|
||||||
|
|
||||||
return fitness;
|
return fitness;
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
@@ -688,30 +811,32 @@ public class TradingBotFitness : IFitness
|
|||||||
return 0.1;
|
return 0.1;
|
||||||
|
|
||||||
var stats = backtest.Statistics;
|
var stats = backtest.Statistics;
|
||||||
|
|
||||||
// Multi-objective fitness function (matching frontend)
|
// Multi-objective fitness function (matching frontend)
|
||||||
var pnlScore = Math.Max(0, (double)stats.TotalPnL / 1000); // Normalize PnL
|
var pnlScore = Math.Max(0, (double)stats.TotalPnL / 1000); // Normalize PnL
|
||||||
var winRateScore = backtest.WinRate / 100.0; // Normalize win rate
|
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 riskRewardScore =
|
||||||
var consistencyScore = 1 - Math.Abs((double)stats.TotalPnL - (double)backtest.FinalPnl) / Math.Max(1, Math.Abs((double)stats.TotalPnL));
|
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) /
|
||||||
// Risk-reward ratio bonus
|
Math.Max(1, Math.Abs((double)stats.TotalPnL));
|
||||||
var riskRewardRatio = (double)(config.MoneyManagement.TakeProfit / config.MoneyManagement.StopLoss);
|
|
||||||
var riskRewardBonus = Math.Min(0.2, (riskRewardRatio - 1.1) * 0.1);
|
// Risk-reward ratio bonus
|
||||||
|
var riskRewardRatio = (double)(config.MoneyManagement.TakeProfit / config.MoneyManagement.StopLoss);
|
||||||
// Drawdown score (normalized to 0-1, where lower drawdown is better)
|
var riskRewardBonus = Math.Min(0.2, (riskRewardRatio - 1.1) * 0.1);
|
||||||
var maxDrawdownPc = Math.Abs((double)stats.MaxDrawdownPc);
|
|
||||||
var drawdownScore = Math.Max(0, 1 - (maxDrawdownPc / 50));
|
// Drawdown score (normalized to 0-1, where lower drawdown is better)
|
||||||
|
var maxDrawdownPc = Math.Abs((double)stats.MaxDrawdownPc);
|
||||||
// Weighted combination
|
var drawdownScore = Math.Max(0, 1 - (maxDrawdownPc / 50));
|
||||||
var fitness =
|
|
||||||
pnlScore * 0.3 +
|
// Weighted combination
|
||||||
winRateScore * 0.2 +
|
var fitness =
|
||||||
riskRewardScore * 0.2 +
|
pnlScore * 0.3 +
|
||||||
consistencyScore * 0.1 +
|
winRateScore * 0.2 +
|
||||||
riskRewardBonus * 0.1 +
|
riskRewardScore * 0.2 +
|
||||||
drawdownScore * 0.1;
|
consistencyScore * 0.1 +
|
||||||
|
riskRewardBonus * 0.1 +
|
||||||
|
drawdownScore * 0.1;
|
||||||
|
|
||||||
return Math.Max(0, fitness);
|
return Math.Max(0, fitness);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -154,6 +154,21 @@ public class GeneticRequest
|
|||||||
/// Progress information (JSON serialized)
|
/// Progress information (JSON serialized)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public string? ProgressInfo { get; set; }
|
public string? ProgressInfo { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The best chromosome found so far (JSON serialized)
|
||||||
|
/// </summary>
|
||||||
|
public string? BestChromosome { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Current generation number when the algorithm was stopped
|
||||||
|
/// </summary>
|
||||||
|
public int CurrentGeneration { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The best fitness score achieved so far
|
||||||
|
/// </summary>
|
||||||
|
public double? BestFitnessSoFar { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|||||||
@@ -32,6 +32,15 @@ public class BacktestRepository : IBacktestRepository
|
|||||||
return backtests.Select(b => MongoMappers.Map(b));
|
return backtests.Select(b => MongoMappers.Map(b));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public IEnumerable<Backtest> GetBacktestsByRequestId(string requestId)
|
||||||
|
{
|
||||||
|
var backtests = _backtestRepository.AsQueryable()
|
||||||
|
.Where(b => b.RequestId == requestId)
|
||||||
|
.ToList();
|
||||||
|
|
||||||
|
return backtests.Select(b => MongoMappers.Map(b));
|
||||||
|
}
|
||||||
|
|
||||||
public Backtest GetBacktestByIdForUser(User user, string id)
|
public Backtest GetBacktestByIdForUser(User user, string id)
|
||||||
{
|
{
|
||||||
var backtest = _backtestRepository.FindById(id);
|
var backtest = _backtestRepository.FindById(id);
|
||||||
|
|||||||
@@ -27,5 +27,9 @@ namespace Managing.Infrastructure.Databases.MongoDb.Collections
|
|||||||
public string? BestIndividual { get; set; }
|
public string? BestIndividual { get; set; }
|
||||||
public string? ErrorMessage { get; set; }
|
public string? ErrorMessage { get; set; }
|
||||||
public string? ProgressInfo { get; set; }
|
public string? ProgressInfo { get; set; }
|
||||||
|
public string? BestChromosome { get; set; }
|
||||||
|
public double? BestFitnessSoFar { get; set; }
|
||||||
|
public int CurrentGeneration { get; set; }
|
||||||
|
public DateTime? UpdatedAt { get; set; }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -498,6 +498,44 @@ export class BacktestClient extends AuthorizedApiBase {
|
|||||||
return Promise.resolve<Backtest>(null as any);
|
return Promise.resolve<Backtest>(null as any);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
backtest_GetBacktestsByRequestId(requestId: string): Promise<Backtest[]> {
|
||||||
|
let url_ = this.baseUrl + "/Backtest/ByRequestId/{requestId}";
|
||||||
|
if (requestId === undefined || requestId === null)
|
||||||
|
throw new Error("The parameter 'requestId' must be defined.");
|
||||||
|
url_ = url_.replace("{requestId}", encodeURIComponent("" + requestId));
|
||||||
|
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_GetBacktestsByRequestId(_response);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected processBacktest_GetBacktestsByRequestId(response: Response): Promise<Backtest[]> {
|
||||||
|
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 Backtest[];
|
||||||
|
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<Backtest[]>(null as any);
|
||||||
|
}
|
||||||
|
|
||||||
backtest_Run(request: RunBacktestRequest): Promise<Backtest> {
|
backtest_Run(request: RunBacktestRequest): Promise<Backtest> {
|
||||||
let url_ = this.baseUrl + "/Backtest/Run";
|
let url_ = this.baseUrl + "/Backtest/Run";
|
||||||
url_ = url_.replace(/[?&]$/, "");
|
url_ = url_.replace(/[?&]$/, "");
|
||||||
@@ -3251,6 +3289,7 @@ export interface Backtest {
|
|||||||
user: User;
|
user: User;
|
||||||
indicatorsValues: { [key in keyof typeof IndicatorType]?: IndicatorsResultBase; };
|
indicatorsValues: { [key in keyof typeof IndicatorType]?: IndicatorsResultBase; };
|
||||||
score: number;
|
score: number;
|
||||||
|
requestId?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TradingBotConfig {
|
export interface TradingBotConfig {
|
||||||
@@ -3691,6 +3730,9 @@ export interface GeneticRequest {
|
|||||||
bestIndividual?: string | null;
|
bestIndividual?: string | null;
|
||||||
errorMessage?: string | null;
|
errorMessage?: string | null;
|
||||||
progressInfo?: string | null;
|
progressInfo?: string | null;
|
||||||
|
bestChromosome?: string | null;
|
||||||
|
currentGeneration?: number;
|
||||||
|
bestFitnessSoFar?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum GeneticRequestStatus {
|
export enum GeneticRequestStatus {
|
||||||
|
|||||||
@@ -236,6 +236,7 @@ export interface Backtest {
|
|||||||
user: User;
|
user: User;
|
||||||
indicatorsValues: { [key in keyof typeof IndicatorType]?: IndicatorsResultBase; };
|
indicatorsValues: { [key in keyof typeof IndicatorType]?: IndicatorsResultBase; };
|
||||||
score: number;
|
score: number;
|
||||||
|
requestId?: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface TradingBotConfig {
|
export interface TradingBotConfig {
|
||||||
@@ -676,6 +677,9 @@ export interface GeneticRequest {
|
|||||||
bestIndividual?: string | null;
|
bestIndividual?: string | null;
|
||||||
errorMessage?: string | null;
|
errorMessage?: string | null;
|
||||||
progressInfo?: string | null;
|
progressInfo?: string | null;
|
||||||
|
bestChromosome?: string | null;
|
||||||
|
currentGeneration?: number;
|
||||||
|
bestFitnessSoFar?: number | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export enum GeneticRequestStatus {
|
export enum GeneticRequestStatus {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import {useQuery} from '@tanstack/react-query'
|
|||||||
|
|
||||||
import useApiUrlStore from '../../app/store/apiStore'
|
import useApiUrlStore from '../../app/store/apiStore'
|
||||||
import {
|
import {
|
||||||
|
type Backtest,
|
||||||
BacktestClient,
|
BacktestClient,
|
||||||
type GeneticRequest,
|
type GeneticRequest,
|
||||||
IndicatorType,
|
IndicatorType,
|
||||||
@@ -55,6 +56,10 @@ const BacktestGeneticBundle: React.FC = () => {
|
|||||||
const [isSubmitting, setIsSubmitting] = useState(false)
|
const [isSubmitting, setIsSubmitting] = useState(false)
|
||||||
const [selectedIndicators, setSelectedIndicators] = useState<IndicatorType[]>(ALL_INDICATORS)
|
const [selectedIndicators, setSelectedIndicators] = useState<IndicatorType[]>(ALL_INDICATORS)
|
||||||
const [geneticRequests, setGeneticRequests] = useState<GeneticRequest[]>([])
|
const [geneticRequests, setGeneticRequests] = useState<GeneticRequest[]>([])
|
||||||
|
const [selectedRequest, setSelectedRequest] = useState<GeneticRequest | null>(null)
|
||||||
|
const [isViewModalOpen, setIsViewModalOpen] = useState(false)
|
||||||
|
const [backtests, setBacktests] = useState<Backtest[]>([])
|
||||||
|
const [isLoadingBacktests, setIsLoadingBacktests] = useState(false)
|
||||||
|
|
||||||
// Form setup
|
// Form setup
|
||||||
const {register, handleSubmit, watch, setValue, formState: {errors}} = useForm<GeneticBundleFormData>({
|
const {register, handleSubmit, watch, setValue, formState: {errors}} = useForm<GeneticBundleFormData>({
|
||||||
@@ -187,6 +192,30 @@ const BacktestGeneticBundle: React.FC = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle view details
|
||||||
|
const handleViewDetails = async (request: GeneticRequest) => {
|
||||||
|
setSelectedRequest(request)
|
||||||
|
setIsViewModalOpen(true)
|
||||||
|
setIsLoadingBacktests(true)
|
||||||
|
|
||||||
|
try {
|
||||||
|
const backtestsData = await backtestClient.backtest_GetBacktestsByRequestId(request.requestId)
|
||||||
|
setBacktests(backtestsData)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching backtests:', error)
|
||||||
|
new Toast('Failed to load backtest details', false)
|
||||||
|
} finally {
|
||||||
|
setIsLoadingBacktests(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close view modal
|
||||||
|
const closeViewModal = () => {
|
||||||
|
setIsViewModalOpen(false)
|
||||||
|
setSelectedRequest(null)
|
||||||
|
setBacktests([])
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
<div className="card bg-base-100 shadow-xl">
|
<div className="card bg-base-100 shadow-xl">
|
||||||
@@ -433,10 +462,7 @@ const BacktestGeneticBundle: React.FC = () => {
|
|||||||
<td>
|
<td>
|
||||||
<div className="flex gap-2">
|
<div className="flex gap-2">
|
||||||
<button
|
<button
|
||||||
onClick={() => {
|
onClick={() => handleViewDetails(request)}
|
||||||
// TODO: Implement view details
|
|
||||||
new Toast('View details not implemented yet', false)
|
|
||||||
}}
|
|
||||||
className="btn btn-sm btn-outline"
|
className="btn btn-sm btn-outline"
|
||||||
>
|
>
|
||||||
View
|
View
|
||||||
@@ -465,6 +491,92 @@ const BacktestGeneticBundle: React.FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
{/* View Details Modal */}
|
||||||
|
{isViewModalOpen && selectedRequest && (
|
||||||
|
<div className="modal modal-open">
|
||||||
|
<div className="modal-box w-11/12 max-w-6xl">
|
||||||
|
<h3 className="font-bold text-lg mb-4">
|
||||||
|
Genetic Request Details - {selectedRequest.requestId.slice(0, 8)}...
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-2 gap-4 mb-6">
|
||||||
|
<div>
|
||||||
|
<strong>Ticker:</strong> {selectedRequest.ticker}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Timeframe:</strong> {selectedRequest.timeframe}
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Status:</strong>
|
||||||
|
<span className={`badge ${getStatusBadgeColor(selectedRequest.status)} ml-2`}>
|
||||||
|
{selectedRequest.status}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<strong>Created:</strong> {new Date(selectedRequest.createdAt).toLocaleString()}
|
||||||
|
</div>
|
||||||
|
{selectedRequest.completedAt && (
|
||||||
|
<div>
|
||||||
|
<strong>Completed:</strong> {new Date(selectedRequest.completedAt).toLocaleString()}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="mb-6">
|
||||||
|
<h4 className="font-semibold mb-2">Backtest Results ({backtests.length})</h4>
|
||||||
|
{isLoadingBacktests ? (
|
||||||
|
<div className="flex justify-center">
|
||||||
|
<span className="loading loading-spinner loading-md"></span>
|
||||||
|
</div>
|
||||||
|
) : backtests.length > 0 ? (
|
||||||
|
<div className="overflow-x-auto">
|
||||||
|
<table className="table table-zebra">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>ID</th>
|
||||||
|
<th>Final PnL</th>
|
||||||
|
<th>Win Rate</th>
|
||||||
|
<th>Growth %</th>
|
||||||
|
<th>Score</th>
|
||||||
|
<th>Positions</th>
|
||||||
|
<th>Created</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{backtests.map((backtest) => (
|
||||||
|
<tr key={backtest.id}>
|
||||||
|
<td className="font-mono text-xs">{backtest.id.slice(0, 8)}...</td>
|
||||||
|
<td className={backtest.finalPnl >= 0 ? 'text-success' : 'text-error'}>
|
||||||
|
${backtest.finalPnl.toFixed(2)}
|
||||||
|
</td>
|
||||||
|
<td>{backtest.winRate}%</td>
|
||||||
|
<td className={backtest.growthPercentage >= 0 ? 'text-success' : 'text-error'}>
|
||||||
|
{backtest.growthPercentage.toFixed(2)}%
|
||||||
|
</td>
|
||||||
|
<td>{backtest.score.toFixed(2)}</td>
|
||||||
|
<td>{backtest.positions?.length || 0}</td>
|
||||||
|
<td>{new Date(backtest.startDate).toLocaleDateString()}</td>
|
||||||
|
</tr>
|
||||||
|
))}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="text-center text-gray-500 py-8">
|
||||||
|
No backtest results found for this request.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="modal-action">
|
||||||
|
<button onClick={closeViewModal} className="btn">
|
||||||
|
Close
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user