diff --git a/js/monte-carlo-modal.js b/js/monte-carlo-modal.js index 2c1e25e..8e25d64 100644 --- a/js/monte-carlo-modal.js +++ b/js/monte-carlo-modal.js @@ -59,11 +59,18 @@ function displayMonteCarloModal(strategy, index, results) {
-
Expected Return
-
- ${expectedReturn > 0 ? '+' : ''}${expectedReturn.toFixed(2)}% +
Max Drawdown (Worst)
+
+ ${results.statistics.drawdown.percentile95.toFixed(2)}%
-
Mean final equity
+
95th percentile (worst 5%)
+
+
+
Max Drawdown (Median)
+
+ ${results.statistics.drawdown.median.toFixed(2)}% +
+
Typical drawdown scenario
Risk of Ruin
@@ -75,96 +82,96 @@ function displayMonteCarloModal(strategy, index, results) {
${results.iterations.toLocaleString()}
Completed in ${results.executionTime.toFixed(0)}ms
-
-
Std Deviation
-
$${stats.stdDev.toFixed(2)}
-
Variability measure
-
- +
-

Equity Distribution

+

📉 Drawdown Distribution

- Distribution of final equity across ${results.iterations.toLocaleString()} randomized trade sequences + Distribution of maximum drawdown across ${results.iterations.toLocaleString()} randomized trade sequences

- + +
+

💡 Understanding Monte Carlo Results

+

+ Why focus on drawdown? When shuffling trades, the final equity stays nearly constant + (sum of trades doesn't change), but the drawdown varies significantly based on trade order. +

+

+ Key Insight: A robust strategy should show consistent low drawdowns across + different trade sequences. High drawdown variance indicates the strategy's performance is highly dependent + on lucky trade timing. +

+
+ +
-

Percentile Analysis

+

Drawdown Percentile Analysis

- - + - - - - + + + - - - + + - - - + + - - - + + - - - - + + +
PercentileFinal EquityReturnMax Drawdown Interpretation
5th (Worst 5%)$${stats.percentile5.toFixed(2)} - ${((stats.percentile5 - 10000) / 10000 * 100) > 0 ? '+' : ''}${((stats.percentile5 - 10000) / 10000 * 100).toFixed(2)}% - Downside risk scenario5th (Best 5%)${results.statistics.drawdown.percentile5.toFixed(2)}%Best-case drawdown scenario
25th$${stats.percentile25.toFixed(2)} - ${((stats.percentile25 - 10000) / 10000 * 100) > 0 ? '+' : ''}${((stats.percentile25 - 10000) / 10000 * 100).toFixed(2)}% - Below average outcome${results.statistics.drawdown.percentile25.toFixed(2)}%Better than average
50th (Median)$${stats.percentile50.toFixed(2)} - ${((stats.percentile50 - 10000) / 10000 * 100) > 0 ? '+' : ''}${((stats.percentile50 - 10000) / 10000 * 100).toFixed(2)}% - Typical outcome${results.statistics.drawdown.median.toFixed(2)}%Typical drawdown
75th$${stats.percentile75.toFixed(2)} - ${((stats.percentile75 - 10000) / 10000 * 100) > 0 ? '+' : ''}${((stats.percentile75 - 10000) / 10000 * 100).toFixed(2)}% - Above average outcome${results.statistics.drawdown.percentile75.toFixed(2)}%Worse than average
95th (Best 5%)$${stats.percentile95.toFixed(2)} - ${((stats.percentile95 - 10000) / 10000 * 100) > 0 ? '+' : ''}${((stats.percentile95 - 10000) / 10000 * 100).toFixed(2)}% - Upside potential95th (Worst 5%)${results.statistics.drawdown.percentile95.toFixed(2)}%Worst-case drawdown scenario
- +
-

Confidence Intervals

+

Drawdown Statistics Summary

-
90% Confidence Range
+
Average Drawdown
- $${results.confidence.range90.lower.toFixed(2)} - $${results.confidence.range90.upper.toFixed(2)} + ${results.statistics.drawdown.mean.toFixed(2)}%
-
90% of outcomes fall within this range
+
Mean across all simulations
-
50% Confidence Range
+
Drawdown Std Dev
- $${results.confidence.range50.lower.toFixed(2)} - $${results.confidence.range50.upper.toFixed(2)} + ${results.statistics.drawdown.stdDev.toFixed(2)}%
-
50% of outcomes fall within this range
+
Variability in drawdown outcomes
+
+
+
Expected Return
+
+ ${expectedReturn > 0 ? '+' : ''}${expectedReturn.toFixed(2)}% +
+
Consistent across simulations
@@ -203,32 +210,34 @@ function drawMonteCarloHistogram(results, stats) { const ctx = canvas.getContext('2d'); - // Create histogram bins + // Create histogram bins for DRAWDOWN distribution const numBins = 30; - const min = Math.min(...results.distribution); - const max = Math.max(...results.distribution); + const drawdownData = results.drawdownDistribution; + const min = Math.min(...drawdownData); + const max = Math.max(...drawdownData); 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)); + binLabels.push((min + i * binWidth).toFixed(1)); } - // Fill bins - results.distribution.forEach(value => { + // Fill bins with drawdown data + drawdownData.forEach(value => { const binIndex = Math.min(Math.floor((value - min) / binWidth), numBins - 1); bins[binIndex]++; }); - // Create gradient colors based on value + // Create gradient colors based on drawdown value (GREEN = low DD, RED = high DD) + const ddStats = results.statistics.drawdown; 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% + if (value < ddStats.percentile25) return 'rgba(72, 187, 120, 0.7)'; // Green for best 25% + if (value < ddStats.percentile50) return 'rgba(56, 178, 172, 0.7)'; // Teal + if (value < ddStats.percentile75) return 'rgba(237, 137, 54, 0.7)'; // Orange + return 'rgba(245, 101, 101, 0.7)'; // Red for worst 25% }); new Chart(ctx, { @@ -258,7 +267,7 @@ function drawMonteCarloHistogram(results, stats) { title: function (context) { const binStart = parseFloat(context[0].label); const binEnd = binStart + binWidth; - return `$${binStart.toFixed(0)} - $${binEnd.toFixed(0)}`; + return `${binStart.toFixed(1)}% - ${binEnd.toFixed(1)}%`; }, label: function (context) { const percentage = (context.parsed.y / results.iterations * 100).toFixed(1); @@ -268,15 +277,15 @@ function drawMonteCarloHistogram(results, stats) { }, annotation: { annotations: { - meanLine: { + medianLine: { type: 'line', - xMin: ((stats.mean - min) / binWidth), - xMax: ((stats.mean - min) / binWidth), + xMin: ((ddStats.median - min) / binWidth), + xMax: ((ddStats.median - min) / binWidth), borderColor: '#667eea', borderWidth: 2, borderDash: [5, 5], label: { - content: 'Mean', + content: 'Median', enabled: true, position: 'top' } @@ -306,7 +315,7 @@ function drawMonteCarloHistogram(results, stats) { }, title: { display: true, - text: 'Final Equity ($)', + text: 'Maximum Drawdown (%)', color: '#a0aec0' } } diff --git a/js/monte-carlo.js b/js/monte-carlo.js index 670a842..876b1f9 100644 --- a/js/monte-carlo.js +++ b/js/monte-carlo.js @@ -21,9 +21,7 @@ class MonteCarloSimulation { * @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++) { @@ -42,8 +40,6 @@ class MonteCarloSimulation { 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, @@ -138,6 +134,9 @@ class MonteCarloSimulation { min: Math.min(...drawdowns), max: Math.max(...drawdowns), percentile5: this.getPercentile(drawdowns, 5), + percentile25: this.getPercentile(drawdowns, 25), + percentile50: this.getPercentile(drawdowns, 50), + percentile75: this.getPercentile(drawdowns, 75), percentile95: this.getPercentile(drawdowns, 95) } }; @@ -260,12 +259,14 @@ class MonteCarloSimulation { throw new Error('Strategy must have backtest results with trades'); } - return strategy.metrics.trades.map(trade => ({ + const trades = strategy.metrics.trades.map(trade => ({ profit: trade.profit, type: trade.type, entry: trade.entry, exit: trade.exit })); + + return trades; } }