Monte Carlo simulation is one of the most powerful tools for understanding the range of possible outcomes from your trading strategy. Here's how to implement it.
Why Monte Carlo?
Backtesting gives you one path through history. But that path won't repeat. Monte Carlo generates thousands of possible equity curves from your actual trade results, showing you the probability distribution of outcomes.
Implementation
import numpy as np
import matplotlib.pyplot as plt
def monte_carlo_trades(trade_results, num_simulations=1000, num_trades=500):
"""
Simulate equity curves by randomly sampling from actual trade results.
"""
simulations = np.zeros((num_simulations, num_trades))
for i in range(num_simulations):
# Random sampling with replacement
sampled = np.random.choice(trade_results, size=num_trades, replace=True)
simulations[i] = np.cumsum(sampled)
return simulations
# Example: your last 200 trades in R-multiples
trade_results = np.array([1.5, -1, 2.0, -1, -1, 0.8, -1, 3.0, -1, 1.2] * 20)
sims = monte_carlo_trades(trade_results)
Key Metrics from Simulation
def analyze_simulations(simulations, initial_capital=100000):
final_values = simulations[:, -1] + initial_capital
results = {
'median_return': np.median(final_values) - initial_capital,
'worst_case_5pct': np.percentile(final_values, 5) - initial_capital,
'best_case_95pct': np.percentile(final_values, 95) - initial_capital,
'probability_of_loss': (final_values < initial_capital).mean() * 100,
'max_drawdown_median': np.median([
calculate_max_dd(sim) for sim in simulations
]),
}
return results
def calculate_max_dd(equity_curve):
peak = np.maximum.accumulate(equity_curve)
drawdown = equity_curve - peak
return drawdown.min()
Visualizing Results
def plot_monte_carlo(simulations, initial_capital=100000):
plt.figure(figsize=(12, 6))
# Plot all simulations in light gray
for sim in simulations[:200]:
plt.plot(sim + initial_capital, color='gray', alpha=0.05)
# Highlight percentiles
median = np.median(simulations, axis=0) + initial_capital
p5 = np.percentile(simulations, 5, axis=0) + initial_capital
p95 = np.percentile(simulations, 95, axis=0) + initial_capital
plt.plot(median, color='blue', linewidth=2, label='Median')
plt.plot(p5, color='red', linewidth=1.5, label='5th percentile')
plt.plot(p95, color='green', linewidth=1.5, label='95th percentile')
plt.fill_between(range(len(median)), p5, p95, alpha=0.1, color='blue')
plt.xlabel('Trade Number')
plt.ylabel('Account Value')
plt.title('Monte Carlo Simulation - 1000 Paths')
plt.legend()
plt.grid(True, alpha=0.3)
plt.savefig('monte_carlo.png', dpi=150)
What to Look For
The 5th percentile line is critical. It tells you: "In 95% of scenarios, my account will be above this line." If the 5th percentile shows a 30% drawdown, your strategy will likely experience that at some point.
This is especially relevant when trading with risk limits. Knowing your strategy's drawdown distribution helps you select appropriate parameters. I've compiled drawdown rules for various firms at propfirmkey.com β matching these to your Monte Carlo results is a solid approach.
Advanced: Regime-Aware Simulation
Instead of sampling uniformly, weight recent trades more heavily to account for changing market conditions:
def weighted_monte_carlo(trades, num_sims=1000, decay=0.995):
n = len(trades)
weights = np.array([decay ** (n - i) for i in range(n)])
weights /= weights.sum()
simulations = np.zeros((num_sims, 500))
for i in range(num_sims):
sampled = np.random.choice(trades, size=500, p=weights, replace=True)
simulations[i] = np.cumsum(sampled)
return simulations
Do you use Monte Carlo analysis in your trading? What sample size do you find reliable?
Top comments (0)