Monte Carlo without a covariance matrix

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 matrixA table holding, for every pair of assets, how much they tend to move together. With a thousand assets it has a million entries, which is why avoiding it is worth the trouble. is never built.

Why avoid the covariance matrix

The textbook route to correlated normal draws is to estimate the N × N covariance matrix S, factor it, and multiply the factor by a standard normal vector. Both standard factorizations have problems, and they are not the same problems.

Cholesky decompositionA way of splitting a covariance matrix into a factor times its own transpose, so that multiplying independent random numbers by that factor produces correlated ones. is brittle. The textbook factorization — L lower triangular with a strictly positive diagonal — exists and is unique exactly when S is positive definite. Zero eigenvalues are not fatal in principle: a positive semi-definite matrix still admits S = LL’ with zeros on the diagonal, and a pivoted, rank-revealing variant will find it. The unpivoted routine you actually call will not. Each pivot is computed as a difference, and at rank deficiency that difference is a rounding error of arbitrary sign; take its square root and the factorization fails outright.

That is not a corner case in this application: the sample covariance of T days of N factors has rank at most T, so whenever N exceeds T the matrix is singular by construction. Even short of that it is fragile, because near-linear dependence among factors is the norm rather than the exception — a stock and its ADR, two adjacent tenors on the same yield curve, an index and its largest constituent. Those pairs drive S toward singularity, and a Cholesky routine that survives them at all returns a factor contaminated by rounding.

SVD is robust but not cheap. A singular value decomposition handles rank deficiency gracefully, which is the honest fix for the brittleness above. It does not, however, address either cost that actually binds:

In a real risk system N is a factor count, not a handful of stocks — every tenor on every curve, every equity index, every exchange rate. At N in the thousands, the cube dominates everything else in the calculation.

The decay factor bounds the other dimension. Exponential weighting discounts day t by λ raised to its age, and RiskMetrics uses λ between 0.94 and 0.97 for daily data. Those correspond to half-lives of about 11 and 23 trading days, and the weight surviving a full year is λ250\lambda^{250} — roughly 2×10−72 \times 10^{-7} at λ = 0.94, and 5×10−45 \times 10^{-4} at λ = 0.97. Past a year of history you are adding rows that contribute nothing. So T is not a free parameter that grows with your ambitions; it is pinned near 250.

That is the whole argument for drawing per day instead of per factor. The method below never forms S and never factors it, so the O(N2)O(N^2) storage and the O(N3)O(N^3) factorization both disappear. What remains is O(TN)O(TN) per draw against a T that λ has already capped — linear in the number of factors, with a constant of about 250. Per draw that beats multiplying by a Cholesky factor, which costs O(N2)O(N^2), once N exceeds T; the setup cost and the storage it beats unconditionally.

Methodology

Start with a matrix R of historical Log returnThe natural logarithm of today’s price divided by yesterday’s. It behaves better than a percentage change when returns are added up over time.. 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:

z∼N(0,IT)one draw for each of the T historical daysr=R′zan 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 R′RR'R, which is the sample covariance of the historical returns. So rr has exactly the distribution you wanted, and SS was never constructed — with the consequences for cost and conditioning described above.

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.

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 −3.02%
MMM3M
σ 1.65%VaR −3.10%
JPMJPMorgan Chase
σ 1.41%VaR −2.70%

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.29−1+1historical 0.180.10historical 0.16MMM0.27historical 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 riskA loss level a portfolio should exceed only rarely — a 95% one-day VaR is the loss you expect to be worse than on about one trading day in twenty. 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.