mirror of
https://github.com/FxPouya/FxMathQuantWebApp.git
synced 2026-07-27 18:27:44 +00:00
277 lines
9.0 KiB
JavaScript
277 lines
9.0 KiB
JavaScript
/**
|
|
* 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() {
|
|
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();
|
|
|
|
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),
|
|
percentile25: this.getPercentile(drawdowns, 25),
|
|
percentile50: this.getPercentile(drawdowns, 50),
|
|
percentile75: this.getPercentile(drawdowns, 75),
|
|
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');
|
|
}
|
|
|
|
const trades = strategy.metrics.trades.map(trade => ({
|
|
profit: trade.profit,
|
|
type: trade.type,
|
|
entry: trade.entry,
|
|
exit: trade.exit
|
|
}));
|
|
|
|
return trades;
|
|
}
|
|
}
|
|
|
|
// Export for use in other modules
|
|
if (typeof module !== 'undefined' && module.exports) {
|
|
module.exports = MonteCarloSimulation;
|
|
}
|