QuantJourney Backtester

QuantJourney Backtester

Share product feedback

Thank you.

Your note is now in the QuantJourney inbox.

backtester/portfolio/calc/montecarlo.py

montecarlo.py:
Bootstrap simulation for NAV bands, drawdown risk and probability queries.

This file contains the MonteCarloSimulation dataclass. It block-bootstraps historical returns into simulated paths, computes NAV and drawdown matrices, then exposes summary statistics and probability queries.

Import from backtester.portfolio.calc.montecarlo import MonteCarloSimulation

When To Read This

  • 01
    You need to explain how Monte Carlo results are generated from historical returns.
  • 02
    You want probability of bust, probability of target return or horizon VaR.
  • 03
    You are adding simulation outputs to PDF tear-sheets.

File Anatomy

  • Dataclass configuration: returns, n_sims, block_size, horizon, initial_nav and seed.
  • Simulation lifecycle: instantiate -> run() -> read nav_paths, drawdown_paths and summary.
  • Block bootstrap: samples contiguous blocks to preserve some autocorrelation.
  • Plot helper: builds fan chart with confidence bands and sample paths.

Data Contract

Inputs

  • returns: pd.Series of period returns, usually daily strategy returns.
  • n_sims, block_size, horizon, initial_nav and seed parameters.
  • Optional plotting environment with matplotlib.

Outputs

  • nav_paths: ndarray shape n_sims x horizon_plus_initial.
  • drawdown_paths: ndarray with same shape as nav_paths.
  • summary: dict with final NAV, return, CAGR, Sharpe and drawdown percentiles.

Invariants

  • Call run() before reading probability queries or percentile paths.
  • block_size=1 gives iid bootstrap; larger blocks preserve short-run dependence.
  • The simulation is reproducible when seed is fixed.

Public API And Key Internals

MonteCarloSimulation

class
MonteCarloSimulation(returns, n_sims=1000, block_size=21, horizon=None, initial_nav=1.0, seed=42)

Dataclass holding simulation configuration and generated outputs.

run

method
run() -> MonteCarloSimulation

Runs block bootstrap, populates nav_paths, drawdown_paths and summary.

Returns

self for chaining.

bust_probability / goal_probability

method
bust_probability(threshold=-0.20)

Answers probability questions from simulated drawdown or final return paths.

Returns

float.

var_at_horizon / percentile_path

method
percentile_path(pct)

Reads path or return percentiles from completed simulation results.

Returns

float or ndarray.

plot

method
plot(n_sample_paths=50, figsize=(11, 6))

Creates NAV fan chart with 5-95 and 25-75 percentile bands.

Returns

matplotlib Figure.

Implementation Notes

  • The simulation stores full path matrices, not just summary values. That allows plots and follow-up probability queries.
  • The path includes an initial NAV column before simulated periods, which matters for chart x-axis length.
  • The method computes CAGR and Sharpe per simulated path, then summarizes distributions.

Code Walkthrough

Run simulation and ask probability questions

This is the public API a strategy packet or notebook would use.

montecarlo_usage.py Python
from backtester.portfolio.calc.montecarlo import MonteCarloSimulation

mc = MonteCarloSimulation(
    returns=strategy_returns,
    n_sims=2_000,
    block_size=21,
    seed=42,
).run()

median_nav = mc.summary["median_final_nav"]
worst_band = mc.summary["dd_5pct"]
bust_30 = mc.bust_probability(threshold=-0.30)
goal_50 = mc.goal_probability(target=0.50)
fan_chart = mc.plot()

Key implementation: block-bootstrap path construction

The simulator samples block starts, concatenates blocks and truncates to horizon.

backtester/portfolio/calc/montecarlo.py Python
rng = np.random.default_rng(self.seed)
r = self.returns.dropna().values
n = len(r)
T = self.horizon or n
bs = max(self.block_size, 1)
n_blocks = int(np.ceil(T / bs))

starts = rng.integers(0, n - bs + 1, size=(self.n_sims, n_blocks))
sim_returns = np.empty((self.n_sims, T))
for i in range(self.n_sims):
    blocks = np.concatenate([r[s : s + bs] for s in starts[i]])
    sim_returns[i] = blocks[:T]

Key implementation: summary outputs

These values are what the report layer can serialize into metrics or plots.

backtester/portfolio/calc/montecarlo.py Python
final_nav = self.nav_paths[:, -1]
final_return = final_nav / self.initial_nav - 1
max_dd = self.drawdown_paths.min(axis=1)

self.summary = {
    "median_final_nav": float(np.median(final_nav)),
    "nav_5pct": float(np.percentile(final_nav, 5)),
    "nav_95pct": float(np.percentile(final_nav, 95)),
    "median_total_return": float(np.median(final_return)),
    "median_cagr": float(np.median(cagr)),
    "median_sharpe": float(np.median(sharpe)),
    "worst_dd": float(np.min(max_dd)),
}