Add files via upload

This commit is contained in:
FxPouya
2025-12-26 15:19:15 +03:30
committed by GitHub
parent b54fbf0aa8
commit 185ec2ed5d
7 changed files with 603 additions and 17 deletions
+36 -2
View File
@@ -14,9 +14,15 @@ class GeneticOptimizer {
minTrades: config.minTrades || 30,
minPF: config.minPF || 1.5,
maxDD: config.maxDD || 25,
minWR: config.minWR || 45
minWR: config.minWR || 45,
// Walk-Forward Analysis settings
enableWalkForward: config.enableWalkForward || false,
trainingRatio: config.trainingRatio || 0.7
};
// Debug: Log walk-forward config
console.log('🔧 GA Config - Walk-Forward:', this.config.enableWalkForward, 'Ratio:', this.config.trainingRatio);
this.population = [];
this.foundStrategies = [];
this.isRunning = false;
@@ -40,10 +46,38 @@ class GeneticOptimizer {
* Evaluate fitness for all strategies
*/
evaluatePopulation() {
// Log walk-forward status (only once per generation)
if (this.currentGeneration === 0) {
console.log('🔬 Walk-Forward Analysis:', this.config.enableWalkForward ? 'ENABLED' : 'DISABLED');
if (this.config.enableWalkForward) {
console.log('📊 Training Ratio:', (this.config.trainingRatio * 100) + '%');
}
}
for (const strategy of this.population) {
// Regular backtest on full data
const backtester = new Backtester(this.data, strategy, this.symbol);
strategy.metrics = backtester.run();
strategy.fitness = this.calculateFitness(strategy.metrics);
// Walk-forward analysis (if enabled)
if (this.config.enableWalkForward) {
const wfAnalysis = new WalkForwardAnalysis(
this.data,
this.config.trainingRatio || 0.7
);
strategy.walkForward = wfAnalysis.analyze(strategy, this.symbol);
// Penalize strategies that fail walk-forward validation
if (!strategy.walkForward.passed) {
strategy.fitness = this.calculateFitness(strategy.metrics) * 0.5; // 50% penalty
} else {
// Bonus for high robustness
const robustnessBonus = 1 + (strategy.walkForward.robustness / 200); // Up to 50% bonus
strategy.fitness = this.calculateFitness(strategy.metrics) * robustnessBonus;
}
} else {
strategy.fitness = this.calculateFitness(strategy.metrics);
}
}
// Sort by fitness (descending)
+56 -1
View File
@@ -281,7 +281,10 @@ async function startGeneration() {
minPF: parseFloat(document.getElementById('min-pf').value),
minWR: parseFloat(document.getElementById('min-wr').value),
maxDD: parseFloat(document.getElementById('max-dd').value),
minTrades: parseInt(document.getElementById('min-trades').value)
minTrades: parseInt(document.getElementById('min-trades').value),
// Walk-Forward Analysis settings
enableWalkForward: document.getElementById('enable-walkforward').checked,
trainingRatio: parseInt(document.getElementById('training-ratio').value) / 100
};
// Show progress section
@@ -469,9 +472,30 @@ function createStrategyCard(strategy, number) {
const card = document.createElement('div');
card.className = 'strategy-card';
// Generate walk-forward badge HTML (only if walk-forward was enabled and data exists)
let wfBadgeHTML = '';
if (strategy.walkForward && typeof strategy.walkForward === 'object') {
const wf = strategy.walkForward;
if (wf.robustness !== undefined && wf.passed !== undefined) {
const badgeClass = wf.passed ? 'wf-badge-pass' : 'wf-badge-fail';
const badgeIcon = wf.passed ? '✓' : '⚠';
const badgeText = wf.passed ? 'Robust' : 'Overfitted';
wfBadgeHTML = `
<div class="wf-badge ${badgeClass}" title="Robustness Score: ${wf.robustness}/100">
<span class="wf-icon">${badgeIcon}</span>
<span class="wf-text">${badgeText}</span>
<span class="wf-score">${wf.robustness}</span>
</div>
`;
}
}
const name = `Strategy #${number}`;
card.innerHTML = `
<div class="strategy-header">
<div class="strategy-name">${name}</div>
${wfBadgeHTML}
<input type="checkbox" class="strategy-select" onchange="toggleStrategySelection(${number - 1}, this.checked)">
</div>
<div class="strategy-metrics">
@@ -750,10 +774,41 @@ function formatTime(seconds) {
return `${minutes}m ${secs}s`;
}
/**
* Update training/testing split display
*/
function updateTrainingSplit() {
const ratio = parseInt(document.getElementById('training-ratio').value);
document.getElementById('training-percent').textContent = ratio;
// Update bar counts if data is loaded
if (appData.csvData) {
const totalBars = appData.csvData.length;
const trainingBars = Math.floor(totalBars * (ratio / 100));
const testingBars = totalBars - trainingBars;
document.getElementById('training-bars').textContent = trainingBars.toLocaleString();
document.getElementById('testing-bars').textContent = testingBars.toLocaleString();
}
}
/**
* Toggle walk-forward settings visibility
*/
function toggleWalkForwardSettings() {
const enabled = document.getElementById('enable-walkforward').checked;
const settings = document.getElementById('walkforward-settings');
if (settings) {
settings.style.display = enabled ? 'block' : 'none';
}
}
// Explicitly expose functions to global scope for inline onclick handlers
window.downloadStrategy = downloadStrategy;
window.downloadAllStrategies = downloadAllStrategies;
window.toggleStrategySelection = toggleStrategySelection;
window.showComparison = showComparison;
window.updateTrainingSplit = updateTrainingSplit;
window.toggleWalkForwardSettings = toggleWalkForwardSettings;
window.hideComparison = hideComparison;
window.toggleCustomBarsInput = toggleCustomBarsInput;
+2
View File
@@ -100,6 +100,8 @@ class Strategy {
newStrategy.closeAtOpposite = this.closeAtOpposite;
newStrategy.metrics = this.metrics ? JSON.parse(JSON.stringify(this.metrics)) : {};
newStrategy.fitness = this.fitness || 0;
// Copy walk-forward analysis results
newStrategy.walkForward = this.walkForward ? JSON.parse(JSON.stringify(this.walkForward)) : undefined;
return newStrategy;
}
+203
View File
@@ -0,0 +1,203 @@
/**
* Walk-Forward Analysis Module
* Validates strategies on out-of-sample data to detect overfitting
*/
class WalkForwardAnalysis {
constructor(data, trainingRatio = 0.7) {
this.data = data;
this.trainingRatio = trainingRatio;
this.splitIndex = Math.floor(data.length * trainingRatio);
}
/**
* Split data into training and testing periods
* @returns {Object} { training: Array, testing: Array }
*/
split() {
return {
training: this.data.slice(0, this.splitIndex),
testing: this.data.slice(this.splitIndex),
splitIndex: this.splitIndex,
trainingBars: this.splitIndex,
testingBars: this.data.length - this.splitIndex
};
}
/**
* Run walk-forward analysis on a strategy
* @param {Object} strategy - Strategy to analyze
* @param {string} symbol - Trading symbol
* @returns {Object} Analysis results
*/
analyze(strategy, symbol = '') {
const { training, testing } = this.split();
// Backtest on training data
const trainingBacktest = new Backtester(training, strategy, symbol);
const trainingResults = trainingBacktest.run();
// Backtest on testing data
const testingBacktest = new Backtester(testing, strategy, symbol);
const testingResults = testingBacktest.run();
// Calculate degradation
const degradation = this.calculateDegradation(trainingResults, testingResults);
// Calculate robustness score
const robustnessScore = this.calculateRobustness(degradation, trainingResults, testingResults);
// Determine if strategy passed
const passed = robustnessScore >= 60;
return {
training: trainingResults,
testing: testingResults,
degradation: degradation,
robustness: robustnessScore,
passed: passed,
splitInfo: {
trainingBars: training.length,
testingBars: testing.length,
trainingRatio: this.trainingRatio
}
};
}
/**
* Calculate performance degradation between training and testing
* @param {Object} train - Training results
* @param {Object} test - Testing results
* @returns {Object} Degradation metrics
*/
calculateDegradation(train, test) {
return {
profitFactor: this.calcPercentDegradation(train.profitFactor, test.profitFactor),
winRate: train.winRate - test.winRate,
maxDrawdown: test.maxDrawdown - train.maxDrawdown,
netProfit: this.calcPercentDegradation(train.netProfit, test.netProfit),
totalTrades: test.totalTrades,
avgWin: this.calcPercentDegradation(train.avgWin, test.avgWin),
avgLoss: this.calcPercentDegradation(train.avgLoss, test.avgLoss)
};
}
/**
* Calculate percentage degradation
* @param {number} trainValue - Training value
* @param {number} testValue - Testing value
* @returns {number} Degradation percentage
*/
calcPercentDegradation(trainValue, testValue) {
if (trainValue === 0) return 0;
return ((trainValue - testValue) / trainValue * 100);
}
/**
* Calculate robustness score (0-100)
* Higher score = more robust strategy
* @param {Object} degradation - Degradation metrics
* @param {Object} train - Training results
* @param {Object} test - Testing results
* @returns {number} Robustness score
*/
calculateRobustness(degradation, train, test) {
// Profit Factor score (40% weight)
// Lower degradation = higher score
const pfDeg = Math.abs(degradation.profitFactor);
let pfScore = 100;
if (pfDeg > 50) pfScore = 0;
else if (pfDeg > 30) pfScore = 30;
else if (pfDeg > 15) pfScore = 60;
else pfScore = 100;
// Win Rate score (30% weight)
const wrDeg = Math.abs(degradation.winRate);
let wrScore = 100;
if (wrDeg > 20) wrScore = 0;
else if (wrDeg > 10) wrScore = 40;
else if (wrDeg > 5) wrScore = 70;
else wrScore = 100;
// Drawdown score (30% weight)
// For drawdown, increase is bad
const ddDeg = degradation.maxDrawdown;
let ddScore = 100;
if (ddDeg > 15) ddScore = 0;
else if (ddDeg > 10) ddScore = 40;
else if (ddDeg > 5) ddScore = 70;
else if (ddDeg < -5) ddScore = 100; // Better drawdown in testing
else ddScore = 85;
// Additional checks
// Penalize if testing has too few trades
let tradesPenalty = 0;
if (test.totalTrades < 20) {
tradesPenalty = 20;
} else if (test.totalTrades < 30) {
tradesPenalty = 10;
}
// Bonus if testing performs better
let bonus = 0;
if (test.profitFactor > train.profitFactor && test.winRate >= train.winRate) {
bonus = 10;
}
// Weighted average
const score = (pfScore * 0.4) + (wrScore * 0.3) + (ddScore * 0.3) - tradesPenalty + bonus;
return Math.max(0, Math.min(100, Math.round(score)));
}
/**
* Get degradation severity level
* @param {number} value - Degradation value
* @param {boolean} inverse - If true, higher is worse (for drawdown)
* @returns {string} 'good', 'warning', or 'bad'
*/
getDegradationLevel(value, inverse = false) {
const absValue = Math.abs(value);
if (inverse) {
// For drawdown increase
if (value < 0) return 'good'; // Improved
if (absValue < 5) return 'good';
if (absValue < 10) return 'warning';
return 'bad';
} else {
// For profit factor, win rate degradation
if (absValue < 10) return 'good';
if (absValue < 20) return 'warning';
return 'bad';
}
}
/**
* Generate summary text for walk-forward results
* @param {Object} results - Walk-forward results
* @returns {string} Summary text
*/
generateSummary(results) {
const { robustness, passed, degradation } = results;
if (passed) {
if (robustness >= 80) {
return 'Excellent! Strategy shows strong robustness with minimal overfitting.';
} else if (robustness >= 70) {
return 'Good! Strategy performs well on out-of-sample data.';
} else {
return 'Acceptable. Strategy shows reasonable robustness but monitor performance.';
}
} else {
if (robustness < 40) {
return 'Warning! High overfitting risk. Strategy may not perform well in live trading.';
} else {
return 'Caution. Strategy shows some overfitting. Consider re-optimization or more data.';
}
}
}
}
// Export for use in other modules
window.WalkForwardAnalysis = WalkForwardAnalysis;