Linear Algebra for Quantitative Finance: Portfolio Math

Michael BrenndoerferOctober 23, 202552 min read

Part of Quantitative Finance

Covers vectors, matrices, and decompositions for portfolio optimization, risk analysis, and factor models. Essential math foundations for quant finance.

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Linear Algebra for Quantitative Finance

A portfolio return can be a dot product, its variance a quadratic form, and a multi-factor hedge a system of equations. The same small set of linear-algebra operations turns hundreds of asset-level quantities into calculations you can inspect and implement. Eigenvectors of a covariance matrix then provide directions of variation that can be used to study common movements in returns.

This chapter builds your fluency in linear algebra with a constant eye toward financial applications. We start with vectors and matrices, showing how they naturally represent portfolios and return data. We then tackle systems of linear equations through factor replication, hedging, and regression examples. Finally, we explore matrix decompositions, the techniques that enable principal component analysis and expose patterns in financial data.

Vectors in Finance

A vector is an ordered list of numbers. In finance, vectors appear everywhere: asset returns, portfolio weights, and factor exposures. The power of vectors lies in how we can manipulate them mathematically to answer financial questions.

Why do we need vectors rather than simply tracking individual numbers? Consider the alternative: if you manage a portfolio of 500 stocks, you could track 500 separate weight variables and 500 separate return variables. But this approach quickly becomes unwieldy. You'd need to write out 500 terms every time you calculate portfolio return, and any formula involving all assets would span pages. Vectors solve this organizational challenge by packaging related quantities into a single mathematical object that we can manipulate as a unit. This abstraction isn't merely notational convenience; it reveals structure. Operations that require hundreds of scalar terms can be written compactly and evaluated with standard vector routines.

Vector Basics

Consider a portfolio containing three assets. We can represent the weights allocated to each asset as a vector:

w=[w1w2w3]=[0.40.350.25]\mathbf{w} = \begin{bmatrix} w_1 \\ w_2 \\ w_3 \end{bmatrix} = \begin{bmatrix} 0.4 \\ 0.35 \\ 0.25 \end{bmatrix}

where:

  • w\mathbf{w}: the portfolio weight vector
  • wiw_i: the fraction of portfolio value allocated to asset ii

Here w1=0.4w_1 = 0.4 means 40% of the portfolio value is in asset 1. The vector w\mathbf{w} lives in R3\mathbb{R}^3 (three dimensional real space) because it has three components. Geometrically, each asset corresponds to an axis, and the weight vector points to a specific location in this three-dimensional "asset space." Every possible portfolio allocation corresponds to some point in this space. Portfolio constraints, like requiring weights to sum to one, define surfaces or regions within it.

Similarly, we can represent the returns of these three assets on a given day:

r=[r1r2r3]=[0.02−0.010.015]\mathbf{r} = \begin{bmatrix} r_1 \\ r_2 \\ r_3 \end{bmatrix} = \begin{bmatrix} 0.02 \\ -0.01 \\ 0.015 \end{bmatrix}

where:

  • r\mathbf{r}: the return vector for a single time period
  • rir_i: the return of asset ii (expressed as a decimal, so 0.02 = 2%)

This says asset 1 returned 2%, asset 2 lost 1%, and asset 3 gained 1.5%. Notice how naturally this representation captures a snapshot of market behavior: all three returns belong together because they occurred simultaneously, and the vector keeps them organized as a coherent unit.

Vector Operations

The fundamental vector operations translate directly into financial calculations:

Scalar multiplication scales every element by a constant. If you double your position in everything:

2w=[0.80.70.5]2\mathbf{w} = \begin{bmatrix} 0.8 \\ 0.7 \\ 0.5 \end{bmatrix}

Geometrically, scalar multiplication stretches or shrinks the vector without changing its direction. Financially, this corresponds to leveraging or deleveraging a portfolio, maintaining the same relative allocations. A leveraged portfolio with 2x weights has double the exposure to every asset, magnifying both gains and losses proportionally.

Vector addition combines vectors element-wise. If r1\mathbf{r}_1 and r2\mathbf{r}_2 are returns on consecutive days:

r1+r2=[r1,1+r1,2r2,1+r2,2r3,1+r3,2]\mathbf{r}_1 + \mathbf{r}_2 = \begin{bmatrix} r_{1,1} + r_{1,2} \\ r_{2,1} + r_{2,2} \\ r_{3,1} + r_{3,2} \end{bmatrix}

where ri,tr_{i,t} denotes the return of asset ii on day tt.

For small returns, this approximates cumulative returns (the exact formula uses geometric compounding). Vector addition also models combining different portfolios. If two funds each contribute capital, the combined portfolio's weight vector is approximately the sum of the individual weight vectors, scaled by the relative capital contributions.

The Dot Product: Portfolio Returns

The dot product (or inner product) of two vectors is the sum of the products of corresponding elements:

w⋅r=w1r1+w2r2+w3r3=∑i=1nwiri\mathbf{w} \cdot \mathbf{r} = w_1 r_1 + w_2 r_2 + w_3 r_3 = \sum_{i=1}^{n} w_i r_i

where:

  • nn: the number of assets in the portfolio.

This single number is the one-period portfolio simple return when w\mathbf{w} contains beginning-of-period weights, r\mathbf{r} contains same-period simple returns, the weights sum to one, and there are no intra-period cash flows or trading costs. It is not generally the exact portfolio log return when r\mathbf{r} contains log returns. Each asset contributes to the simple return in proportion to both its individual simple return and the fraction of beginning capital allocated to it. The dot product sums these contributions.

Why does this work? Consider a $1 portfolio allocated at the beginning of the period and held without intra-period rebalancing. The amount invested in asset 1 is \w_1,andthisgrowsto, and this grows to $w_1(1 + r_1).Similarlyforeachasset.Withnocashflowsortradingcosts,theendingvalueis. Similarly for each asset. With no cash flows or trading costs, the ending value is $\sum w_i(1 + r_i) = $(\sum w_i + \sum w_i r_i).Sincethefullyinvestedweightssumto1,thisequals. Since the fully invested weights sum to 1, this equals $(1 + \sum w_i r_i),confirmingtheone−periodsimplereturn, confirming the one-period simple return \mathbf{w} \cdot \mathbf{r}$. Portfolios with borrowing or short positions must include their cash or financing leg, and a new period uses the weights prevailing at that period's start.

In[2]:
Code
import numpy as np

# Portfolio weights (must sum to 1 for a fully invested portfolio)
weights = np.array([0.4, 0.35, 0.25])

# Daily returns for three assets
returns = np.array([0.02, -0.01, 0.015])

# Portfolio return is the dot product
portfolio_return = np.dot(weights, returns)
Out[3]:
Console
Asset weights: [0.4  0.35 0.25]
Asset returns: [ 0.02  -0.01   0.015]
Portfolio return: 0.0083 (0.83%)

Calculation: 0.4×0.02 + 0.35×(-0.01) + 0.25×0.015 = 0.0083
Out[4]:
Visualization
Bar chart of three weighted simple-return contributions, with positive contributions in green and negative in red, and a dashed line marking the total one-period portfolio simple return.
One-period portfolio simple return as the sum of beginning weights times same-period asset simple returns. Each bar shows one asset's contribution under the fully invested, no-cash-flow example.

The portfolio earned a 0.83% simple return by beginning the period with 40% in the 2% gainer, 35% in the 1% loser, and 25% in the 1.5% gainer. Under the conditions stated above, this dot product aggregates the asset simple returns into the one-period portfolio simple return.

Vector Norms: Measuring Size

The norm of a vector measures its "size" in various ways. But what does "size" mean for a vector? There's no single answer. Different norms capture different notions of magnitude, each useful in different contexts. The most common is the Euclidean norm (or L2 norm):

∥v∥2=∑i=1nvi2\|\mathbf{v}\|_2 = \sqrt{\sum_{i=1}^{n} v_i^2}

where:

  • ∥v∥2\|\mathbf{v}\|_2: the L2 (Euclidean) norm of vector v\mathbf{v}
  • viv_i: the ii-th component of the vector
  • nn: the dimension of the vector

The Euclidean norm corresponds to our intuitive notion of distance: it's the straight-line distance from the origin to the point represented by the vector. In finance, the L2 norm of a return vector relates to volatility. For a vector of deviations from the mean return, the L2 norm (scaled appropriately) gives the standard deviation. This connection between geometric distance and financial risk is one reason the L2 norm appears so frequently in portfolio optimization.

The L1 norm sums absolute values:

∥v∥1=∑i=1n∣vi∣\|\mathbf{v}\|_1 = \sum_{i=1}^{n} |v_i|

The L1 norm measures total "travel distance" if you could only move along coordinate axes, like navigating a city grid where you can only travel along streets, not diagonally through blocks. In portfolio optimization, an L1 penalty can promote sparse positions when weights may take either sign or when the budget does not already fix their L1 norm. For a long-only, fully invested portfolio, however, ∥w∥1=∑iwi=1\|\mathbf{w}\|_1 = \sum_i w_i = 1, so an L1 penalty alone cannot select a sparser portfolio. An L2 penalty can discourage concentrated weights under a specified objective and constraints, but it does not guarantee an evenly spread allocation.

In[5]:
Code
# Different norms of a return vector
returns_vector = np.array([0.02, -0.01, 0.015, -0.005, 0.01])

l2_norm = np.linalg.norm(returns_vector, ord=2)  # Euclidean norm
l1_norm = np.linalg.norm(returns_vector, ord=1)  # Sum of absolute values
linf_norm = np.linalg.norm(returns_vector, ord=np.inf)  # Max absolute value
Out[6]:
Console
Return vector: [ 0.02  -0.01   0.015 -0.005  0.01 ]
L2 norm: 0.029155
L1 norm: 0.060000
L∞ norm (max absolute): 0.020000

The L2 norm of 0.029 represents the Euclidean magnitude of the return vector, which relates to total variability. The L1 norm of 0.06 sums absolute returns and can support sparsity-inducing penalties when the surrounding constraints do not make it constant. The L∞ norm of 0.02 identifies the largest absolute return, which shows the most extreme daily movement.

Matrices in Finance

A matrix is a rectangular array of numbers. While vectors represent single entities (one portfolio, one day of returns), matrices represent collections and relationships. These include return history across time, covariances between assets, and transformations between coordinate systems.

The jump from vectors to matrices is conceptually significant. A vector captures one snapshot, such as today's returns or a single portfolio's weights. A matrix captures an entire dataset or a complete description of how quantities relate to each other. When we write down a covariance matrix, we're encoding each asset's volatility and every pairwise relationship in the investment universe. When we write down a return matrix, we're capturing the complete history of how multiple assets performed over multiple time periods. This compression of information into a structured rectangular array is what makes quantitative finance computationally tractable.

Matrix Fundamentals

An m×nm \times n matrix has mm rows and nn columns. In finance, we commonly organize data with rows as time periods and columns as assets:

R=[r1,1r1,2r1,3r2,1r2,2r2,3r3,1r3,2r3,3r4,1r4,2r4,3]\mathbf{R} = \begin{bmatrix} r_{1,1} & r_{1,2} & r_{1,3} \\ r_{2,1} & r_{2,2} & r_{2,3} \\ r_{3,1} & r_{3,2} & r_{3,3} \\ r_{4,1} & r_{4,2} & r_{4,3} \end{bmatrix}

Here rt,ir_{t,i} is the return of asset ii on day tt. This 4×34 \times 3 matrix contains 4 days of returns for 3 assets.

This chapter uses time as rows and assets as columns. Under that convention, each row represents the cross-section of the represented asset universe at one moment, while each column represents the complete time series of one asset. Extracting a row gives you all represented assets' returns on a specific day; extracting a column gives you one asset's return history. Other sources and libraries may use the transpose, so check the documented orientation before applying a matrix operation.

In[7]:
Code
# Simulated return matrix: 4 days, 3 assets
# Each row is a day, each column is an asset
return_matrix = np.array(
    [
        [0.02, -0.01, 0.015],  # Day 1
        [0.01, 0.02, -0.005],  # Day 2
        [-0.015, 0.005, 0.01],  # Day 3
        [0.005, -0.005, 0.02],  # Day 4
    ]
)
Out[8]:
Console
Return matrix (4 days × 3 assets):
[[ 0.02  -0.01   0.015]
 [ 0.01   0.02  -0.005]
 [-0.015  0.005  0.01 ]
 [ 0.005 -0.005  0.02 ]]

Shape: (4, 3)
Day 2 returns (row 1): [ 0.01   0.02  -0.005]
Asset 3 returns (column 2): [ 0.015 -0.005  0.01   0.02 ]

Matrix Multiplication

Matrix multiplication is the workhorse operation of linear algebra. For matrices A\mathbf{A} (size m×nm \times n) and B\mathbf{B} (size n×pn \times p), the product C=AB\mathbf{C} = \mathbf{A}\mathbf{B} is an m×pm \times p matrix where:

cij=∑k=1naikbkjc_{ij} = \sum_{k=1}^{n} a_{ik} b_{kj}

where:

  • cijc_{ij}: element in row ii, column jj of the result matrix C\mathbf{C}
  • aika_{ik}: element in row ii, column kk of matrix A\mathbf{A}
  • bkjb_{kj}: element in row kk, column jj of matrix B\mathbf{B}

Each element of the result is a dot product of a row from A\mathbf{A} with a column from B\mathbf{B}.

Each element of the result shows how much the ii-th row of A\mathbf{A} aligns with the jj-th column of B\mathbf{B}. In financial terms, when multiplying a return matrix by a weight vector, each resulting element aggregates the weighted contributions of all assets for that time period. You can think of matrix multiplication as performing many dot products simultaneously. The (i,j)(i,j) entry of the product answers the question "how does row ii of the first matrix relate to column jj of the second?"

This interpretation illuminates why matrix multiplication has the dimensional requirements it does. The dot product requires vectors of equal length, so for each row-column pair to produce a dot product, the row length (number of columns in A\mathbf{A}) must equal the column length (number of rows in B\mathbf{B}). The result matrix takes its row count from A\mathbf{A} and its column count from B\mathbf{B} because we compute one number for each possible row-column pairing.

Matrix Dimension Compatibility

For matrix multiplication AB\mathbf{AB} to be valid, the number of columns in A\mathbf{A} must equal the number of rows in B\mathbf{B}. The result has the number of rows from A\mathbf{A} and columns from B\mathbf{B}: (m×n)(n×p)=(m×p)(m \times n)(n \times p) = (m \times p).

A critical financial application is computing portfolio returns across multiple days. If R\mathbf{R} is a T×NT \times N matrix of returns (TT days, NN assets) and w\mathbf{w} is an N×1N \times 1 weight vector, then Rw\mathbf{R}\mathbf{w} gives a T×1T \times 1 vector of portfolio returns:

In[9]:
Code
# Portfolio returns across multiple days via matrix multiplication
weights = np.array([0.4, 0.35, 0.25])

# Matrix-vector multiplication: (4×3) @ (3,) = (4,)
portfolio_returns = return_matrix @ weights
Out[10]:
Console
Daily portfolio returns:
  Day 1: +0.0083 (+0.83%)
  Day 2: +0.0097 (+0.97%)
  Day 3: -0.0018 (-0.18%)
  Day 4: +0.0053 (+0.53%)

Mean portfolio return: 0.0054
Portfolio volatility: 0.0044

The Covariance Matrix

The covariance matrix captures how assets move together. For NN assets, it's an N×NN \times N symmetric matrix where element (i,j)(i,j) is the covariance between assets ii and jj:

Σij=Cov(ri,rj)=E[(ri−μi)(rj−μj)]\Sigma_{ij} = \text{Cov}(r_i, r_j) = \mathbb{E}[(r_i - \mu_i)(r_j - \mu_j)]

where:

  • Σij\Sigma_{ij}: the (i,j)(i,j) element of the covariance matrix Σ\Sigma
  • ri,rjr_i, r_j: returns of assets ii and jj
  • μi,μj\mu_i, \mu_j: expected (mean) returns of assets ii and jj
  • E[⋅]\mathbb{E}[\cdot]: the expectation operator

The formula shows what covariance measures: we're looking at the product of deviations from the mean. When asset ii is above its average and asset jj is also above its average, the product is positive. When both are below average, the product is again positive. But when one is above and the other below, the product is negative. By averaging these products across many observations, covariance tells us whether two assets tend to move together linearly (positive covariance), move oppositely linearly (negative covariance), or have little linear co-movement (covariance near zero). Zero covariance does not imply independence without additional assumptions; for example, a variable and its square can be dependent while having zero covariance under a symmetric distribution.

The diagonal elements Σii\Sigma_{ii} are variances of individual assets. The off-diagonal elements measure co-movement. Positive covariance means assets tend to move together. Negative covariance means they move oppositely.

In[11]:
Code
# Compute covariance matrix from return data
# Use one trading year of simulated observations for a more stable illustration
np.random.seed(42)
n_days = 252  # One trading year
n_assets = 3

# Generate correlated returns
mean_returns = np.array([0.0005, 0.0003, 0.0004])  # Daily means
volatilities = np.array([0.02, 0.015, 0.025])  # Daily vols

# True correlation structure
correlation = np.array([[1.0, 0.6, 0.3], [0.6, 1.0, 0.4], [0.3, 0.4, 1.0]])

# Convert correlation to covariance
true_cov = np.outer(volatilities, volatilities) * correlation

# Generate returns from multivariate normal
returns_data = np.random.multivariate_normal(mean_returns, true_cov, n_days)

# Estimate covariance matrix from data
sample_cov = np.cov(returns_data, rowvar=False)
Out[12]:
Console
Sample covariance matrix (×10,000 for readability):
[[3.4765 1.4642 0.9198]
 [1.4642 2.1172 0.8783]
 [0.9198 0.8783 5.4973]]

Volatilities (annualized):
  Asset 1: 29.60%
  Asset 2: 23.10%
  Asset 3: 37.22%

Correlation matrix:
[[1.    0.54  0.21 ]
 [0.54  1.    0.257]
 [0.21  0.257 1.   ]]
Out[13]:
Visualization
Heatmap of a 3 by 3 asset correlation matrix with each correlation value annotated.
Correlation matrix heatmap showing pairwise relationships between assets. Hue indicates sign and color intensity indicates magnitude; this sample contains positive pairwise correlations.

Portfolio Variance: The Quadratic Form

Portfolio variance demonstrates the power of matrix notation. For a portfolio with weights w\mathbf{w} and asset covariance matrix Σ\Sigma, the portfolio variance is:

σp2=wTΣw\sigma_p^2 = \mathbf{w}^T \Sigma \mathbf{w}

where:

  • σp2\sigma_p^2: the variance of the portfolio's returns
  • w\mathbf{w}: the N×1N \times 1 vector of portfolio weights
  • wT\mathbf{w}^T: the transpose of w\mathbf{w} (a 1×N1 \times N row vector)
  • Σ\Sigma: the N×NN \times N covariance matrix of asset returns

This compact formula packs a lot of computation: it accounts for each asset's variance and all pairwise covariances, weighted by the portfolio allocations. The expression wTΣw\mathbf{w}^T \Sigma \mathbf{w} is called a quadratic form because if you expand it, you get a polynomial where each term involves products of two weights. It's quadratic in the portfolio allocations.

Expanding for two assets:

σp2=w12σ12+w22σ22+2w1w2σ12\sigma_p^2 = w_1^2 \sigma_1^2 + w_2^2 \sigma_2^2 + 2 w_1 w_2 \sigma_{12}

where:

  • σ12,σ22\sigma_1^2, \sigma_2^2: the variances of assets 1 and 2
  • σ12\sigma_{12}: the covariance between assets 1 and 2

The first two terms are variance contributions from each asset. The third term involving covariance is why diversification works. When σ12<0\sigma_{12} < 0 (negative correlation), it reduces portfolio variance. Even when covariance is positive but less than the geometric mean of the variances, diversification still helps by ensuring the portfolio variance is less than the weighted average of individual variances.

The matrix formula has the same form for any number of assets. For 500 stocks, it remains wTΣw\mathbf{w}^T \Sigma \mathbf{w} while the ordered double sum contains 250,000 terms: 500 variance terms and 249,500 off-diagonal terms. Because the covariance matrix is symmetric, only 125,250 entries are unique. Compact notation keeps the calculation manageable as a portfolio grows from a toy example to a production system.

In[14]:
Code
# Portfolio variance calculation
weights = np.array([0.4, 0.35, 0.25])

# Using the quadratic form
portfolio_variance = weights @ sample_cov @ weights
portfolio_volatility = np.sqrt(portfolio_variance)

# Annualize
annual_vol = portfolio_volatility * np.sqrt(252)
Out[15]:
Console
Portfolio weights: [0.4  0.35 0.25]
Daily portfolio variance: 0.00019068
Daily portfolio volatility: 1.3809%
Annualized volatility: 21.92%

Weighted average volatility (no diversification): 29.23%
Diversification benefit: 7.31 percentage points
Out[16]:
Visualization
Bar chart comparing annualized volatilities of three individual assets, their weighted-average volatility, and the actual portfolio volatility.
Diversification benefit illustrated by comparing individual asset volatilities with portfolio volatility. The portfolio achieves lower volatility than the weighted average of individual volatilities due to imperfect correlation between assets.

The portfolio volatility is lower than the weighted average of individual volatilities because the assets are imperfectly correlated. This is the mathematical basis of diversification.

Matrix Transpose and Special Matrices

The transpose of matrix A\mathbf{A}, written AT\mathbf{A}^T, flips rows and columns: (AT)ij=Aji(A^T)_{ij} = A_{ji}.

Several special matrix types appear frequently in finance:

  • Symmetric matrices: A=AT\mathbf{A} = \mathbf{A}^T. Covariance matrices are always symmetric. This property reflects a fundamental reality: the covariance between assets A and B must equal the covariance between B and A, since we're measuring the same relationship from both directions.
  • Diagonal matrices: Non-zero elements only on the diagonal. Used to represent variance contributions when assets are uncorrelated, or to scale different variables by different amounts.
  • Identity matrix: Diagonal matrix with 1s on the diagonal. Acts as the "1" of matrix multiplication: IA=A\mathbf{I}\mathbf{A} = \mathbf{A}. It's the matrix equivalent of multiplying by one, leaving any matrix unchanged.
  • Positive definite matrices: A real symmetric matrix A\mathbf{A} is positive definite when xTAx>0\mathbf{x}^T \mathbf{A} \mathbf{x} > 0 for every non-zero real vector x\mathbf{x}. Valid covariance matrices must be positive semi-definite (allowing zero). This ensures that every portfolio variance wTΣw\mathbf{w}^T\Sigma\mathbf{w} is non-negative, as variance must be by definition.
In[17]:
Code
# Verify covariance matrix properties
# 1. Symmetric
is_symmetric = np.allclose(sample_cov, sample_cov.T)

# 2. Positive semi-definite (all eigenvalues ≥ 0)
eigenvalues = np.linalg.eigvalsh(sample_cov)
is_psd = np.all(
    eigenvalues >= -1e-10
)  # Small tolerance for numerical precision
Out[18]:
Console
Covariance matrix is symmetric: True
Eigenvalues: [0.0001167  0.00036008 0.00063233]
Covariance matrix is positive semi-definite: True

Both properties confirm we have a valid covariance matrix. Symmetry ensures that the covariance between assets A and B equals the covariance between B and A. Positive semi-definiteness guarantees that every portfolio variance is non-negative.

Systems of Linear Equations

Many financial problems involve systems of linear equations. Hedge construction, factor-exposure matching, and related tasks can often be formulated as Ax=b\mathbf{A}\mathbf{x} = \mathbf{b} for unknown x\mathbf{x}, although practical versions may add constraints, approximation, or optimization.

Why is this formulation so ubiquitous? Because linear systems capture the essence of constraints and requirements. In finance, we often face situations where multiple conditions must hold simultaneously. A hedge must neutralize exposure to several risk factors at once, a replicating portfolio must match the payoffs of a target in multiple scenarios, and factor loadings must explain returns across many time periods. Each condition contributes one equation, and the unknowns are the positions or weights we need to determine. Linear algebra provides systematic machinery for finding solutions when they exist and for characterizing what's possible when they don't.

The General Problem

A system of linear equations has the form:

a11x1+a12x2+⋯+a1nxn=b1a21x1+a22x2+⋯+a2nxn=b2⋮am1x1+am2x2+⋯+amnxn=bm\begin{aligned} a_{11}x_1 + a_{12}x_2 + \cdots + a_{1n}x_n &= b_1 \\ a_{21}x_1 + a_{22}x_2 + \cdots + a_{2n}x_n &= b_2 \\ &\vdots \\ a_{m1}x_1 + a_{m2}x_2 + \cdots + a_{mn}x_n &= b_m \end{aligned}

where:

  • aija_{ij}: the coefficient in equation ii for unknown xjx_j
  • xjx_j: the jj-th unknown variable we're solving for
  • bib_i: the right-hand side constant of equation ii
  • mm: the number of equations
  • nn: the number of unknowns

In matrix form: Ax=b\mathbf{A}\mathbf{x} = \mathbf{b}, where A\mathbf{A} is m×nm \times n, x\mathbf{x} is n×1n \times 1, and b\mathbf{b} is m×1m \times 1.

The matrix A\mathbf{A} encodes the structure of the problem: how each unknown contributes to each equation. Finding x\mathbf{x} means finding the combination of unknowns that simultaneously satisfies all constraints. In financial applications, A\mathbf{A} often represents sensitivities (like Greeks or factor exposures), x\mathbf{x} represents positions or weights we're solving for, and b\mathbf{b} represents target values we want to achieve.

The geometry of linear systems provides useful intuition. Each equation with at least one nonzero coefficient defines a hyperplane in the space of unknowns (a line in 2D, a plane in 3D, and so on). Solving the system means finding the point (or points, or nothing at all) where all these hyperplanes intersect. When there are exactly as many independent equations as unknowns, and the equations aren't contradictory, the hyperplanes intersect at a single point, giving the unique solution.

Matrix Inverses and Solving Square Systems

When A\mathbf{A} is square (n×nn \times n) and invertible, the solution is x=A−1b\mathbf{x} = \mathbf{A}^{-1}\mathbf{b}. The inverse A−1\mathbf{A}^{-1} satisfies AA−1=A−1A=I\mathbf{A}\mathbf{A}^{-1} = \mathbf{A}^{-1}\mathbf{A} = \mathbf{I}.

The inverse matrix "undoes" the transformation represented by A\mathbf{A}. If A\mathbf{A} transforms inputs to outputs, then A−1\mathbf{A}^{-1} transforms outputs back to inputs. In the context of our linear system, we know the outputs (b\mathbf{b}, the targets we want to achieve) and need to find the inputs (x\mathbf{x}, the positions that achieve those targets). Multiplying by the inverse reverses the process, revealing the required inputs.

When Does an Inverse Exist?

A square matrix is invertible (or non-singular) when its determinant is non-zero, equivalently when its rows (or columns) are linearly independent. An invertible coefficient matrix gives a unique solution for every right-hand side b\mathbf{b}. Redundancy is a property of the coefficient rows, while consistency or contradiction depends on both A\mathbf{A} and b\mathbf{b}.

In numerical work, solve Ax=b\mathbf{A}\mathbf{x}=\mathbf{b} directly rather than forming A−1\mathbf{A}^{-1} explicitly. A solver can use an appropriate factorization and avoids extra rounding error and computation. Rank and condition-number checks are still necessary when the system may be singular or ill-conditioned.

Application: Factor Replication

Consider replicating a target portfolio's factor exposures using available assets. Suppose you have three assets with known exposures to two factors (market and size), and you want to construct a portfolio with specific target exposures:

In[19]:
Code
# Factor exposures: rows are assets, columns are factors (market, size)
factor_exposures = np.array(
    [
        [1.2, 0.8],  # Asset 1: high market beta, positive size exposure
        [0.8, -0.3],  # Asset 2: moderate market beta, negative size exposure
        [1.0, 0.2],  # Asset 3: market beta 1.0, small positive size exposure
    ]
)

# Target exposures: we want these factor loadings
target_exposure = np.array([1.0, 0.0])  # Market beta of 1, size-neutral

# We have 3 assets and 2 constraints, so we need an additional constraint
# Add constraint: weights sum to 1 (fully invested)
A = np.vstack([factor_exposures.T, np.ones(3)])
b = np.append(target_exposure, 1.0)
Out[20]:
Console
Factor exposure matrix (assets × factors):
[[ 1.2  0.8]
 [ 0.8 -0.3]
 [ 1.   0.2]]

Target factor exposures: [1. 0.]
Additional constraint: weights sum to 1

Augmented system A:
[[ 1.2  0.8  1. ]
 [ 0.8 -0.3  0.2]
 [ 1.   1.   1. ]]

Target vector b: [1. 0. 1.]

We have 3 unknowns (weights) and 3 equations (2 factor constraints + 1 budget constraint). Let's solve:

In[21]:
Code
# Solve the system
weights = np.linalg.solve(A, b)

# Verify the solution
achieved_exposures = factor_exposures.T @ weights
weight_sum = np.sum(weights)
Out[22]:
Console
Solution weights: [-2. -2.  5.]

Verification:
  Factor exposures achieved: [ 1.00000000e+00 -2.99760217e-16]
  Weight sum: 1.000000

Interpretation:
  Asset 1: -200.00% of portfolio
  Asset 2: -200.00% of portfolio
  Asset 3: +500.00% of portfolio

The solution tells us exactly how to combine the three assets to achieve our target factor profile: market beta of 1 with zero size exposure, while being net fully invested. Here the weights are -200%, -200%, and +500%, so the exact match requires short positions and 900% gross exposure. In practice, leverage or short-sale limits can make the exact system infeasible; constrained optimization or regularized least squares can then trade replication error against implementability.

Application: Delta Hedging

In derivatives trading, you often need to hedge exposure to multiple risk factors. Suppose you hold a portfolio of options and want to eliminate sensitivity to the underlying price (delta) and volatility (vega). You have two hedging instruments available:

In[23]:
Code
# Current portfolio Greeks (what we need to hedge)
portfolio_delta = 150  # Long 150 deltas
portfolio_vega = -2000  # Short 2000 vegas

# Available hedging instruments
# Instrument 1: Stock (delta=1, vega=0)
# Instrument 2: ATM option (delta=0.5, vega=100)

hedge_matrix = np.array(
    [
        [1.0, 0.5],  # Delta of each instrument
        [0.0, 100.0],  # Vega of each instrument
    ]
)

# Target: neutralize the portfolio Greeks
target = np.array([-portfolio_delta, -portfolio_vega])

# Solve for hedge quantities
hedge_quantities = np.linalg.solve(hedge_matrix, target)
Out[24]:
Console
Portfolio Greeks to hedge:
  Delta: +150
  Vega: -2000

Hedging instruments (Delta, Vega):
  Stock: (1, 0)
  ATM Option: (0.5, 100)

Required hedge quantities:
  Stock: -160 shares
  Options: +20 contracts

Resulting portfolio Greeks:
  Delta: 0.00
  Vega: 0.00

In this illustrative calculation, shorting 160 shares of stock and buying 20 options neutralizes both delta and vega at the stated sensitivities.

Least Squares: Overdetermined Systems

When we have more equations than unknowns (m>nm > n), the system is overdetermined and typically has no exact solution. This happens often in finance. We have many data points (days of returns) but few parameters to estimate (factor exposures).

The least squares solution minimizes the sum of squared residuals:

x^=arg⁡min⁡x∥Ax−b∥22\hat{\mathbf{x}} = \arg\min_{\mathbf{x}} \|\mathbf{A}\mathbf{x} - \mathbf{b}\|_2^2

where:

  • x^\hat{\mathbf{x}}: the least squares estimate of x\mathbf{x}
  • ∥⋅∥22\|\cdot\|_2^2: the squared L2 norm (sum of squared elements)
  • Ax−b\mathbf{A}\mathbf{x} - \mathbf{b}: the residual vector

The solution is given by the normal equations (assuming ATA\mathbf{A}^T\mathbf{A} is invertible):

x^=(ATA)−1ATb\hat{\mathbf{x}} = (\mathbf{A}^T\mathbf{A})^{-1}\mathbf{A}^T\mathbf{b}

where:

  • ATA\mathbf{A}^T\mathbf{A}: the n×nn \times n Gram matrix of the explanatory-variable columns, whose entries are their pairwise inner products
  • ATb\mathbf{A}^T\mathbf{b}: a vector containing each explanatory-variable column's inner product with the target vector

Differentiating the squared residual norm gives the first-order condition AT(Ax^−b)=0\mathbf{A}^T(\mathbf{A}\hat{\mathbf{x}}-\mathbf{b})=\mathbf{0}, or ATAx^=ATb\mathbf{A}^T\mathbf{A}\hat{\mathbf{x}}=\mathbf{A}^T\mathbf{b}. When the Gram matrix is invertible, solving these normal equations yields the displayed formula. The Gram matrix captures column scale and non-orthogonality; it is not generally a correlation matrix, and its inverse is not a literal correction for "double-counting."

When ATA\mathbf{A}^T\mathbf{A} is invertible, the matrix (ATA)−1AT(\mathbf{A}^T\mathbf{A})^{-1}\mathbf{A}^T is the Moore-Penrose pseudoinverse of A\mathbf{A}.

Intuitively, least squares finds the x\mathbf{x} that makes Ax\mathbf{A}\mathbf{x} as close as possible to b\mathbf{b} in Euclidean distance. The residual vector b−Ax^\mathbf{b} - \mathbf{A}\hat{\mathbf{x}} is orthogonal to the column space of A\mathbf{A}. It is the component not represented by the chosen explanatory-variable columns; changing those columns or the model form can change the residual.

This geometric picture explains why least squares produces the best fit within the chosen column space. Among all possible values of x\mathbf{x}, the least squares solution generates an Ax\mathbf{A}\mathbf{x} that is the orthogonal projection of b\mathbf{b} onto the subspace spanned by the columns of A\mathbf{A}. Orthogonal projection onto a subspace always yields the closest point in that subspace. The residual is therefore the in-sample component unexplained by this span. It may contain noise, measurement error, omitted predictors, or misspecification; the projection geometry alone does not identify irreducible statistical error.

This is exactly what linear regression computes.

In[25]:
Code
# Estimate factor exposures from return data
# We have many days of returns (observations) and want to find factor betas

np.random.seed(123)
n_days = 100

# True factor betas we're trying to discover
true_betas = np.array([1.2, -0.3])  # Market beta, size beta

# Simulated factor returns
market_returns = np.random.normal(0.0005, 0.01, n_days)
size_returns = np.random.normal(0.0001, 0.008, n_days)

# Factor matrix: each row is a day, each column is a factor
factor_matrix = np.column_stack([market_returns, size_returns])

# Stock returns = factor exposures × factor returns + noise
noise = np.random.normal(0, 0.005, n_days)
stock_returns = factor_matrix @ true_betas + noise

# Estimate betas via least squares
estimated_betas = np.linalg.lstsq(factor_matrix, stock_returns, rcond=None)[0]
Out[26]:
Console
True factor betas: [ 1.2 -0.3]
Estimated betas: [ 1.1715 -0.2855]

Estimation error:
  Market beta error: -0.0285
  Size beta error: +0.0145
Out[27]:
Visualization
Scatter plot of factor-model predicted returns versus simulated stock returns, with a dashed diagonal line indicating perfect agreement.
Least squares factor estimation showing the relationship between simulated stock returns and factor-predicted returns. Points close to the diagonal line indicate accurate predictions of the simulated response.

The least squares estimates are close to the true values in this seeded simulation, with small errors due to the noise in returns. More observations improve the basis for convergence when the model is correctly specified, the noise is exogenous with stable finite moments, and the limiting regressor covariance has full rank; more data alone does not guarantee consistency.

Matrix Decompositions

Matrix decompositions break a matrix into simpler components, exposing structure that's hidden in the raw numbers. In quantitative finance, decompositions help us study risk directions, enable dimensionality reduction through truncation, and improve numerical stability.

Think of decomposition as a kind of mathematical X-ray. A covariance matrix appears as a dense array of numbers, but its eigendecomposition reveals modes of variation, the basic ways in which assets tend to move together. A return matrix might contain hundreds of thousands of numbers, but its singular value decomposition exposes dominant patterns in the observed variation. These mathematical components can support financial interpretation after their loadings, scores, and stability have been examined; the decomposition itself does not assign economic meaning.

Eigenvalue Decomposition

A diagonalizable square matrix A\mathbf{A} has a complete eigenvector basis and can be expressed as:

A=VΛV−1\mathbf{A} = \mathbf{V}\mathbf{\Lambda}\mathbf{V}^{-1}

where:

  • V\mathbf{V}: an n×nn \times n matrix whose columns are the eigenvectors v1,v2,…,vn\mathbf{v}_1, \mathbf{v}_2, \ldots, \mathbf{v}_n
  • Λ\mathbf{\Lambda}: a diagonal matrix with eigenvalues λ1,λ2,…,λn\lambda_1, \lambda_2, \ldots, \lambda_n on the diagonal
  • V−1\mathbf{V}^{-1}: the inverse of the eigenvector matrix, which "undoes" the coordinate transformation

This decomposition shows that A\mathbf{A} acts as: (1) changing into the eigenvector coordinate system via V−1\mathbf{V}^{-1}, (2) scaling each coordinate by the corresponding eigenvalue via Λ\mathbf{\Lambda}, and (3) changing back via V\mathbf{V}. These changes of basis are rotations or reflections only when V\mathbf{V} is orthogonal, as it can be for a real symmetric covariance matrix. In that covariance setting, the decomposition separates uncorrelated directions of variation.

The power of this decomposition lies in the simplicity of the diagonal matrix Λ\mathbf{\Lambda}. A diagonal matrix just scales each coordinate independently, with no mixing between directions. All the complexity of A\mathbf{A} is absorbed into finding the right coordinate system (the eigenvectors) in which the matrix action becomes simple scaling.

Eigenvalues and Eigenvectors

An eigenvector v\mathbf{v} of matrix A\mathbf{A} satisfies Av=λv\mathbf{A}\mathbf{v} = \lambda\mathbf{v}, where λ\lambda is the eigenvalue. Its span is an invariant line: a nonzero λ\lambda preserves that line, with an orientation reversal when λ<0\lambda < 0, while λ=0\lambda = 0 maps the eigenvector to the zero vector. A real symmetric matrix has real eigenvalues and admits an orthonormal eigenbasis. Eigenvectors associated with distinct eigenvalues are orthogonal; within a repeated eigenspace, an orthonormal basis can be chosen.

For a covariance matrix, the eigenvectors represent the principal directions of variation in the data, and the eigenvalues represent the variance along each direction. Among unit-length directions v\mathbf{v}, the leading eigenvector maximizes vTΣv\mathbf{v}^T\Sigma\mathbf{v} and the maximum equals the largest eigenvalue. This describes the highest-variance direction in the asset universe; the contribution to a particular portfolio's variance also depends on that portfolio's weights.

In[28]:
Code
# Eigendecomposition of the covariance matrix
eigenvalues, eigenvectors = np.linalg.eigh(sample_cov)

# Sort by eigenvalue (descending)
sort_idx = np.argsort(eigenvalues)[::-1]
eigenvalues = eigenvalues[sort_idx]
eigenvectors = eigenvectors[:, sort_idx]

# Calculate variance explained by each eigenvector
total_variance = np.sum(eigenvalues)
variance_explained = eigenvalues / total_variance
cumulative_variance = np.cumsum(variance_explained)
Out[29]:
Console
Eigenvalues of covariance matrix:
  λ1 = 0.00063233 (57.0% of variance)
  λ2 = 0.00036008 (32.5% of variance)
  λ3 = 0.00011670 (10.5% of variance)

Total variance: 0.00110911

First eigenvector (principal direction):
  [0.43878792 0.32747683 0.83679393]

Interpretation: This is the direction in asset-space along which
returns vary most. Its economic meaning must be checked in this sample.
Out[30]:
Visualization
Bar chart showing the percentage of variance explained by each principal component.
Individual variance contribution by each principal component. The first component captures the majority of total variance.
Line chart showing cumulative variance explained as principal components are added, with a dashed 95% threshold line.
Cumulative variance explained as components are added. The first two explain 89.5% in this seeded three-asset example; all three are needed to cross the displayed 95% threshold.

Principal Component Analysis (PCA)

PCA uses eigendecomposition to transform correlated variables into uncorrelated principal components. The full projection preserves the original dimension; dimensionality reduction occurs only when we retain k<Nk<N leading components. Whether a small number explains most variation depends on the assets, sample period, return frequency, scaling, and preprocessing.

The principal components are projections of the data onto the eigenvectors:

Z=XV\mathbf{Z} = \mathbf{X}\mathbf{V}

where:

  • Z\mathbf{Z}: the full T×NT \times N matrix of principal component scores; retaining its first k<Nk<N columns gives a reduced T×kT \times k representation
  • X\mathbf{X}: the T×NT \times N centered data matrix (each row is an observation, each column is a variable minus its mean)
  • V\mathbf{V}: the N×NN \times N matrix of eigenvectors (principal component loadings)

Each column of Z\mathbf{Z} represents a principal component, a new synthetic variable that captures a specific pattern of co-movement in the original data. The first principal component captures the most variance, the second captures the most remaining variance while being uncorrelated with the first, and so on. Analysts may assign economic labels such as market, sector, or style factors after inspecting the loadings and scores, but PCA does not guarantee that a component has a clean economic interpretation.

Why does this step produce uncorrelated components? Let Σ\Sigma be the covariance matrix of the centered data and let the columns of V\mathbf{V} be its orthonormal eigenvectors. Then

Cov⁡(Z)=Cov⁡(XV)=VTΣV=Λ.\operatorname{Cov}(\mathbf{Z}) = \operatorname{Cov}(\mathbf{X}\mathbf{V}) = \mathbf{V}^T\Sigma\mathbf{V} = \mathbf{\Lambda}.

Because Λ\mathbf{\Lambda} is diagonal, distinct principal-component scores have zero sample covariance. Orthogonal axes alone would not guarantee this for arbitrary data; the result holds because PCA chooses axes that diagonalize Σ\Sigma. The eigenvalues give the variances along those axes, so ordering them places the main variance direction first.

In[31]:
Code
# Apply PCA to return data
from sklearn.decomposition import PCA

# Center the returns
centered_returns = returns_data - returns_data.mean(axis=0)

# Fit PCA
pca = PCA()
principal_components = pca.fit_transform(centered_returns)
reduced_scores = principal_components[:, :2]
# Examine results
pc_variance = pca.explained_variance_ratio_
loadings = pca.components_  # Each row is an eigenvector
Out[32]:
Console
PCA Results:
--------------------------------------------------
PC1: 57.0% variance explained
PC2: 32.5% variance explained
PC3: 10.5% variance explained

Cumulative variance: [0.57012852 0.8947838  1.        ]
Reduced score shape: (252, 2); variance retained: 89.5%

Factor loadings (how each PC relates to original assets):
          Asset1    Asset2    Asset3
PC1:    +0.4388    +0.3275    +0.8368
PC2:    +0.7356    +0.4040    -0.5438
PC3:    -0.5162    +0.8541    -0.0636
Out[33]:
Visualization
Grouped bar chart of principal component loadings for three PCs across three assets.
Principal component loadings for the seeded three-asset sample. PC1 has same-sign loadings under the displayed sign convention, while PC2 and PC3 contrast assets within this sample.

In this seeded sample, the first principal component has same-sign loadings and can be read as a broad co-movement direction. The later components have mixed signs and contrast assets. These interpretations are sample-dependent, and multiplying any loading vector by -1 describes the same principal axis, so loading signs are a convention rather than an economic direction by themselves.

PCA in Practice: Interest Rate Curves

PCA is often applied to yield-curve movements because rates at different maturities tend to co-move. In the synthetic example below, the first three components explain 92.3% of sample variation and their loading shapes resemble three commonly used descriptions:

  1. Level (PC1): Parallel shift in the curve. All rates move together.
  2. Slope (PC2): Steepening or flattening. Short and long rates move in opposite directions.
  3. Curvature (PC3): Bending. The middle of the curve moves relative to the ends.

Retaining the first three components below produces a 500×3500 \times 3 score matrix instead of the original 500×8500 \times 8 data matrix. This reduces the dimension, but the variance retained and the economic labels depend on the sample and preprocessing. A fixed-income risk analysis can track these component exposures alongside maturity-specific risks that the reduced representation leaves out.

In[34]:
Code
# Simulate yield curve data (simplified example)
np.random.seed(456)
n_days = 500
maturities = [1, 2, 3, 5, 7, 10, 20, 30]  # Years

# Generate correlated yield changes using an illustrative index-distance kernel
# Correlation decays equally with the number of maturity-grid steps between rates
n_rates = len(maturities)
base_corr = np.eye(n_rates)
for i in range(n_rates):
    for j in range(n_rates):
        base_corr[i, j] = np.exp(-0.15 * abs(i - j))

# Volatilities decrease slightly with maturity
vols = 0.05 * np.array([1.0, 0.95, 0.9, 0.85, 0.8, 0.75, 0.7, 0.65])
cov_matrix = np.outer(vols, vols) * base_corr

# Generate yield changes
yield_changes = np.random.multivariate_normal(
    np.zeros(n_rates), cov_matrix, n_days
)

# Apply PCA
pca_yields = PCA()
yield_reduced_scores = pca_yields.fit_transform(yield_changes)[:, :3]
Out[35]:
Console
Yield Curve PCA Results:
--------------------------------------------------
Variance explained by each component:
  PC1: 72.8%
  PC2: 14.4%
  PC3: 5.2%
  PC4: 2.7%
  PC5: 1.8%

First 3 PCs explain 92.3% of variance; reduced score shape: (500, 3)
Out[36]:
Visualization
Line chart showing three principal component loadings across bond maturities from 1 to 30 years.
Principal component loadings in the synthetic yield-curve example. Under the displayed sign convention, PC1 resembles a level shift, PC2 contrasts short and long maturities, and PC3 contrasts the middle with the ends.

In this synthetic sample, PC1 has same-sign loadings across maturities and resembles a level shift. PC2 contrasts short and long maturities, while PC3 contrasts the middle with the ends and resembles curvature. The signs could all be reversed without changing the components. These loading patterns can be used to describe and hedge component exposures, subject to the fit and stability of the estimated PCA model.

Singular Value Decomposition (SVD)

While eigendecomposition only applies to square matrices, Singular Value Decomposition works for any matrix. For an m×nm \times n matrix A\mathbf{A}:

A=UΣVT\mathbf{A} = \mathbf{U}\mathbf{\Sigma}\mathbf{V}^T

where:

  • U\mathbf{U}: an m×mm \times m orthogonal matrix whose columns are the left singular vectors. The vectors associated with non-zero singular values span the column space of A\mathbf{A}; for a time-by-asset return matrix, their coordinates run across observations such as days.
  • Σ\mathbf{\Sigma}: an m×nm \times n diagonal matrix with non-negative singular values σ1≥σ2≥⋯≥0\sigma_1 \geq \sigma_2 \geq \cdots \geq 0 on the diagonal
  • V\mathbf{V}: an n×nn \times n orthogonal matrix whose columns are the right singular vectors. The vectors associated with non-zero singular values span the row space of A\mathbf{A}; for a time-by-asset return matrix, their coordinates run across assets.

The code below requests NumPy's reduced SVD. With k=min⁡(m,n)k=\min(m,n), full_matrices=False returns U with shape m×km \times k, singular_values as a length-kk vector, and Vt with shape k×nk \times n. NumPy returns the diagonal entries rather than an explicit Σ\mathbf{\Sigma} matrix; U @ np.diag(singular_values) @ Vt reconstructs A\mathbf{A}.

The singular values measure the "importance" of each component. For a return matrix with days as rows and assets as columns, U\mathbf{U} shows when certain patterns occurred, V\mathbf{V} shows which assets participated in each pattern, and Σ\Sigma shows how strong each pattern was.

The strength of SVD lies in its universality and interpretability. Any matrix, not just square symmetric ones, can be decomposed into rotations and scalings. The singular values are always non-negative and ordered by magnitude, and they provide a natural ranking by component strength. Truncating the SVD by keeping only the largest singular values gives the best low-rank approximation to the original matrix in terms of Frobenius norm, a result known as the Eckart-Young theorem. This makes SVD the mathematical foundation for dimensionality reduction and data compression.

SVD is useful for handling non-square return matrices (different numbers of days vs. assets) and for computing the pseudoinverse used in least squares solutions.

In[37]:
Code
# SVD of return matrix
U, singular_values, Vt = np.linalg.svd(centered_returns, full_matrices=False)

# Singular values relate to eigenvalues of covariance matrix
# For centered data X (T×N), eigenvalues of cov(X) equal σ²/(T-1)
n_obs = centered_returns.shape[0]
eigenvalue_estimate = singular_values**2 / (n_obs - 1)
Out[38]:
Console
Singular values of return matrix:
  σ1 = 0.3984
  σ2 = 0.3006
  σ3 = 0.1711

Relationship to eigenvalues:
  Eigenvalues of cov matrix: [0.00063233 0.00036008 0.0001167 ]
  σ²/(T-1):                  [0.00063233 0.00036008 0.0001167 ]

The close match between eigenvalues and σ²/(T-1) confirms the mathematical relationship between SVD and eigendecomposition. The largest singular value corresponds to the dominant variance pattern in the return matrix. That pattern may be interpreted as a market factor only after inspecting and validating its asset loadings and time scores; SVD alone does not assign an economic label. This relationship is why PCA can be computed efficiently via SVD, which is numerically more stable than computing the eigendecomposition of the covariance matrix directly.

Cholesky Decomposition

For positive definite matrices, including covariance matrices that are strictly positive definite, Cholesky decomposition provides a "square root":

Σ=LLT\mathbf{\Sigma} = \mathbf{L}\mathbf{L}^T

where:

  • L\mathbf{L}: a lower triangular matrix (all entries above the diagonal are zero)
  • LT\mathbf{L}^T: the transpose of L\mathbf{L} (an upper triangular matrix)

For a dense n×nn \times n matrix, Cholesky factorization requires about n3/3n^3/3 floating-point operations. Dense LU factorization requires about 2n3/32n^3/3, so both are O(n3)O(n^3) but Cholesky has roughly half the leading operation count when its positive-definite assumptions hold.

When the diagonal entries of L\mathbf{L} are required to be positive, this lower-triangular Cholesky factor is unique. The factor can be applied efficiently to independent draws to simulate correlated variables, a common step in Monte Carlo derivatives-pricing and risk workflows.

The lower triangular structure of L\mathbf{L} gives a sequential construction relative to the chosen variable ordering. The first simulated component uses one independent shock, the second mixes that shock with a second one, and the third mixes the first two shocks with a third. This is a computational representation of the target covariance, not a causal claim that later-listed assets depend on earlier-listed assets. Reordering the assets changes the factor L\mathbf{L} while preserving the appropriately reordered covariance matrix.

In[39]:
Code
# Cholesky decomposition for generating correlated random samples
L = np.linalg.cholesky(sample_cov)

# Generate uncorrelated standard normal samples
n_simulations = 10000
uncorrelated = np.random.standard_normal((n_simulations, 3))

# Transform to correlated samples
correlated = uncorrelated @ L.T

# Verify the correlation structure
simulated_cov = np.cov(correlated, rowvar=False)
Out[40]:
Console
Original covariance matrix (×10,000):
[[3.4765 1.4642 0.9198]
 [1.4642 2.1172 0.8783]
 [0.9198 0.8783 5.4973]]

Simulated covariance matrix (×10,000):
[[3.5015 1.4471 0.8961]
 [1.4471 2.1432 0.8691]
 [0.8961 0.8691 5.6059]]

Cholesky factor L (lower triangular, ×100):
[[1.8645 0.     0.    ]
 [0.7853 1.225  0.    ]
 [0.4933 0.4008 2.2569]]
Out[41]:
Visualization
Scatter plot of simulated correlated returns for Asset 1 versus Asset 2, with the sample correlation annotated.
Scatter plot of Asset 1 vs Asset 2 returns showing positive correlation.
Scatter plot of simulated correlated returns for Asset 1 versus Asset 3, with the sample correlation annotated.
Scatter plot of Asset 1 vs Asset 3 returns showing weak positive correlation in this simulation.
Scatter plot of simulated correlated returns for Asset 2 versus Asset 3, with the sample correlation annotated.
Scatter plot of Asset 2 vs Asset 3 returns showing weak positive correlation in this simulation.

The simulated covariance matrix closely matches the original, confirming that our Cholesky-based sampling reproduces the target covariance up to Monte Carlo error. For the chosen asset order, the lower triangular factor mixes successive independent shocks into each simulated component. This triangular construction is efficient, but it does not imply a causal ordering among the assets.

The Cholesky decomposition provides one efficient way to generate correlated random returns for Monte Carlo pricing and risk calculations, including some path-dependent derivative and Value-at-Risk workflows. It is not required: alternative matrix square roots, copulas, historical simulation, and other scenario methods can produce dependence in different ways.

A Complete Example: Minimum Variance Portfolio

Let's bring together the linear algebra concepts to solve a standard portfolio optimization problem: finding the portfolio with minimum variance subject to being fully invested.

The optimization problem is:

min⁡wwTΣw\min_{\mathbf{w}} \mathbf{w}^T \mathbf{\Sigma} \mathbf{w}

subject to:

1Tw=1\mathbf{1}^T \mathbf{w} = 1

where 1\mathbf{1} is a vector of ones (enforcing that weights sum to 1, meaning the portfolio is fully invested).

This problem asks which portfolio, among all portfolios with net weights summing to 100%, has the smallest variance. The objective function is the quadratic form we encountered earlier, portfolio variance expressed in matrix notation. The equality constraint fixes net investment but still allows short positions and gross leverage. Prohibiting those would require additional constraints such as wi≥0w_i \geq 0 or a gross-exposure limit.

The following closed form assumes that Σ\Sigma is symmetric positive definite, so Σ−1\Sigma^{-1} exists and the minimizer is unique. If Σ\Sigma is singular and merely positive semi-definite, the inverse is undefined and minimizers may be non-unique; use a generalized-inverse, regularized, or constrained formulation instead. Under the positive-definite assumption, Lagrange multipliers give:

w∗=Σ−111TΣ−11\mathbf{w}^* = \frac{\mathbf{\Sigma}^{-1} \mathbf{1}}{\mathbf{1}^T \mathbf{\Sigma}^{-1} \mathbf{1}}

where:

  • w∗\mathbf{w}^*: the optimal weight vector that minimizes portfolio variance
  • Σ−1\mathbf{\Sigma}^{-1}: the inverse of the covariance matrix
  • The numerator Σ−11\mathbf{\Sigma}^{-1} \mathbf{1} jointly accounts for all variances and covariances; it does not guarantee positive weights or an asset-by-asset ordering
  • The denominator 1TΣ−11\mathbf{1}^T \mathbf{\Sigma}^{-1} \mathbf{1} normalizes these weights to sum to 1

The appearance of Σ−1\mathbf{\Sigma}^{-1} in the solution formula is typical of quadratic optimization with linear constraints. Intuitively, the inverse covariance matrix reweights assets to account for correlation. An asset that seems low-variance in isolation might contribute more risk than expected if it's highly correlated with other holdings. Under the chosen covariance estimate and net-budget constraint, this adjustment identifies the model's unconstrained minimum-variance allocation; estimation error or practical constraints can change the implementable result.

In[42]:
Code
# Find the minimum variance portfolio
ones = np.ones(3)

# Solve Sigma y = 1 without explicitly forming the inverse
unnormalized_weights = np.linalg.solve(sample_cov, ones)
# This uses a matrix factorization internally and avoids an explicit inverse.

# Normalize the solution so the weights sum to one
denominator = ones @ unnormalized_weights
min_var_weights = unnormalized_weights / denominator

# Calculate portfolio statistics
min_var_portfolio_var = min_var_weights @ sample_cov @ min_var_weights
min_var_portfolio_vol = np.sqrt(min_var_portfolio_var) * np.sqrt(252)

# Compare with equal-weight portfolio
equal_weights = np.array([1 / 3, 1 / 3, 1 / 3])
equal_weight_var = equal_weights @ sample_cov @ equal_weights
equal_weight_vol = np.sqrt(equal_weight_var) * np.sqrt(252)
Out[43]:
Console
Minimum Variance Portfolio:
--------------------------------------------------
Optimal weights:
  Asset 1: 19.59%
  Asset 2: 61.58%
  Asset 3: 18.83%

Annualized volatility: 21.04%

Equal-Weight Portfolio:
--------------------------------------------------
Weights: [0.33333333 0.33333333 0.33333333]
Annualized volatility: 22.21%

Volatility reduction: 1.17 percentage points
Out[44]:
Visualization
Side-by-side bar chart comparing minimum-variance weights and equal-weight allocations across three assets.
Portfolio weight allocations comparing minimum variance and equal-weight strategies.
Bar chart comparing annualized volatility of the minimum-variance portfolio versus the equal-weight portfolio.
Annualized volatility comparison showing the risk reduction from optimization.

The minimum variance portfolio allocates more to lower-volatility assets and uses the covariance relationships in this sample to reduce overall portfolio risk. The implementation solves Σy=1\Sigma\mathbf{y}=\mathbf{1} directly and then normalizes y\mathbf{y}, obtaining the analytical result without explicitly forming Σ−1\Sigma^{-1}. This follows the numerical guidance introduced earlier in the chapter.

Key Parameters

The key parameters for linear algebra operations in quantitative finance are:

  • Portfolio weights (w): The fraction of portfolio value allocated to each asset. Must sum to 1 for a fully invested portfolio.
  • Covariance matrix (Σ): Captures variance of individual assets (diagonal) and co-movement between assets (off-diagonal). Must be positive semi-definite.
  • Eigenvalues (λ): Represent sample variance along each principal direction. Larger eigenvalues identify directions that explain more variance in the fitted covariance model; they do not by themselves establish economic importance.
  • Eigenvectors (V): Define principal directions of variation. For covariance matrices, they form orthogonal sample variance directions that analysts may investigate as candidate factor patterns; eigendecomposition alone does not establish economic meaning or stability.
  • Condition number: In the 2-norm, the ratio of largest to smallest singular value. For a positive definite covariance matrix, this equals the ratio of its largest to smallest eigenvalue. High values indicate sensitivity in linear solves and inversion.

Limitations and Practical Considerations

Linear algebra provides clean solutions, but real-world implementation requires caution around several issues.

In high-dimensional covariance estimation, estimation error is a major challenge. Covariance matrices estimated from historical data are noisy, especially with many assets relative to the number of observations. A 500-stock universe with 252 days of data has 125,250 unique covariance entries and 126,000 scalar returns, but those scalar values come from only 252 multivariate observations and are not independent information for each covariance entry. The sharper limitation is structural: a centered sample covariance based on TT observations has rank at most T−1T-1, so it is singular when the number of assets NN is at least TT. This leads to unstable or nonexistent matrix inverses and portfolio weights that can swing wildly with small changes in the data. Practitioners address this through shrinkage estimators, factor models, and regularization techniques that we'll explore in later chapters.

Numerical stability compounds the estimation problem. Positive definite covariance matrices with widely separated eigenvalues, especially a near-zero smallest eigenvalue, have high condition numbers and make linear solves sensitive to perturbations. Double-precision floating point arithmetic can introduce errors that propagate through calculations. Prefer direct solves to explicit inverses, and check rank and condition numbers first. An SVD-based pseudoinverse is useful when singular values are truncated or regularized; an exact pseudoinverse that reciprocates tiny retained singular values can still amplify noise.

Non-stationarity presents a major challenge to our matrix-based models. The covariance structure of asset returns shifts over time as market conditions change. A covariance matrix estimated during calm markets can underestimate risk during crises. This is exactly when accurate risk measurement matters most. Rolling window estimation, exponentially weighted covariance, and regime-switching models attempt to capture time-varying structure, but their forecasts remain subject to model and estimation error.

Summary

This chapter covered the linear algebra foundations that underpin quantitative finance. Key concepts include:

  • Vectors represent portfolios, returns, and factor exposures. For beginning-of-period weights that sum to one, the dot product with same-period simple returns gives the one-period fully invested portfolio simple return when there are no intra-period cash flows or trading costs. Vector norms measure size and appear in regularization constraints.

  • Matrices organize returns over time and across assets. The covariance matrix describes asset relationships, and the quadratic form wTΣw\mathbf{w}^T \Sigma \mathbf{w} computes portfolio variance. Matrix multiplication transforms between spaces and aggregates calculations efficiently.

  • Systems of linear equations solve hedging problems, factor replication, and regression. Hedging finds instrument quantities to neutralize risk, replication matches target exposures, and regression estimates factor loadings. Least squares handles overdetermined systems by minimizing squared residuals.

  • Matrix decompositions expose mathematical patterns in raw data. Eigendecomposition of covariance matrices identifies principal variance directions. PCA reduces dimensionality when only the leading components are retained. SVD provides another route to dominant patterns and pseudoinverses, while Cholesky decomposition enables efficient simulation of correlated variables.

These tools form the computational backbone for the portfolio optimization, factor models, and risk management techniques developed in subsequent chapters. The goal of the derivations and examples is to make later uses of standard library routines easier to inspect and reason about.

Quiz

Ready to test your understanding? Take this quick quiz to reinforce what you've learned about linear algebra in quantitative finance.

Linear Algebra for Quantitative Finance

Question 1 of 80 of 8 completed
If portfolio weights are w=[0.5,0.3,0.2]T\mathbf{w}=[0.5,0.3,0.2]^T and asset returns are r=[0.04,−0.02,0.01]T\mathbf{r}=[0.04,-0.02,0.01]^T, what is the portfolio return?

Comments

No comments yet. Be the first to share your thoughts!

Reference

Citation details

Cite or share this article.

BIBTEXAcademic
@misc{brenndoerfer2025linearalgebra, author = {Michael Brenndoerfer}, title = {Linear Algebra for Quantitative Finance: Portfolio Math}, year = {2025}, url = {https://mbrenndoerfer.com/writing/linear-algebra-quantitative-finance-vectors-matrices-pca}, organization = {mbrenndoerfer.com}, note = {Accessed: 2026-09-30} }
APAAcademic
Michael Brenndoerfer (2025). Linear Algebra for Quantitative Finance: Portfolio Math. Retrieved from https://mbrenndoerfer.com/writing/linear-algebra-quantitative-finance-vectors-matrices-pca
MLAAcademic
Michael Brenndoerfer. "Linear Algebra for Quantitative Finance: Portfolio Math." 2026. Web. September 30, 2026. <https://mbrenndoerfer.com/writing/linear-algebra-quantitative-finance-vectors-matrices-pca>.
CHICAGOAcademic
Michael Brenndoerfer. "Linear Algebra for Quantitative Finance: Portfolio Math." Accessed September 30, 2026. https://mbrenndoerfer.com/writing/linear-algebra-quantitative-finance-vectors-matrices-pca.
HARVARDAcademic
Michael Brenndoerfer (2025) 'Linear Algebra for Quantitative Finance: Portfolio Math'. Available at: https://mbrenndoerfer.com/writing/linear-algebra-quantitative-finance-vectors-matrices-pca (Accessed: September 30, 2026).
SimpleBasic
Michael Brenndoerfer (2025). Linear Algebra for Quantitative Finance: Portfolio Math. https://mbrenndoerfer.com/writing/linear-algebra-quantitative-finance-vectors-matrices-pca

About the author

Continue with the full handbook

This chapter is part of Quantitative Finance. Use the handbook page to browse the complete table of contents and continue reading in sequence.

Explore Quantitative Finance
Newsletter

Stay up to date

Get articles, book updates, and news delivered to your inbox.

No spam, unsubscribe anytime.

or

Join the community

Sign in to remove popups, track your reading progress, and join the discussion.