Update front

This commit is contained in:
2025-07-09 01:08:20 +07:00
parent 9c01dce461
commit 28e29c7bbf

View File

@@ -291,6 +291,94 @@ const BacktestGenetic: React.FC = () => {
return Math.min(formValues.maxTakeProfit || 4, PARAMETER_RANGES.takeProfit.max)
}
// Calculate total number of backtests that will be run
const calculateTotalBacktests = (): number => {
const populationSize = formValues.populationSize || GENETIC_CONFIG.populationSize
const totalGenerations = formValues.generations || GENETIC_CONFIG.generations
// Each phase uses the full generation count
const phase1Generations = totalGenerations
const phase2Generations = totalGenerations
const phase3Generations = totalGenerations
const phase4Generations = totalGenerations
// For Phase 1, we test each eligible indicator with the full population size for each generation
const phase1Backtests = phase1Generations * populationSize * eligibleIndicators.length
// For subsequent phases, we use combinations of the best indicators from Phase 1
const maxBestIndicators = Math.min(8, eligibleIndicators.length)
// Calculate combinations for each phase
const phase2Combinations = Math.min(populationSize, maxBestIndicators * (maxBestIndicators - 1) / 2) // C(n,2)
const phase3Combinations = Math.min(populationSize, maxBestIndicators * (maxBestIndicators - 1) * (maxBestIndicators - 2) / 6) // C(n,3)
const phase4Combinations = Math.min(populationSize, maxBestIndicators * (maxBestIndicators - 1) * (maxBestIndicators - 2) * (maxBestIndicators - 3) / 24) // C(n,4)
const phase2Backtests = phase2Generations * phase2Combinations
const phase3Backtests = phase3Generations * phase3Combinations
const phase4Backtests = phase4Generations * phase4Combinations
return phase1Backtests + phase2Backtests + phase3Backtests + phase4Backtests
}
// Helper function to calculate progress accurately
const calculateProgress = () => {
if (totalIndividuals === 0) return { completed: 0, total: 1, percentage: 0 }
const populationSize = geneticConfig.populationSize
const totalGenerations = formValues.generations || GENETIC_CONFIG.generations
let completedBacktests = 0
let totalBacktests = 0
// Phase 1: Each generation tests populationSize * eligibleIndicators.length
const phase1Backtests = totalGenerations * populationSize * eligibleIndicators.length
totalBacktests += phase1Backtests
if (currentPhase === 'phase1') {
completedBacktests = (currentGeneration - 1) * populationSize + currentIndividual
} else {
completedBacktests += phase1Backtests
// Phase 2: Use best indicators (max 8)
const maxBestIndicators = Math.min(8, eligibleIndicators.length)
const phase2Combinations = Math.min(populationSize, maxBestIndicators * (maxBestIndicators - 1) / 2)
const phase2Backtests = totalGenerations * phase2Combinations
totalBacktests += phase2Backtests
if (currentPhase === 'phase2') {
completedBacktests += (currentGeneration - totalGenerations - 1) * populationSize + currentIndividual
} else {
completedBacktests += phase2Backtests
// Phase 3: 3-indicator combinations
const phase3Combinations = Math.min(populationSize, maxBestIndicators * (maxBestIndicators - 1) * (maxBestIndicators - 2) / 6)
const phase3Backtests = totalGenerations * phase3Combinations
totalBacktests += phase3Backtests
if (currentPhase === 'phase3') {
completedBacktests += (currentGeneration - totalGenerations * 2 - 1) * populationSize + currentIndividual
} else {
completedBacktests += phase3Backtests
// Phase 4: 4-indicator combinations
const phase4Combinations = Math.min(populationSize, maxBestIndicators * (maxBestIndicators - 1) * (maxBestIndicators - 2) * (maxBestIndicators - 3) / 24)
const phase4Backtests = totalGenerations * phase4Combinations
totalBacktests += phase4Backtests
if (currentPhase === 'phase4') {
completedBacktests += (currentGeneration - totalGenerations * 3 - 1) * populationSize + currentIndividual
}
}
}
}
return {
completed: Math.min(completedBacktests, totalBacktests),
total: totalBacktests,
percentage: Math.round((completedBacktests / totalBacktests) * 100)
}
}
// Create a random indicator with bias towards CustomScenario defaults
const createRandomIndicator = (eligibleIndicators: IndicatorType[]): GeneticIndicator => {
const type = eligibleIndicators[Math.floor(Math.random() * eligibleIndicators.length)]
@@ -378,6 +466,53 @@ const BacktestGenetic: React.FC = () => {
return uniqueIndicators
}
// Create a population for Phase 1 that ensures every eligible indicator is tested
const createPhase1Population = (populationSize: number, eligibleIndicators: IndicatorType[]): GeneticIndividual[] => {
const population: GeneticIndividual[] = []
// Create a full population where each individual uses a different eligible indicator
// If we have more population slots than indicators, we cycle through indicators
for (let i = 0; i < populationSize; i++) {
const indicatorType = eligibleIndicators[i % eligibleIndicators.length]
const indicator = createRandomIndicator([indicatorType]) // Force this specific indicator type
// Generate stop loss first
const minTP = 0.9;
const maxTP = getMaxTakeProfit();
const tp = getRandomInRange({ min: minTP, max: maxTP });
const minSL = 0.5;
const maxSL = Math.min(tp / 1.1, tp - 0.1);
const sl = maxSL >= minSL ? getRandomInRange({ min: minSL, max: maxSL }) : minSL;
const individual: GeneticIndividual = {
// Money Management Parameters
stopLoss: sl,
takeProfit: tp,
leverage: 1,
// Trading Bot Parameters
cooldownPeriod: getRandomIntInRange(PARAMETER_RANGES.cooldownPeriod),
maxLossStreak: getRandomIntInRange(PARAMETER_RANGES.maxLossStreak),
maxPositionTimeHours: 0, // Always 0 to prevent early position cutting
flipOnlyWhenInProfit: Math.random() > 0.5,
closeEarlyWhenProfitable: Math.random() > 0.5,
// Synth API Parameters (always false for performance)
useSynthApi: false,
useForPositionSizing: false,
useForSignalFiltering: false,
useForDynamicStopLoss: false,
// Indicator Configuration
indicators: [indicator],
}
population.push(individual)
}
return population
}
// Create a random individual with positive risk-reward ratio
const createIndividual = (phase: 'phase1' | 'phase2' | 'phase3' | 'phase4' = 'phase1', bestIndicators: Array<{individual: GeneticIndividual, backtest: Backtest | null, fitness: number}> = [], currentEligibleIndicators: IndicatorType[] = eligibleIndicators): GeneticIndividual => {
// Generate stop loss first
@@ -474,9 +609,17 @@ const BacktestGenetic: React.FC = () => {
const removeBadIndicators = (fitnessResults: Array<{individual: GeneticIndividual, backtest: Backtest | null, fitness: number}>) => {
const badIndicators = new Set<IndicatorType>()
// Find indicators with very poor performance (fitness < 0.1)
// Calculate the maximum possible fitness score for reference
// PnL Score (30%) + Win Rate Score (20%) + Risk-Reward Score (20%) + Consistency Score (10%) + Risk-Reward Bonus (10%) + Drawdown Score (10%)
// Maximum theoretical fitness: 0.3 + 0.2 + 0.4 + 0.1 + 0.02 + 0.1 = 1.12
const maxPossibleFitness = 1.12
// Set threshold to 10% of maximum possible fitness (0.112)
const badIndicatorThreshold = maxPossibleFitness * 0.1
// Find indicators with very poor performance
fitnessResults.forEach(result => {
if (result.fitness < 0.1 && result.individual.indicators.length === 1) {
if (result.fitness < badIndicatorThreshold && result.individual.indicators.length === 1) {
const indicatorType = result.individual.indicators[0].type
badIndicators.add(indicatorType)
}
@@ -487,8 +630,12 @@ const BacktestGenetic: React.FC = () => {
setEligibleIndicators(prev => prev.filter(indicator => !badIndicators.has(indicator)))
setRemovedIndicators(prev => new Set([...prev, ...badIndicators]))
// Log removed indicators
console.log(`Removed bad indicators: ${Array.from(badIndicators).join(', ')}`)
// Log removed indicators with fitness scores for transparency
const removedWithScores = fitnessResults
.filter(result => badIndicators.has(result.individual.indicators[0]?.type))
.map(result => `${result.individual.indicators[0]?.type} (fitness: ${result.fitness.toFixed(3)})`)
console.log(`Removed bad indicators (threshold: ${badIndicatorThreshold.toFixed(3)}): ${removedWithScores.join(', ')}`)
}
}
@@ -508,13 +655,19 @@ const BacktestGenetic: React.FC = () => {
const riskRewardRatio = individual.takeProfit / individual.stopLoss
const riskRewardBonus = Math.min(0.2, (riskRewardRatio - 1.1) * 0.1) // Bonus for R:R > 1.1
// Weighted combination
// Drawdown score (normalized to 0-1, where lower drawdown is better)
// maxDrawdownPc is in percentage, so we normalize it
const maxDrawdownPc = Math.abs(stats.maxDrawdownPc || 0) // Use absolute value and default to 0
const drawdownScore = Math.max(0, 1 - (maxDrawdownPc / 50)) // Normalize: 0% drawdown = 1.0, 50% drawdown = 0.0
// Weighted combination with drawdown consideration
const fitness =
pnlScore * 0.35 +
winRateScore * 0.25 +
pnlScore * 0.3 +
winRateScore * 0.2 +
riskRewardScore * 0.2 +
consistencyScore * 0.1 +
riskRewardBonus * 0.1
riskRewardBonus * 0.1 +
drawdownScore * 0.1 // Add drawdown score with a weight of 0.1
return Math.max(0, fitness)
}
@@ -720,10 +873,30 @@ const BacktestGenetic: React.FC = () => {
setEligibleIndicators(ALL_INDICATORS)
setRemovedIndicators(new Set())
const phase1Individuals = GENETIC_CONFIG.phase1Generations * geneticConfig.populationSize
const phase2Individuals = GENETIC_CONFIG.phase2Generations * geneticConfig.populationSize
const phase3Individuals = GENETIC_CONFIG.phase3Generations * geneticConfig.populationSize
const phase4Individuals = GENETIC_CONFIG.phase4Generations * geneticConfig.populationSize
// Use the full user-specified generation count for each phase
const totalGenerations = formValues.generations || GENETIC_CONFIG.generations
// Each phase uses the full generation count
const phase1Generations = totalGenerations
const phase2Generations = totalGenerations
const phase3Generations = totalGenerations
const phase4Generations = totalGenerations
// For Phase 1, we test each eligible indicator with the full population size
const phase1Individuals = phase1Generations * geneticConfig.populationSize * eligibleIndicators.length
// For subsequent phases, we use combinations of the best indicators from Phase 1
// We'll assume we keep the top 8 indicators (as per the current logic)
const maxBestIndicators = Math.min(8, eligibleIndicators.length)
// Calculate combinations for each phase
const phase2Combinations = Math.min(geneticConfig.populationSize, maxBestIndicators * (maxBestIndicators - 1) / 2) // C(n,2)
const phase3Combinations = Math.min(geneticConfig.populationSize, maxBestIndicators * (maxBestIndicators - 1) * (maxBestIndicators - 2) / 6) // C(n,3)
const phase4Combinations = Math.min(geneticConfig.populationSize, maxBestIndicators * (maxBestIndicators - 1) * (maxBestIndicators - 2) * (maxBestIndicators - 3) / 24) // C(n,4)
const phase2Individuals = phase2Generations * phase2Combinations
const phase3Individuals = phase3Generations * phase3Combinations
const phase4Individuals = phase4Generations * phase4Combinations
const totalIndividuals = phase1Individuals + phase2Individuals + phase3Individuals + phase4Individuals
setTotalIndividuals(totalIndividuals)
@@ -738,10 +911,10 @@ const BacktestGenetic: React.FC = () => {
t.update('info', 'Phase 1: Testing single indicators...')
setCurrentPhase('phase1')
let population = Array.from({length: geneticConfig.populationSize}, () => createIndividual('phase1'))
let population = createPhase1Population(geneticConfig.populationSize, eligibleIndicators)
const phase1Results: Array<{individual: GeneticIndividual, backtest: Backtest | null, fitness: number}> = []
for (let generation = 0; generation < GENETIC_CONFIG.phase1Generations; generation++) {
for (let generation = 0; generation < phase1Generations; generation++) {
setCurrentGeneration(generation + 1)
// Evaluate fitness for individuals one by one for real-time updates
@@ -798,7 +971,8 @@ const BacktestGenetic: React.FC = () => {
// Update progress toast
const progress = (processedIndividuals / totalIndividuals * 100).toFixed(1)
const avgTimeFormatted = formatTimeDuration(averageTimeMs)
t.update('info', `Phase 1 - Progress: ${progress}% - Gen ${generation + 1}/${GENETIC_CONFIG.phase1Generations} - Individual ${i + 1}/${geneticConfig.populationSize} - Best: ${bestFitnessScore.toFixed(2)} - Avg: ${avgTimeFormatted} - ETA: ${estimatedCompletionTime}`)
const testedIndicators = new Set(fitnessResults.map(r => r.individual.indicators[0]?.type)).size
t.update('info', `Phase 1 - Progress: ${progress}% - Gen ${generation + 1}/${phase1Generations} - Individual ${i + 1}/${geneticConfig.populationSize} - Tested: ${testedIndicators}/${eligibleIndicators.length} indicators - Best: ${bestFitnessScore.toFixed(2)} - Avg: ${avgTimeFormatted} - ETA: ${estimatedCompletionTime}`)
} catch (error) {
console.error('Fitness calculation error:', error)
@@ -811,7 +985,7 @@ const BacktestGenetic: React.FC = () => {
fitnessResults.sort((a, b) => b.fitness - a.fitness)
// Store best single indicators for subsequent phases
if (generation === GENETIC_CONFIG.phase1Generations - 1) {
if (generation === phase1Generations - 1) {
const topSingleIndicators = fitnessResults
.filter(result => result.fitness > 0)
.slice(0, Math.min(8, fitnessResults.length)) // Keep top 8 single indicators
@@ -821,97 +995,9 @@ const BacktestGenetic: React.FC = () => {
removeBadIndicators(fitnessResults)
}
// Create next generation for phase 1
const newPopulation = []
const eliteCount = Math.max(1, Math.floor(geneticConfig.populationSize * (formValues.elitismPercentage || 15) / 100))
for (let i = 0; i < eliteCount; i++) {
newPopulation.push(fitnessResults[i].individual)
}
while (newPopulation.length < geneticConfig.populationSize) {
const parent1 = selectParent(fitnessResults)
const parent2 = selectParent(fitnessResults)
const child = {...parent1}
if (Math.random() < geneticConfig.crossoverRate) {
child.stopLoss = parent2.stopLoss
child.takeProfit = parent2.takeProfit
child.leverage = 1
child.cooldownPeriod = parent2.cooldownPeriod
child.indicators = parent2.indicators
// Validate risk-reward ratio after crossover
const minTakeProfit = child.stopLoss * 1.1
if (child.takeProfit < minTakeProfit) {
child.takeProfit = minTakeProfit + getRandomInRange({
min: 0,
max: getMaxTakeProfit() - minTakeProfit
})
}
}
// Mutation
if (Math.random() < geneticConfig.mutationRate) {
// Mutate stop loss
child.stopLoss = getRandomInRange(PARAMETER_RANGES.stopLoss);
// Adjust take profit to maintain minimum R:R ratio and respect max TP
const minTP = 0.9;
const maxTP = getMaxTakeProfit();
child.takeProfit = getRandomInRange({ min: minTP, max: maxTP });
const minSL = 0.5;
const maxSL = Math.min(child.takeProfit / 1.1, child.takeProfit - 0.1);
child.stopLoss = maxSL >= minSL ? getRandomInRange({ min: minSL, max: maxSL }) : minSL;
}
if (Math.random() < geneticConfig.mutationRate) {
// Mutate take profit while respecting minimum R:R ratio and max TP
const minTP = 0.9;
const maxTP = getMaxTakeProfit();
child.takeProfit = getRandomInRange({ min: minTP, max: maxTP });
const minSL = 0.5;
const maxSL = Math.min(child.takeProfit / 1.1, child.takeProfit - 0.1);
child.stopLoss = maxSL >= minSL ? getRandomInRange({ min: minSL, max: maxSL }) : minSL;
}
if (Math.random() < geneticConfig.mutationRate) {
// 50% chance to create completely new indicator, 50% chance to slightly modify existing
if (Math.random() < 0.5) {
child.indicators = [createRandomIndicator(eligibleIndicators)]
} else {
// Slight modification of existing indicator parameters
const existingIndicator = child.indicators[0]
const modifiedIndicator = {...existingIndicator}
// Randomly modify one parameter with small variation
const paramKeys = Object.keys(modifiedIndicator).filter(key =>
key !== 'type' && typeof modifiedIndicator[key as keyof GeneticIndicator] === 'number'
) as Array<keyof GeneticIndicator>
if (paramKeys.length > 0) {
const randomParam = paramKeys[Math.floor(Math.random() * paramKeys.length)]
const currentValue = modifiedIndicator[randomParam] as number
const defaultValue = DEFAULT_INDICATOR_VALUES[randomParam as keyof typeof DEFAULT_INDICATOR_VALUES]
// Add small random variation (±20% of the difference from default)
const variation = (currentValue - defaultValue) * 0.2 * (Math.random() - 0.5)
const newValue = Math.max(1, Math.round(currentValue + variation))
;(modifiedIndicator as any)[randomParam] = newValue
child.indicators = [modifiedIndicator]
}
}
}
// Enforce RR >= 1.1:1
if (child.takeProfit < child.stopLoss * 1.1) {
child.takeProfit = Math.min(getMaxTakeProfit(), child.stopLoss * 1.1);
}
if (child.stopLoss > child.takeProfit / 1.1) {
child.stopLoss = Math.max(PARAMETER_RANGES.stopLoss.min, child.takeProfit / 1.1);
}
newPopulation.push(child)
}
population = newPopulation
// For Phase 1, create a new population for the next generation
// Each generation tests every eligible indicator with the full population size
population = createPhase1Population(geneticConfig.populationSize, eligibleIndicators)
}
// Phase 2: Test 2-indicator combinations
@@ -921,8 +1007,8 @@ const BacktestGenetic: React.FC = () => {
const bestIndicators = bestSingleIndicators
population = Array.from({length: geneticConfig.populationSize}, () => createIndividual('phase2', bestIndicators))
for (let generation = 0; generation < GENETIC_CONFIG.phase2Generations; generation++) {
setCurrentGeneration(GENETIC_CONFIG.phase1Generations + generation + 1)
for (let generation = 0; generation < phase2Generations; generation++) {
setCurrentGeneration(phase1Generations + generation + 1)
const fitnessResults = []
@@ -954,7 +1040,7 @@ const BacktestGenetic: React.FC = () => {
individual: result.individual,
backtest: result.backtest,
fitness: result.fitness,
generation: GENETIC_CONFIG.phase1Generations + generation + 1,
generation: phase1Generations + generation + 1,
phase: 'phase2',
}]
return newResults
@@ -976,7 +1062,7 @@ const BacktestGenetic: React.FC = () => {
// Update progress toast
const progress = (processedIndividuals / totalIndividuals * 100).toFixed(1)
const avgTimeFormatted = formatTimeDuration(averageTimeMs)
t.update('info', `Phase 2 - Progress: ${progress}% - Gen ${generation + 1}/${GENETIC_CONFIG.phase2Generations} - Individual ${i + 1}/${geneticConfig.populationSize} - Best: ${bestFitnessScore.toFixed(2)} - Avg: ${avgTimeFormatted} - ETA: ${estimatedCompletionTime}`)
t.update('info', `Phase 2 - Progress: ${progress}% - Gen ${generation + 1}/${phase2Generations} - Individual ${i + 1}/${geneticConfig.populationSize} - Best: ${bestFitnessScore.toFixed(2)} - Avg: ${avgTimeFormatted} - ETA: ${estimatedCompletionTime}`)
} catch (error) {
console.error('Fitness calculation error:', error)
@@ -1096,8 +1182,8 @@ const BacktestGenetic: React.FC = () => {
population = Array.from({length: geneticConfig.populationSize}, () => createIndividual('phase3', bestIndicators))
for (let generation = 0; generation < GENETIC_CONFIG.phase3Generations; generation++) {
setCurrentGeneration(GENETIC_CONFIG.phase1Generations + GENETIC_CONFIG.phase2Generations + generation + 1)
for (let generation = 0; generation < phase3Generations; generation++) {
setCurrentGeneration(phase1Generations + phase2Generations + generation + 1)
const fitnessResults = []
@@ -1129,7 +1215,7 @@ const BacktestGenetic: React.FC = () => {
individual: result.individual,
backtest: result.backtest,
fitness: result.fitness,
generation: GENETIC_CONFIG.phase1Generations + GENETIC_CONFIG.phase2Generations + generation + 1,
generation: phase1Generations + phase2Generations + generation + 1,
phase: 'phase3',
}]
return newResults
@@ -1151,7 +1237,7 @@ const BacktestGenetic: React.FC = () => {
// Update progress toast
const progress = (processedIndividuals / totalIndividuals * 100).toFixed(1)
const avgTimeFormatted = formatTimeDuration(averageTimeMs)
t.update('info', `Phase 3 - Progress: ${progress}% - Gen ${generation + 1}/${GENETIC_CONFIG.phase3Generations} - Individual ${i + 1}/${geneticConfig.populationSize} - Best: ${bestFitnessScore.toFixed(2)} - Avg: ${avgTimeFormatted} - ETA: ${estimatedCompletionTime}`)
t.update('info', `Phase 3 - Progress: ${progress}% - Gen ${generation + 1}/${phase3Generations} - Individual ${i + 1}/${geneticConfig.populationSize} - Best: ${bestFitnessScore.toFixed(2)} - Avg: ${avgTimeFormatted} - ETA: ${estimatedCompletionTime}`)
} catch (error) {
console.error('Fitness calculation error:', error)
@@ -1271,8 +1357,8 @@ const BacktestGenetic: React.FC = () => {
population = Array.from({length: geneticConfig.populationSize}, () => createIndividual('phase4', bestIndicators))
for (let generation = 0; generation < GENETIC_CONFIG.phase4Generations; generation++) {
setCurrentGeneration(GENETIC_CONFIG.phase1Generations + GENETIC_CONFIG.phase2Generations + GENETIC_CONFIG.phase3Generations + generation + 1)
for (let generation = 0; generation < phase4Generations; generation++) {
setCurrentGeneration(phase1Generations + phase2Generations + phase3Generations + generation + 1)
const fitnessResults = []
@@ -1304,7 +1390,7 @@ const BacktestGenetic: React.FC = () => {
individual: result.individual,
backtest: result.backtest,
fitness: result.fitness,
generation: GENETIC_CONFIG.phase1Generations + GENETIC_CONFIG.phase2Generations + GENETIC_CONFIG.phase3Generations + generation + 1,
generation: phase1Generations + phase2Generations + phase3Generations + generation + 1,
phase: 'phase4',
}]
return newResults
@@ -1326,7 +1412,7 @@ const BacktestGenetic: React.FC = () => {
// Update progress toast
const progress = (processedIndividuals / totalIndividuals * 100).toFixed(1)
const avgTimeFormatted = formatTimeDuration(averageTimeMs)
t.update('info', `Phase 4 - Progress: ${progress}% - Gen ${generation + 1}/${GENETIC_CONFIG.phase4Generations} - Individual ${i + 1}/${geneticConfig.populationSize} - Best: ${bestFitnessScore.toFixed(2)} - Avg: ${avgTimeFormatted} - ETA: ${estimatedCompletionTime}`)
t.update('info', `Phase 4 - Progress: ${progress}% - Gen ${generation + 1}/${phase4Generations} - Individual ${i + 1}/${geneticConfig.populationSize} - Best: ${bestFitnessScore.toFixed(2)} - Avg: ${avgTimeFormatted} - ETA: ${estimatedCompletionTime}`)
} catch (error) {
console.error('Fitness calculation error:', error)
@@ -1451,7 +1537,7 @@ const BacktestGenetic: React.FC = () => {
}
}, [geneticConfig, runBacktestForIndividual, calculateFitnessScore])
// Prepare 3D plot data
// Prepare 3D plot data for Fitness vs Score vs Win Rate
const plotData = results.length > 0 ? [
{
type: 'scatter3d' as const,
@@ -1482,11 +1568,78 @@ const BacktestGenetic: React.FC = () => {
const riskRewardRatio = r.individual.takeProfit / r.individual.stopLoss
const riskRewardColor = riskRewardRatio >= 2 ? '🟢' : riskRewardRatio >= 1.5 ? '🟡' : '🔴'
// Calculate drawdown color based on severity
const maxDrawdownPc = Math.abs(r.backtest?.statistics?.maxDrawdownPc || 0)
const drawdownColor = maxDrawdownPc <= 10 ? '🟢' : maxDrawdownPc <= 25 ? '🟡' : '🔴'
return `Phase: ${r.phase || 'Unknown'}<br>` +
`Gen: ${r.generation}<br>` +
`Fitness: ${r.fitness.toFixed(2)}<br>` +
`PnL: $${r.backtest?.statistics?.totalPnL?.toFixed(2) || 'N/A'}<br>` +
`Win Rate: ${r.backtest?.winRate?.toFixed(1) || 'N/A'}%<br>` +
`Max Drawdown: ${drawdownColor} ${maxDrawdownPc.toFixed(1)}%<br>` +
`SL: ${r.individual.stopLoss.toFixed(1)}%<br>` +
`TP: ${r.individual.takeProfit.toFixed(1)}%<br>` +
`R/R: ${riskRewardColor} ${riskRewardRatio.toFixed(2)}<br>` +
`Leverage: ${r.individual.leverage}x<br>` +
`Cooldown: ${r.individual.cooldownPeriod} candles<br>` +
`Max Loss Streak: ${r.individual.maxLossStreak}<br>` +
`Max Time: ${r.individual.maxPositionTimeHours}h<br>` +
`Indicators: ${indicatorDetails}<br>` +
`Loopback: ${loopbackPeriod}`
}),
hovertemplate: '%{text}<extra></extra>',
},
] : []
// Prepare 3D plot data for TP% vs SL% vs PnL
const plotDataTPvsSL = results.length > 0 ? [
{
type: 'scatter3d' as const,
mode: 'markers' as const,
x: results.map(r => r.individual.takeProfit),
y: results.map(r => r.individual.stopLoss),
z: results.map(r => r.backtest?.statistics?.totalPnL || 0),
marker: {
size: 5,
color: results.map(r => {
const pnl = r.backtest?.statistics?.totalPnL || 0
return pnl > 0 ? pnl : 0 // Color by PnL (positive values only for better visualization)
}),
colorscale: 'RdYlGn' as const, // Red to Yellow to Green
opacity: 0.8,
colorbar: {
title: 'PnL ($)',
titleside: 'right',
},
},
text: results.map(r => {
const loopbackPeriod = r.individual.indicators.length === 1
? LOOPBACK_CONFIG.singleIndicator
: '5-15'
// Format indicator parameters for display
const indicatorDetails = r.individual.indicators.map((indicator: GeneticIndicator, index: number) => {
let details = indicator.type.toString()
if (indicator.period) details += ` (${indicator.period})`
if (indicator.fastPeriods && indicator.slowPeriods) details += ` (${indicator.fastPeriods}/${indicator.slowPeriods})`
if (indicator.multiplier) details += ` (${indicator.multiplier.toFixed(1)}x)`
return details
}).join(', ')
const riskRewardRatio = r.individual.takeProfit / r.individual.stopLoss
const riskRewardColor = riskRewardRatio >= 2 ? '🟢' : riskRewardRatio >= 1.5 ? '🟡' : '🔴'
// Calculate drawdown color based on severity
const maxDrawdownPc = Math.abs(r.backtest?.statistics?.maxDrawdownPc || 0)
const drawdownColor = maxDrawdownPc <= 10 ? '🟢' : maxDrawdownPc <= 25 ? '🟡' : '🔴'
return `Phase: ${r.phase || 'Unknown'}<br>` +
`Gen: ${r.generation}<br>` +
`Fitness: ${r.fitness.toFixed(2)}<br>` +
`PnL: $${r.backtest?.statistics?.totalPnL?.toFixed(2) || 'N/A'}<br>` +
`Win Rate: ${r.backtest?.winRate?.toFixed(1) || 'N/A'}%<br>` +
`Max Drawdown: ${drawdownColor} ${maxDrawdownPc.toFixed(1)}%<br>` +
`SL: ${r.individual.stopLoss.toFixed(1)}%<br>` +
`TP: ${r.individual.takeProfit.toFixed(1)}%<br>` +
`R/R: ${riskRewardColor} ${riskRewardRatio.toFixed(2)}<br>` +
@@ -1528,20 +1681,31 @@ const BacktestGenetic: React.FC = () => {
<div>
<h4 className="font-bold">Four-Phase Optimization Strategy:</h4>
<p className="text-sm mt-2">
<strong>Phase 1:</strong> Tests individual indicators (3 generations) to identify the best performing single indicators.
<strong>Phase 1:</strong> Tests individual indicators using the full user-specified generation count. Each generation runs populationSize backtests. The algorithm cycles through eligible indicators to ensure comprehensive coverage, but the total backtests remain populationSize per generation.
</p>
<p className="text-sm mt-1">
<strong>Phase 2:</strong> Tests 2-indicator combinations (2 generations) using the top 8 single indicators.
<strong>Phase 2:</strong> Tests 2-indicator combinations using the full user-specified generation count. Each generation runs populationSize backtests with combinations of the best indicators from Phase 1.
</p>
<p className="text-sm mt-1">
<strong>Phase 3:</strong> Tests 3-indicator combinations (2 generations) using the top 8 single indicators.
<strong>Phase 3:</strong> Tests 3-indicator combinations using the full user-specified generation count. Each generation runs populationSize backtests with combinations of the best indicators from Phase 1.
</p>
<p className="text-sm mt-1">
<strong>Phase 4:</strong> Tests 4-indicator combinations (2 generations) using the top 8 single indicators.
<strong>Phase 4:</strong> Tests 4-indicator combinations using the full user-specified generation count. Each generation runs populationSize backtests with combinations of the best indicators from Phase 1.
</p>
<p className="text-sm mt-1">
<strong>No Duplicates:</strong> Each scenario contains unique indicator types to avoid redundancy.
</p>
<p className="text-sm mt-1">
<strong>Population Size:</strong> For Phase 1, set population size to at least the number of indicators you want to test to ensure comprehensive coverage.
</p>
<p className="text-sm mt-1">
<strong>Total Backtests:</strong> {calculateTotalBacktests()} backtests will be run.
{(() => {
const populationSize = formValues.populationSize || GENETIC_CONFIG.populationSize
const totalGenerations = formValues.generations || GENETIC_CONFIG.generations
return ` (${totalGenerations}×${populationSize}×4 phases = ${totalGenerations * populationSize * 4} total)`
})()}.
</p>
<p className="text-sm mt-1">
<strong>Parameter Optimization:</strong> Uses CustomScenario defaults as starting points with 70% bias towards proven values.
</p>
@@ -1565,6 +1729,34 @@ const BacktestGenetic: React.FC = () => {
</div>
</div>
{/* Fitness Function Components */}
<div className="alert alert-info mb-4">
<div>
<h4 className="font-bold">Fitness Function Components:</h4>
<p className="text-sm mt-2">
<strong>PnL Score (30%):</strong> Normalized profit/loss performance
</p>
<p className="text-sm mt-1">
<strong>Win Rate Score (20%):</strong> Percentage of winning trades
</p>
<p className="text-sm mt-1">
<strong>Risk-Reward Score (20%):</strong> Ratio of winning to losing trades
</p>
<p className="text-sm mt-1">
<strong>Consistency Score (10%):</strong> Stability of performance over time
</p>
<p className="text-sm mt-1">
<strong>Risk-Reward Bonus (10%):</strong> Bonus for better R:R ratios (&gt;1.1)
</p>
<p className="text-sm mt-1">
<strong>Drawdown Score (10%):</strong> Penalty for maximum drawdown (0% = 1.0, 50% = 0.0)
</p>
<p className="text-sm mt-1">
<strong>Drawdown Legend:</strong> 🟢 10% (Low Risk) | 🟡 10-25% (Medium Risk) | 🔴 &gt;25% (High Risk)
</p>
</div>
</div>
{/* Eligible Indicators Feature */}
<div className="alert alert-info mb-4">
<div>
@@ -1573,7 +1765,7 @@ const BacktestGenetic: React.FC = () => {
<strong>Manual Selection:</strong> Choose which indicators to include in the optimization process.
</p>
<p className="text-sm mt-1">
<strong>Automatic Removal:</strong> After Phase 1, indicators with fitness scores below 0.1 are automatically removed from consideration.
<strong>Automatic Removal:</strong> After Phase 1, indicators with fitness scores below the configured threshold (default: 10% of max fitness) are automatically removed from consideration.
</p>
<p className="text-sm mt-1">
<strong>Smart Optimization:</strong> The algorithm focuses on promising indicators, improving convergence and reducing computation time.
@@ -1722,6 +1914,8 @@ const BacktestGenetic: React.FC = () => {
/>
</div>
<div className="form-control">
<label className="label">
<span className="label-text">Eligible Indicators</span>
@@ -1766,7 +1960,7 @@ const BacktestGenetic: React.FC = () => {
disabled={isRunning}
className="btn btn-primary"
>
{isRunning ? 'Running...' : 'Start Genetic Algorithm'}
{isRunning ? 'Running...' : `Start Genetic Algorithm (${calculateTotalBacktests()} backtests)`}
</button>
<button
@@ -1795,18 +1989,22 @@ const BacktestGenetic: React.FC = () => {
<div className="mb-6">
<div className="flex justify-between mb-2">
<span>Phase: {currentPhase === 'phase1' ? '1 - Single Indicators' : currentPhase === 'phase2' ? '2 - 2-Indicators' : currentPhase === 'phase3' ? '3 - 3-Indicators' : '4 - 4-Indicators'}</span>
<span>Generation: {currentGeneration}/{GENETIC_CONFIG.phase1Generations + GENETIC_CONFIG.phase2Generations + GENETIC_CONFIG.phase3Generations + GENETIC_CONFIG.phase4Generations}</span>
<span>Generation: {currentGeneration}/{(formValues.generations || GENETIC_CONFIG.generations) * 4}</span>
<span>Individual: {currentIndividual}/{geneticConfig.populationSize}</span>
<span>Best Fitness: {bestFitness.toFixed(2)}</span>
</div>
<div className="flex justify-between mb-2">
<span>Overall Progress: {totalIndividuals > 0 ? Math.round(((currentGeneration - 1) * geneticConfig.populationSize + currentIndividual) / totalIndividuals * 100) : 0}%</span>
<span>Overall Progress: {calculateProgress().percentage}%</span>
<span>Results: {results.length}</span>
<span>Best Single Indicators: {bestSingleIndicators.length}</span>
{currentPhase === 'phase1' && (
<span>Indicators Tested: {new Set(results.filter(r => r.phase === 'phase1').map(r => r.individual.indicators[0]?.type)).size}/{eligibleIndicators.length}</span>
)}
</div>
<div className="flex justify-between mb-2">
<span>Eligible Indicators: {eligibleIndicators.length}/{ALL_INDICATORS.length}</span>
<span>Removed Indicators: {removedIndicators.size}</span>
<span>Bad Indicator Threshold: 0.112 (10% of max fitness)</span>
{removedIndicators.size > 0 && (
<span className="text-warning">
Removed: {Array.from(removedIndicators).slice(0, 3).join(', ')}
@@ -1819,9 +2017,9 @@ const BacktestGenetic: React.FC = () => {
<span>Estimated Completion: {estimatedCompletion}</span>
</div>
<progress
className="progress progress-primary w-full"
value={(currentGeneration - 1) * geneticConfig.populationSize + currentIndividual}
max={totalIndividuals}
className="progress progress-info w-full"
value={calculateProgress().completed}
max={calculateProgress().total}
/>
</div>
)}
@@ -1829,33 +2027,66 @@ const BacktestGenetic: React.FC = () => {
{/* Results */}
{showResults && results.length > 0 && (
<div className="space-y-6">
{/* 3D Visualization */}
<div className="card bg-base-100 shadow-xl">
<div className="card-body">
<h3 className="card-title">3D Optimization Results</h3>
<Plot
data={plotData}
layout={{
title: {text: 'Fitness Score vs Score vs Win Rate'},
scene: {
xaxis: {title: {text: 'Fitness Score'}},
yaxis: {title: {text: 'Score'}},
zaxis: {title: {text: 'Win Rate (%)'}},
},
width: 1100,
height: 800,
margin: {
b: 20,
l: 0,
pad: 0,
r: 0,
t: 0,
},
paper_bgcolor: '#121212',
plot_bgcolor: theme.secondary,
}}
config={{displayModeBar: false}}
/>
{/* 3D Visualizations */}
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
{/* Fitness vs Score vs Win Rate */}
<div className="card bg-base-100 shadow-xl">
<div className="card-body">
<h3 className="card-title">Fitness vs Score vs Win Rate</h3>
<Plot
data={plotData}
layout={{
title: {text: 'Fitness Score vs Score vs Win Rate'},
scene: {
xaxis: {title: {text: 'Fitness Score'}},
yaxis: {title: {text: 'Score'}},
zaxis: {title: {text: 'Win Rate (%)'}},
},
width: 750,
height: 600,
margin: {
b: 20,
l: 0,
pad: 0,
r: 0,
t: 0,
},
paper_bgcolor: '#121212',
plot_bgcolor: theme.secondary,
}}
config={{displayModeBar: false}}
/>
</div>
</div>
{/* TP% vs SL% vs PnL */}
<div className="card bg-base-100 shadow-xl">
<div className="card-body">
<h3 className="card-title">Take Profit vs Stop Loss vs PnL</h3>
<Plot
data={plotDataTPvsSL}
layout={{
title: {text: 'Take Profit % vs Stop Loss % vs PnL'},
scene: {
xaxis: {title: {text: 'Take Profit (%)'}},
yaxis: {title: {text: 'Stop Loss (%)'}},
zaxis: {title: {text: 'PnL ($)'}},
},
width: 750,
height: 600,
margin: {
b: 20,
l: 0,
pad: 0,
r: 0,
t: 0,
},
paper_bgcolor: '#121212',
plot_bgcolor: theme.secondary,
}}
config={{displayModeBar: false}}
/>
</div>
</div>
</div>