Unit 3 · The Quant Math Toolkit — Session 05 of 06

Simulation & Monte Carlo

How deep can a drawdown get? How often will a stop-loss fire? How sure are you of a volatility estimate? Most questions a quant asks have no formula, and simulation answers all of them — seeded, repeatable and, done right, with an honest error bar. This session builds Monte Carlo from random walks to bootstraps and checks every result against theory. The core move: every simulated number is a measurement, so report it with its standard error.

DURATION 1.5 h + lab LEVEL Foundations TRACKS QR · QT · QD · IND HANDS-ON Simulate, bootstrap & audit your own asserts

3.5.1 Why simulate

A formula answers one question exactly. A simulation answers almost any question approximately — and, done properly, tells you how approximately.

In 1946, recovering from brain surgery, the mathematician Stanislaw Ulam played solitaire and wondered what fraction of deals could be won. The combinatorics were hopeless; dealing a hundred hands and counting was easy. With John von Neumann he turned the idea into a way to compute neutron diffusion at Los Alamos, and Nicholas Metropolis named it after the casino where Ulam's uncle liked to gamble (Metropolis 1987; LANL 2023). Phelim Boyle brought it to option pricing in 1977 (Boyle 1977).

Monte Carlo simulation estimates a quantity by drawing many random scenarios from a model, computing the quantity in each, and averaging. It is the law of large numbers of Session 3.1 put to work: the average over n independent scenarios converges to the expected value, and its error shrinks like 1/√n. Quants reach for it for three jobs.

WHY SIMULATE — three jobs a formula can't always do PATH-DEPENDENT the answer depends on the route, not just the end drawdown, stop-loss, margin call, barrier NO CLOSED FORM fat tails, clustering, rules, many assets at once a strategy's whole P&L distribution CHECK A FORMULA formulas have bugs too: simulate, then compare every lab in this course asserts one against the other One recipe for all three: a model, many random paths, an average — and its standard error.
fig 3.5.1 — the three jobs of simulation: path-dependent questions, questions with no closed form, and checking the formulas you do have

The first job is the one formulas are worst at. A stop-loss, a margin call or the maximum drawdown depends on the route the price took, not only on where it ended (§3.5.4). The second is realism: add fat tails, volatility clustering or a strategy's trading rules and the closed form disappears, while a simulation only needs a model it can sample from — or a history it can resample (§3.5.5). The third matters most in this course: formulas have bugs too, so every lab computes its key answers twice, from theory and by simulation, and asserts that they agree — the volatility drag of Session 2.3, the margin-call odds of Session 2.4, the law of large numbers of Session 3.1.

The random numbers are not random. A pseudo-random generator is a deterministic function of its seed. NumPy's default generator, PCG64, has a period of 2128, and Generator.spawn derives child streams whose chance of ever colliding is, by NumPy's own pessimistic estimate, about n² · 2−128 for n streams (NumPy docs). Session 1.5 explained why you seed and why each experiment gets its own stream, so that editing one section never shifts the numbers in another. This session's lab starts the way every lab does, with seven streams for seven experiments:

lab = qn.Lab("3.5")  # seeds the random generators: every run gives the same numbers
(rng_se, rng_gbm, rng_dd, rng_bar, rng_boot, rng_vr, rng_mt) = lab.rng.spawn(7)
📌 Key idea — a simulation is an experiment

It has a sample size, a seed and an error bar, like any measurement, and like any experiment it can be run badly. A simulated number without its standard error is a rumour, not a result. The rest of this session is about producing that error bar and keeping it honest.

🧭 Margin note — the engineering analogy

Monte Carlo is property-based testing for a model: you cannot enumerate every path a price might take, so you generate thousands at random and measure, the way a fuzzer probes code you cannot prove correct. Like any randomised test suite, it can pass or fail by luck (§3.5.7).

3.5.2 The Monte Carlo workflow & its standard error

Every simulation in this course, from a coin flip to a margin call, follows the same five steps. The fourth — attaching a standard error — is the one people skip.

THE MONTE CARLO WORKFLOW — the same five steps in every lab of this course 1 · MODEL GBM, GARCH, or resampled history 2 · GENERATE seeded, one stream per experiment 3 · COMPUTE one number per path: a payoff, a drawdown 4 · ESTIMATE ± SE the average, with SE = s / √n 5 · CHECK against theory, at 4 SE, where it exists a failed check → fix the model or the code, never the seed Steps 4 and 5 turn a simulation from an anecdote into a measurement.
fig 3.5.2 — the Monte Carlo workflow: model, generate, compute, estimate with a standard error, check against theory — and when a check fails, suspect the code before the luck

Take a question with a known answer so you can watch the method work. This session's base asset is the one from Session 2.3: drift μ = 8% and volatility σ = 20% a year, no dividends, and a risk-free rate of zero throughout. What is the chance it loses money over a year? Under geometric Brownian motion (§3.5.3) the one-year log return is normal with mean ν = μ − σ²/2 = 6% and standard deviation σ, so the exact answer is Φ(−ν/σ) = 38.21%, where Φ is the standard normal cumulative distribution function. Pretend you don't know that. Step 1 is the model; step 2 draws n one-year log returns; step 3 records a 1 for each losing year and a 0 otherwise.

Step 4 is where the statistics come in. The estimate is the average of the n simulated values YiY_i, and its Monte Carlo standard error is the standard error of Session 3.1 applied to your own simulated draws: their sample standard deviation sYs_Y divided by √n. A probability is the expected value of an indicator (1 if the event happens, 0 if not), so its standard error has a closed form, and it is never more than 0.5/√n:

θˆn=1n∑i=1nYi,SE(θˆn)=sYn,SE(pˆn)=pˆn(1−pˆn)n≤0.5n\hat\theta_n = \frac{1}{n}\sum_{i=1}^{n} Y_i, \qquad \text{SE}(\hat\theta_n) = \frac{s_Y}{\sqrt{n}}, \qquad \text{SE}(\hat p_n) = \sqrt{\frac{\hat p_n\,(1 - \hat p_n)}{n}} \le \frac{0.5}{\sqrt{n}}
Paths nEstimated P(losing year)Estimate ± 2 SEError, in SEs
10043.00%33.10% to 52.90%+0.97
1,00038.20%35.13% to 41.27%−0.01
10,00036.95%35.98% to 37.92%−2.61
100,00037.93%37.63% to 38.24%−1.79
1,000,00038.26%38.17% to 38.36%+1.13
Truth, Φ(−ν/σ)38.21%——

The interval narrows by √10 ≈ 3.2 with every tenfold increase in paths — 9.9 times narrower from 10,000 to 1,000,000. That is the √n rule of Session 3.1, and the economics of Monte Carlo: each extra decimal place costs a hundred times the computing. Notice also that 1 of the 5 estimates misses the truth by more than 2 SE. Nothing is wrong. "Estimate ± 2 SE" promises that about 95% of such intervals contain the truth, not all of them, and the lab checks the promise directly: it reruns the n = 1,000 experiment 4,000 times, and 96.2% of the intervals contain the true value, against 95.6% exactly for this discrete count (95.45% for a normal).

🧭 Margin note — statistics with no formula for their standard error

For an average, s/√n is the whole story. For a median, a quantile or a maximum drawdown there is no simple formula. Split the paths into 50 batches, compute the statistic in each batch, and take the standard deviation of the 50 values divided by √50. That is the batch-means standard error, qn.stats.batch_se, used in §3.5.3 and §3.5.4.

📌 Key idea — report a simulation like a measurement

Write "38.26% ± 0.10 pp (2 SE, 1,000,000 paths)", never a bare 38.26%. The standard error tells you how well you computed the model's answer — not whether the model is right. Only better models and better data can shrink the second error; §3.5.5 shows how stubborn it is.

3.5.3 Random walks & GBM: simulate in log space

The workhorse model of a price is a random walk in its logarithm. Simulate it in log space and it is exact at any step size; simulate it the obvious way and it is not.

A random walk is what you get by adding up independent random steps: Xk=Xk−1+εkX_k = X_{k-1} + \varepsilon_k. Prices make a poor random walk — a $1 move means something different at $10 and at $1,000 — but log prices make a good one, because log returns add across time (Session 2.3). Geometric Brownian motion (GBM) is the model in which the log price is a random walk with normally distributed steps. Over a step of Δ years:

ln⁡Pt+Δ=ln⁡Pt+(μ−12σ2)Δ+σΔZ,Z∼N(0,1)\ln P_{t+\Delta} = \ln P_t + \left(\mu - \tfrac{1}{2}\sigma^2\right)\Delta + \sigma\sqrt{\Delta}\,Z, \qquad Z \sim N(0, 1)

Here t and Δ are measured in years, μ is the expected (arithmetic) growth rate and σ the volatility. The log price drifts at ν = μ − σ²/2, lower than μ by the volatility drag of Session 2.3. Because the steps are independent normals, their sum over a horizon of τ years is normal with mean ντ and variance σ²τ, whatever the step. So this update — the exact scheme — is correct for any Δ: one 10-year step gives exactly the same distribution of the final price as 2,520 daily steps. Final prices are lognormal and every path stays above zero. Fig 3.5.3 shows three simulated decades of the base asset.

THREE SIMULATED 10-YEAR GBM PATHS: 8% DRIFT, 20% VOLATILITY (LOG SCALE) path 1 path 2 path 3 median path $0.50 $1.00 $2.00 0 2 4 6 8 10 path 1 path 1: $1.34 path 2 path 2: $0.59 path 3 path 3: $1.56 median path median path: $1.82 median: $1.82 path 3: $1.56 path 1: $1.34 path 2: $0.59
fig 3.5.3 — three simulated 10-year paths of the base asset (daily steps, log scale, x-axis in years): the same model ends at $1.34, $0.59 and $1.56 per starting dollar, around a median path (grey) that ends at $1.82

The obvious way to simulate a price is to draw normal simple returns with mean μΔ and standard deviation σ√Δ and compound them — the Euler scheme (the Euler–Maruyama scheme applied to the price itself):

Pt+Δ=Pt(1+μΔ+σΔZ)P_{t+\Delta} = P_t\left(1 + \mu\Delta + \sigma\sqrt{\Delta}\,Z\right)

It converges to GBM as Δ shrinks, but at any finite step it is an approximation, and with big steps a bad one: any draw with Z<−(1+μΔ)/(σΔ)Z \lt -(1 + \mu\Delta)/(\sigma\sqrt{\Delta}) takes the price to zero or below, which no stock can do (fig 3.5.4).

ONE STEP OF A SIMULATED PRICE, TWO RECIPES — same Z, same μ and σ NAIVE: EULER ON PRICES P ← P · (1 + μΔ + σ√Δ · Z) normal SIMPLE returns, compounded step by step error grows with the step Δ; the tails bend a big negative Z can push the price below zero EXACT: GBM IN LOG SPACE ln P ← ln P + (μ − σ²/2)Δ + σ√Δ · Z normal LOG returns, added: a random walk exact at any Δ, even one 10-year step P = exp(log price) is always above zero Both recipes agree as Δ → 0; only the exact one is right at every step size.
fig 3.5.4 — two ways to take one step of a simulated price: compounding normal simple returns (Euler) is an approximation; adding normal log returns is exact

How bad is the approximation? The lab simulates 40,000 ten-year paths of a volatile asset — 10% drift, 40% volatility, like a single growth stock. For a fair comparison it uses common random numbers: one set of daily shocks per path, summed into longer steps, so every scheme sees the same luck. It tracks two tail quantities with exact answers: the chance of losing more than half, and the 5th percentile of what $1 becomes.

Scheme, stepP(lose half+ in 10 y)5th pct of $1Paths through zero
Theory (lognormal)24.01%$0.152none
Exact, one 10-year step24.22%$0.149none
Exact, ten 1-year steps24.22%$0.149none
Euler, 1-day steps24.27%$0.1490.00%
Euler, 1-week steps24.26%$0.1490.00%
Euler, 1-month steps24.46%$0.1450.00%
Euler, 1-quarter steps25.13%$0.1310.00%
Euler, 1-year steps27.58%$0.0353.06%
Log space, but drift μ instead of μ − σ²/29.25%$0.332none

The exact scheme lands on identical final prices whatever the step (the lab asserts it), within sampling error of theory; the first column's standard error is 0.21 percentage points. Euler with yearly steps puts the chance of losing half at 27.6%, 17 standard errors above theory, puts the 5th percentile at $0.035 instead of $0.152, and sends 3.1% of paths through zero. Measured on the same paths against the exact scheme, the excess shrinks with the step: +3.36 points with yearly steps, +0.91 quarterly, +0.24 monthly, and +0.05 ± 0.03 daily, lost in the noise. Euler gets the middle of the distribution nearly right at every step; its error lives in the tails, exactly where risk questions live.

The last row is the most common bug of all: simulating in log space but with drift μ instead of μ − σ²/2. Every path is shifted up by the same factor eσ2τ/2e^{\sigma^2\tau/2} = 2.23, so the median final wealth comes out at $2.70 instead of $1.22, and the average at $6.07 instead of $2.72. The two schemes as the lab writes them:

def euler_terminal(dW: np.ndarray, step_days: int, mu: float, sigma: float) -> tuple[np.ndarray, np.ndarray]:
    """Naive Euler on PRICES with steps of `step_days` days: P ← P·(1 + μΔ + σΔW).

    dW holds daily Brownian increments (paths × days). Returns (terminal price from P0 = 1,
    ruined flag); a path whose price reaches zero or below is absorbed at 0 (it is bust).
    """
    n_paths = dW.shape[0]
    dW_k = dW.reshape(n_paths, -1, step_days).sum(axis=2)       # Brownian increment per step
    factor = 1.0 + mu * step_days * DT + sigma * dW_k
    ruined = (factor <= 0).any(axis=1)
    terminal = np.where(ruined, 0.0, np.prod(np.maximum(factor, 1e-300), axis=1))
    return terminal, ruined


def exact_terminal(dW: np.ndarray, step_days: int, mu: float, sigma: float) -> np.ndarray:
    """Exact GBM in log space with steps of `step_days` days: ln P += (μ − σ²/2)Δ + σΔW."""
    n_paths = dW.shape[0]
    dW_k = dW.reshape(n_paths, -1, step_days).sum(axis=2)
    return np.exp(((mu - 0.5 * sigma ** 2) * step_days * DT + sigma * dW_k).sum(axis=1))
⚠️ Pitfall — "normal returns, compounded, is close enough"

Only for the middle of the distribution and small steps. A spreadsheet that compounds normally distributed yearly returns runs this Euler scheme with one-year steps, and is most wrong about the bad outcomes it exists to measure. For lognormal prices, draw log returns with mean (μ − σ²/2)Δ and exponentiate; for fat tails, change the distribution of Z on purpose (Session 3.2), not by accident.

💡 Looking ahead

Why −σ²/2 appears is Itô's lemma, derived in Session 10.2 — Stochastic Calculus for Engineers with Brownian motion and the Euler–Maruyama scheme for models with no exact recipe. Session 10.6 — Stochastic Volatility & Numerical Methods prices options by simulation, where discretisation bias is a pricing error.

3.5.4 Drawdowns & barriers: when the path matters

The questions a risk manager asks — how deep can it fall, will it hit the stop — are about the whole path. For the drawdown, simulation is not a check on the formula; it is the formula.

Start with the maximum drawdown, the worst fall from a running peak (Session 2.3). For GBM its distribution has no simple closed form — Magdon-Ismail et al. (2004) express it as an infinite series — so the lab simulates 6,000 ten-year daily paths of the base asset, whose Sharpe ratio is μ/σ = 0.4 at a zero risk-free rate, and computes each path's maximum drawdown with qn.returns.max_drawdown_paths.

MAXIMUM DRAWDOWN OVER 10 YEARS — 6,000 SIMULATED PATHS, 8% DRIFT, 20% VOLATILITY −80% −60% −40% −20% −86% to −85% −85% to −83% −83% to −82% −82% to −81% −81% to −79% −79% to −78% −78% to −77% −77% to −75% −75% to −74% −74% to −72% −72% to −71% −71% to −70% −70% to −68% −68% to −67% −67% to −66% −66% to −64% −64% to −63% −63% to −61% −61% to −60% −60% to −59% −59% to −57% −57% to −56% −56% to −55% −55% to −53% −53% to −52% −52% to −50% −50% to −49% −49% to −48% −48% to −46% −46% to −45% −45% to −44% −44% to −42% −42% to −41% −41% to −40% −40% to −38% −38% to −37% −37% to −35% −35% to −34% −34% to −33% −33% to −31% −31% to −30% −30% to −29% −29% to −27% −27% to −26% −26% to −24% −24% to −23% −23% to −22% −22% to −20% −20% to −19% −19% to −18% worst 5% ←
fig 3.5.5 — the maximum drawdown over ten years of an asset with a Sharpe ratio of 0.4, across 6,000 simulated paths: the median decade falls −41.0% from a peak, and the worst 5% (coral) fall further than −63.7%

The typical decade of a perfectly healthy asset contains a drawdown of −41.0% (batch-means SE 0.2 percentage points), and one decade in twenty goes beyond −63.7%. A drawdown worse than −30% happens in 86% of decades, worse than −50% in 25%. So a kill criterion (Session 1.3) must be set against the distribution, not a gut feeling: a rule that shuts a strategy at a −20% drawdown would have shut this asset down in 99.9% of decades.

Touching a barrier

Now a question with a formula: what is the chance the base asset falls 20% below its starting price at some point within a year — a stop-loss, or the margin call of Session 2.4? For a price watched continuously, the reflection principle gives the answer (fig 3.5.6). Take a driftless path that touches the barrier and ends above it. Reflect everything after the first touch in the barrier, and you get a path that ends below it — just as likely, because from the barrier a driftless random walk is as likely to go down as up. So the touched-and-ended-above paths pair off one-for-one with the ended-below paths, all of which touched, and P(touch) = 2 × P(end below).

THE REFLECTION PRINCIPLE — mirror the path after its first touch start barrier first touch ends above (it touched) mirror twin: ends below 0 time → τ No drift: each ‘touched, ended above’ path has an equally likely twin that ended below, so P(touch) = P(end below) + P(touch, end above) = 2 · P(end below).
fig 3.5.6 — the reflection principle (schematic): reflecting a log-price path in the barrier after its first touch pairs every path that touched and recovered with one that ended below

With a drift the two halves of the pairing are no longer equally likely — the mirror image of an upward-drifting path drifts downward — and the second probability picks up the factor e2νh/σ2e^{2\nu h/\sigma^2} (Shreve 2004, §3.7 has the derivation). With h=ln⁡(B/P0)<0h = \ln(B/P_0) \lt 0 the log distance down to a barrier B:

ℙ(min0≤t≤τ⁡ln⁡PtP0≤h)=Φ(h−ντστ)⏟end below+e2νh/σ2Φ(h+ντστ)⏟touch, end above\Prob\Big(\min_{0 \le t \le \tau} \ln \tfrac{P_t}{P_0} \le h\Big) = \underbrace{\Phi\!\left(\frac{h - \nu\tau}{\sigma\sqrt{\tau}}\right)}_{\text{end below}} + \underbrace{e^{2\nu h/\sigma^2}\,\Phi\!\left(\frac{h + \nu\tau}{\sigma\sqrt{\tau}}\right)}_{\text{touch, end above}}

This is qn.stats.p_touch. For a 20% fall within a year it gives 18.46%: 7.84% of ending below plus 10.62% of touching and recovering, the drift factor being 0.512. The lab measures the two terms separately at 7.86% and 10.51%.

A simulation, like your broker, only looks at the price at its time steps, and a dip that recovers before the next check goes unseen, so the discretely monitored probability is lower. Broadie, Glasserman & Kou (1997) showed that you can correct for this by moving the barrier further away: replace h by h − βσ√Δt, where Δt is the time between checks and β = −ζ(1/2)/√(2π) ≈ 0.5826 is their constant (ζ is the Riemann zeta function; this β is not a market beta). p_touch(..., dt=Δt) applies it. The lab simulates 200,000 one-year paths on a daily grid and looks at them daily, weekly, monthly and quarterly:

How often the price is checkedSimulated P(touch)SE (pp)FormulaSimulated − formula (pp)
Continuously (Brownian-bridge estimate)18.38%0.0918.46%, no correction−0.08
Daily17.17%0.0817.19%, corrected−0.01
Weekly15.78%0.0815.71%, corrected+0.07
Monthly13.43%0.0813.19%, corrected+0.24
Quarterly11.04%0.0710.16%, corrected+0.88

Checking at the daily close instead of continuously lowers the probability from 18.5% to 17.2%, far more than the sampling error, and the corrected formula matches daily checks within it. Weekly it is within a tenth of a point; with monthly and quarterly checks it undershoots, being an approximation built for frequent monitoring. And a drawdown is a barrier that moves up with every new peak, so it is always at least as likely as the same fall from the start: in the same simulation a −20% drawdown within the year happened on 37.4% of paths, against 17.2% for a 20% fall below the starting price.

🧭 Margin note — seeing between the checks

The top row uses a trick worth knowing. Between neighbouring daily log prices a and b above the log barrier h, a Brownian path dips to the barrier with probability exp(−2(a − h)(b − h)/(σ²Δt)), so each path can contribute the probability of the touches nobody saw. Replacing a random event by its conditional probability is also a variance-reduction technique (§3.5.6).

📌 Key idea — how often you look is part of the question

A barrier probability is only defined together with how often the barrier is checked. A continuous formula overstates a stop that triggers on daily closes; an intraday margin system is closer to continuous. Match the model's monitoring to the real rule, and simulate path-dependent risks — like the drawdowns of a Kelly bettor in Session 3.6 — rather than guessing them.

3.5.5 The bootstrap: resampling history

When you do not trust any model, let the data be the model and resample the history you have. It is one of the most useful ideas in empirical finance — and it cannot see anything your history did not contain.

The bootstrap (Efron 1979) estimates how much a statistic would vary across samples you do not have by treating the sample you do have as the population. Draw a same-length history from it with replacement, recompute the statistic, repeat a few thousand times, and the spread of the results estimates its standard error — no formula needed, which is why Session 3.1 suggested it for fat-tailed returns.

Resampling days one at a time — the iid bootstrap — assumes the days are independent. Returns are not: big moves cluster in time (volatility clustering, Session 5.1). The block bootstrap (Künsch 1989) resamples runs of consecutive days instead, so calm and stormy stretches survive inside each block (fig 3.5.7). The lab uses the circular variant, which wraps around the end of the sample, with the block length chosen by the Politis–White rule (Politis & White 2004, corrected by Patton, Politis & White 2009) as implemented in the arch package: 93 trading days here. The stationary bootstrap of Politis & Romano (1994), with random block lengths, is the common alternative.

RESAMPLING ONE HISTORY — day by day, or in blocks HISTORY IID BOOTSTRAP BLOCK BOOTSTRAP calm and stormy days arrive in runs: volatility clustering the same days, order scrambled: storms become isolated and the clustering is gone runs inside each block survive; only the joins between blocks are random calm day stormy day one resampled block of consecutive days Blocks must be longer than the memory you want to keep.
fig 3.5.7 — two ways to resample one history (schematic): the iid bootstrap keeps every day but scrambles their order; the block bootstrap keeps runs of days together

The lab's history is 10 years of daily returns from a GARCH model with Student-t shocks — synthetic, so the truth is known, and built to have the clustering and fat tails of real returns (Session 5.5). Clustering shows up as positive autocorrelation of squared returns: a big move today makes a big move tomorrow more likely.

AUTOCORRELATION OF SQUARED RETURNS, BY LAG IN TRADING DAYS original history block bootstrap iid bootstrap −0.05 0.00 0.05 0.10 0.15 0.20 5 10 15 20 original history original history: 0.10 block bootstrap block bootstrap: 0.08 iid bootstrap iid bootstrap: 0.00 original: 0.10 block: 0.08 iid: 0.00
fig 3.5.8 — autocorrelation of squared daily returns in the GARCH history (blue), and averaged over 200 iid resamples (orange) and 200 block resamples (green): the iid bootstrap erases volatility clustering; blocks keep most of it

Averaged over lags 1 to 10, the history's autocorrelation of squared returns is 0.124; iid resamples average 0.000 and block resamples 0.118. Why it matters: ask each bootstrap how precisely ten years pin down the annual volatility, and compare with the truth — the spread of the volatility estimate across 500 fresh ten-year histories drawn from the model.

MethodSE of the 10-year annualised volatilityWhat it assumes
Truth: fresh histories from the model2.26 ppthat you know the model — never true in practice
Block bootstrap (93-day blocks)0.73 ppdependence dies out within a block
IID bootstrap0.28 ppdays are independent

The iid bootstrap understates the uncertainty 8-fold. The block bootstrap does better yet still falls short: volatility shocks here fade over months, so even 93-day blocks cut some of the memory, and one history can only show the storms it happened to contain.

What no bootstrap can fix

Two things, both visible in the lab. First, the bootstrap is centred on your sample, not on the truth: this history's volatility is 13.3%, the model's long-run volatility is 15.9%, and every resample inherits the gap. Second, it cannot invent what it has not seen. Keep only the first two years: their worst day is −2.23%, so no bootstrap year, iid or block, can contain a worse one — the lab finds a probability of exactly 0%. Under the model that generated the data, 89.1% of years contain a worse day.

⚠️ Pitfall — "the bootstrap turns my history into every possible history"

Only into every reshuffle of it. A short or calm sample gives a confident, narrow, wrong answer, and blocks fix only the ordering. Use the longest relevant history, add stress scenarios it lacks, and treat bootstrap error bars as a lower bound. Session 5.2 — Regression & Inference That Doesn't Lie uses the bootstrap for inference on Sharpe ratios and regressions, and Session 9.7 — VaR, ES, EVT & VaR Backtests combines it with a volatility model — filtered historical simulation — to work around exactly this problem.

3.5.6 Variance reduction: same precision, fewer paths

The √n rule makes precision expensive. Variance reduction beats it honestly: same model, same answer, a smaller standard error for the same number of random draws.

Variance reduction is any technique that lowers the variance of a Monte Carlo estimator at the same cost. Its payoff is the variance-reduction ratio (VRR): plain variance over new variance at an equal number of random draws — how many times more paths the plain method would need for the same standard error. The lab tests two classics on the expected payoff of a one-year call option on the base asset struck at its starting price, E[max(Pτ − K, 0)] with P0 = K = $100. That expectation has a closed form, $13.11 — the Black–Scholes formula with the drift in place of the interest rate and no discounting, a black box until Session 10.3 derives it — which here serves only as the answer key.

Antithetic variates (Hammersley & Morton 1956) use every normal draw twice, as Z and −Z, and average the pair (fig 3.5.9): when f(Z) and f(−Z) are negatively correlated, their errors cancel. Control variates subtract a quantity whose mean you know exactly — here the final price PτP_\tau, with 𝔼[Pτ]=P0eμτ\E[P_\tau] = P_0 e^{\mu\tau} — scaled to soak up the noise the two share:

θˆanti=1n∑i=1nf(Zi)+f(−Zi)2,VRR=11+ρ\hat\theta_{\text{anti}} = \frac{1}{n}\sum_{i=1}^{n} \frac{f(Z_i) + f(-Z_i)}{2}, \qquad \text{VRR} = \frac{1}{1 + \rho}
θˆcv=1n∑i=1n(Yi−b(Ci−𝔼[C])),b=Cov⁡(Y,C)Var⁡(C),VRR=11−ρYC2\hat\theta_{\text{cv}} = \frac{1}{n}\sum_{i=1}^{n} \Big(Y_i - b\,\big(C_i - \E[C]\big)\Big), \qquad b = \frac{\Cov(Y, C)}{\Var(C)}, \qquad \text{VRR} = \frac{1}{1 - \rho_{YC}^2}

Here ρ is the correlation between f(Z) and f(−Z), and ρYC\rho_{YC} the correlation between the payoff Y and the control C. Estimating b from the same paths adds a bias of order 1/n, negligible at these sizes.

ANTITHETIC PAIRS — every draw used twice, as Z and −Z A CALL PAYOFF: RISES WITH Z Z pair average −Z 0 +Z one high, one low: the errors cancel ρ < 0, so the variance falls A STRADDLE: SYMMETRIC IN Z Z pair average −Z 0 +Z both high or both low: the errors add up ρ > 0, so the variance rises At equal cost the variance is multiplied by (1 + ρ): pairing helps only when ρ < 0.
fig 3.5.9 — antithetic pairs (schematic): for a payoff that rises with Z the pair's errors cancel; for a payoff symmetric in Z they reinforce each other
Method (200,000 normal draws each)Estimate ± SECorrelationVRR: measured · formula
Closed form$13.11——
Plain Monte Carlo$13.14 ± $0.038—1
Antithetic pairs$13.10 ± $0.024ρ = −0.582.40 · 2.40
Control variate, the final price$13.12 ± $0.013ρYC = +0.948.75 · 8.75
Antithetic pairs on a straddle, |Pτ − K|—ρ = +0.360.73

The control variate turns 200,000 draws into the precision of about 8.7 times as many, for the price of one subtraction — and Boyle's 1977 paper already used both tricks. The ratios match their formulas, so one correlation from a small pilot run tells you in advance whether a trick will pay.

⚠️ Pitfall — "antithetic variates always help"

They help when the payoff moves one way with Z. A straddle, |Pτ − K|, pays on big moves in either direction, so Z and −Z give similar payoffs, ρ is positive (+0.36) and at equal cost the variance goes up: a VRR of 0.73. The same goes for anything nearly symmetric in Z, such as the chance of a big move either way. Measure ρ first.

💡 Looking ahead

The toolbox is bigger: stratified sampling, importance sampling (which steers paths toward rare events such as tail losses), quasi-random Sobol sequences, and conditional Monte Carlo — the Brownian-bridge estimate of §3.5.4 is one. Session 10.6 — Stochastic Volatility & Numerical Methods puts them to work pricing options; Glasserman (2003, ch. 4) is the reference.

3.5.7 Many checks, one lab: the multiple-testing trap

A lab that asserts dozens of Monte Carlo results against theory is running dozens of statistical tests. Set the bar at 2 or 3 standard errors and a perfectly correct lab will fail by luck.

Each assert is a test. If the code is right, an estimate's error measured in standard errors is roughly standard normal, so a check at c standard errors fails by pure luck with probability 2Φ(−c), the test's size. A lab with k independent checks fails at least one of them with probability

ℙ(a correct lab fails)=1−(1−2Φ(−c))k\Prob(\text{a correct lab fails}) = 1 - \big(1 - 2\Phi(-c)\big)^{k}

This is the family-wise error rate of multiple testing, which Session 5.2 treats properly. This course learned it the hard way: in review, one lab with about 27 asserts at 3 SE failed on 8% of alternative seeds (Quant Notebook authoring guide, §12a). The formula predicts 7.0% — close, for checks that are not exactly independent or exactly normal. The lab confirms the formula by simulating 200,000 imaginary labs with perfect code and 27 checks each:

Threshold cOne check fails (its size)A lab with 27 checks fails: formula · simulatedThis lab's 22 checks
2 SE4.55%71.6% · 71.7%64.1%
3 SE0.27%7.0% · 7.0%5.8%
4 SE0.0063%0.17% · 0.18%0.14%

Hence the rule every lab in this course follows: assert Monte Carlo results against theory at 4 standard errors. This lab routes all 22 of its statistical checks through one helper that counts them; on the committed seed the largest miss was 2.61 standard errors. Two refinements keep it honest. A looser threshold catches only bigger bugs, so buy sensitivity with more paths, not a tighter c. And a tolerance wider than 4 SE is allowed only for a known, written-down bias in the theory, like the 0.15-point allowance the lab gives the Broadie–Glasserman–Kou correction at weekly checks (§3.5.4):

def check_mc(name: str, estimate: float, truth: float, se: float, n_se: float = N_SE,
             allowance: float = 0.0) -> float:
    """Assert that a Monte Carlo estimate lies within n_se standard errors of its theoretical value.

    `allowance` widens the tolerance by a known approximation error of the THEORY (never used to
    rescue noise; every use carries a comment). Returns the z-score and remembers it, so the lab
    can count how many statistical checks it makes.
    """
    z = (estimate - truth) / se
    MC_CHECKS.append((name, float(z)))
    assert abs(estimate - truth) < n_se * se + allowance, (
        f"{name}: {estimate:.6g} vs theory {truth:.6g} is {z:+.2f} SE away")
    return float(z)
📌 Key idea — when a check fails, the seed is not the suspect

In a correct lab of this size a 4-SE failure is rarer than one in 500; a bug is far more likely, so investigate the code or the model. Never rerun with new seeds until the check passes: that is p-hacking your own test suite — the same best-of-N search that turns noise into a winning backtest (Session 1.3, Session 1.5), and the reason Session 6.7 — Overfitting Statistics: PSR, DSR, MinTRL & PBO charges you for every trial.

3.5.8 Try it

Open the companion lab, predict before you run, and watch the standard errors do what the formulas say.

🔧 Try it (20 min, in the lab)

1. The √n rule. In section 1, predict the standard error at 4,000,000 paths from the one at 1,000,000. Then add 4_000_000 to N_LIST and run the section.
2. Mirror twins. In section 4, add the line MU_B = SIGMA_B ** 2 / 2 just before NU_B is computed. The log price is now driftless: predict the two reflection terms before you run, then compare them with the printed values and with 2Φ(h/σ).
3. Block length. In section 5, override block_len with 21 (a month), then with 250 (a year), just before the resampling. Watch the green line in the autocorrelation chart and the block bootstrap's standard error. How many different 250-day blocks does a 10-year history contain?
4. Stack the tricks. In section 6, apply antithetic pairs to the control-variate-adjusted payoff and measure the combined VRR against the plain estimate.
Success: step 1 lands within a few percent of half the 1,000,000-path standard error, and step 2's two terms agree within their standard errors.


3.5.9 Glossary · Check yourself · Go deeper

Monte Carlo simulation — estimating a quantity by computing it on many random scenarios drawn from a model and averaging; the answer comes with a standard error.
Pseudo-random number generator — a deterministic function of a seed that produces a stream of numbers indistinguishable from random; the same seed and library version always give the same stream.
Monte Carlo standard error — the standard deviation of a simulation estimate, s/√n for an average over n independent paths; report every simulated number as estimate ± 2 SE.
Batch-means standard error — the standard error of a statistic with no simple formula (a median, a quantile, a maximum drawdown): split the paths into batches, compute the statistic in each, and divide the standard deviation of the batch values by √(number of batches).
Random walk — a sequence built by adding independent random steps; under GBM the log price is one.
Geometric Brownian motion — the price model whose log moves by (μ − σ²/2)Δ + σ√Δ·Z each step; simulated exactly in log space, at any step size.
Common random numbers — feeding every scheme or scenario being compared the same underlying random draws, so a difference in the result reflects the method, not new luck.
Reflection principle — pairing each path that touches a barrier and ends on one side with an equally likely (or drift-reweighted) mirror path that ends on the other, turning a path-dependent touch probability into a formula.
Discrete monitoring — checking a barrier only at set times (daily closes, say) rather than continuously; it always understates the continuous probability of touching, which the Broadie–Glasserman–Kou correction adjusts for.
Brownian bridge — the conditional distribution of a Brownian path between two known points; used here to recover the probability that a path dipped through a barrier between two observations that both lie above it.
Bootstrap — estimating the sampling distribution of a statistic by recomputing it on resamples of the data drawn with replacement; resample blocks of days when the data are dependent.
Block bootstrap — a bootstrap that resamples runs of consecutive observations instead of single points, so dependence within a block, such as volatility clustering, survives in the resample.
Variance reduction — any technique that lowers the variance of a Monte Carlo estimator at equal cost; measured by the variance-reduction ratio.
Variance-reduction ratio — the variance of a plain Monte Carlo estimate divided by the variance of an improved one at equal cost; equivalently, how many times more paths the plain method would need for the same precision.
Control variate — a variance-reduction technique that subtracts a quantity of known expected value, scaled to soak up the noise it shares with the estimate, leaving an estimator with the same mean and smaller variance.
Antithetic variates — using every random draw Z together with −Z and averaging the pair; helps when the payoff moves one way with Z, hurts when it is symmetric.

Check yourself

1. A colleague reports that a strategy's simulated probability of a 30% drawdown is 12.4%, from 500 paths. What do you ask, and roughly what is the answer?
Ask for the standard error. For a probability it is √(p(1 − p)/n) = √(0.124 × 0.876 / 500) ≈ 1.5 percentage points, so the honest report is 12.4% ± 2.9 pp at 2 SE — anywhere from about 9.5% to 15.3%. To get ± 0.5 pp at 2 SE you need an SE of 0.25 pp, about 17,400 paths: precision costs the square.
2. A retirement spreadsheet draws each year's return from a normal distribution with mean 7% and standard deviation 18% and compounds 30 of them. What is it, and what does it get wrong?
It is the Euler scheme on prices with one-year steps (§3.5.3). It gets the middle of the distribution roughly right and the tails wrong — too many very bad outcomes, and even a small chance of a year below −100%. In the lab, yearly Euler steps put the 5th percentile of a volatile asset at $0.035 instead of $0.152. If lognormal prices are the intent, draw log returns with mean μ − σ²/2 and exponentiate.
3. The barrier formula says there is an 18.5% chance of touching −20% within a year; your daily simulation says 17.2%. Is there a bug?
Probably not. The formula assumes continuous monitoring and your simulation only looks at daily closes, so it misses dips that recover within a day. Shift the barrier by βσ√Δt (Broadie–Glasserman–Kou) and the formula gives 17.19%, matching the lab's 17.17%. Decide which monitoring your real stop uses before you decide which number is right.
4. You bootstrap two calm years of daily returns to estimate the chance of a −5% day within a year and get zero. What went wrong, and would a block bootstrap fix it?
A bootstrap can only resample days it has seen; if the worst day in the sample was −2%, no resampled year can contain a −5% day. Blocks fix the ordering, not the missing tail, so no. In the lab the bootstrap said 0% for "a day worse than the sample's worst" and the true model said 89.1%. Use a longer history, a model with fat tails, or explicit stress scenarios.
5. Antithetic variates cut the standard error of a call payoff estimate. You switch to estimating the probability of a move of more than 20% in either direction, and the standard error gets worse. Why?
That probability is (nearly) symmetric in Z, so f(Z) and f(−Z) are positively correlated and the variance-reduction ratio 1/(1 + ρ) falls below 1. In the lab the straddle, another symmetric payoff, had ρ = +0.36 and a ratio of 0.73. Drop the pairing, or use a control variate instead.
6. A lab with 40 Monte Carlo asserts at 3 SE fails on a new seed. Do you fix the lab, raise the threshold, or change the seed?
Never change the seed to make it pass. First look at the failing check: 3.2 SE is plausibly luck, 15 SE is a bug. With 40 checks at 3 SE a correct lab fails about 1 − (1 − 0.0027)40 ≈ 10% of the time, so move to 4 SE (about 0.25%) and, if the check then lacks power, add paths.
🔧 Homework (~1 h before Session 3.6)

1. Bet sizing by simulation. Simulate 10,000 paths of 1,000 bets on a coin that wins your stake with probability 55% and loses it otherwise, staking a fixed fraction f of current wealth, for f = 0.05, 0.10 and 0.20. Report the median final wealth with its batch-means standard error and the share of paths that end below where they started. Which f grows fastest? Session 3.6 derives why.
2. Euler and the drawdown. Repeat the §3.5.3 comparison for a path-dependent quantity: the probability of a −50% drawdown within 10 years, using the prices at every step. Does Euler's error grow or shrink compared with the terminal-wealth question?
3. Audit a lab. Count the statistical asserts in the Session 3.1 lab, note each one's threshold, and compute the chance that a correct version fails by luck.
4. Read Glasserman (2003), §4.1–4.2 on control and antithetic variates, and the first two sections of Efron (1979).

📚 Go deeper

  1. Glasserman, P. (2003). Monte Carlo Methods in Financial Engineering, ch. 3 "Generating Sample Paths" and ch. 4 "Variance Reduction Techniques". Springer. ISBN 978-0-387-00451-8
  2. Efron, B. & Tibshirani, R. J. (1993). An Introduction to the Bootstrap. Chapman & Hall. ISBN 978-0-412-04231-7
  3. Efron, B. (1979). "Bootstrap Methods: Another Look at the Jackknife." Annals of Statistics 7(1), 1–26. doi:10.1214/aos/1176344552
  4. Künsch, H. R. (1989). "The Jackknife and the Bootstrap for General Stationary Observations." Annals of Statistics 17(3), 1217–1241. doi:10.1214/aos/1176347265
  5. Politis, D. N. & Romano, J. P. (1994). "The Stationary Bootstrap." Journal of the American Statistical Association 89(428), 1303–1313. doi:10.1080/01621459.1994.10476870
  6. Broadie, M., Glasserman, P. & Kou, S. (1997). "A Continuity Correction for Discrete Barrier Options." Mathematical Finance 7(4), 325–349. doi:10.1111/1467-9965.00035
  7. Shreve, S. E. (2004). Stochastic Calculus for Finance II: Continuous-Time Models, §3.7 "Reflection Principle". Springer. ISBN 978-0-387-40101-0

Sources for the numbers

  1. Metropolis, N. & Ulam, S. (1949). "The Monte Carlo Method." Journal of the American Statistical Association 44(247), 335–341. doi:10.1080/01621459.1949.10483310
  2. Metropolis, N. (1987). "The Beginning of the Monte Carlo Method." Los Alamos Science 15 (special issue), 125–130. https://mcnp.lanl.gov/pdf_files/Article_1987_LAS_Metropolis_125--130.pdf
  3. Los Alamos National Laboratory (2023). "Hitting the Jackpot: The Birth of the Monte Carlo Method." Actinide Research Quarterly. https://www.lanl.gov/media/publications/actinide-research-quarterly/1123-hitting-the-jackpot-the-birth-of-the-monte-carlo-method
  4. Boyle, P. P. (1977). "Options: A Monte Carlo Approach." Journal of Financial Economics 4(3), 323–338. doi:10.1016/0304-405X(77)90005-8
  5. Hammersley, J. M. & Morton, K. W. (1956). "A New Monte Carlo Technique: Antithetic Variates." Mathematical Proceedings of the Cambridge Philosophical Society 52(3), 449–475. doi:10.1017/S0305004100031455
  6. Magdon-Ismail, M., Atiya, A. F., Pratap, A. & Abu-Mostafa, Y. S. (2004). "On the Maximum Drawdown of a Brownian Motion." Journal of Applied Probability 41(1), 147–161. doi:10.1239/jap/1077134674
  7. Politis, D. N. & White, H. (2004). "Automatic Block-Length Selection for the Dependent Bootstrap." Econometric Reviews 23(1), 53–70; with Patton, A., Politis, D. N. & White, H. (2009). "Correction." Econometric Reviews 28(4), 372–375. doi:10.1081/ETC-120028836 · doi:10.1080/07474930802459016
  8. NumPy developers (2026). "Parallel random number generation" (SeedSequence spawning; PCG64). NumPy documentation. https://numpy.org/doc/stable/reference/random/parallel.html
  9. Quant Notebook (2026). Authoring guide, §12a "Lessons from review" (the 27-assert lab that failed on 8% of alternative seeds). https://github.com/georgevrs/quant-notebook/blob/main/AUTHORING.md