Monte Carlo Simulation in Python
A computational technique that models a strategy's range of possible future outcomes by repeatedly sampling from its historical return distribution.
Definition
Monte Carlo Simulation in quantitative finance uses random sampling from a strategy's empirical or parametric return distribution to generate thousands of possible equity curve trajectories. Rather than relying on a single deterministic backtest path — which is heavily dependent on the specific sequence of returns observed — Monte Carlo analysis reveals the full probability distribution of outcomes: best case, worst case, median case, and confidence intervals. It is the gold standard for stress-testing a strategy's robustness before live deployment and for setting realistic drawdown expectations for investors.
Quantitative Formula
Where is the number of simulation iterations, is a randomly sampled return sequence drawn from the empirical return distribution (or a fitted parametric distribution), and is a function of that sequence — typically the terminal equity value or maximum drawdown. By the Law of Large Numbers, the average converges to the true expected value as .
Why It Matters in Backtesting
A single backtest represents one specific path through history — but the future will follow a different path. Monte Carlo simulation answers the critical question: 'If this strategy's true edge holds, what is the probability of surviving a 3-year drawdown of 25% or more?' Running 10,000 simulations allows computation of precise confidence intervals for Sharpe Ratio, MDD, and terminal wealth. Any strategy that looks fragile across the simulation distribution should not be deployed regardless of its historical backtest performance.
Python Implementation
import numpy as np
import pandas as pd
def monte_carlo_simulation(returns: pd.Series, n_simulations: int = 10000,
n_days: int = 252, initial_capital: float = 10000.0,
confidence_level: float = 0.95) -> dict:
"""
Runs a Monte Carlo simulation by bootstrapping from historical daily returns.
Returns terminal equity distribution and drawdown statistics across all paths.
"""
terminal_values = np.zeros(n_simulations)
max_drawdowns = np.zeros(n_simulations)
for i in range(n_simulations):
sampled_returns = np.random.choice(returns.values, size=n_days, replace=True)
equity_curve = initial_capital * np.cumprod(1 + sampled_returns)
terminal_values[i] = equity_curve[-1]
peak = np.maximum.accumulate(equity_curve)
drawdown = (equity_curve - peak) / peak
max_drawdowns[i] = drawdown.min()
return {
"median_terminal_value": np.median(terminal_values),
"percentile_5_terminal": np.percentile(terminal_values, 5),
"percentile_95_terminal": np.percentile(terminal_values, 95),
"prob_of_profit": (terminal_values > initial_capital).mean(),
"median_max_drawdown": np.median(max_drawdowns),
"worst_case_drawdown_5pct": np.percentile(max_drawdowns, 5),
"terminal_values": terminal_values,
"max_drawdowns": max_drawdowns
}Test this in a live environment
Stop running Jupyter notebooks locally. Paste this Monte Carlo Simulation code directly into Valetha's Strategy Lab and run a full historical backtest in seconds.
Open the Python Strategy Lab