Update front
This commit is contained in:
@@ -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 (>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) | 🔴 >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>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user