The Metropolis-Hastings algorithm, correlated price paths, and what this model does and doesn't capture.
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:
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.
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.
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.
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.
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.
Each simulation run generates two correlated price series (stocks and bonds) using the MH algorithm. The steps are:
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.
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.
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.
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.
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.
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.
| Parameter | Role | Default |
|---|---|---|
| nSims | Independent simulation runs. More = smoother estimates. | 500 |
| n_timesteps | Length of each run. ~1 trading day per step. | 1000 |
| perc_stocks | Target stock allocation (remainder in bonds). | 60% |
| rebal_interval | Days between rebalancing events. | 100 |
| s_sd / b_sd | Price volatility per asset. Asymmetric by default. | 6 / 3 |
| s_b_corr | Correlation between stock and bond returns. | 0.7 |
| s_int / b_int | Compound growth rate per interest period. | 0.06 / 0.02 |
| propStepDivisor | Controls MH proposal width. Larger = smaller steps. | 20 |
| widthVal | Rolling window for SD calculation. | 600 |
| int_period | Days per compounding period (~1 year). | 300 |