Bootstrap & resampling
Most statistics you care about — the median, a Sharpe ratio, a maximum drawdown — have no clean formula for their sampling distribution. The bootstrap’s answer is radical: treat the sample as if it were the population and simulate the sampling distribution by resampling. This chapter builds the plug-in principle, the nonparametric and parametric bootstrap, the jackknife, and permutation tests — proves the pieces that can be proved — and then shows, honestly, the famous cases where the bootstrap fails: the maximum, heavy tails, and dependent data.
You want to know how much your statistic would wobble if you could rerun history. You cannot rerun history — but your sample of observations is itself an estimate of the distribution history draws from. So rerun that: draw points from your own data with replacement, recompute the statistic, repeat thousands of times. The spread of those recomputed values estimates the spread you actually care about. Two approximations are being made — the empirical distribution stands in for the true one, and Monte Carlo stands in for exhaustive enumeration — and the whole theory of the bootstrap is about when the first approximation is good enough.
The plug-in principle and the empirical CDF#
Write the parameter of interest as a functional of the distribution: — the mean is , the median is , a Sharpe ratio is a ratio of two such functionals. The empirical CDF of a sample is
the distribution putting mass on each observed point. The plug-in principle estimates by . Why should this work? Because the empirical CDF converges to the truth, uniformly:
For iid ,
Pointwise convergence at each is just the strong law of large numbers applied to the indicators (see LLN & the CLT); the content of Glivenko–Cantelli is that the convergence is uniform over all simultaneously — proved by controlling the CDF on a finite grid of quantiles and using monotonicity to squeeze everything in between. Uniform closeness of to is what licenses substituting one for the other inside any reasonably smooth functional .
The nonparametric bootstrap#
The sampling distribution of is determined by (unknown) and . The bootstrap replaces by , under which sampling iid points means drawing times with replacement from the observed data. The algorithm:
- For : draw a resample with replacement from the data; compute .
- Use the empirical distribution of as the estimated sampling distribution: its standard deviation is the bootstrap standard error, its quantiles feed confidence intervals.
The object of interest is the law of the (centred, scaled) statistic, . The bootstrap estimates it by — same map, plugged-in argument. Consistency needs three ingredients: (1) (Glivenko–Cantelli); (2) the map is continuous in the right topology — the statistic depends smoothly on the distribution (Hadamard differentiability of ); (3) the limit law is continuous in . For the mean, everything is explicit: conditional on the data, the resample mean has expectation and variance (plug-in variance), and the CLT applies to the resampling too — so the bootstrap distribution converges to the same normal limit as the true sampling distribution. Every classical bootstrap failure below is a failure of ingredient (2): the statistic is not a smooth functional of at the point that matters.
Worked example (fully by hand). Data: , statistic = mean, so . With there are only equally likely resamples, so we can compute the exact bootstrap distribution, no Monte Carlo needed. Conditional on the data, each draw has mean 3 and variance
the plug-in variance (divide by , not — the bootstrap world’s population is the sample). The bootstrap variance of the resample mean is therefore , giving bootstrap standard error . Compare the textbook : the bootstrap se is smaller by the factor — a known small-sample bias of the plug-in variance that vanishes as grows.

Confidence intervals: percentile, basic, bootstrap-t#
Let denote the -quantile of the bootstrap replicates. Three standard 90% intervals:
| Interval | Formula | Logic |
|---|---|---|
| Percentile | [q*₀.₀₅, q*₀.₉₅] | Read the bootstrap distribution directly |
| Basic (pivotal) | [2θ̂ − q*₀.₉₅, 2θ̂ − q*₀.₀₅] | Bootstrap the error θ̂* − θ̂, flip it around θ̂ |
| Bootstrap-t | [θ̂ − t*₀.₉₅·se, θ̂ − t*₀.₀₅·se] | Bootstrap the studentised statistic (θ̂* − θ̂)/se* |
The percentile interval assumes the bootstrap distribution sits around the way the sampling distribution sits around — true only if the distribution of is symmetric and unbiased. The basic interval fixes the direction of the flip (if tends to overshoot, the interval should extend further below). The bootstrap-t studentises first, which typically gives the most accurate coverage (second-order accurate, coverage error versus for percentile) at the cost of needing a standard-error estimate inside every resample. The parametric bootstrap is the same machinery with a fitted model in place of : fit (say a normal or a fitted MLE model), simulate samples of size from it, recompute. It is more efficient when the model is right and biased when it is wrong — the usual trade.
The jackknife#
The bootstrap’s deterministic grandparent: recompute the statistic times, leaving out one observation each time. Write for the leave-one-out estimate and for their average. The jackknife estimates
Suppose the estimator’s bias expands as . The leave-one-out estimates use points, so . Then
which matches the leading bias term — so subtracting it kills the bias. Sanity check on the mean: for , each , whose average over is . So and the estimated bias is exactly zero — as it must be, since the sample mean is unbiased. Meanwhile the jackknife variance formula reproduces exactly for the mean (a two-line algebra check), which is why the odd-looking factor is there. The jackknife fails for non-smooth statistics like the median — its leave-one-out perturbations are too small to explore the distribution — which is precisely where the bootstrap takes over.
Permutation tests#
Resampling also yields exact hypothesis tests. Two samples, and ; null hypothesis: the two groups have the same distribution. Under that null the group labels are arbitrary — the combined data are exchangeable — so every one of the relabellings was equally likely to have produced the observed split.
Let be any test statistic (say the difference in group means) and let be its values over all label assignments. Under , conditional on the pooled data, the observed assignment is uniform over the possibilities — that is exchangeability. Hence the permutation p-value satisfies for every — no asymptotics, no normality, no variance formula. The test is exact for any statistic you like; the choice of statistic affects only power. In practice you sample a few thousand random permutations instead of enumerating all , adding only Monte-Carlo error you control.
Worked example. Strategy returns on 4 days (mean 2.5), benchmark on 3 days (mean 0). Observed difference 2.5. There are relabellings. Writing for the sum of the 4-group, the difference is — achieved only by taking the four largest values , and since the pool contains two tied 1s there are exactly two such splits, so . With seven data points the test has done everything the data permit — and the granularity (smallest possible p-value ) is itself a lesson about tiny samples.
When the bootstrap fails#
The bootstrap is not magic; it inherits the smoothness assumptions in the proof sketch above. Three canonical failures, in increasing order of practical importance for quants:
1. Extremes — the maximum. Take and . The true sampling theory (from Order statistics) says converges to an Exponential(1) — a smooth, atomless limit. The bootstrap version cannot reproduce it:
because the resample max equals the sample max whenever at least one draw hits the largest observation. So the bootstrap distribution of has a permanent atom of mass 0.632 at zero — it converges to the wrong limit, no matter how large or . The failure is ingredient (2): the max is not a smooth functional of . (Remedies exist — the -out-of- bootstrap with , or parametric extreme-value theory.)
import numpy as np
rng = np.random.default_rng(11)
n, B, theta = 100, 4000, 1.0
x = rng.uniform(0, theta, n)
mx = x.max()
# bootstrap the maximum
boot_max = np.array([rng.choice(x, n, replace=True).max() for _ in range(B)])
atom = (boot_max == mx).mean()
print(f"P*(max* == max) : {atom:.3f} (theory: 1 - (1-1/n)^n -> 0.632)")
# compare: true sampling dist of n*(theta - max)/theta is ~ Exp(1), atomless.
# the bootstrap analogue n*(max - max*)/max has a big atom at exactly 0:
print(f"bootstrap mass at 0 : {(mx - boot_max == 0).mean():.3f}")
# ~0.63 of all bootstrap replicates sit on a single point -- the bootstrap
# 'distribution' of the max is degenerate and its CIs are garbage.2. Heavy tails. If (a Pareto with tail index below 2, a t(2) — see the distribution zoo), the sample mean has a non-normal stable limit, and the bootstrap distribution of the mean fails to converge to it: each resample is dominated by whether the few enormous observations were drawn 0, 1, or 3 times, so the bootstrap distribution stays random even as . For drawdown and tail-risk work on fat-tailed returns, bootstrap results deserve wide error bars of their own.
3. Dependence — and the block bootstrap. The iid bootstrap destroys time structure: resampling daily returns one at a time scrambles autocorrelation and volatility clustering, so it understates the variance of anything that accumulates. From time-series statistics, positive autocorrelation inflates the variance of the sample mean by the factor — the iid bootstrap silently sets that factor to 1. The block bootstrap fixes it by resampling contiguous blocks (length ) instead of single points, preserving dependence within blocks and sacrificing only the joints between them.

import numpy as np
rng = np.random.default_rng(11)
n, phi = 2000, 0.5 # AR(1) daily returns, rho_1 = 0.5
eps = rng.normal(0, 0.01, n)
r = np.empty(n); r[0] = eps[0]
for t in range(1, n):
r[t] = phi * r[t-1] + eps[t]
true_factor = (1 + phi) / (1 - phi) # variance inflation for the mean = 3.0
se_true = r.std(ddof=1)/np.sqrt(n) * np.sqrt(true_factor)
B, ell = 2000, 25
iid = np.array([rng.choice(r, n, replace=True).mean() for _ in range(B)])
nblk = n // ell
starts = rng.integers(0, n - ell + 1, size=(B, nblk))
blocks = r[starts[:, :, None] + np.arange(ell)] # (B, nblk, ell)
blk = blocks.reshape(B, -1).mean(axis=1)
print(f"true se of mean : {se_true:.5f}")
print(f"iid bootstrap : {iid.std():.5f} (too small by ~sqrt(3))")
print(f"block bootstrap : {blk.std():.5f} (close to the truth)")Practice problems#
You have 101 daily P&L observations and want a standard error for the median. Explain why the textbook route is awkward, give the bootstrap recipe, and state one caveat.
Solution. The asymptotic variance of the sample median is where is the density at the true median — it requires density estimation, which is noisy and bandwidth-dependent; and the jackknife is inconsistent for the median (non-smooth statistic). The bootstrap: resample the 101 points with replacement times, take the median of each resample, report the standard deviation of the medians. It is consistent because the median is Hadamard-differentiable wherever . Caveat: the bootstrap distribution of a median is discrete (it can only take observed values), so for small it is lumpy — quantile-based intervals can behave better than the raw se, and smoothing the bootstrap slightly improves accuracy.
A colleague reports a 90% percentile-bootstrap CI for a strategy’s Sharpe ratio from two years of data. Give two distinct reasons the true coverage may be below 90%.
Solution. (1) Bias and skew. The percentile interval implicitly assumes has the same distribution as and that this distribution is symmetric. A Sharpe estimator on two years is skewed (its denominator is random) and biased; the percentile interval reads the skew in the wrong direction — the basic interval flips it, and bootstrap-t or BCa corrects it properly. Coverage error is for percentile vs for studentised methods. (2) Dependence. Daily returns are autocorrelated and volatility-clustered; the iid bootstrap under-disperses the resampled Sharpes (variance-inflation factor silently set to 1), shrinking the interval. The fix is the block bootstrap — and even then, two years is little data for a ratio statistic. A complete answer names both failure modes: interval construction error and resampling-scheme error.
Given 252 paired daily returns (strategy, benchmark), design an exact test of “no outperformance” without distributional assumptions.
Solution. Work with paired differences . The sharp null is that the strategy and benchmark are exchangeable within each day, i.e. each is symmetric about 0 — under it, flipping the sign of any leaves the joint distribution unchanged. So: compute ; for each of iterations draw random signs and recompute ; p-value = fraction of . Pairing by day kills the shared market factor (the dominant variance), a two-sample label shuffle would not — that is the design point being tested. Caveats to volunteer: sign-flipping assumes symmetry of under the null and independence across days; with autocorrelation, flip signs of blocks of days instead, trading a little exactness for honesty.
Let be iid Uniform(0, θ). Show the nonparametric bootstrap is inconsistent for the distribution of the maximum.
Solution. Truth first: — an Exponential(1) limit, continuous with no mass at 0. Bootstrap: conditional on the data, each resample draw misses the sample maximum with probability , so
Hence the bootstrap law of places asymptotic mass 0.632 at exactly 0, while the true limit places mass 0 there — the bootstrap distribution does not converge to the truth, for any sample size. One line of intuition to close: the max depends on a single order statistic, and resampling from atoms cannot generate new candidate maxima above it. Fixes: -out-of- bootstrap (), or a parametric/EVT model for the tail.
For data (n = 2) and the statistic , write down the exact bootstrap distribution of and its standard error.
Solution. Four equally likely resamples: , giving — the distribution puts mass on . Mean 3 (=, as always); variance , so bootstrap se . Check against theory: plug-in variance , and ✓. This tiny example is worth carrying with you: it shows the bootstrap is a deterministic object (Monte Carlo only approximates it) and that its centre is , not — the whole reason basic/t intervals recentre.
Next: the block bootstrap existed because financial data are dependent in time — now meet that dependence head-on: stationarity, autocorrelation, AR(1), and why regressions on trending series lie. Time-series statistics.

