Unit 3 · The Quant Math Toolkit — Session 03 of 06

Linear Algebra for Portfolios

You already know matrices. What you don't know yet is what they mean for money. A portfolio is a vector of weights, its risk is what the covariance matrix does to that vector, and the eigenvectors of that matrix are the handful of hidden portfolios a whole market is really made of. The core move: stop adding up positions one at a time and read risk as a quadratic form, w⊤Σww^\top \Sigma w.

DURATION 1.5 h + lab LEVEL Foundations TRACKS QR · QT · QD · IND HANDS-ON PCA on a 50-asset market with a known truth

3.3.1 A portfolio is a vector

Engineers already think in vectors and matrices. The only new thing here is what the numbers mean: each coordinate is a bet, and the matrix that turns bets into risk is built from how assets move together.

A portfolio of NN assets is a list of NN numbers: the fraction of your capital in each one. Stack them in a column and you have the weight vector w=(w1,…,wN)⊤w = (w_1, \dots, w_N)^\top. Long positions are positive and shorts negative; a fully invested long-only book has weights that sum to 1, a dollar-neutral long-short book has weights that sum to 0. Stack the assets' simple returns for one period into a vector RR as well, and the portfolio's return is a dot product, Rp=w⊤R=∑iwiRiR_p = w^\top R = \sum_i w_i R_i. That is the "simple returns add across assets" rule of Session 2.3 — Returns, P&L, Compounding & Leverage, written as linear algebra.

Risk needs one more object. In Session 3.1 — Probability & Expected Value you met the variance of a sum of two bets: Var⁡(X+Y)\Var(X + Y) is Var⁡(X)+Var⁡(Y)\Var(X) + \Var(Y) plus a cross term, 2Cov⁡(X,Y)2\Cov(X, Y). With NN assets there are NN variances and N(N−1)/2N(N-1)/2 distinct covariances, and the natural place to keep them is an N×NN \times N table: the covariance matrix Σ\Sigma, whose entry in row ii, column jj is Σij=Cov⁡(Ri,Rj)\Sigma_{ij} = \Cov(R_i, R_j), with the variances σi2\sigma_i^2 on the diagonal. It is symmetric, because Cov⁡(Ri,Rj)=Cov⁡(Rj,Ri)\Cov(R_i, R_j) = \Cov(R_j, R_i). The portfolio variance — the variance of the book's return — is then

σp2=Var⁡(w⊤R)=w⊤Σw=∑i=1N∑j=1NwiwjΣij\sigma_p^2 = \Var(w^\top R) = w^\top \Sigma\, w = \sum_{i=1}^{N} \sum_{j=1}^{N} w_i\, w_j\, \Sigma_{ij}

An expression of the form w⊤Σww^\top \Sigma w is called a quadratic form: every term multiplies two weights. Fig 3.3.1 draws it as a grid. Each cell is visited by one pair of weights; the diagonal cells hold each position's own risk, the off-diagonal cells its co-movement with every other position. Add up all N2N^2 cells and you have the variance of the book.

PORTFOLIO VARIANCE w′Σw — ONE CELL PER PAIR OF POSITIONS w₁ w₂ w₃ w₁ w₂ w₃ w₁² σ₁² w₁w₂ Σ₁₂ w₁w₃ Σ₁₃ w₂w₁ Σ₂₁ w₂² σ₂² w₂w₃ Σ₂₃ w₃w₁ Σ₃₁ w₃w₂ Σ₃₂ w₃² σ₃² own risk co-movement co-movement co-movement own risk co-movement co-movement co-movement own risk the grid is Σ; the weights enter twice, once from each side add PORTFOLIO VARIANCE w′Σw — one number blue: N own-risk cells yellow: N(N − 1) co-movement cells with N = 100 positions: 100 blue vs 9,900 yellow diversification lives in the yellow Double every weight and every cell quadruples: return is linear in w, risk is quadratic.
fig 3.3.1 — the quadratic form w⊤Σww^\top \Sigma w as a grid: weights on both edges, one cell per pair of positions; own risk on the diagonal, co-movement everywhere else

A worked example with illustrative inputs: the classic 60/40 portfolio, 60% in equities with 16% annual volatility and 40% in bonds with 6%, correlated 0.2. The four cells add up to a variance of 0.0107 a year, a volatility of 10.35%. That is less than the weighted average of the two volatilities, 12.00%, because the two co-movement cells are small. The gap between the average of the parts and the whole is diversification, and you now have a formula for it.

📌 Key idea — return is a dot product, risk is a quadratic form

Double every weight and the expected return doubles, while the variance quadruples and the volatility doubles. Return is linear in the weights, w⊤Rw^\top R; risk is quadratic, w⊤Σww^\top \Sigma w — and almost all of it lives off the diagonal. With NN positions there are NN own-risk cells and N(N−1)N(N-1) co-movement cells, so in any real book the covariances, not the individual volatilities, decide how risky you are.

3.3.2 From returns to risk: the pipeline

Everything in this session is one pipeline: a table of returns goes in, a covariance matrix comes out, you check it, you take it apart into eigenvectors, and you use the pieces. Each stage has one classic bug.

RETURNS → Σ → CHECK → EIGEN-DECOMPOSITION → FACTORS AND RISK fails → fix the data, or shrink 1 · RETURNS T × N table, aligned dates 2 · ESTIMATE Σ N × N, symmetric, from one clean table 3 · CHECK PSD: all λ ≥ 0? cond(Σ) acceptable? 4 · DECOMPOSE Σ = V Λ V′: eigen-portfolios 5 · USE PCA factors, w′Σw, risk contributions bug: stale or missing prices bug: pairwise deletion of gaps bug: skipping it — nothing raises an error bug: trusting the small eigenvalues bug: a raw Σ⁻¹ inside an optimizer One clean table in, one checked matrix out — every later tool inherits the matrix's flaws.
fig 3.3.2 — the session as a pipeline: returns to covariance matrix to checks to eigen-decomposition to factors and risk, with the classic bug at each stage in coral

Step 1 is a T×NT \times N matrix of returns: TT periods in the rows, NN assets in the columns, on aligned dates (Session 4.4 — Time, Calendars, As-of Joins & Bars builds the joins that make this true). Step 2 estimates Σ\Sigma. The sample covariance matrix, with R‾\bar R the vector of average returns, is an average of outer products, one per day:

S=1T−1∑t=1T(Rt−R‾)(Rt−R‾)⊤S = \frac{1}{T-1} \sum_{t=1}^{T} (R_t - \bar R)(R_t - \bar R)^\top

Each outer product gives any portfolio a squared, hence non-negative, contribution, so a matrix built this way from one complete table can never assign a negative variance to anything. Estimates built any other way can (§3.3.6), which is why step 3 exists. Steps 4 and 5 are §3.3.4 and §3.3.5.

The correlation matrix CC holds the correlations ρij\rho_{ij}, with ones on the diagonal. It carries the same information as Σ\Sigma once you know the volatilities: with D=diag⁡(σ1,…,σN)D = \diag(\sigma_1, \dots, \sigma_N),

Σ=DCD,Σij=σiρijσj\Sigma = D\, C\, D, \qquad \Sigma_{ij} = \sigma_i\, \rho_{ij}\, \sigma_j

Practitioners often estimate the two parts separately and reassemble them: volatilities move fast and can be forecast (Session 5.5 — Modeling Volatility), while correlations are often estimated over longer windows. The correlation matrix is also the better input to PCA when assets have very different volatilities (§3.3.5).

ObjectShapeWhat it holdsIn this session's lab
Returns matrix RRT×NT \times None row per day, one column per asset2,520 days × 50 assets
Weight vector wwN×1N \times 1fraction of capital in each asset60/40 book: (0.6, 0.4)
Covariance matrix Σ\SigmaN×NN \times N, symmetricvariances on the diagonal, covariances off it1,275 distinct entries to estimate
Correlation matrix CCN×NN \times N, symmetriccorrelations, ones on the diagonalΣ=DCD\Sigma = DCD holds exactly
Portfolio variance w⊤Σww^\top \Sigma w1×11 \times 1the variance of the whole bookequal-weight book: volatility 16.9% true, 16.8% estimated
Eigenvectors VV, eigenvalues λ\lambdaN×NN \times N and NNuncorrelated portfolios and their variancesthe first carries 28.6% of all variance

How good is a sample covariance matrix? With 10 years of daily data on 50 assets, the lab's estimate is off by 5.8% of Σ\Sigma's size (the relative error in the Frobenius norm, the square root of the sum of squared entry errors); for normal returns the root-mean-square error can be computed exactly, and it is 6.4%. That sounds fine, and for the big structure it is. §3.3.7 shows why it is not fine for everything.

💡 Looking ahead

Estimating Σ\Sigma well is a whole session: Session 9.2 — Covariance Estimation covers the failure modes of the sample covariance and the shrinkage and denoising fixes. Session 9.4 — Factor Risk Models replaces the N(N+1)/2N(N+1)/2 free entries with a factor model that has far fewer. In this session the sample covariance matrix is the default, and §3.3.6–§3.3.7 show where it breaks.

3.3.3 Risk as a quadratic form, and the diversification floor

Write the variance as a quadratic form and the limit of diversification falls out in one line: adding assets shrinks the diagonal of fig 3.3.1, but the off-diagonal cells never go away.

Take the simplest possible market: NN assets, all with volatility σ\sigma, every pair correlated ρ\rho, held in equal weights wi=1/Nw_i = 1/N. Count the cells of fig 3.3.1. The NN diagonal cells are each worth σ2/N2\sigma^2/N^2, a total of σ2/N\sigma^2/N. The N(N−1)N(N-1) off-diagonal cells are each worth ρσ2/N2\rho\sigma^2/N^2, a total of ρσ2(1−1/N)\rho\sigma^2(1 - 1/N). Add them:

w⊤Σw=(1−ρ)σ2N⏟diversifiable+ρσ2⏟systematic→N→∞ρσ2w^\top \Sigma\, w = \underbrace{\frac{(1-\rho)\,\sigma^2}{N}}_{\text{diversifiable}} + \underbrace{\rho\,\sigma^2}_{\text{systematic}} \;\xrightarrow{\;N \to \infty\;}\; \rho\,\sigma^2

Session 3.1 — Probability & Expected Value previewed this limit; the matrix form makes it a one-line count. The first term dies away as you add assets; the second never does. Fig 3.3.3 draws the anatomy.

WHERE THE VARIANCE GOES AS YOU ADD ASSETS — equal weights, equal volatilities, common ρ N = 1 σ² ρσ² 0 DIVERSIFIABLE · (1 − ρ)σ²/N shrinks like 1/N as you add assets SYSTEMATIC · ρσ² · never diversifies number of assets N → TWO KNOBS MORE ASSETS (N ↑) shrinks the blue part only; past a few dozen, little is left LOWER CORRELATION (ρ ↓) the only knob that lowers the floor IN VOLATILITY TERMS floor = √ρ × one asset's volatility The curve is ρσ² + (1 − ρ)σ²/N: it falls fast at first, then barely at all.
fig 3.3.3 — the anatomy of diversification (schematic): the diversifiable part shrinks like 1/N, the systematic part ρσ² stays however many assets you add

With ρ=0\rho = 0 the variance falls like 1/N1/N and the volatility like 1/N1/\sqrt N — the √n rule of Session 3.1. With ρ>0\rho \gt 0 the variance has a floor at ρσ2\rho\sigma^2. For single stocks with 30% volatility and a pairwise correlation of 0.3, no number of stocks takes an equal-weight portfolio below ρσ\sqrt{\rho}\,\sigma = 16.4%. Fig 3.3.4 plots the curves, computed in the lab both from the closed form and as w⊤Σww^\top \Sigma w with the full matrix (they agree to machine precision for every NN).

EQUAL-WEIGHT PORTFOLIO VOLATILITY AS A SHARE OF ONE ASSET'S, BY NUMBER OF ASSETS N ρ = 0 ρ = 0.3 ρ = 0.6 0% 20% 40% 60% 80% 100% 20 40 60 80 100 ρ = 0 ρ = 0: 10% ρ = 0.3 ρ = 0.3: 55% ρ = 0.6 ρ = 0.6: 78% ρ = 0.6: 78% ρ = 0.3: 55% ρ = 0: 10%
fig 3.3.4 — equal-weight portfolio volatility as a share of one asset's volatility: with correlation, the curve flattens onto the floor √ρ after a few dozen assets

Two summary numbers make the curve portable. The diversification ratio of Choueifaty & Coignard (2008) divides the weighted-average volatility of the holdings by the portfolio's volatility (the 60/40 book scores 1.16). Its square, in this equal-correlation market N/(1+(N−1)ρ)N/(1 + (N-1)\rho), is the effective number of independent bets: the number of uncorrelated assets that would give the same risk reduction. The ten correlated positions of Session 3.1 are worth only 2.70 independent bets, which is why they carried 1.92 times the risk of ten independent ones. Carver-style position sizing (Session 9.5 — Sizing, Vol Targeting & Allocating Across Strategies) applies the same idea to volatility-scaled positions and calls it the diversification multiplier.

Common correlation ρ\rhoVolatility left, N=10N = 10N=100N = 100N→∞N \to \inftyDiversification ratio, N=100N = 100Effective bets, N→∞N \to \infty
031.6%10.0%010.0unlimited
0.360.8%55.4%54.8%1.803.33
0.680.0%77.7%77.5%1.291.67
📌 Key idea — correlation, not the number of names, limits diversification

Past a few dozen assets, adding names barely helps; only lower correlation lowers the floor. The floor ρσ2\rho\sigma^2 is the risk every asset shares — which is why the first principal component in §3.3.5 turns out to be the market. A book of 200 stocks at an average correlation of 0.3 is worth fewer than 3.3 independent bets on "stocks", not 200.

Who is really driving the risk?

Because variance is a quadratic form, it splits exactly into one piece per position. Scale every weight by cc and the variance scales by c2c^2; Euler's theorem for such functions says that the weights times the gradient ∂σp2/∂w=2Σw\partial \sigma_p^2/\partial w = 2\Sigma w add back up to twice the function. Halve both sides:

w⊤Σw=∑i=1Nwi(Σw)iw^\top \Sigma\, w = \sum_{i=1}^{N} w_i\, (\Sigma w)_i

Each term is the position's risk contribution: its weight times its covariance with the whole portfolio. For the 60/40 book the split is lopsided:

AssetShare of capitalVolatilityShare of portfolio variance
Equities60%16%90.3%
Bonds40%6%9.7%

A "balanced" portfolio by capital is an equity portfolio by risk. Session 9.3 — Risk Parity & HRP turns this decomposition into portfolios that balance risk instead of capital.

3.3.4 Eigenvectors: the portfolios of risk

The covariance matrix mixes everything with everything. Its eigenvectors un-mix it into a set of portfolios that are uncorrelated with each other and whose variances add up to the total.

An eigenvector of Σ\Sigma is a direction vv that Σ\Sigma only stretches, Σv=λv\Sigma v = \lambda v; the stretch factor λ\lambda is its eigenvalue. (The course reserves λ\lambda for multipliers, and an eigenvalue is exactly that: the number Σ\Sigma multiplies vv by.) A symmetric N×NN \times N matrix has NN mutually orthogonal unit eigenvectors and NN real eigenvalues — the spectral theorem — so it factors as

Σ=VΛV⊤=∑k=1Nλkvkvk⊤,V⊤V=I\Sigma = V \Lambda V^\top = \sum_{k=1}^{N} \lambda_k\, v_k v_k^\top, \qquad V^\top V = I

with the eigenvectors vkv_k as the columns of VV and the eigenvalues, sorted from largest to smallest, on the diagonal of Λ\Lambda.

Now read each eigenvector as a portfolio, with weights vkv_k. Its variance is vk⊤Σvk=λkv_k^\top \Sigma v_k = \lambda_k, and the covariance between two different ones is vj⊤Σvk=0v_j^\top \Sigma v_k = 0. So the eigen-decomposition re-expresses the market as NN uncorrelated eigen-portfolios, the kk-th with variance λk\lambda_k. Every portfolio is a mix of them, and its variance splits cleanly, with no cross terms:

w⊤Σw=∑k=1Nλk(vk⊤w)2,∑k=1Nλk=tr⁡(Σ)=∑i=1Nσi2w^\top \Sigma\, w = \sum_{k=1}^{N} \lambda_k\, (v_k^\top w)^2, \qquad \sum_{k=1}^{N} \lambda_k = \tr(\Sigma) = \sum_{i=1}^{N} \sigma_i^2

The lab checks each of these identities to machine precision on its 50-asset market. It also finds that 99.0% of the equal-weight portfolio's variance sits in the first eigen-portfolio alone: an equal-weight book is, for risk purposes, a bet on one thing.

TWO ASSETS, ONE CLOUD OF DAILY RETURNS — THE EIGENVECTORS ARE ITS AXES asset A return → asset B return ↑ PC1 PC2 PC1 · THE COMMON MOVE weights (1, 1)/√2 — both assets up together variance λ₁ = σ²(1 + ρ): the long axis of the cloud PC2 · THE SPREAD weights (1, −1)/√2 — long one, short the other variance λ₂ = σ²(1 − ρ): the short axis, what is left ROTATE TO THESE AXES the two new return series are uncorrelated, and λ₁ + λ₂ = the two assets' total variance Equal volatilities σ, correlation ρ. PCA finds the same axes in N dimensions, longest first.
fig 3.3.5 — two positively correlated assets: the returns form a tilted ellipse, its long axis is PC1 (the common move) and its short axis PC2 (the spread)

Fig 3.3.5 shows the geometry for two assets with equal volatility σ\sigma and correlation ρ\rho. The cloud of daily returns is an ellipse. Its long axis, both assets up together, is the first eigenvector, and the variance along it is λ1=σ2(1+ρ)\lambda_1 = \sigma^2(1 + \rho). Its short axis, one up and the other down, is the second, with λ2=σ2(1−ρ)\lambda_2 = \sigma^2(1 - \rho). The higher the correlation, the longer and thinner the ellipse — and the less variance is left in the spread. Rotating the coordinates to these axes turns two correlated return series into two uncorrelated ones without losing any variance.

Because each eigenvalue is the variance of a portfolio, none can be negative. A symmetric matrix is positive semi-definite (PSD) when w⊤Σw≥0w^\top \Sigma w \ge 0 for every ww; equivalently, when all its eigenvalues are ≥0\ge 0. Every true covariance matrix is PSD, and so is every sample covariance matrix computed from one complete table (it is an average of outer products). A zero (or near-zero) eigenvalue means some portfolio has (almost) no variance: a perfect hedge, or a redundant asset such as an ETF held alongside all its constituents. When all eigenvalues are strictly positive the matrix is positive definite and can be inverted. A negative eigenvalue means the "matrix" is not the covariance matrix of anything (§3.3.6).

📌 Key idea — eigenvectors are portfolios, eigenvalues are their variances

Diagonalising Σ\Sigma re-expresses the market as uncorrelated portfolios whose variances add up to the total. The big eigenvalues are the risks everything shares; the small ones are the fine print — and, as §3.3.7 shows, the fine print is where estimation error hides.

3.3.5 PCA: the hidden structure of a market

Principal component analysis is the eigen-decomposition of a covariance matrix estimated from data. On a stock market the first component is almost always the market itself. On synthetic data you can check what PCA gets right, and what it cannot know.

Principal component analysis (PCA) estimates the covariance (or correlation) matrix of returns, eigen-decomposes it, and calls the eigenvectors principal components. PC1 is the unit-length weight vector with the largest variance; PC2 is the largest-variance direction uncorrelated with PC1; and so on down. Keep the first few and you have compressed NN correlated return series into a handful of uncorrelated factors plus what is left over.

The lab tests PCA where the answer is known. It simulates 10 years (2,520 trading days) of returns on 50 assets driven by 3 factors: a market factor that every asset loads on (loadings β\beta scattered around 1) and two style factors with loadings scattered around 0. Each factor has 15% annual volatility, and every asset adds 25% of its own noise. That is a factor model with known factor loadings BB — the structure Session 7.2 — CAPM, APT & Factor Regressions estimates by regression — so the true covariance matrix is known too:

Σ=Bdiag⁡(σf2)B⊤+σe2I\Sigma = B\, \diag(\sigma_f^2)\, B^\top + \sigma_e^2\, I

It has 3 large eigenvalues and 47 identical small ones, each worth 1.1% of the total. Fig 3.3.6 compares the estimate with the truth.

SHARE OF TOTAL VARIANCE CARRIED BY EACH PRINCIPAL COMPONENT (50 ASSETS) sample, 10 years of data truth 0% 10% 20% 30% 40% PC1 · sample, 10 years of data: 29% PC1 · truth: 29% PC1 PC2 · sample, 10 years of data: 10% PC2 · truth: 10% PC2 PC3 · sample, 10 years of data: 8% PC3 · truth: 8% PC3 PC4 · sample, 10 years of data: 1% PC4 · truth: 1% PC4 PC5 · sample, 10 years of data: 1% PC5 · truth: 1% PC5 PC6 · sample, 10 years of data: 1% PC6 · truth: 1% PC6 PC7 · sample, 10 years of data: 1% PC7 · truth: 1% PC7 PC8 · sample, 10 years of data: 1% PC8 · truth: 1% PC8
fig 3.3.6 — the eigenvalue spectrum: three components stand out, then an "elbow" and a flat floor of noise; the sample spectrum spreads the floor a little

PC1 carries 28.6% of the total variance (truth: 29.1%), and the first three together 46.4% (truth: 46.9%). After PC3 the spectrum drops to a floor; sampling noise spreads that floor out, so PC4 is estimated at 1.5% although its true share is 1.1%. The elbow is how you would guess the number of factors on real data, where nobody tells you.

PC1 is the market mode: 100% of its weights are positive, and they line up with the planted market loadings with a correlation of 0.958 (fig 3.3.7). Even the true PC1 correlates only 0.961 with the market loadings, because the style loadings are not exactly orthogonal to them, so the top eigenvector mixes a little of each in.

PC1 WEIGHT VS THE PLANTED MARKET LOADING, ONE DOT PER ASSET 0.05 0.10 0.15 0.20 0.25 0.50 0.75 1.00 1.25 1.50 1.75 2.00 true market loading (β) PC1 weight OLS fit: slope 0.119
fig 3.3.7 — each dot is one asset: its weight in the sample PC1 against its planted market loading; PCA found the market without being told it exists

How close is close? Sampling theory for PCA (Anderson, 1963) gives the expected squared angle θ1\theta_1 between the sample PC1 and the true one for normal returns and long samples:

𝔼[sin2⁡θ1]≈1T−1∑k≠1λ1λk(λ1−λk)2\E[\sin^2 \theta_1] \approx \frac{1}{T-1} \sum_{k \ne 1} \frac{\lambda_1 \lambda_k}{(\lambda_1 - \lambda_k)^2}

In words: an eigenvector is pinned down well when you have many observations and its eigenvalue stands well clear of the others. Here the formula predicts a typical angle of 2.1°, and the lab's sample PC1 sits 1.8° from the truth. Across 300 simulated decades from the same market, the average sin2⁡θ1\sin^2\theta_1 is 0.00137 ± 0.00003 against a prediction of 0.00130, inside the four standard errors the lab asserts. The same theory predicts what share of the market loading vector falls outside the estimated three-factor subspace: 0.081%, against 0.081% simulated.

What PCA cannot know is the labels. PC2 and PC3 together span the same plane as the two style factors — at least 99.7% of each loading vector lies in the space of the top three components — but PC2 correlates only 0.79 and 0.70 with the two style loadings. It is a blend, and any rotation inside that plane explains the data equally well. PCA recovers the subspace, not the factors' names. Naming them ("value", "momentum") takes information PCA never sees, such as company characteristics, which is how the risk models of Session 9.4 — Factor Risk Models are built. The lab's code, with the eigenvector signs fixed (an eigenvector's sign is arbitrary, and numerical libraries return either):

def eig_desc(m: np.ndarray) -> tuple[np.ndarray, np.ndarray]:
    """Eigenvalues (largest first) and eigenvectors of a symmetric matrix, signs fixed so sum(v) ≥ 0."""
    lam, vec = np.linalg.eigh(m)            # eigh: for symmetric matrices; ascending order
    lam, vec = lam[::-1], vec[:, ::-1]
    vec = vec * np.where(vec.sum(axis=0) < 0, -1.0, 1.0)
    return lam, vec

S = R.cov().to_numpy()                      # sample covariance (divides by T − 1)
lam_s, V_s = eig_desc(S)
assert np.allclose(V_s @ np.diag(lam_s) @ V_s.T, S)                 # S = V Λ V'
pcs = (R - R.mean()).to_numpy() @ V_s                               # returns of the eigen-portfolios

Real markets look the same at the top. In Laloux et al.'s (1999) study of 406 S&P 500 stocks over 1991–96, the largest eigenvalue of the correlation matrix sat far above anything pure noise could produce, and its eigenvector had "roughly equal components on all the N stocks" — the market — while 94% of the eigenvalues were indistinguishable from noise. Session 9.2 — Covariance Estimation makes that "indistinguishable from noise" precise.

🧭 Margin note — covariance or correlation?

PCA on Σ\Sigma weights each asset by its variance, so in a mixed book the most volatile assets dominate PC1: a crypto coin swamps a bond fund. Run PCA on the correlation matrix CC (standardised returns) when volatilities differ a lot and you want the co-movement structure; run it on Σ\Sigma when the units are comparable and you care about risk in money.

💡 Looking ahead

PCA comes back three times: Session 8.3 — Pairs Trading & Statistical Arbitrage trades the residuals left after removing the top components (PCA-residual statistical arbitrage), Session 9.4 — Factor Risk Models builds factor risk models in which a few factors explain the common risk, and Session 11.2 — Duration, Convexity & Curve PCA finds that three components — level, slope and curvature — drive almost every yield curve.

3.3.6 When the matrix lies: positive semi-definiteness

A covariance matrix must be positive semi-definite, because variances cannot be negative. A matrix estimated one entry at a time can break that rule without any error message — and an optimizer will find the break before you do.

Correlations cannot be chosen freely. If asset A is 0.9-correlated with both B and C, then B and C are forced to be at least 0.62 correlated: the smallest value that keeps the 3×33 \times 3 matrix PSD is ρABρAC−(1−ρAB2)(1−ρAC2)\rho_{AB}\rho_{AC} - \sqrt{(1-\rho_{AB}^2)(1-\rho_{AC}^2)}. Type in −0.9 instead and the "correlation matrix" has an eigenvalue of −0.80. Its eigenvector — short A, long B and long C in equal parts — is a portfolio with negative variance. No market can produce it, but a minimum-variance optimizer handed this matrix will pile into that portfolio without limit, because every extra unit "reduces" the risk.

Nobody types an impossible correlation on purpose. The common way to get a non-PSD matrix is missing data. The lab takes one year (252 days) of returns on the same 50 assets and blanks out each asset on a random 40% of days, as happens with illiquid assets, different exchange holidays or a flaky feed. Listwise deletion — keep only the days on which every asset has a price — leaves 0 days. pandas' DataFrame.cov() instead uses pairwise deletion by default: each entry is estimated from the days on which that particular pair overlaps, 92 days on average. Every entry is a sensible estimate; the documentation itself warns that the matrix as a whole "is not guaranteed to be positive semi-definite".

S_pw = X_obs.cov().to_numpy()                             # pandas default: pairwise-complete
lam_pw, V_pw = eig_desc(S_pw)
n_neg = int((lam_pw < 0).sum())
assert n_neg >= 1, "pairwise estimate should not be PSD in this design"
...
S_clip = V_pw @ np.diag(np.clip(lam_pw, 0, None)) @ V_pw.T

Here 9 of the 50 eigenvalues are negative. Scale the worst eigen-portfolio to 100% gross exposure and the matrix assigns it an annual variance of −0.0007 — while its true volatility is 4.4%.

The standard repair is eigenvalue clipping, the last line of the snippet: set the negative eigenvalues to zero and rebuild. The result is the nearest PSD matrix in the Frobenius norm, and because that is a projection onto a convex set that contains the true Σ\Sigma, it can only move the estimate closer to the truth (the lab asserts this). But read the small print:

  • Valid is not accurate. The error against the true Σ\Sigma goes from 34.6% to 33.9%. The matrix was noisy because each entry saw only about 92 days, and clipping does not fix that.
  • Clipped directions now claim zero risk. The worst portfolio's estimated variance is now exactly 0, and its true volatility is still 4.4%. An optimizer will treat those directions as free hedges. Floor the eigenvalues at a small positive value instead, or better, use a shrinkage estimator (Session 9.2) or a factor model (Session 9.4).
  • It moves the variances too. Here by up to 5.3%. For a correlation matrix you also need the unit diagonal back: that is the nearest correlation matrix problem, which Higham (2002) solves by alternating projections.
  • It treats the symptom. The cause is the data: align it (Session 4.4) and check it (Session 4.6) before you estimate anything.
SituationWhat breaksWhat to do
Missing or misaligned data, pairwise deletionnegative eigenvalues; "negative-variance" portfoliosalign the data; a model-based estimate; clip only as a last resort
Hand-edited or stress-scenario correlationsimpossible combinations, as in the 3 × 3 example abovecheck the smallest eigenvalue after every edit; nearest correlation matrix
Fewer periods than assets (T≤NT \le N)PSD but singular: zero eigenvalues, no inverseshrinkage or a factor model; more data per asset
A redundant asset (an ETF plus all its constituents)a zero or near-zero eigenvaluedrop the redundant asset
⚠️ Pitfall — "pandas returned a matrix, so it is a covariance matrix"

The call succeeded, the diagonal is positive, every pairwise correlation looks plausible — and the matrix can still be invalid. Every time you build Σ\Sigma from anything other than one complete, aligned table, check the smallest eigenvalue: np.linalg.eigvalsh(S)[0] >= -tol. It is a one-line unit test, and it catches the bug before an optimizer does.

3.3.7 The condition number: why inverting Σ amplifies noise

Every tool that uses Σ−1\Sigma^{-1} divides by the small eigenvalues — the directions your data measured worst. The condition number tells you how much that division can magnify your estimation error.

The condition number of a positive definite matrix is the ratio of its largest to its smallest eigenvalue:

cond(Σ)=λmax⁡λmin⁡\mathrm{cond}(\Sigma) = \frac{\lambda_{\max}}{\lambda_{\min}}

(Numerical analysts write κ\kappa; this course reserves κ\kappa for the borrow fee of Session 2.2.) It bounds how errors are amplified: if you solve Σx=b\Sigma x = b and bb carries a relative error ε\varepsilon, the solution xx can carry a relative error up to cond(Σ)ε\mathrm{cond}(\Sigma)\,\varepsilon (Golub & Van Loan, 2013, §2.6). The reason is visible in the eigen-decomposition: Σ−1=VΛ−1V⊤\Sigma^{-1} = V \Lambda^{-1} V^\top, so inverting flips the spectrum and the smallest eigenvalue becomes the largest multiplier. The minimum-variance portfolio w∝Σ−1𝟏w \propto \Sigma^{-1}\mathbf 1, mean-variance weights Σ−1μ\Sigma^{-1}\mu and regression coefficients all lean hardest on the directions with the least variance.

In a sample, those are exactly the directions estimated worst, and wrong in a predictable way: the smallest sample eigenvalues are biased down and the largest up, more so as the number of observations per asset, T/NT/N, falls. Random-matrix theory — the Marchenko–Pastur law, which Session 9.2 puts to work — predicts by how much. So a sample Σ\Sigma is worse conditioned than the truth: the lab's true matrix has cond(Σ)\mathrm{cond}(\Sigma) = 25.7, and even 10 years of daily data give an estimate at 33.1.

SAME NOISE, TWO MATRICES — WHAT INVERTING Σ DOES WITH IT sample 1 sample 2 WELL-CONDITIONED · cond(Σ) small truth (solid) estimate (dashed) min-variance weights, 5 assets two samples, nearly the same book every direction is well measured; Σ⁻¹ stays tame ILL-CONDITIONED · cond(Σ) huge truth (solid) estimate (dashed) min-variance weights, 5 assets two samples, opposite bets the thin direction is barely measured; Σ⁻¹ blows it up An optimizer divides by the smallest eigenvalue — the direction your data measured worst.
fig 3.3.8 — well- versus ill-conditioned (illustrative bars): the same estimation noise barely moves Σ⁻¹-based weights when every direction is well measured, and flips them when one direction is thin

The lab measures the damage. For each ratio T/NT/N it draws 500 samples from the same 50-asset market, estimates SS, and builds the sample global minimum-variance portfolio wˆ=S−1𝟏/(𝟏⊤S−1𝟏)\hat w = S^{-1}\mathbf 1 / (\mathbf 1^\top S^{-1} \mathbf 1). It then compares the variance the portfolio promises (measured with SS) and the variance it delivers (measured with the true Σ\Sigma) with the true minimum variance σmin⁡2\sigma^2_{\min}. For normal returns both have exact expectations, derived in the lab from the Wishart distribution of SS (Kan & Smith, 2008, develop the full finite-sample theory):

𝔼[wˆ⊤Swˆ]σmin⁡2=T−NT−1,𝔼[wˆ⊤Σwˆ]σmin⁡2=T−2T−N−1\frac{\E[\hat w^\top S\, \hat w]}{\sigma^2_{\min}} = \frac{T-N}{T-1}, \qquad \frac{\E[\hat w^\top \Sigma\, \hat w]}{\sigma^2_{\min}} = \frac{T-2}{T-N-1}
T/NT/NDays TTMean cond(S)\mathrm{cond}(S)cond(S)/cond(Σ)\mathrm{cond}(S) / \mathrm{cond}(\Sigma)Promised variance ÷ true minimumDelivered variance ÷ true minimum (exact)Weight error
1.2602,8641110.176.37 (6.44)250%
210026510.30.512.01 (2.00)111%
5250783.00.811.25 (1.25)55%
10500522.00.901.11 (1.11)37%
502,500341.30.981.02 (1.02)16%

The weight error is the total absolute difference between the estimated and the true minimum-variance weights, as a share of the true weights' gross size. The simulated ratios match the exact formulas within four standard errors at every T/NT/N, which the lab asserts.

MIN-VARIANCE PORTFOLIO: VARIANCE ÷ TRUE MINIMUM, BY DAYS PER ASSET (T/N) promised (in-sample) realised (true) 0.0× 2.0× 4.0× 6.0× 8.0× 1.2 · promised (in-sample): 0.2× 1.2 · realised (true): 6.4× 1.2 1.5 · promised (in-sample): 0.3× 1.5 · realised (true): 3.1× 1.5 2 · promised (in-sample): 0.5× 2 · realised (true): 2.0× 2 3 · promised (in-sample): 0.7× 3 · realised (true): 1.5× 3 5 · promised (in-sample): 0.8× 5 · realised (true): 1.2× 5 10 · promised (in-sample): 0.9× 10 · realised (true): 1.1× 10 20 · promised (in-sample): 1.0× 20 · realised (true): 1.1× 20 50 · promised (in-sample): 1.0× 50 · realised (true): 1.0× 50
fig 3.3.9 — the sample minimum-variance portfolio promises less risk than is possible and delivers more; the gap closes only when there are many days per asset

Read the T/N=2T/N = 2 row as a story: 100 days of data on 50 assets. The optimizer promises about half the true minimum variance and delivers about twice it, so the realised volatility is 1.99 times what was promised, and the weights miss by 111% of their own gross size. Nothing about the code is buggy. It is the arithmetic of dividing by eigenvalues that the data could not pin down.

📌 Key idea — an optimizer amplifies whatever you failed to measure

Inverting a sample covariance matrix points an amplifier at its smallest, noisiest eigenvalues. The defences are more observations per asset, a better-conditioned estimate (shrinkage, factor models) and constraints on the weights. Session 9.1 — Mean-Variance & Why It Breaks shows why unconstrained mean-variance optimisation is called an error maximiser, and Session 3.4 — Convex Optimization shows how constraints tame the optimizer.

💡 Looking ahead

The standard fix pulls SS toward a well-conditioned target: Ledoit–Wolf shrinkage (Session 9.2), a form of the shrinkage of Session 5.3. It raises the small eigenvalues and lowers the large ones — the exact opposite of the sample bias.

3.3.8 Try it

Open the companion lab, predict before you run, and change one thing at a time.

🔧 Try it (15 min, in the lab)

1. Predict, then check. With NN = 50, for what number of days TT does the formula (T−2)/(T−N−1)(T-2)/(T-N-1) say the minimum-variance portfolio delivers 1.25 times the true minimum variance? Solve it by hand, then compare with the T/N=5T/N = 5 row of the table in §3.3.7.
2. Less noise, stronger market. In section 3 of the lab, change IDIO_VOL from 0.25 to 0.10 and rerun. Before you look, predict three things: does PC1's share of variance go up or down, does the predicted PC1 angle (pc1_angle_deg_theory) grow or shrink, and do you get more or fewer negative eigenvalues in section 7? (Hint: λmin⁡\lambda_{\min} is σe2\sigma_e^2.)
3. Less missing data. Set P_MISS to 0.30 in section 7. How many negative eigenvalues are left, and does clipping help more or less?
Success looks like this: every assert still passes, and you can explain each change from a formula on this page.


3.3.9 Glossary · Check yourself · Go deeper

Weight vector — the column w=(w1,…,wN)⊤w = (w_1, \dots, w_N)^\top of capital fractions in each asset (negative for shorts); the portfolio return is the dot product w⊤Rw^\top R.
Covariance matrix — the N×NN \times N symmetric matrix Σ\Sigma with Σij=Cov⁡(Ri,Rj)\Sigma_{ij} = \Cov(R_i, R_j): variances on the diagonal, covariances off it.
Correlation matrix — the matrix CC of pairwise correlations, ones on the diagonal; Σ=DCD\Sigma = DCD with DD the diagonal matrix of volatilities.
Portfolio variance — the variance of a portfolio's return, the quadratic form w⊤Σw=∑i∑jwiwjΣijw^\top \Sigma w = \sum_i \sum_j w_i w_j \Sigma_{ij}.
Quadratic form — an expression such as w⊤Σww^\top \Sigma w in which every term multiplies two entries of a vector; portfolio variance is the quadratic form built from the weight vector and the covariance matrix.
Eigenvalue — the factor λ\lambda by which a matrix stretches one of its eigenvectors, Σv=λv\Sigma v = \lambda v; for a covariance matrix, the variance of the eigen-portfolio vv.
Eigenvector — a direction vv that a matrix only stretches, Σv=λv\Sigma v = \lambda v; a covariance matrix's eigenvectors are uncorrelated portfolios.
Eigen-portfolio — the portfolio whose weights are one eigenvector of Σ\Sigma; eigen-portfolios are mutually uncorrelated, and the kk-th has variance equal to its eigenvalue λk\lambda_k.
Principal component analysis — eigen-decomposing an estimated covariance or correlation matrix; the components are uncorrelated portfolios sorted by variance, the first usually the market.
Market mode — the first principal component of a market's returns: usually all its weights share one sign, so it moves every asset together.
Positive semi-definite — a symmetric matrix with w⊤Σw≥0w^\top \Sigma w \ge 0 for every ww, equivalently all eigenvalues ≥0\ge 0; every valid covariance matrix is one.
Positive definite — a positive semi-definite matrix whose eigenvalues are all strictly positive rather than merely non-negative; unlike a positive semi-definite matrix, it can be inverted.
Condition number — cond(Σ)=λmax⁡/λmin⁡\mathrm{cond}(\Sigma) = \lambda_{\max}/\lambda_{\min}, how far a matrix is from singular; it bounds how much inverting it can amplify relative errors.
Diversification ratio — the weighted-average volatility of a portfolio's holdings divided by the portfolio's own volatility; the more the holdings' risk cancels out, the higher it is.
Effective number of independent bets — the square of the diversification ratio: the number of uncorrelated assets that would give the same risk reduction as the actual, correlated book.
Eigenvalue clipping — repairing a non-PSD matrix by setting its negative eigenvalues to zero and rebuilding it; the result is the nearest PSD matrix in the Frobenius norm, but every clipped direction is left claiming zero risk.
Nearest correlation matrix — the closest valid correlation matrix (positive semi-definite, unit diagonal) to a broken one, found by alternating projections (Higham, 2002).
Pairwise deletion — estimating each entry of a covariance or correlation matrix from whichever observations that one pair of assets happens to share; convenient with missing data, but the resulting matrix need not be positive semi-definite.
Listwise deletion — keeping only the rows on which every asset has an observation before estimating a covariance matrix; always positive semi-definite, but can throw away most of the data.

Check yourself

1. Two assets, each with 20% volatility and correlation 0.5, held 50/50. What is the portfolio's volatility?
w⊤Σw=0.25×0.04+0.25×0.04+2×0.25×0.5×0.04=0.03w^\top \Sigma w = 0.25 \times 0.04 + 0.25 \times 0.04 + 2 \times 0.25 \times 0.5 \times 0.04 = 0.03, so the volatility is 0.03≈17.3%\sqrt{0.03} \approx 17.3\%. Three equal parts: the two own-risk cells, and the two co-movement cells together, worth as much as either. The diversification benefit, from 20% down to 17.3%, is modest because the correlation is high.
2. You hold 200 stocks in equal weights; average volatility 30%, average pairwise correlation 0.25. Roughly how volatile is the book, and what does doubling it to 400 stocks buy you?
0.25+0.75/200×30%≈15.1%\sqrt{0.25 + 0.75/200} \times 30\% \approx 15.1\%, and with 400 stocks ≈15.06%\approx 15.06\%: almost nothing. You are already on the floor ρσ=15%\sqrt{\rho}\,\sigma = 15\%. Only lower correlation — other asset classes, market-neutral positions — lowers it further.
3. A colleague's risk model has eigenvalues 12.1, 3.4, 0.9, …, −0.02. What does the last one mean, and what do you do?
The matrix is not positive semi-definite: some portfolio has "negative variance", and an optimizer will exploit it without limit. Find the cause first — pairwise deletion of missing data, misaligned dates, a hand-edited correlation. Fix the data or switch to a proper estimator (shrinkage, factor model). Clipping the eigenvalue to zero is a stopgap that leaves a "zero-risk" portfolio behind.
4. PCA on 500 stocks: PC1 has all-positive weights and explains 35% of the variance. Your manager says "PC2 is obviously the value factor." What is right and what is overreach?
PC1 being the market mode is the usual finding. But PC2 is defined only as the largest-variance direction uncorrelated with PC1: its sign is arbitrary, and when PC2 and PC3 have similar eigenvalues any rotation of the two fits the data equally well. PCA recovers a subspace, not labels. To call it "value" you must check it against value characteristics — which is what a factor risk model does.
5. You have 60 days of data on 100 stocks and want the minimum-variance portfolio. What goes wrong?
With T≤NT \le N the sample covariance matrix has rank at most T−1T - 1, so it has zero eigenvalues: its condition number is infinite and S−1S^{-1} does not exist. The optimizer finds "zero-risk" portfolios that are really just directions the data never saw. You need far more days per asset, a shrinkage or factor-model estimate, or constraints.
6. A 60/40 portfolio holds 40% of its capital in bonds. Why is "40% bonds" a poor description of its risk?
Risk contributions are wi(Σw)iw_i (\Sigma w)_i, not wiw_i. Equities are far more volatile, so with the illustrative inputs of §3.3.3 they carry about 90% of the variance. Capital weights describe where the money is; risk contributions describe where the risk is.
🔧 Homework (~1 h before Session 3.4)

1. Write portfolio_vol(w, vols, corr) that builds Σ=DCD\Sigma = DCD and returns w⊤Σw\sqrt{w^\top \Sigma w}. Unit-test it against the two-asset formula (agreement to 10−1210^{-12}) and against the sample volatility of simulated portfolio returns (agreement within four standard errors).
2. Add a cell to the lab that runs eig_desc on the correlation matrix C instead of S. Compare PC1's share of variance and PC1's weights with the covariance version, and explain the difference in one sentence.
3. Prepare for Session 3.4: write down the problem "minimise w⊤Σww^\top \Sigma w subject to 𝟏⊤w=1\mathbf 1^\top w = 1" and check numerically, with the lab's true Σ\Sigma, that no random fully invested portfolio beats Σ−1𝟏/(𝟏⊤Σ−1𝟏)\Sigma^{-1}\mathbf 1 / (\mathbf 1^\top \Sigma^{-1} \mathbf 1). Session 3.4 derives why.
4. Read Laloux et al. (1999), four pages, below.

📚 Go deeper

  1. Strang, G. (2016). Introduction to Linear Algebra, 5th ed., §6.4 "Symmetric Matrices", §6.5 "Positive Definite Matrices", §7.3 "Principal Component Analysis (PCA by the SVD)" and §12.2 "Covariance Matrices and Joint Probabilities". Wellesley-Cambridge Press. ISBN 978-0-9802327-7-6
  2. Jolliffe, I. T. & Cadima, J. (2016). "Principal component analysis: a review and recent developments." Philosophical Transactions of the Royal Society A 374(2065), 20150202. doi:10.1098/rsta.2015.0202
  3. Meucci, A. (2009). "Managing Diversification." Risk 22(5), 74–79. Effective number of bets from principal components. SSRN 1358533
  4. Higham, N. J. (2002). "Computing the nearest correlation matrix — a problem from finance." IMA Journal of Numerical Analysis 22(3), 329–343. doi:10.1093/imanum/22.3.329

Sources for the numbers

  1. Anderson, T. W. (1963). "Asymptotic Theory for Principal Component Analysis." Annals of Mathematical Statistics 34(1), 122–148. doi:10.1214/aoms/1177704248
  2. Kan, R. & Smith, D. R. (2008). "The Distribution of the Sample Minimum-Variance Frontier." Management Science 54(7), 1364–1380. doi:10.1287/mnsc.1070.0852
  3. Laloux, L., Cizeau, P., Bouchaud, J.-P. & Potters, M. (1999). "Noise Dressing of Financial Correlation Matrices." Physical Review Letters 83(7), 1467–1470. doi:10.1103/PhysRevLett.83.1467
  4. Choueifaty, Y. & Coignard, Y. (2008). "Toward Maximum Diversification." Journal of Portfolio Management 35(1), 40–51. doi:10.3905/JPM.2008.35.1.40
  5. Golub, G. H. & Van Loan, C. F. (2013). Matrix Computations, 4th ed., §2.6 "The Sensitivity of Square Systems". Johns Hopkins University Press. ISBN 978-1-4214-0794-4
  6. pandas development team (2026). "pandas.DataFrame.cov", pandas 3.0 API reference (notes on missing data and positive semi-definiteness). https://pandas.pydata.org/docs/reference/api/pandas.DataFrame.cov.html