Asset Returns Monte Carlo

Simulated one-day returns for three stocks, generated in your browser from a year of daily closing prices. The returns are drawn from a multivariate normal distribution fitted to the historical data — but the covariance matrix is never built.

Methodology

Start with a matrix R of historical log returns. Each row is a trading day, each column an asset, and each column has had its mean removed. With T days and N assets, R is T × N.

The usual way to simulate correlated normal returns is to estimate the N × N covariance matrix S, factor it as S = LL’ by Cholesky decomposition, and set r = Lz for a standard normal vector z of length N. That works, but it makes you form and factor S first.

You don’t have to. Draw one standard normal per day rather than per asset:

zN(0,IT)one draw for each of the T historical daysr=Rzan N-vector of simulated asset returns\begin{aligned} z &\sim N(0, I_T) &&\text{one draw for each of the } T \text{ historical days} \\[2pt] r &= R'z &&\text{an } N\text{-vector of simulated asset returns} \end{aligned}

The covariance of rr is RRR'R, which is the sample covariance of the historical returns. So rr has exactly the distribution you wanted, and SS was never constructed. Each draw costs O(TN) instead of the O(N³) of a factorization, and the gap widens as the number of assets grows relative to the length of the history. The method also stays well behaved when N exceeds T — the case where the sample covariance matrix is singular and Cholesky simply fails.

This is the approach described in Peter Benson and Peter Zangari, “A general approach to calculating VaR without volatilities and correlations”, RiskMetrics Monitor, Second Quarter 1997.

The same method, implemented six times over — in Python, Go, TypeScript, Rust, C++, and Java — is on GitHub at pbenson/var-without-covariance. All six share one specified random number generator, run against the same price data used on this page, and are tested to produce the same numbers.

Weighting the observations

Equal weighting treats a return from eleven months ago as being as informative as yesterday’s. Exponential weighting instead discounts day t by λ raised to its age in days, so recent market conditions dominate; RiskMetrics uses λ = 0.94 for daily data.

Weighting drops into the same expression. Scale row t of R by the square root of its weight w(t), normalized so the weights sum to one:

R~[t]=w(t)R[t]then, as before,r=R~z\tilde{R}[t] = \sqrt{w(t)}\, R[t] \qquad\text{then, as before,}\qquad r = \tilde{R}'z

which gives rr covariance tw(t)r(t)r(t)\sum_t w(t)\, r(t) r(t)' — the weighted sample covariance. Equal weighting is the special case w(t)=1/Tw(t) = 1/T, where the scale factor collapses to the familiar 1/T1/\sqrt{T}.

What is simulated

Each histogram below shows one-day continuously compounded returns, centered on zero. Dropping the historical drift is the usual convention for one-day risk: over a single day the estimated mean return is far smaller than the noise around it, so including it adds bias without adding information.

The three assets are simulated jointly — every point in the run shares one draw of z — so the correlations among them are preserved even though each panel shows only a marginal distribution.

Weighting of historical observations

251 aligned trading sessions, 2025-08-22 to 2026-08-21. Yahoo Finance chart API (adjusted close).

AAPLApple
95% VaR
σ 1.58%VaR −2.57%
MMM3M
σ 1.65%VaR −2.75%
JPMJPMorgan Chase
σ 1.41%VaR −2.30%

One shared axis, one hue: the three distributions differ only in width. Solid bars fall below the 5th percentile, left of the dashed one-day 95% value at risk.

What the histograms cannot show

Three near-identical bells, and yet these assets do not move independently. A marginal distribution is silent about joint behavior — and joint behavior is the whole reason a portfolio is not simply the sum of its risks. The matrix below is the same simulation, viewed as pairs.

AAPL0.19−1+1historical 0.180.15historical 0.16MMM0.31historical 0.31JPM
Below the diagonal, each panel plots one asset's simulated return against another's — a tilted cloud is correlation. Both axes span the same ±7% range as the histograms above, plotted from a 1,200-point sample of the run. Above the diagonal, the bar gives the simulated coefficient on a −1 to +1 scale; the upright tick marks the historical value the simulation is reproducing.

The bars land on their ticks: r = R'z reproduces the historical correlations without ever estimating one. Sampling error is the only gap, and it shrinks as you raise the number of simulations.

What to look for

Switch from equal to exponential weighting and watch the histograms breathe: if the last few weeks have been calmer than the year as a whole, the distributions tighten and the 95% value at risk pulls in toward zero. That responsiveness is the point of exponential weighting, and it is also its cost — the estimate reacts to recent quiet just as eagerly as to recent turbulence.

Then watch the correlogram under the same switch. Volatility and correlation move independently: a pair can grow more volatile while becoming less correlated, which is why a risk estimate built from volatilities alone can be right about each asset and wrong about the portfolio.

Raise the simulation count and the bars converge on their ticks. That convergence is the claim being tested — sampling error shrinks, and what remains is the historical covariance structure, reproduced exactly, from a matrix that was never built.