What is Metropolis-Hastings?

Metropolis-Hastings (MH) is a Markov chain Monte Carlo (MCMC) algorithm, typically used to sample from probability distributions that lack closed-form solutions — a common situation in practice. In those settings, MCMC is valued for where the chain converges: the collected samples reveal the shape of a distribution that would otherwise be difficult to characterize. Our use is a bit different. The target distributions here are ordinary normals, easy to work with analytically. We use MH not for what it converges to, but for the paths it traces along the way — the propose-then-accept/reject mechanism naturally produces price sequences with realistic persistence, where values drift and fluctuate rather than jumping erratically. The core idea is simple:

1

Propose a new state by perturbing the current one. In this simulator, the proposal is drawn from a multivariate normal distribution centered on the current price, with a covariance matrix that encodes the stock-bond correlation.

2

Evaluate the proposal. Compare the probability of the proposed price under the target distribution to the probability of the current price. The target distribution is a normal with the specified mean and standard deviation for each asset.

3

Accept or reject. If the new price is more probable, always accept. If less probable, accept with probability equal to the density ratio (the probability of the proposed price divided by the probability of the current price — a number between 0 and 1). If rejected, the chain stays at the current value. This guarantees the chain's stationary distribution matches the target.

4

Repeat for each timestep. The result is a correlated random walk that respects the target distribution while exhibiting realistic short-term persistence — prices don't jump erratically from step to step.

steps
Press Play to start the Metropolis-Hastings sampler.

Propose: The gold curve that appears at each step is the proposal distribution — a normal centered on the current value, with spread controlled by the step size slider. The dashed gold line shows the proposed value drawn from it. This is not the target distribution; proposals are local perturbations, which is what gives the chain its path-like structure.

Evaluate and accept or reject: When a proposal lands in a less probable region, the green bar shows the acceptance probability — the ratio of the proposed density to the current density. A uniform random draw determines the outcome: if it falls in the green zone, the proposal is accepted despite being less probable. This is what allows the chain to occasionally explore lower-probability regions rather than getting trapped at the peak.

The key property: over long runs, the histogram of visited prices converges to the target distribution. But the path through those prices has temporal structure — each price depends on the previous one, which is exactly what makes it useful for simulating market behavior.

How this simulator uses it

Each simulation run generates two correlated price series (stocks and bonds) using the MH algorithm. The steps are:

Price generation

At day t, the current prices [s_t, b_t] are the chain state. A proposal [s*, b*] is drawn from a bivariate normal centered at [s_t, b_t] with covariance matrix determined by the asset volatilities, correlation, and proposal step divisor. Each asset's acceptance is evaluated independently against its marginal target density.

Interest/drift

After the MH chain generates the base price paths, compound interest is added as a deterministic drift: price_t += mean * ((1 + rate)^(t/period) - 1). This separates the stochastic volatility from the expected growth, making both independently controllable.

Portfolio accounting

For each price path, two portfolios are tracked simultaneously:

Rebalanced: At every rebalance interval, the portfolio is reset to the target allocation (e.g., 60/40). Overweight assets are sold, underweight assets are bought, keeping total value unchanged.

Non-rebalanced: Same initial allocation, but left to drift. If stocks outperform, the portfolio gradually becomes stock-heavy. This is the "do nothing" baseline.

Summary statistics

Annualized gain and rolling standard deviation are computed for each simulation. The "Gain vs SD" plot shows the cloud of all sim outcomes plus the mean (point estimate). The frontier sweep repeats this at each allocation from 0% to 100% stocks, tracing out the efficient frontier.

What the model captures

Gets right

  • Random-walk price behavior with temporal persistence
  • Correlated asset returns (tunable)
  • Non-negative prices (proposals below threshold are rejected)
  • Deterministic drift (compound interest/growth)
  • Mechanical rebalancing vs. drift comparison
  • Distribution of outcomes, not just expected values

Gets wrong

  • Stationarity — real markets have regime changes
  • No volatility clustering (GARCH-like effects)
  • Thin tails — real returns are fat-tailed
  • No mean reversion or momentum effects
  • Fixed correlation — real correlations spike in crises
  • No transaction costs, taxes, or bid-ask spreads

Why simplify?

The limitations above are deliberate, not accidental. The goal isn't to predict real market outcomes — it's to isolate the mechanical effect of rebalancing from the noise of market-specific dynamics.

By holding the statistical properties constant, we can see what rebalancing does in a "fair" environment — one where neither asset has a structural advantage from regime changes, momentum, or crisis correlations. The rebalancing benefit (or cost) that emerges is purely a function of volatility, correlation, and return differential.

Adding realistic features (fat tails, regime switching, stochastic volatility) would make the model more accurate but also make it harder to attribute outcomes to any single cause. The simplicity is the point.

Key parameters

ParameterRoleDefault
nSimsIndependent simulation runs. More = smoother estimates.500
n_timestepsLength of each run. ~1 trading day per step.1000
perc_stocksTarget stock allocation (remainder in bonds).60%
rebal_intervalDays between rebalancing events.100
s_sd / b_sdPrice volatility per asset. Asymmetric by default.6 / 3
s_b_corrCorrelation between stock and bond returns.0.7
s_int / b_intCompound growth rate per interest period.0.06 / 0.02
propStepDivisorControls MH proposal width. Larger = smaller steps.20
widthValRolling window for SD calculation.600
int_periodDays per compounding period (~1 year).300
← Open the simulator