SigmaField
Mathematical Foundations for Quant Finance
0 of 256 lessons
      1. 4.6.1 Utility and mean–variance preferencesConcept · 35 min
      2. 4.6.2 The tangency portfolio and the Sharpe ratioConcept · 30 min
      3. 4.6.3 The capital asset pricing modelConcept · 30 min
      4. 4.6.4 Practice set: portfolio theoryPractice set · 25 min
      5. 4.6.5 Case study: the tangency portfolio from dataCase study · 35 min

Level 4, Multivariable calculus and optimisation 4.6 Portfolio theory Lesson 4 of 5

Practice setLesson 4.6.4 in Level 4, Multivariable calculus and optimisation. About 25 minutes.

Practice set: portfolio theory

Utility and mean–variance preferences

Utility and mean–variance preferences (lesson 4.6.1)

0 of 8 correct

  1. 1.
  2. 2.
  3. 3.
  4. 4.
  5. 5.
  6. 6.
  7. 7.
  8. 8.
How to type answers
  • Powers with ^: x^7, 2^(1/3), a^-2.
  • Multiply with * or by writing side by side: 3x^2, 2(x+1).
  • ln(x) is the natural log, log(x, 2) is log base 2, e is Euler's number, sqrt(x) is a square root.
  • sin(x), cos(x) and tan(x) take radians; write pi for π and sin(x)^2 for sin²x.
  • Your first answer to each question is saved: it counts towards your score and brings the question back for review.
  • Exact answers like ln(20)/ln(3) are best. Decimals need about 4 significant figures.

The tangency portfolio and the Sharpe ratio

The tangency portfolio and the Sharpe ratio (lesson 4.6.2)

0 of 8 correct

  1. 1.
  2. 2.
  3. 3.
  4. 4.
  5. 5.
  6. 6.
  7. 7.
  8. 8.
How to type answers
  • Powers with ^: x^7, 2^(1/3), a^-2.
  • Multiply with * or by writing side by side: 3x^2, 2(x+1).
  • ln(x) is the natural log, log(x, 2) is log base 2, e is Euler's number, sqrt(x) is a square root.
  • sin(x), cos(x) and tan(x) take radians; write pi for π and sin(x)^2 for sin²x.
  • Your first answer to each question is saved: it counts towards your score and brings the question back for review.
  • Exact answers like ln(20)/ln(3) are best. Decimals need about 4 significant figures.

The capital asset pricing model

The capital asset pricing model (lesson 4.6.3)

0 of 8 correct

  1. 1.
  2. 2.
  3. 3.
  4. 4.
  5. 5.
  6. 6.
  7. 7.
  8. 8.
How to type answers
  • Powers with ^: x^7, 2^(1/3), a^-2.
  • Multiply with * or by writing side by side: 3x^2, 2(x+1).
  • ln(x) is the natural log, log(x, 2) is log base 2, e is Euler's number, sqrt(x) is a square root.
  • sin(x), cos(x) and tan(x) take radians; write pi for π and sin(x)^2 for sin²x.
  • Your first answer to each question is saved: it counts towards your score and brings the question back for review.
  • Exact answers like ln(20)/ln(3) are best. Decimals need about 4 significant figures.

Coding exercise

Exercise

The tangency portfolio and betas

Write two functions, with NumPy.

  1. tangency(mu, cov, rf) returns (w, mean, vol, sharpe). w holds the tangency weights 1kΣ−1(μ−rf1)\frac{1}{k}\Sigma^{-1}(\boldsymbol{\mu} - r_f\mathbf{1}) of lesson 4.6.2, then come the portfolio's expected return, volatility and Sharpe ratio. Use np.linalg.solve, not an inverse.
  2. betas(cov, w) returns the array of betas Σww⊤Σw\frac{\Sigma\mathbf{w}}{\mathbf{w}^\top\Sigma\mathbf{w}} against the portfolio w, as in lesson 4.6.3.

Both accept lists as well as arrays. Raise ValueError when the sizes do not match, or cov is not symmetric or not positive definite (lesson 3.7.1: check its smallest eigenvalue with np.linalg.eigvalsh). tangency also raises it when k≤0k \le 0, and betas when the portfolio has no variance.

The tests use the three assets, the pair A and C, and the 50/30/20 benchmark. They check that no random portfolio beats your Sharpe ratio and that your betas rebuild the security market line. They also try a risk-free rate of 7%, an asymmetric matrix and the impossible correlations of case study 3.2.5.

See the tests
import numpy as np

_MU = np.array([0.06, 0.08, 0.11])
_SIGMA = np.array([0.12, 0.18, 0.25])
_R = np.array([[1.0, 0.3, 0.1], [0.3, 1.0, 0.5], [0.1, 0.5, 1.0]])
_COV = np.diag(_SIGMA) @ _R @ np.diag(_SIGMA)


def _raises(f, *args):
    try:
        f(*args)
    except ValueError:
        return True
    return False


def test_three_assets():
    """The three assets with rf = 3%: weights (0.5185, 0.1601, 0.3214)"""
    w, mean, vol, sharpe = tangency(_MU, _COV, 0.03)
    expect_close(w, [0.5184600126166481, 0.1601145467890288, 0.32142544059432315], what="weights", rel=1e-9)


def test_three_assets_numbers():
    """Its expected return is 7.93%, volatility 12.47% and Sharpe ratio 0.3951"""
    w, mean, vol, sharpe = tangency(_MU, _COV, 0.03)
    expect_close(mean, 0.07927356296549673, what="expected return", rel=1e-9)
    expect_close(vol, 0.12470023950760678, what="volatility", rel=1e-9)
    expect_close(sharpe, 0.3951360731948796, what="Sharpe ratio", rel=1e-9)


def test_two_assets_lists():
    """A and C alone, given as lists: 60.62% in A, Sharpe ratio 0.3878"""
    w, mean, vol, sharpe = tangency([0.06, 0.11], [[0.0144, 0.003], [0.003, 0.0625]], 0.03)
    expect_close(w, [0.6062291434927698, 0.39377085650723026], what="weights", rel=1e-9)
    expect_close(sharpe, 0.3878195977565348, what="Sharpe ratio", rel=1e-9)


def test_highest_sharpe():
    """No random fully invested portfolio has a higher Sharpe ratio"""
    w, mean, vol, sharpe = tangency(_MU, _COV, 0.03)
    W = np.random.default_rng(4).normal(size=(20_000, 3))
    W = W / W.sum(axis=1, keepdims=True)
    best = np.max((W @ _MU - 0.03) / np.sqrt(np.sum((W @ _COV) * W, axis=1)))
    assert best <= sharpe + 1e-12, f"a random portfolio has Sharpe ratio {best:.6f}, above yours, {sharpe:.6f}"


def test_rf_too_high():
    """rf = 7%, above the minimum-variance return of 6.81%: ValueError"""
    assert _raises(tangency, _MU, _COV, 0.07), "expected ValueError: k < 0, so there is no tangency portfolio"


def test_not_symmetric():
    """An asymmetric covariance matrix raises ValueError"""
    bad = _COV.copy()
    bad[0, 1] = 0.0648
    assert _raises(tangency, _MU, bad, 0.03), "expected ValueError for an asymmetric matrix"


def test_not_positive_definite():
    """Impossible correlations (0.9, 0.9, -0.9) raise ValueError"""
    R = np.array([[1.0, 0.9, 0.9], [0.9, 1.0, -0.9], [0.9, -0.9, 1.0]])
    assert _raises(tangency, _MU, np.diag(_SIGMA) @ R @ np.diag(_SIGMA), 0.03), "expected ValueError: this matrix has a negative eigenvalue"


def test_sizes():
    """Two expected returns with a 3 x 3 matrix raise ValueError"""
    assert _raises(tangency, [0.06, 0.08], _COV, 0.03), "expected ValueError when the sizes do not match"


def test_betas_tangency():
    """Betas against the tangency portfolio are (0.6088, 1.0147, 1.6236)"""
    w = tangency(_MU, _COV, 0.03)[0]
    expect_close(betas(_COV, w), [0.6088457621992378, 1.01474293699873, 1.6235886991979676], what="betas", rel=1e-9)


def test_security_market_line():
    """Against the tangency portfolio, rf + beta (mu_T - rf) gives back each expected return"""
    w, mean, vol, sharpe = tangency(_MU, _COV, 0.03)
    expect_close(0.03 + betas(_COV, w) * (mean - 0.03), _MU, what="rf + beta (mu_T - rf)", rel=1e-9)


def test_betas_benchmark():
    """Against the 50/30/20 mix, betas are (0.6833, 1.2244, 1.4551) and average to 1"""
    w = np.array([0.5, 0.3, 0.2])
    b = betas(_COV, w)
    expect_close(b, [0.6833099579242636, 1.2244039270687237, 1.4551192145862553], what="betas", rel=1e-9)
    expect_close(float(w @ b), 1.0, what="weighted average beta")


def test_betas_zero_portfolio():
    """Betas against a portfolio with no variance raise ValueError"""
    assert _raises(betas, _COV, [0.0, 0.0, 0.0]), "expected ValueError: the portfolio has no variance"

Free further reading

Optional. This lesson covers what you need; these add more examples and practice. All are free to read.