Quant for Free
Home / Guides / Monte Carlo Trade-Sequence
Path-dependent risk

Monte Carlo trade-sequence stress test

A backtest equity curve is one ordering of your closed trades. Bootstrap Monte Carlo asks what happens if the same P&L list arrives in a different order — thousands of times — and turns the answer into a percentile fan chart plus drawdown and ruin metrics you can size against.

The path that lied

Net profit and profit factor summarize the realized sequence. They do not tell you how often a different shuffle of the same trades would have hit a margin call, tripped a prop-firm daily-loss rule, or forced you to cut size mid-sample. Path dependency is not a curiosity — it is the difference between “the edge worked” and “the edge worked and the order was kind.”

Quantitative risk managers have answered this for decades with Monte Carlo: generate many what-if paths from the same data and report a distribution, not a single curve. For closed trades, the lightest assumption-free version is bootstrap resampling of the trade P&L list.

This guide is pedagogy for that loop. For parameter honesty across time, pair it with walk-forward in pure Python. For the broader validation stack, see the validation gauntlet.

Bootstrap in plain English

Start with N closed-trade P&L values (wins positive, losses negative). Treat each as an observation drawn from an unknown distribution. For each simulation:

  1. Draw N trades at random with replacement from that pool.
  2. Accumulate them into a synthetic equity curve from a fixed initial balance.
  3. Record final equity and peak-to-trough max drawdown for that path.

Repeat (typically on the order of 1,000 runs in teaching setups). You now have a cloud of plausible equity paths — not a forecast of tomorrow’s trade, but a map of how volatile the journey could have been given the same empirical edge.

historical P&L pool  →  resample with replacement × M sims
                      →  M equity curves
                      →  percentiles at each trade step (fan)
                      →  DD / VaR / ruin summaries

Why not fit a normal and sample from it?

Parametric Monte Carlo (fit μ, σ; draw Gaussian noise) is tempting and usually wrong for trade lists. Real P&L is fat-tailed, often skewed, sometimes multimodal — a mean-reversion book can look like a pile of small wins, a few large stops, and a fat right tail when a trend day finally pays. A normal fit smooths out the outliers that dominate drawdown. Bootstrap does not assume a shape; it samples from what actually happened.

Reading the fan chart

At every trade step, take the cross-section of all simulated equities and plot a few percentiles. A common teaching fan uses the 5th, 25th, 50th, 75th, and 95th:

Narrow fan + rising median: outcomes converge; order luck matters less. Wide early fan: same edge, fragile path — size and stop rules matter more than the headline profit factor.

Four risk numbers worth caching

Collapse the simulation cloud into a short dashboard (definitions match common risk practice; exact thresholds are yours to lock a priori):

If stress drawdown or VaR feels uncomfortable relative to account size, the first lever is usually lot size, not strategy replacement — see the position-size tool.

A pure-Python sketch

You do not need MetaTrader for the idea. Given a 1-D array of trade P&Ls, the engine is a few dozen lines of numpy. Pedagogy rewritten here for Quant for Free — not a paste of vendor source.

import numpy as np

def max_drawdown_pct(equity):
    peak = np.maximum.accumulate(equity)
    dd = (peak - equity) / np.maximum(peak, 1e-12)
    return 100.0 * dd.max()

def bootstrap_paths(pnls, n_sims=1000, balance0=10_000.0, seed=42):
    rng = np.random.default_rng(seed)
    n = len(pnls)
    curves = np.empty((n_sims, n + 1))
    finals = np.empty(n_sims)
    max_dds = np.empty(n_sims)
    for i in range(n_sims):
        sample = rng.choice(pnls, size=n, replace=True)
        eq = np.empty(n + 1)
        eq[0] = balance0
        eq[1:] = balance0 + np.cumsum(sample)
        curves[i] = eq
        finals[i] = eq[-1]
        max_dds[i] = max_drawdown_pct(eq)
    return curves, finals, max_dds

def fan_percentiles(curves, q=(5, 25, 50, 75, 95)):
    return {p: np.percentile(curves, p, axis=0) for p in q}

def summarize(finals, max_dds, balance0=10_000.0, ruin_pct=20.0):
    p5 = np.percentile(finals, 5)
    return {
        "p50_final": float(np.percentile(finals, 50)),
        "p5_final": float(p5),
        "var_5pct": float(balance0 - p5),
        "median_max_dd": float(np.percentile(max_dds, 50)),
        "stress_dd_p95": float(np.percentile(max_dds, 95)),
        "prob_ruin_pct": float(100.0 * np.mean(max_dds >= ruin_pct)),
    }

Export the fan columns (step, P5, P25, P50, P75, P95) to CSV and plot in matplotlib or a notebook. Percentiles with linear interpolation on a sorted copy match common spreadsheet / NumPy defaults — keep the method identical if you compare tools.

Pedagogy note: the sketch above is rewritten for this guide. A clear public MQL5 walkthrough of the same bootstrap-fan idea (CSV trade list → CCanvas fan → exported percentile curves) is Stress Testing Trade Sequences with Monte Carlo in MQL5 by Duy Van Nguy (MQL5.com, May 2026). Numbers quoted below from that article are labeled as such — not Quant for Free backtests.

Commission and slippage stress

Optional second pass: subtract a fixed commission plus a random slippage draw from [0, slip_max] on every resampled trade (conservative worst-side framing). Re-run the fan and metrics. Strategies that look fine on raw P&L often thin out once friction is applied uniformly — which is the point of the stress, not a claim about your broker’s exact fills.

Attributed example (source article)

Attributed example · MQL5 article #22291 (May 2026)

GOLD H1 trend-follower trade list

The article reports a Strategy Tester export of a trend-following EA on GOLD H1: 182 closed trades (January–April 2026 in their write-up), initial balance $10,000, 1,000 bootstrap sims, ruin threshold 20% drawdown, slippage layer off for the headline run. Their statistics panel / narrative includes approximately:

  • Median path ending near $13,654 (~36.5% gain on the median curve).
  • Probability of hitting the 20% ruin threshold in about 25.4% of sims.
  • 5% VaR on the order of $648.65 from the $10,000 start.
  • Median max drawdown about 15.1%; 95th-percentile (“stress”) max drawdown about 30.6%.

Earlier in the same article, a separate EURUSD M30 anecdote claims roughly 12% of alternative orderings of a ~200-trade history would have triggered a margin call before trade 80 — same trades, different luck. Those figures are the author’s illustrations, not a Quant for Free live or published backtest.

LessonA single historical equity curve can hide a fat left tail of sequences. If you cannot absorb the stress drawdown at your intended size, cut risk before you debate expectancy.

The same article notes a practical rule of thumb from their testing across several trend systems: the 95th-percentile Monte Carlo drawdown often lands around 1.5×–2.5× the drawdown of the original backtest sequence. Treat that as their reported experience, not a universal constant — and never substitute it for running the bootstrap on your trade list.

Limitations (do not paper over these)

Stress drawdown uncomfortable? Size the account before you blame the edge.
Open position size →

A pre-trust checklist

Before believing a Monte Carlo fan — mine, yours, or a vendor’s — I want all of these to be true:

  • Inputs are closed-trade P&Ls (or fixed-fraction returns), not open equity ticks with look-ahead.
  • Ruin threshold, sim count, and cost model were locked before reading the fan.
  • Stress drawdown and VaR are sized against the live account, not against hope.
  • Independence / regime / lot-size limits are acknowledged; block bootstrap considered if streaks matter.
  • Sample length is thick enough that re-seeding does not reshuffle the story.
  • Walk-forward / trial logging / DSR still apply — sequence MC is not a free pass on overfitting.

Questions

What is a trade-sequence Monte Carlo stress test?
You resample a closed-trade P&L list with replacement into thousands of synthetic equity paths, then report percentile fans plus drawdown and ruin summaries. It measures path sensitivity of a fixed trade set — not a prediction of the next fill.
Why bootstrap instead of a normal model?
Trade P&L is rarely Gaussian. Bootstrap keeps the empirical tails and skew that drive drawdowns; a fitted normal often erases them.
Does this replace walk-forward or CPCV?
No. Sequence stress and out-of-sample design answer different questions. Use both, plus costs and position sizing.
Is this investment advice?
No. This is an educational guide on risk pedagogy. It does not recommend any security, strategy, or trade.
A
ridingyo
Systematic-trading developer. Builds and validates MT4/MT5 expert advisors using López de Prado's validation methodology — DSR, PBO, purged walk-forward and CPCV. Writes the tools and guides at Quant for Free.
Read the methodology →
Educational content, not investment advice. The methods and attributed examples here describe research practice and statistical evaluation. They do not predict performance and are not a recommendation to buy, sell, or hold any instrument. Simulated results do not guarantee future outcomes. Source figures labeled as from MQL5 article #22291 are that article’s illustrations, not Quant for Free live results. No Quant for Free backtest numbers appear on this page.