Monte Carlo simulation for trading (stress-test your bot)
Your backtest produced one equity curve — but that curve is just one ordering of your trades, and the order was luck. Shuffle the same trades into a different sequence and the drawdown, the worst losing streak, even the final balance change dramatically. Monte Carlo simulation generates thousands of those alternate histories to reveal the range of outcomes a strategy can produce — including the ugly ones you'd otherwise only meet live. This guide explains the resampling methods, risk of ruin, and the code.
The core idea
A single backtest is one sample from a universe of possible outcomes. The exact sequence in which your wins and losses arrived was chance — and sequence drives drawdown. Monte Carlo simulation treats your trade results as a pool and rebuilds the equity curve thousands of times in different random orders (or by resampling), so you see not one outcome but a whole distribution of them.
Shuffle and resample
- Reshuffle — keep the same set of trade returns but randomize their order; shows how much your result depended on a lucky sequence.
- Resample with replacement (bootstrap) — draw trades randomly, allowing repeats; models samples you didn't happen to get.
- Randomize entries — perturb entry timing to test robustness to noise.
What it reveals
- Drawdown distribution — not “max drawdown was 18%” but “5% of orderings exceeded 31%.”
- Range of returns — how wide the band of plausible final balances really is.
- Worst losing streak — the run of losers you must be financially and emotionally ready for.
Risk of ruin
Risk of ruin is the probability that an ordering of trades drives your account below a threshold you can't recover from. A strategy with a great average return can still have an uncomfortable risk of ruin if its drawdowns cluster. Monte Carlo estimates it directly: count how many of the thousands of simulated paths hit your ruin level. If that fraction isn't tiny, cut your size with the position calculator.
The code
python · monte_carlo.pyimport random
def monte_carlo(trade_returns, start=10000, runs=5000):
max_dds = []
for _ in range(runs):
seq = random.sample(trade_returns, len(trade_returns))
eq, peak, dd = start, start, 0.0
for r in seq:
eq *= (1+r); peak = max(peak, eq)
dd = max(dd, (peak-eq)/peak)
max_dds.append(dd)
max_dds.sort()
return max_dds[int(0.95*runs)] # 95th-percentile drawdown
Honest limits
Monte Carlo is only as good as its input. Reshuffling assumes trades are independent — but real markets have regimes and autocorrelation, so true worst cases can be worse than a naive shuffle suggests. And it can't conjure outcomes outside your historical trade pool (a market crash you never sampled). Use it alongside walk-forward analysis after honest optimization, and pressure-test the strategy in the backtester first.
Frequently asked questions
What is Monte Carlo simulation in trading?
Monte Carlo simulation rebuilds a strategy's equity curve thousands of times in different random orders or by resampling its trades, revealing the full range of possible outcomes rather than the single lucky sequence a backtest happened to produce. It exposes worst-case drawdowns and risk of ruin.
Why shuffle trade order in a backtest?
Because the sequence in which wins and losses arrive was chance, and sequence drives drawdown. Reshuffling the same trades shows how much your backtest result depended on a lucky ordering, and how deep the drawdown could have been with an unluckier sequence.
What is risk of ruin?
Risk of ruin is the probability that some ordering of trades drives an account below a level it cannot recover from. Monte Carlo estimates it by counting how many simulated paths hit that level; if the fraction is not tiny, the position size is too large.
What are the limits of Monte Carlo for trading?
Reshuffling assumes trades are independent, but real markets have regimes and autocorrelation, so true worst cases can be worse than a naive shuffle implies. It also cannot produce outcomes outside the historical trade pool, so it should complement walk-forward analysis, not replace it.