mirror of
https://github.com/FxPouya/FxMathQuantWebApp.git
synced 2026-08-01 04:27:43 +00:00
Add files via upload
This commit is contained in:
+69
@@ -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;
|
||||
|
||||
@@ -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()">×</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;
|
||||
@@ -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;
|
||||
}
|
||||
Reference in New Issue
Block a user