Cholesky Decomposition & Correlated Random Variables

A casual walkthrough of Cholesky decomposition — from real and Hermitian matrices to positive definite covariance matrices — and how it turns independent random numbers into correlated stock prices.

This time we’re tackling Cholesky Decomposition. To get there, I have to do a little math first. The front half is going to be pretty much pure math, and then I’ll push the financial-engineering side to the back — why we use Cholesky in financial engineering, how we use it, that whole thing.

OK so. A real matrix is a matrix that comes back as itself when you take its complex conjugate.

(These terms… translating them into Korean just gives you stiff Sino-Korean jargon anyway, so I’ll mostly stick with the English names.)

So, a real matrix:

A, complex-conjugated, gives Ā = A: a real matrix

— you could write it like this, right? A matrix with that property is called a real matrix. (Which just means it only has real numbers in it.)

Now there’s also a kind of matrix that comes back as itself even after you take both the complex conjugate and the transpose:

A, transposed and complex-conjugated, written A† (A dagger): the Hermitian case

Doing transpose-and-complex-conjugate together is called taking the dagger (which is why the symbol is, well, a dagger). If you dagger it and get the same matrix back, we call that matrix Hermitian (the complex cousin of a symmetric matrix).

But honestly — we’re dealing with a covariance matrix of real stock returns here. So instead of the broad Hermitian definition, I’ll carry on with real symmetric matrices, one notch narrower than Hermitian.

Now, if this A is real symmetric and

Definitions: if every non-zero x gives xᵀ·A·x above 0, A is positive definite (PD); below 0, negative definite (ND); if every x gives at least 0, positive semidefinite (PSD); at most 0, negative semidefinite (NSD)

— that’s what we call it.

We’re not going to study all of these in detail, though. We’ll just take a quick look at positive definite and move on. Why? Because we’ll use a positive-definite covariance matrix in the examples. Covariance matrices in general are only guaranteed to be positive semidefinite — a zero-variance combination is possible.

What’s a covariance matrix??????? I’ll cover it in my corporate finance series. Haven’t written that one yet, but I’ll get to it in the next few days, and the second it’s up I’ll drop a link here. — Done, linked it :) Minimum Variance Portfolio (Corporate Finance I Studied #10)

OK, let me just touch on a couple of theorems about positive definite before moving on.

Theorem 1. A is real symmetric. Then the necessary and sufficient condition for A to be positive definite is that all eigenvalues of A are positive.

proof. Suppose A is positive definite, λ is an eigenvalue of A, and x is any eigenvector corresponding to λ. Then

Proof: xᵀ·A·x = λ|x|², so xᵀ·A·x over λ|x|² is 1, which is positive; A is positive definite, so the denominator is positive, and so is λ

Gotta check the arrow going the other way too.

Suppose A’s eigenvalues are all positive. For the set of A’s eigenvectors (orthonormal ones — a real symmetric A always has them) The eigenvectors {x₁, ..., xₙ}, take any nonzero vector x ∈ ℜ and let

Coefficients αᵢ = xᵀxᵢ (i = 1, 2, 3, …, n)

Then any vector can be written as a linear combination of the αᵢxᵢ terms. That is,

The vector x = α₁x₁ + α₂x₂ + … + αₙxₙ

Then xᵀ·A·x is

Proof, other direction: xᵀ·A·x = Σ λᵢ(αᵢ)², at least λₖ(α₁² + … + αₙ²) with λₖ the smallest eigenvalue, and Σ αᵢ² = ‖x‖² is positive, so xᵀ·A·x is positive

OK — but isn’t requiring a matrix to be Hermitian or real symmetric kind of restrictive? Like, there aren’t even that many of those, so what’s the point…???

Nope, not at all….. In physics, Hermitian shows up everywhere. Especially in quantum mechanics….. ugh, I hated it…… ;_;

Anyway, a few more: if A is Hermitian and positive definite → it’s invertible; if A is Hermitian & positive definite, then det(A) > 0. That kind of stuff… I was going to write them up through property #6, but I think I can get to Cholesky decomposition without them, so I’m just going to skip them. heh heh. If you’re curious, crack open a linear algebra textbook.

On to Cholesky decomposition

We did LU decomposition before, right???? You can think of this as doing LU decomposition here too. Except! The assumption on the matrix A we’re going to hit with LU is different from before: the A we’re going to LU-decompose here is Hermitian (being a covariance matrix) and positive definite.

(From here on this ties into LU Decomposition (Financial Engineering Programming #19), so I’m leaving the link.)

Decompose that kind of A and it’ll look like this:

Handwritten: A = L·U, with L lower triangular (l₁₁ to lₙₙ on the diagonal) and U upper triangular with 1s on the diagonal

Since A is symmetric, U has to be L’s mirror image once L’s diagonal is pulled out: $u_{j,i} = l_{i,j}/l_{j,j}$. So I’ll write it like this:

Handwritten: the u’s mirrored into L, then the product split into (unit lower) × diag(l₁₁, …, lₙₙ) × (unit upper)

Handwritten: the diagonal split into two square-root diagonals, which gives L·Lᵀ

— we can write it like that. In the end,

A = L·Lᵀ

A can be decomposed like this. This decomposition is called Cholesky Decomposition.

The first reason Cholesky decomposition matters: when you want the solution vector x of the matrix equation $Ax = b$ and A is Hermitian and positive definite, Cholesky factorization uses about half the leading arithmetic work of general LU factorization. The actual running time still depends on the implementation. We want our code as lean as possible, and faster is better, so that’s why Cholesky decomposition matters.

Why we use it here: correlated random variables

But that’s not the meaning of Cholesky decomposition we’re going to put to use here.

Hmm… something I need to lay down first. A covariance matrix of real returns is symmetric, and every portfolio variance is nonnegative. That makes it positive semidefinite, not automatically positive definite!

Zero variance can happen — for example, the difference between two identical return series. For the Cholesky routine here, we additionally assume that no nonzero combination has zero variance. That gives us the positive-definite case.

So we’ll write this positive-definite covariance matrix as Σ, and put it to work. Cholesky decomposition is on the table. That is,

Σ = L·Lᵀ

it can be decomposed like this. And the piece we’re actually going to use after the decomposition is L. The lower triangular matrix!!!!

Why are we using it!!!!

To inject correlation into stock price movements.

See, there’s a problem with everything we’ve learned up to this point. Stock price movement is random — yeah, sure — but when you look at two stocks at once, say Hyundai Motor and Kia, we want their random shocks to be correlated. In this model the shocks are Gaussian; the simulated stock prices themselves are lognormal. If you just sketch the two paths any old way like this:

Hand-drawn sketch of two random stock paths, Hyundai (blue) and Kia (red), each wiggling on its own

— that probably doesn’t reflect reality very well. The two stock prices are each random on their own, true, but when you look at them together, it’s more accurate to see them as random in a way that’s correlated with each other…..

So now we want to draw two random variables, and we’d like these two random numbers to be random in a somewhat-correlated way. Random variables like that are called correlated random variables.

Back in the day we learned how to draw random numbers $\varepsilon \sim N(0,1)$. But here, if we just draw two of them

Two independent standard normal draws, ε₁ and ε₂

and use them as they are, it’s a little inappropriate. So we transform these two random variables appropriately

Handwritten: (ε₁, ε₂) transformed into (z₁, z₂)

and want to draw $z_1, z_2$ — correlated random variables, random but with some correlation baked in!!!!

That’s exactly where we use the L matrix in Σ = L·Lᵀ!!!!!!!

L(ε₁, ε₂) = (z₁, z₂)

We’re going to use it like this to make $z_1, z_2$!!!!!

But then why does doing

L(ε₁, ε₂) = (z₁, z₂)

give you correlated random variables??!! Honestly — I myself can’t prove this cleanly with math……

BUT!!!!! If we draw $z_1, z_2$ a huge number of times, reconstruct $\mathrm{cov}(z_1, z_2)$, build the covariance matrix from those samples, and show that it comes back to Σ again — wouldn’t that be undeniable???

VBA program CorrelatedRandomVariables: a 3 × 3 matrix with 0.2, 0.5 and 0.9 off the diagonal, Cholesky(), 50,000 draws multiplied by L with MMult, and the means, standard deviations and correlations printed in the Immediate window

We can check it with this code!!!!!! We pin down one covariance matrix,

The matrix with rows (1, 0.2, 0.5), (0.2, 1, 0.9) and (0.5, 0.9, 1)

decompose it with a function procedure called Cholesky() (how to build that function procedure comes right after this), and then with MMult we multiply the Gaussian random numbers $\varepsilon_{1,2,3}$ by the L that came out of the Cholesky decomposition.

Then $z_{1,2,3}$ — called rv() here, right??? (I’m guessing it’s short for “random variable”) — we draw as many of them as the iteration count, have the code recompute their means and standard deviations plus the correlation for each pair (1,2), (1,3), (2,3), and the sample results come close to the targets in the covariance matrix, with some sampling noise.

Or they do once a slip in the Cholesky() function further down is fixed: its inner sum should read L(i, k) * L(j, k). As written, it printed the 0.477, 0.953 and 1.053 on the screen; fixed, those come out at about 0.500, 0.900 and 1.000.

What was the point of all that????

What we wanted to check was whether, if we really take the covariance matrix sigma, Σ = L·Lᵀ, Cholesky-decompose it like this, take the L we made there,

L(ε₁, ε₂) = (z₁, z₂)

and use it like this, the random variables $Z_{1,2}$ that pop out really are correlated with each other???!?~~~

Result of the check: confirmed. heh heh heh

Oh yeah, on to the next part then lololol

The algorithm that spits out L

OK — now let’s study the thing I didn’t cover above, the algorithm that spits out L after the Cholesky decomposition, and wrap it up there. hehe hihi

Handwritten: the n × n matrix A = L·Lᵀ, with L lower triangular (l₁₁ to lₙₙ) times its transpose

Say the Cholesky decomposition goes like that. Then let’s look at $a_{11}$ first. Obvious at a glance: ℓ₁₁ = √a₁₁ — like this?!?!?!

Let’s look at the $a_{21}$ part too. Multiplying row 2 of the front matrix by column 1 of the back matrix gives $a_{21}$, a₂₁ = ℓ₂₁ℓ₁₁, and everything except that just dies.

Ack?!?!?! Come to think of it, a₃₁ = ℓ₃₁ℓ₁₁ — same deal with this one, a₄₁ = ℓ₄₁ℓ₁₁ — and this guy too, so aₙ₁ = ℓₙ₁ℓ₁₁ — we can write it like this. Ahh, then

ℓₙ₁ = aₙ₁/ℓ₁₁ = aₙ₁/√a₁₁

we can write it like this!!!!

Column 1 of L: done. Let’s look at column 2.

Multiplying row 2 of the front matrix by column 2 of the back matrix all the way through gives $a_{22}$, so

Column 2: a₂₂ = ℓ₂₁² + ℓ₂₂², and using the one before, ℓ₂₂ = √(a₂₂ − a₂₁²/a₁₁)

Next up would be $l_{32}$, but before that let me peek at $l_{33}$ and $l_{44}$ real quick.

Diagonal entries: a₃₃ = ℓ₃₁² + ℓ₃₂² + ℓ₃₃², so ℓ₃₃ = √(a₃₃ − (ℓ₃₁² + ℓ₃₂²)); likewise ℓ₄₄ = √(a₄₄ − (ℓ₄₁² + ℓ₄₂² + ℓ₄₃²))

Ah — now I can see the pattern.

ℓᵢᵢ = √(aᵢᵢ − Σ ℓᵢₖ² over k = 1 to i − 1)

We can write it like this. And looking at row 3 of the front matrix times column 2 of the back matrix,

ℓ₃₂ = (a₃₂ − ℓ₃₁ℓ₂₁)/ℓ₂₂: L’s column 1 is done, so ℓ₃₂ is the only unknown; same deal for ℓ₄₂

somehow it feels like we can generalize column 2:

ℓᵢ₂ = (aᵢ₂ − ℓᵢ₁ℓ₂₁)/ℓ₂₂

Let’s check column 3 too:

ℓ₄₃ = (a₄₃ − (ℓ₄₁ℓ₃₁ + ℓ₄₂ℓ₃₂))/ℓ₃₃: columns 1 and 2 are done, so ℓ₄₃ is the only unknown; the same goes for ℓ₅₃

I think I can generalize column 3 too:

ℓᵢ₃ = (aᵢ₃ − Σ ℓᵢₖℓ₃ₖ over k = 1 to 2)/ℓ₃₃

And looking at it all written out like this, it looks like I can write it for every entry below the diagonal of L!!!!!

ℓᵢⱼ = (aᵢⱼ − Σ ℓᵢₖℓⱼₖ over k = 1 to j − 1)/ℓⱼⱼ

— for $i>j$, we can just write it like this!!!! On the diagonal, use the square-root formula above. And since this is a lower triangular matrix, everything with $(j \gt i)$ is 0~~ — that goes without saying. heh

And this is something I noticed while deriving it just now: looks like we have to work out L in order — column 1, column 2, column 3…. one after another.

Alright, now that we roughly get the principle, let’s study the function procedure that spits out the matrix L after the Cholesky decomposition!!!

VBA function Cholesky(mat, n): L filled column by column, each diagonal entry from a sum of squares and the entries below it from a sum of products

It’s short, and simple too, so… well;;;; there’s not really anything else to study here, I guess.

Generating correlated stock prices

So now we can generate the price movements of two or more stocks. For two or more stocks, work out the shock covariances, put a positive-definite covariance matrix into the code above, pull out L, and combine L with the Gaussian draws $\varepsilon$ to make Z. In the price-path code below we use a correlation matrix, then apply volatility separately, and generate the stock prices just like we did in Monte Carlo Method (Financial Engineering Programming #9).

Here’s one example you could try:

VBA program simulation_stock_Process: two price paths with correlation rho = 0.9, written to sheet3

Putting in 0.9 as the correlation coefficient,

Excel sheet and chart of the two paths for rho = 0.9: they move together

it came out like this. That was kind of cool, so I tried putting in -0.9.

Then the random shocks should tend to point in opposite directions, right? The price paths will not be exact mirror images — the shocks are not perfectly anticorrelated, and drift still matters.

The same program with rho = -0.9

All I did was put -0.9 in the rho slot, OK???

Excel sheet and chart for rho = -0.9: the two paths move in opposite directions

Whoa. Magic. Wild. LOL. I’m pumped lol

OK, so now instead of generating $z_{1,2}$ to make the stock prices, I just let the independent random numbers $\varepsilon_{1,2}$ drive them. The part of the code I touched is right there

The program with the independent draws gaussian(1) and gaussian(2) fed straight into the two price paths

in the for loop: I deleted the rv variables and just put the Gaussian variables in. That way $\varepsilon_{1,2}$ goes in instead of $z_{1,2}$, right?

Excel sheet and chart with independent shocks: the two paths wander on their own

Ah, this is fun lol. So cool lol

Anyway, I’m done now. Let’s see if I ever study financial engineering again. (-_-)


Heads-up. The figures here are the original notes, slips and all. A few things to watch for:

  • A matrix is Hermitian when $A^\dagger = A$ (the first picture’s $\bar{A}^T = A^\dagger$ only defines the dagger). And in the definitions, $x^TAx \le 0$ for all $x$ is negative semidefinite, as the label (NSD) says; the word positive there is a slip.
  • For a symmetric $A$, the LDU pictures need $l_{ij} = u_{ji}\,l_{jj}$ (for example $l_{21} = u_{12}\,l_{11}$), and in the second line the (2,1) entry of the unit lower factor is $u_{12}$. In the square-root form, the off-diagonal entries carry the root too ($u_{ji}\sqrt{l_{jj}}$), and the last diagonal entry is $\sqrt{l_{nn}}$, not $\sqrt{l_{22}}$.
  • The shocks are $\varepsilon_1, \varepsilon_2 \sim N(0,1)$, not $N(1,0)$. In the element formulas, $l_{22} = \sqrt{a_{22} - a_{21}^2/a_{11}}$: the picture writes $a_{n1}^2$ for $a_{21}^2$.
  • In the VBA function Cholesky(), the inner sum should read s = s + L(i, k) * L(j, k), as in the formula’s sum of l_ik l_jk; with L(k, j) it picks up the empty upper half, so for the 3 x 3 example the Immediate window shows 1.053, 0.477 and 0.953 instead of about 1.000, 0.500 and 0.900. The 2 x 2 runs and their Excel sheets are unaffected.
  • The routine assumes a real symmetric positive-definite input. If a diagonal pivot is nonpositive, report failure rather than using the partly filled L returned after Exit For; a valid singular covariance matrix needs a method that handles semidefinite input.

Comments

Discussion happens via GitHub Discussions. You'll need a GitHub account to comment.