Project write-up · Strategy / Backtesting

Stress-testing portfolio strategies with a block bootstrap

Live app (may take ~30 s to wake up) · Source code

The problem

A historical backtest tells you how a strategy did on the one path history happened to take. To compare buy-and-hold, DCA, rebalancing and momentum rules fairly, you want many plausible futures. The textbook way is to simulate Gaussian, independent returns, but real returns have fat tails, volatility clusters, and correlations between assets that the simulation then throws away. A momentum rule in particular depends on how returns follow each other, which is exactly what independent draws destroy.

Approach: resample blocks of real history

Instead of drawing from a fitted distribution, the simulator cuts the historical monthly returns into blocks (6 months by default) and stitches randomly chosen blocks together into new paths. Each block is taken for all assets at once. That keeps:

starts = rng.integers(0, T - block_size + 1, size=(n_sims, n_blocks))
for b in range(n_blocks):
    idx = starts[:, b][:, None] + np.arange(block_size)[None, :]
    out[:, b*block_size:(b+1)*block_size, :] = hist_returns[idx]   # all assets together

Every strategy then runs on all paths at once (1,000 by default, NumPy-vectorised). Momentum rules compute their trailing returns from each simulated path, so they only ever see that path's past.

Strategies and outputs

Limits and what I'd change

← Back to all projects