Add files via upload

This commit is contained in:
FxPouya
2025-12-26 17:20:46 +03:30
committed by GitHub
parent 185ec2ed5d
commit f48570f64c
7 changed files with 1426 additions and 8 deletions
+69
View File
@@ -520,6 +520,7 @@ function createStrategyCard(strategy, number) {
</div>
<div class="strategy-actions">
<button class="btn-secondary" onclick="viewStrategyDetails(${number - 1})">View Details</button>
<button class="btn-info" onclick="runMonteCarloForStrategy(${number - 1})" title="Run Monte Carlo Simulation">🎲 MC</button>
<button class="btn-primary" onclick="downloadStrategy(${number - 1}, 'mq4')">MQ4</button>
<button class="btn-primary" onclick="downloadStrategy(${number - 1}, 'mq5')">MQ5</button>
<button class="btn-success" onclick="downloadStrategy(${number - 1}, 'report')">Report</button>
@@ -803,6 +804,71 @@ function toggleWalkForwardSettings() {
}
}
/**
* Update Monte Carlo settings display
*/
function updateMonteCarloSettings() {
const iterations = parseInt(document.getElementById('mc-iterations').value);
const threshold = parseInt(document.getElementById('ror-threshold').value);
document.getElementById('mc-iterations-value').textContent = iterations.toLocaleString();
document.getElementById('ror-threshold-value').textContent = threshold;
}
/**
* Toggle Monte Carlo settings visibility
*/
function toggleMonteCarloSettings() {
const enabled = document.getElementById('enable-montecarlo').checked;
const settings = document.getElementById('montecarlo-settings');
if (settings) {
settings.classList.toggle('hidden', !enabled);
}
}
/**
* Run Monte Carlo analysis for a specific strategy
*/
function runMonteCarloForStrategy(index) {
const strategy = appData.foundStrategies[index];
if (!strategy) {
alert('Strategy not found!');
return;
}
const mcEnabled = document.getElementById('enable-montecarlo')?.checked;
if (!mcEnabled) {
alert('Monte Carlo is not enabled!\\n\\nPlease enable it in the settings before generating strategies.');
return;
}
const m = strategy.metrics;
if (!m.trades || m.trades.length < 20) {
alert(`Monte Carlo requires at least 20 trades.\\nThis strategy has ${m.trades?.length || 0} trades.`);
return;
}
try {
const iterations = parseInt(document.getElementById('mc-iterations').value) || 1000;
const rorThreshold = parseInt(document.getElementById('ror-threshold').value) / 100 || 0.2;
console.log(`🎲 Running Monte Carlo analysis (${iterations} iterations)...`);
const trades = MonteCarloSimulation.extractTrades(strategy);
const mc = new MonteCarloSimulation(trades, iterations, rorThreshold);
const results = mc.run();
strategy.monteCarlo = results;
// Display results in professional modal with Chart.js visualization
displayMonteCarloModal(strategy, index, results);
} catch (error) {
console.error('Monte Carlo error:', error);
alert(`Monte Carlo analysis failed: ${error.message}`);
}
}
// Explicitly expose functions to global scope for inline onclick handlers
window.downloadStrategy = downloadStrategy;
window.downloadAllStrategies = downloadAllStrategies;
@@ -810,5 +876,8 @@ window.toggleStrategySelection = toggleStrategySelection;
window.showComparison = showComparison;
window.updateTrainingSplit = updateTrainingSplit;
window.toggleWalkForwardSettings = toggleWalkForwardSettings;
window.updateMonteCarloSettings = updateMonteCarloSettings;
window.toggleMonteCarloSettings = toggleMonteCarloSettings;
window.runMonteCarloForStrategy = runMonteCarloForStrategy;
window.hideComparison = hideComparison;
window.toggleCustomBarsInput = toggleCustomBarsInput;
+338
View File
@@ -0,0 +1,338 @@
/**
* Display Monte Carlo results in a professional modal with Chart.js visualization
*/
function displayMonteCarloModal(strategy, index, results) {
const m = strategy.metrics;
const stats = results.statistics.equity;
const expectedReturn = ((stats.mean - 10000) / 10000 * 100);
// Determine risk level
let riskLevel, riskClass, riskEmoji, riskMessage;
if (results.riskOfRuin < 5) {
riskLevel = 'LOW RISK';
riskClass = 'good';
riskEmoji = '✅';
riskMessage = 'Excellent robustness. This strategy shows consistent performance across different trade sequences. Safe to trade!';
} else if (results.riskOfRuin < 15) {
riskLevel = 'MODERATE RISK';
riskClass = 'warning';
riskEmoji = '⚠️';
riskMessage = 'Acceptable but monitor closely. Strategy has some variability. Consider reducing position size or using tighter risk management.';
} else {
riskLevel = 'HIGH RISK';
riskClass = 'bad';
riskEmoji = '❌';
riskMessage = 'High variance. Strategy shows significant variability in outcomes. Consider avoiding or significantly reducing position size.';
}
// Create modal if it doesn't exist
let modal = document.getElementById('montecarlo-modal');
if (!modal) {
modal = document.createElement('div');
modal.id = 'montecarlo-modal';
modal.className = 'modal';
document.body.appendChild(modal);
}
modal.innerHTML = `
<div class="modal-content" style="max-width: 1200px;">
<div class="modal-header">
<h2>🎲 Monte Carlo Simulation Results</h2>
<button class="modal-close" onclick="closeMonteCarloModal()">&times;</button>
</div>
<div class="modal-body">
<!-- Strategy Info -->
<div class="mc-strategy-info">
<h3>Strategy #${index + 1}</h3>
<p>PF: ${m.profitFactor.toFixed(2)} | WR: ${m.winRate.toFixed(1)}% | Trades: ${m.totalTrades}</p>
</div>
<!-- Risk Assessment Banner -->
<div class="mc-risk-banner mc-${riskClass}">
<div class="mc-risk-icon">${riskEmoji}</div>
<div class="mc-risk-content">
<h3>${riskLevel}</h3>
<p>${riskMessage}</p>
</div>
</div>
<!-- Summary Statistics -->
<div class="mc-summary">
<div class="mc-stat-card">
<div class="mc-label">Expected Return</div>
<div class="mc-value ${expectedReturn >= 0 ? 'positive' : 'negative'}">
${expectedReturn > 0 ? '+' : ''}${expectedReturn.toFixed(2)}%
</div>
<div class="mc-sublabel">Mean final equity</div>
</div>
<div class="mc-stat-card">
<div class="mc-label">Risk of Ruin</div>
<div class="mc-value mc-${riskClass}">${results.riskOfRuin.toFixed(2)}%</div>
<div class="mc-sublabel">Probability of ${(results.rorThreshold * 100).toFixed(0)}% loss</div>
</div>
<div class="mc-stat-card">
<div class="mc-label">Iterations</div>
<div class="mc-value">${results.iterations.toLocaleString()}</div>
<div class="mc-sublabel">Completed in ${results.executionTime.toFixed(0)}ms</div>
</div>
<div class="mc-stat-card">
<div class="mc-label">Std Deviation</div>
<div class="mc-value">$${stats.stdDev.toFixed(2)}</div>
<div class="mc-sublabel">Variability measure</div>
</div>
</div>
<!-- Equity Distribution Histogram -->
<div class="chart-section">
<h3>Equity Distribution</h3>
<p style="color: #a0aec0; margin-bottom: 15px;">
Distribution of final equity across ${results.iterations.toLocaleString()} randomized trade sequences
</p>
<canvas id="mc-histogram" style="max-height: 300px;"></canvas>
</div>
<!-- Percentile Statistics -->
<div class="mc-percentiles">
<h3>Percentile Analysis</h3>
<table class="mc-percentile-table">
<thead>
<tr>
<th>Percentile</th>
<th>Final Equity</th>
<th>Return</th>
<th>Interpretation</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>5th (Worst 5%)</strong></td>
<td>$${stats.percentile5.toFixed(2)}</td>
<td class="${((stats.percentile5 - 10000) / 10000 * 100) >= 0 ? 'positive' : 'negative'}">
${((stats.percentile5 - 10000) / 10000 * 100) > 0 ? '+' : ''}${((stats.percentile5 - 10000) / 10000 * 100).toFixed(2)}%
</td>
<td>Downside risk scenario</td>
</tr>
<tr>
<td><strong>25th</strong></td>
<td>$${stats.percentile25.toFixed(2)}</td>
<td class="${((stats.percentile25 - 10000) / 10000 * 100) >= 0 ? 'positive' : 'negative'}">
${((stats.percentile25 - 10000) / 10000 * 100) > 0 ? '+' : ''}${((stats.percentile25 - 10000) / 10000 * 100).toFixed(2)}%
</td>
<td>Below average outcome</td>
</tr>
<tr class="highlight-row">
<td><strong>50th (Median)</strong></td>
<td>$${stats.percentile50.toFixed(2)}</td>
<td class="${((stats.percentile50 - 10000) / 10000 * 100) >= 0 ? 'positive' : 'negative'}">
${((stats.percentile50 - 10000) / 10000 * 100) > 0 ? '+' : ''}${((stats.percentile50 - 10000) / 10000 * 100).toFixed(2)}%
</td>
<td>Typical outcome</td>
</tr>
<tr>
<td><strong>75th</strong></td>
<td>$${stats.percentile75.toFixed(2)}</td>
<td class="${((stats.percentile75 - 10000) / 10000 * 100) >= 0 ? 'positive' : 'negative'}">
${((stats.percentile75 - 10000) / 10000 * 100) > 0 ? '+' : ''}${((stats.percentile75 - 10000) / 10000 * 100).toFixed(2)}%
</td>
<td>Above average outcome</td>
</tr>
<tr>
<td><strong>95th (Best 5%)</strong></td>
<td>$${stats.percentile95.toFixed(2)}</td>
<td class="${((stats.percentile95 - 10000) / 10000 * 100) >= 0 ? 'positive' : 'negative'}">
${((stats.percentile95 - 10000) / 10000 * 100) > 0 ? '+' : ''}${((stats.percentile95 - 10000) / 10000 * 100).toFixed(2)}%
</td>
<td>Upside potential</td>
</tr>
</tbody>
</table>
</div>
<!-- Confidence Intervals -->
<div class="mc-confidence">
<h3>Confidence Intervals</h3>
<div class="mc-confidence-grid">
<div class="mc-confidence-card">
<div class="mc-confidence-label">90% Confidence Range</div>
<div class="mc-confidence-value">
$${results.confidence.range90.lower.toFixed(2)} - $${results.confidence.range90.upper.toFixed(2)}
</div>
<div class="mc-confidence-sublabel">90% of outcomes fall within this range</div>
</div>
<div class="mc-confidence-card">
<div class="mc-confidence-label">50% Confidence Range</div>
<div class="mc-confidence-value">
$${results.confidence.range50.lower.toFixed(2)} - $${results.confidence.range50.upper.toFixed(2)}
</div>
<div class="mc-confidence-sublabel">50% of outcomes fall within this range</div>
</div>
</div>
</div>
<!-- Explanation -->
<div class="mc-explanation">
<h4>📖 What is Monte Carlo Simulation?</h4>
<p>
Monte Carlo simulation tests strategy robustness by randomly shuffling the order of trades
${results.iterations.toLocaleString()} times. This shows how the strategy would perform under
different market conditions and helps identify if good results are due to luck or genuine edge.
</p>
<p style="margin-top: 10px;">
<strong>Key Insight:</strong> A robust strategy should show consistent positive returns across
most simulations, with low risk of ruin and tight confidence intervals.
</p>
</div>
</div>
</div>
`;
modal.style.display = 'flex';
// Draw histogram after modal is visible
setTimeout(() => {
drawMonteCarloHistogram(results, stats);
}, 100);
}
/**
* Draw Monte Carlo histogram using Chart.js
*/
function drawMonteCarloHistogram(results, stats) {
const canvas = document.getElementById('mc-histogram');
if (!canvas) return;
const ctx = canvas.getContext('2d');
// Create histogram bins
const numBins = 30;
const min = Math.min(...results.distribution);
const max = Math.max(...results.distribution);
const binWidth = (max - min) / numBins;
const bins = new Array(numBins).fill(0);
const binLabels = [];
for (let i = 0; i < numBins; i++) {
binLabels.push((min + i * binWidth).toFixed(0));
}
// Fill bins
results.distribution.forEach(value => {
const binIndex = Math.min(Math.floor((value - min) / binWidth), numBins - 1);
bins[binIndex]++;
});
// Create gradient colors based on value
const backgroundColors = bins.map((_, i) => {
const value = min + (i + 0.5) * binWidth;
if (value < stats.percentile5) return 'rgba(245, 101, 101, 0.7)'; // Red for worst 5%
if (value < stats.percentile25) return 'rgba(237, 137, 54, 0.7)'; // Orange
if (value < stats.percentile75) return 'rgba(72, 187, 120, 0.7)'; // Green
return 'rgba(56, 178, 172, 0.7)'; // Teal for best 25%
});
new Chart(ctx, {
type: 'bar',
data: {
labels: binLabels,
datasets: [{
label: 'Frequency',
data: bins,
backgroundColor: backgroundColors,
borderColor: backgroundColors.map(c => c.replace('0.7', '1')),
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
plugins: {
legend: { display: false },
tooltip: {
backgroundColor: '#1a1f3a',
titleColor: '#fff',
bodyColor: '#a0aec0',
borderColor: '#2d3748',
borderWidth: 1,
callbacks: {
title: function (context) {
const binStart = parseFloat(context[0].label);
const binEnd = binStart + binWidth;
return `$${binStart.toFixed(0)} - $${binEnd.toFixed(0)}`;
},
label: function (context) {
const percentage = (context.parsed.y / results.iterations * 100).toFixed(1);
return `${context.parsed.y} outcomes (${percentage}%)`;
}
}
},
annotation: {
annotations: {
meanLine: {
type: 'line',
xMin: ((stats.mean - min) / binWidth),
xMax: ((stats.mean - min) / binWidth),
borderColor: '#667eea',
borderWidth: 2,
borderDash: [5, 5],
label: {
content: 'Mean',
enabled: true,
position: 'top'
}
}
}
}
},
scales: {
y: {
beginAtZero: true,
grid: { color: '#2d3748' },
ticks: { color: '#a0aec0' },
title: {
display: true,
text: 'Frequency',
color: '#a0aec0'
}
},
x: {
grid: { display: false },
ticks: {
color: '#a0aec0',
maxRotation: 45,
minRotation: 45,
autoSkip: true,
maxTicksLimit: 10
},
title: {
display: true,
text: 'Final Equity ($)',
color: '#a0aec0'
}
}
}
}
});
}
/**
* Close Monte Carlo modal
*/
function closeMonteCarloModal() {
const modal = document.getElementById('montecarlo-modal');
if (modal) {
modal.style.display = 'none';
}
}
// Close modal when clicking outside
window.addEventListener('click', function (event) {
const modal = document.getElementById('montecarlo-modal');
if (event.target === modal) {
closeMonteCarloModal();
}
});
// Expose functions globally
window.displayMonteCarloModal = displayMonteCarloModal;
window.closeMonteCarloModal = closeMonteCarloModal;
+275
View File
@@ -0,0 +1,275 @@
/**
* Monte Carlo Simulation - Analyzes strategy robustness through trade randomization
*
* This module shuffles trade sequences thousands of times to calculate:
* - Distribution of possible outcomes
* - Confidence intervals (5th-95th percentile)
* - Risk of ruin probability
* - Expected vs worst-case scenarios
*/
class MonteCarloSimulation {
constructor(trades, iterations = 1000, rorThreshold = 0.2) {
this.trades = trades; // Array of trade objects with profit/loss
this.iterations = iterations;
this.rorThreshold = rorThreshold; // Risk of ruin threshold (default 20%)
this.startingEquity = 10000;
}
/**
* Run the full Monte Carlo simulation
* @returns {Object} Statistical results
*/
run() {
console.log(`🎲 Running Monte Carlo simulation (${this.iterations} iterations)...`);
const startTime = performance.now();
const results = [];
for (let i = 0; i < this.iterations; i++) {
const shuffled = this.shuffleTrades();
const equity = this.calculateEquityCurve(shuffled);
results.push({
finalEquity: equity[equity.length - 1],
maxEquity: Math.max(...equity),
minEquity: Math.min(...equity),
maxDrawdown: this.calculateMaxDrawdown(equity),
equityCurve: equity
});
}
const statistics = this.calculateStatistics(results);
const endTime = performance.now();
console.log(`✅ Monte Carlo complete in ${(endTime - startTime).toFixed(0)}ms`);
return {
iterations: this.iterations,
statistics,
distribution: results.map(r => r.finalEquity),
drawdownDistribution: results.map(r => r.maxDrawdown),
riskOfRuin: this.calculateRiskOfRuin(results),
confidence: this.calculateConfidenceIntervals(results),
executionTime: endTime - startTime
};
}
/**
* Shuffle trades using Fisher-Yates algorithm
* @returns {Array} Shuffled copy of trades
*/
shuffleTrades() {
const shuffled = [...this.trades];
for (let i = shuffled.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
}
return shuffled;
}
/**
* Calculate equity curve for a sequence of trades
* @param {Array} trades - Ordered array of trades
* @returns {Array} Equity values over time
*/
calculateEquityCurve(trades) {
const equity = [this.startingEquity];
let currentEquity = this.startingEquity;
for (const trade of trades) {
currentEquity += trade.profit;
equity.push(currentEquity);
}
return equity;
}
/**
* Calculate maximum drawdown from equity curve
* @param {Array} equity - Equity curve
* @returns {Number} Max drawdown percentage
*/
calculateMaxDrawdown(equity) {
let maxEquity = equity[0];
let maxDD = 0;
for (const value of equity) {
if (value > maxEquity) {
maxEquity = value;
}
const drawdown = ((maxEquity - value) / maxEquity) * 100;
if (drawdown > maxDD) {
maxDD = drawdown;
}
}
return maxDD;
}
/**
* Calculate comprehensive statistics from results
* @param {Array} results - Array of simulation results
* @returns {Object} Statistical measures
*/
calculateStatistics(results) {
const finalEquities = results.map(r => r.finalEquity);
const drawdowns = results.map(r => r.maxDrawdown);
return {
equity: {
mean: this.mean(finalEquities),
median: this.median(finalEquities),
stdDev: this.standardDeviation(finalEquities),
min: Math.min(...finalEquities),
max: Math.max(...finalEquities),
percentile5: this.getPercentile(finalEquities, 5),
percentile25: this.getPercentile(finalEquities, 25),
percentile50: this.getPercentile(finalEquities, 50),
percentile75: this.getPercentile(finalEquities, 75),
percentile95: this.getPercentile(finalEquities, 95)
},
drawdown: {
mean: this.mean(drawdowns),
median: this.median(drawdowns),
stdDev: this.standardDeviation(drawdowns),
min: Math.min(...drawdowns),
max: Math.max(...drawdowns),
percentile5: this.getPercentile(drawdowns, 5),
percentile95: this.getPercentile(drawdowns, 95)
}
};
}
/**
* Calculate risk of ruin (probability of losing X% of capital)
* @param {Array} results - Simulation results
* @returns {Number} Risk of ruin percentage
*/
calculateRiskOfRuin(results) {
const ruinEquity = this.startingEquity * (1 - this.rorThreshold);
const ruinCount = results.filter(r => r.minEquity <= ruinEquity).length;
return (ruinCount / results.length) * 100;
}
/**
* Calculate confidence intervals
* @param {Array} results - Simulation results
* @returns {Object} Confidence interval data
*/
calculateConfidenceIntervals(results) {
const finalEquities = results.map(r => r.finalEquity);
return {
range90: {
lower: this.getPercentile(finalEquities, 5),
upper: this.getPercentile(finalEquities, 95)
},
range50: {
lower: this.getPercentile(finalEquities, 25),
upper: this.getPercentile(finalEquities, 75)
}
};
}
/**
* Calculate mean (average)
* @param {Array} data - Numeric array
* @returns {Number} Mean value
*/
mean(data) {
return data.reduce((sum, val) => sum + val, 0) / data.length;
}
/**
* Calculate median (50th percentile)
* @param {Array} data - Numeric array
* @returns {Number} Median value
*/
median(data) {
return this.getPercentile(data, 50);
}
/**
* Calculate standard deviation
* @param {Array} data - Numeric array
* @returns {Number} Standard deviation
*/
standardDeviation(data) {
const avg = this.mean(data);
const squareDiffs = data.map(value => Math.pow(value - avg, 2));
const avgSquareDiff = this.mean(squareDiffs);
return Math.sqrt(avgSquareDiff);
}
/**
* Calculate percentile value
* @param {Array} data - Numeric array
* @param {Number} percentile - Percentile to calculate (0-100)
* @returns {Number} Percentile value
*/
getPercentile(data, percentile) {
const sorted = [...data].sort((a, b) => a - b);
const index = (percentile / 100) * (sorted.length - 1);
const lower = Math.floor(index);
const upper = Math.ceil(index);
const weight = index - lower;
if (lower === upper) {
return sorted[lower];
}
return sorted[lower] * (1 - weight) + sorted[upper] * weight;
}
/**
* Generate summary text for results
* @param {Object} results - Monte Carlo results
* @returns {String} Human-readable summary
*/
static generateSummary(results) {
const { statistics, riskOfRuin, confidence } = results;
const expectedReturn = ((statistics.equity.mean - 10000) / 10000 * 100).toFixed(2);
let summary = `Monte Carlo Analysis (${results.iterations} iterations):\n\n`;
summary += `Expected Return: ${expectedReturn}%\n`;
summary += `90% Confidence Range: $${confidence.range90.lower.toFixed(2)} - $${confidence.range90.upper.toFixed(2)}\n`;
summary += `Risk of Ruin (${(results.rorThreshold * 100)}%): ${riskOfRuin.toFixed(2)}%\n\n`;
if (riskOfRuin < 5) {
summary += `✅ Low risk - Strategy shows good robustness`;
} else if (riskOfRuin < 15) {
summary += `⚠️ Moderate risk - Acceptable but monitor closely`;
} else {
summary += `❌ High risk - Consider reducing position size or avoiding`;
}
return summary;
}
/**
* Extract trades from strategy backtest results
* @param {Object} strategy - Strategy with metrics
* @returns {Array} Array of trade objects
*/
static extractTrades(strategy) {
if (!strategy.metrics || !strategy.metrics.trades) {
throw new Error('Strategy must have backtest results with trades');
}
return strategy.metrics.trades.map(trade => ({
profit: trade.profit,
type: trade.type,
entry: trade.entry,
exit: trade.exit
}));
}
}
// Export for use in other modules
if (typeof module !== 'undefined' && module.exports) {
module.exports = MonteCarloSimulation;
}