MV Optimization Details#

Portfolio Fundamentals#

The risk of a portfolio is a nonlinear function of asset risks: covariances matter. Optimal portfolios either (i) maximize expected return for a given risk or (ii) minimize risk for a given return.

Portfolio mean and variance; diversification#

For two assets with weights \(w\) and \(1-w\):

\[ \mu_p = w \, \mu_b + (1-w)\, \mu_s\]
\[ \sigma_p^2 = w^2 \sigma_b^2 + (1-w)^2 \sigma_s^2 + 2w(1-w)\rho \sigma_b \sigma_s\]
  • For long-only weights, volatility is linear in weights when \(\rho = 1\).

  • Diversification occurs whenever \(\rho < 1\) (it does not require \(\rho \le 0\)).

import numpy as np
import matplotlib.pyplot as plt

mu_b, mu_s = 0.08, 0.12
sig_b, sig_s = 0.15, 0.25

ws = np.linspace(0, 1, 251)
def port_stats(w, rho):
    mu = w*mu_b + (1-w)*mu_s
    var = (w**2)*(sig_b**2) + ((1-w)**2)*(sig_s**2) + 2*w*(1-w)*rho*sig_b*sig_s
    return mu, np.sqrt(var)

fig, ax = plt.subplots()
for rho in [1.0, 0.7, 0.3, 0.0, -0.5]:
    sigmas = [port_stats(w, rho)[1] for w in ws]
    ax.plot(sigmas, ws)  # plot risk vs weight to show curvature changes
ax.set_xlabel("Portfolio volatility")
ax.set_ylabel("Weight on asset b (w)")
ax.set_title("Two-asset volatility as a function of correlation (lower is more diversified)")
ax.grid(True)
plt.show()
../_images/3d4607f2e06a326e873ffb7d2c95ad520f642702731b72bd48b28b52f5a72517.png

Perfect hedge (\(\rho=-1\))#

With two assets and \(\rho = -1\), set weights so that \(\sigma_p=0\):

\[ \frac{w_s}{w_b} = \frac{\sigma_b}{\sigma_s} \, .\]

Diversification across n assets (general results)#

Let there be n risky assets with volatilities \(\sigma_i\) and covariances \(\sigma_{i,j}\). For portfolio weights \(w_i\) with \(\sum_i w_i = 1\), the variance is

\[ \sigma_p^2 = \sum_{i=1}^n \sum_{j=1}^n w_i w_j \sigma_{i,j}. \]

Equally weighted case#

For \(w_i = \frac{1}{n}\),

\[ \sigma_p^2 = \frac{1}{n^2}\sum_{i=1}^n \sigma_i^2 \;+\; \frac{1}{n^2} \sum_{i\ne j} \sigma_{i,j}. \]

Define the across-asset averages

\[ \operatorname{avg}[\sigma_i^2] = \frac{1}{n}\sum_{i=1}^n \sigma_i^2, \qquad \operatorname{avg}[\sigma_{i,j}] = \frac{1}{n(n-1)}\sum_{i\ne j} \sigma_{i,j}. \]

Then

\[ \sigma_p^2 = \frac{1}{n}\operatorname{avg}[\sigma_i^2] + \frac{n-1}{n}\operatorname{avg}[\sigma_{i,j}]. \]

As \(n\to\infty\) with a diversified portfolio (no single name has material weight), the idiosyncratic term vanishes:

\[ \lim_{n\to\infty} \sigma_p^2 = \operatorname{avg}[\sigma_{i,j}]. \]

Identical vol and correlation#

If \(\sigma_i=\sigma\) for all i and \(\rho_{i,j}=\rho\) for \(i\neq j\):

\[ \sigma_p^2 = \frac{1}{n}\sigma^2 + \frac{n-1}{n}\rho\sigma^2 \;\;\xrightarrow[n\to\infty]{}\;\; \rho\sigma^2. \]
  • Systematic risk: \(\rho\sigma^2\)

  • Idiosyncratic risk: \(\frac{1-\rho}{n}\sigma^2\) (diversifiable)

Special cases:
• \(\rho=1\): no diversification, \(\sigma_p^2=\sigma^2\).
• \(\rho=0\): fully diversifiable, \(\sigma_p^2=\frac{1}{n}\sigma^2 \to 0\).
• With two assets and \(\rho=-1\), one can choose weights to make \(\sigma_p=0\).

import numpy as np
import matplotlib.pyplot as plt

np.random.seed(0)
sig2 = 0.2**2
rhos = [0.0, 0.2, 0.5, 0.8]
ns = np.arange(2, 201)

def portfolio_var_equal(n, rho, sig2=sig2):
    # identical variances and pairwise correlation rho
    return (1/n)*sig2 + ((n-1)/n)*rho*sig2

fig, ax = plt.subplots()
for rho in rhos:
    vars_ = [portfolio_var_equal(n, rho) for n in ns]
    ax.plot(ns, vars_, label=f"rho={rho}")
ax.set_xlabel("Number of assets (n)")
ax.set_ylabel("Portfolio variance")
ax.set_title("Equal-weight portfolio variance vs n")
ax.grid(True)
ax.legend()
plt.show()
../_images/a06b2f4ffbd2101b44eec04aab26ff3b753dd69125655e6a0e06e900698ae8e1.png

MV with a risk-free asset#

Let \(\mu\) be the vector of total-return means, \(r_f\) a constant risk-free rate, and \(\tilde\mu=\mu-r_f\mathbf1\). Assume \(\Sigma\) is positive definite and \(\tilde\mu\ne0\).

Risky weights \(w\) have no budget constraint. Cash has weight \(1-\mathbf1'w\), so portfolio total mean is \(r_f+w'\tilde\mu\) and variance is \(w'\Sigma w\).

Objective and first-order condition#

For target excess mean \(m\),

\[ \min_w\;\frac12 w'\Sigma w\qquad\text{s.t.}\quad w'\tilde\mu=m. \]

The Lagrangian and first-order condition are

\[ \mathcal L=\frac12 w'\Sigma w-\lambda(w'\tilde\mu-m), \qquad \Sigma w-\lambda\tilde\mu=0. \]

Thus \(w=\lambda\Sigma^{-1}\tilde\mu\). Substituting into the mean constraint,

\[ \lambda\tilde\mu'\Sigma^{-1}\tilde\mu=m. \]

Write \(C_x=\tilde\mu'\Sigma^{-1}\tilde\mu>0\). Then

\[ \boxed{w^*(m)=\frac{m}{C_x}\Sigma^{-1}\tilde\mu.} \]

The objective is strictly convex, so this feasible stationary point is the unique minimum.

Variance and the efficient line#

Substituting the weights gives

\[ \sigma_p^2=\left(\frac{m}{C_x}\right)^2 \tilde\mu'\Sigma^{-1}\Sigma\Sigma^{-1}\tilde\mu =\frac{m^2}{C_x}. \]

The efficient branch uses \(m\ge0\), so

\[ m=\sqrt{C_x}\,\sigma_p,\qquad \mu_p=r_f+\sqrt{C_x}\,\sigma_p. \]

Negative targets form the lower, inefficient branch. At \(m=0\), the minimum-variance portfolio holds only cash.

Tangency and the cash weight#

Let \(B_x=\mathbf1'\Sigma^{-1}\tilde\mu\). When \(B_x\ne0\), normalize the risky bundle:

\[ w_t=\frac{\Sigma^{-1}\tilde\mu}{B_x},\qquad w^*(m)=\delta_m w_t,\qquad \delta_m=\frac{mB_x}{C_x}. \]

The cash weight is \(1-\delta_m\). When \(B_x>0\), the fully invested tangency portfolio has

\[ \tilde\mu_t=\frac{C_x}{B_x},\qquad \sigma_t=\frac{\sqrt{C_x}}{B_x},\qquad \operatorname{SR}_t=\sqrt{C_x}. \]

If \(B_x=0\), the optimal risky bundle sums to zero and cannot be normalized to a fully invested tangency fund. The boxed optimizer remains valid.

MV without a risk-free asset#

Use total-return means \(\mu\) and target total mean \(m\). Risky weights must sum to one:

\[ \min_w\;\frac12w'\Sigma w\qquad\text{s.t.}\quad w'\mu=m,\qquad w'\mathbf1=1. \]

Objective and first-order condition#

\[ \mathcal L=\frac12w'\Sigma w-\lambda(w'\mu-m)-\eta(w'\mathbf1-1). \]

The first-order condition gives

\[ \Sigma w-\lambda\mu-\eta\mathbf1=0, \qquad w=\lambda\Sigma^{-1}\mu+\eta\Sigma^{-1}\mathbf1. \]

Define

\[ A=\mathbf1'\Sigma^{-1}\mathbf1,\quad B=\mathbf1'\Sigma^{-1}\mu,\quad C=\mu'\Sigma^{-1}\mu,\quad D=AC-B^2. \]

Assume the means are not all equal. Then \(D>0\), and the two constraints are independent.

Solving for the coefficients#

Substitute the first-order condition into each constraint:

\[ C\lambda+B\eta=m,\qquad B\lambda+A\eta=1. \]

Invert this two-by-two system:

\[\begin{split} \begin{pmatrix}\lambda\\\eta\end{pmatrix} =\frac1D\begin{pmatrix}A&-B\\-B&C\end{pmatrix} \begin{pmatrix}m\\1\end{pmatrix} =\begin{pmatrix}(Am-B)/D\\(C-Bm)/D\end{pmatrix}. \end{split}\]

Therefore,

\[ \boxed{w^*(m)=\frac{Am-B}{D}\Sigma^{-1}\mu +\frac{C-Bm}{D}\Sigma^{-1}\mathbf1.} \]

Strict convexity again makes this the unique minimum. Weights are affine in the target mean.

Frontier variance and GMV#

From \(\Sigma w=\lambda\mu+\eta\mathbf1\) and the constraints,

\[ \sigma_p^2=w'\Sigma w=\lambda m+\eta =\frac{Am^2-2Bm+C}{D} =\frac1A+\frac{A}{D}\left(m-\frac BA\right)^2. \]

The global minimum-variance portfolio is

\[ w_v=\frac{\Sigma^{-1}\mathbf1}{A},\qquad \mu_v=\frac BA,\qquad \sigma_v^2=\frac1A. \]

Only \(m\ge B/A\) is efficient. Below the GMV mean, another portfolio offers a higher mean with the same variance.

Two risky funds#

When \(B\ne0\), define \(w_0=\Sigma^{-1}\mu/B\). Both \(w_0\) and \(w_v\) sum to one. Substitute these normalizations into the optimizer:

\[ w^*(m)=\frac{B(Am-B)}{D}\,w_0 +\frac{A(C-Bm)}{D}\,w_v. \]

The coefficients sum to \((AC-B^2)/D=1\), so

\[ w^*(m)=\delta_m w_0+(1-\delta_m)w_v, \qquad\delta_m=\frac{B(Am-B)}{D}. \]

\(w_0\) is the zero-rate tangency portfolio. Its tangent passes through zero total return; it generally differs from the tangency portfolio based on excess returns. GMV is still risky.

If \(B=0\), use any two distinct target-mean portfolios in place of \(w_0\) and \(w_v\). The boxed optimizer does not require \(B\ne0\).

Comparing the two problems#

For a fully invested portfolio,

\[ w'(\mu-r_f\mathbf1)=w'\mu-r_f. \]

Thus a total-return target \(m\) and excess-return target \(m-r_f\) select the same weights when the reference rate is constant. Subtracting that constant also leaves the covariance matrix unchanged. Cash availability determines whether the budget constraint is imposed.

The cost of the budget constraint#

Use the same excess means \(\tilde\mu\), covariance \(\Sigma\), and target excess mean \(m\) for both problems. Define \(A=\mathbf1'\Sigma^{-1}\mathbf1\), \(B_x=\mathbf1'\Sigma^{-1}\tilde\mu\), \(C_x=\tilde\mu'\Sigma^{-1}\tilde\mu\), and \(D_x=AC_x-B_x^2>0\).

\[\begin{split} \begin{aligned} \sigma^2_{\mathrm{risky}}(m)-\sigma^2_{\mathrm{cash}}(m) &=\frac{Am^2-2B_xm+C_x}{D_x}-\frac{m^2}{C_x}\\ &=\frac{(C_x-B_xm)^2}{C_xD_x}\ge0. \end{aligned} \end{split}\]

The budget constraint cannot reduce the minimum variance. If \(B_x\ne0\), the two frontiers meet at \(m=C_x/B_x\), where the cash-allowed solution already sums to one. For \(B_x>0\), this is the efficient tangency portfolio.

Cash available

Risky assets only

One risky bundle, rescaled with the target

Two risky funds, mixed with the target

Cash takes the remaining weight

Risky weights sum to one

Efficient straight line in mean-volatility space

Efficient upper branch of the risky frontier

The Harvard case comparison holds excess-return moments fixed to isolate this constraint. Its SHV benchmark varies over time, so that empirical comparison is not an identity between total-return and excess-return covariances.

Orthogonal MV portfolios#

For two fully invested minimum-variance portfolios with means \(m\) and \(q\), the first-order condition gives

\[ \operatorname{Cov}(r_m,r_q) =w(m)'\Sigma w(q) =\lambda_m q+\eta_m =\frac{Amq-B(m+q)+C}{D}. \]

Setting this covariance to zero and solving for \(q\) gives

\[ q=\frac{Bm-C}{Am-B},\qquad m\ne B/A. \]

In mean-volatility space, \(q\) is the intercept of the tangent at the portfolio with mean \(m\). The GMV portfolio has no uncorrelated partner on the fully invested frontier.

Complete Implementation#

Below are complete, dependency-light helpers to compute GMV and tangency portfolios from \(\mu\) and \(\Sigma\), and to draw the risky MV frontier and the CML.

import numpy as np
import matplotlib.pyplot as plt

def gmv_weights(Sigma):
    """Global Minimum Variance portfolio weights."""
    inv = np.linalg.inv(Sigma)
    ones = np.ones(Sigma.shape[0])
    w = inv @ ones
    w = w / (ones @ inv @ ones)
    return w

def risky_tangency_weights(mu, Sigma, rf=0.0):
    """Tangency portfolio weights (risky assets only)."""
    tilde_mu = mu - rf
    inv = np.linalg.inv(Sigma)
    ones = np.ones(Sigma.shape[0])
    w = inv @ tilde_mu
    w = w / (ones @ inv @ tilde_mu)
    return w

def mv_frontier(mu, Sigma, num=100):
    """Return (sigmas, means) for the risky MV frontier (no rf)."""
    inv = np.linalg.inv(Sigma)
    ones = np.ones(Sigma.shape[0])
    phi0 = mu @ inv @ mu
    phi1 = mu @ inv @ ones
    phi2 = ones @ inv @ ones
    # Parameterize by target mean between GMV mean and a high value
    mu_gmv = (gmv_weights(Sigma) @ mu)
    mu_hi = float(max(mu_gmv*3, mu_gmv + np.sqrt(phi0)))  # heuristic span
    mus = np.linspace(mu_gmv*0.8, mu_hi, num)
    vars_ = (phi0 - 2*phi1*mus + phi2*mus**2) / (phi0*phi2 - phi1**2)
    sigs = np.sqrt(np.maximum(vars_, 0))
    return sigs, mus

def normalize_weights(w):
    """Normalize weights to sum to 1."""
    s = w.sum()
    return w / s

def mvp_weights(Sigma):
    """Minimum Variance Portfolio weights."""
    one = np.ones(Sigma.shape[0])
    w_unnorm = np.linalg.solve(Sigma, one)
    return normalize_weights(w_unnorm)

def tangency_weights(mu, Sigma):
    """Tangency portfolio weights."""
    w_unnorm = np.linalg.solve(Sigma, mu)
    return normalize_weights(w_unnorm)

def port_stats_from_w(w, mu, Sigma):
    """Portfolio statistics from weights."""
    mu_p = w @ mu
    sig_p = np.sqrt(w @ Sigma @ w)
    return mu_p, sig_p

Example: 4-asset portfolio#

# Toy example with 4 assets
mu = np.array([0.06, 0.08, 0.10, 0.12])  # expected excess returns
std = np.array([0.10, 0.15, 0.20, 0.25])
corr = np.array([
    [1.0, 0.3, 0.2, 0.1],
    [0.3, 1.0, 0.4, 0.2],
    [0.2, 0.4, 1.0, 0.5],
    [0.1, 0.2, 0.5, 1.0],
])
Sigma = np.outer(std, std) * corr

wV = mvp_weights(Sigma)
wT = tangency_weights(mu, Sigma)

print("MVP weights:", np.round(wV, 4))
print("Tangency weights:", np.round(wT, 4))

# Portfolio statistics
mu_V, sig_V = port_stats_from_w(wV, mu, Sigma)
mu_T, sig_T = port_stats_from_w(wT, mu, Sigma)

print(f"\nMVP: μ={mu_V:.4f}, σ={sig_V:.4f}")
print(f"Tangency: μ={mu_T:.4f}, σ={sig_T:.4f}")
MVP weights: [0.7125 0.1818 0.0396 0.0661]
Tangency weights: [0.5465 0.2207 0.0863 0.1466]

MVP: μ=0.0692, σ=0.0909
Tangency: μ=0.0767, σ=0.0957

Efficient frontier visualization#

# Frontier from combining wV and wT over delta grid
deltas = np.linspace(-0.2, 1.4, 161)
mus, sigs = [], []
for d in deltas:
    w = d*wT + (1-d)*wV
    m, s = port_stats_from_w(w, mu, Sigma)
    mus.append(m)
    sigs.append(s)

# Risk-free rate
rf = 0.02
# Translate to total return space for the plot: add rf back to excess return means
total_mus = np.array(mus) + rf
total_rf = rf

# CML: line through (0, rf) and the tangency point in total-return space
mu_T_excess, sig_T = port_stats_from_w(wT, mu, Sigma)
mu_T_total = mu_T_excess + rf

fig, ax = plt.subplots(figsize=(10, 6))
ax.plot(sigs, total_mus, label="Frontier (wV ↔ wT)", linewidth=2)
ax.plot([0, sig_T], [total_rf, mu_T_total], label="Capital Market Line", linewidth=2, linestyle='--')
ax.scatter([sig_V], [mu_V + rf], marker="x", s=100, label="MVP", color='red')
ax.scatter([sig_T], [mu_T_total], marker="o", s=100, label="Tangency", color='green')
ax.scatter([0], [rf], marker="s", s=100, label="Risk-free", color='blue')
ax.set_xlabel("Volatility")
ax.set_ylabel("Expected total return")
ax.set_title("Efficient Frontier and CML")
ax.grid(True, alpha=0.3)
ax.legend()
plt.tight_layout()
plt.show()
../_images/920efe51494aabe5cdb48b7814979414c7cae12fb62d12be661a95034e334abc.png

Advanced example: 6-asset portfolio with CML#

# --- Advanced example (replace with your estimates) ---
np.random.seed(0)
n = 6
A = np.random.randn(n, n)
Sigma = A @ A.T / n + 0.05*np.eye(n)   # PD covariance
mu = np.linspace(0.03, 0.12, n)        # ascending means
rf = 0.02

w_gmv = gmv_weights(Sigma)
w_tan = risky_tangency_weights(mu, Sigma, rf=rf)

sig_risky, mu_risky = mv_frontier(mu, Sigma, num=200)

# Risk-free mix line (CML): line through (sigma=0, mean=rf) tangent at risky tangency point
tan_sigma = float(np.sqrt(w_tan @ Sigma @ w_tan))
tan_mean = float(w_tan @ mu)
cml_sigmas = np.linspace(0, tan_sigma*2, 100)
# Slope is Sharpe of tangency: (tan_mean - rf) / tan_sigma
cml_means = rf + (tan_mean - rf) / tan_sigma * cml_sigmas

# --- Plot 1: Risky MV frontier ---
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(15, 6))

ax1.plot(sig_risky, mu_risky, label="Risky MV frontier", linewidth=2)
ax1.scatter([np.sqrt(w_gmv @ Sigma @ w_gmv)], [w_gmv @ mu], marker="x", s=100, label="GMV", color='red')
ax1.scatter([tan_sigma], [tan_mean], marker="o", s=100, label="Tangency (risky)", color='green')
ax1.set_xlabel("Annualized Std Dev")
ax1.set_ylabel("Annualized Mean")
ax1.legend()
ax1.set_title("Risky Mean–Vol Frontier")
ax1.grid(True, alpha=0.3)

# --- Plot 2: Capital Market Line (with rf) ---
ax2.plot(sig_risky, mu_risky, label="Risky MV frontier", linewidth=2)
ax2.plot(cml_sigmas, cml_means, label="Capital Market Line", linewidth=2, linestyle='--')
ax2.scatter([tan_sigma], [tan_mean], marker="o", s=100, label="Tangency (risky)", color='green')
ax2.scatter([0], [rf], marker="s", s=100, label="Risk-free", color='blue')
ax2.set_xlabel("Annualized Std Dev")
ax2.set_ylabel("Annualized Mean")
ax2.legend()
ax2.set_title("CML with Tangency Portfolio")
ax2.grid(True, alpha=0.3)

plt.tight_layout()
plt.show()

print("GMV weights:", np.round(w_gmv, 4))
print("Tangency (risky) weights:", np.round(w_tan, 4))
print("Tangency Sharpe:", np.round((tan_mean - rf)/tan_sigma, 4))
../_images/48fd575bd2dec90d1a98fb0a6ee2df94d8ff4fa5eee32d635df83820c13bb3d2.png
GMV weights: [ 0.0449  0.4636  0.382   0.0771 -0.2622  0.2946]
Tangency (risky) weights: [ 0.0788  0.2255  0.294   0.0723 -0.1107  0.4401]
Tangency Sharpe: 0.1602

References#

  • Back, Asset Pricing and Portfolio Choice Theory, Ch. 5.

  • Bodie, Kane, and Marcus, Investments, Ch. 7.