Exercise · m14_portfolio.py
Mean-variance closed forms, risk parity / ERC, Euler risk contributions, shrinkage
not run yet
Not saved: sign up to keep your code"""M14 · Mean-variance closed forms, risk parity / ERC, Euler risk contributions, shrinkage.
Exercise stubs. See notes.md §14.1–§14.7.
Notation: mu (n,) expected returns, cov (n,n) positive-definite covariance, 1 = ones(n),
A = 1'S^-1 1, B = 1'S^-1 mu, C = mu'S^-1 mu, D = AC - B^2. Use np.linalg.solve, not inv.
"""
from __future__ import annotations
import numpy as np
def portfolio_vol(w, cov) -> float:
"""sigma(w) = sqrt(w' S w) as a float."""
raise NotImplementedError
def min_variance_weights(cov) -> np.ndarray:
"""Global minimum-variance portfolio w = S^-1 1 / (1'S^-1 1) (Proposition 14.1)."""
raise NotImplementedError
def tangency_weights(mu, cov, rf: float = 0.0) -> np.ndarray:
"""Tangency portfolio w = S^-1 (mu - rf 1) / 1'S^-1 (mu - rf 1) (Proposition 14.3)."""
raise NotImplementedError
def frontier_weights(mu, cov, target: float) -> np.ndarray:
"""Frontier portfolio with mean `target`: w = [(C - B m) S^-1 1 + (A m - B) S^-1 mu] / D (Prop. 14.2)."""
raise NotImplementedError
def frontier_variance(mu, cov, target: float) -> float:
"""Frontier variance sigma^2(m) = (A m^2 - 2 B m + C)/D."""
raise NotImplementedError
def max_sharpe(mu, cov, rf: float = 0.0) -> float:
"""Tangency Sharpe ratio sqrt((mu - rf)' S^-1 (mu - rf)) (Corollary 14.4)."""
raise NotImplementedError
def numeric_min_variance(cov) -> np.ndarray:
"""Minimise w'Sw subject to sum(w) = 1 with scipy.optimize.minimize (SLSQP, ftol ~1e-15).
Shorting allowed (no bounds). Used to check the closed form."""
raise NotImplementedError
def numeric_tangency(mu, cov, rf: float = 0.0) -> np.ndarray:
"""Maximise (w'mu - rf)/sqrt(w'Sw) subject to sum(w) = 1 numerically (SLSQP). No bounds."""
raise NotImplementedError
def risk_contributions(w, cov) -> np.ndarray:
"""Euler risk contributions RC_i = w_i (S w)_i / sigma(w). They sum to sigma(w) (Prop. 14.7)."""
raise NotImplementedError
def inverse_vol_weights(cov) -> np.ndarray:
"""w_i proportional to 1/sigma_i, normalised to sum to 1."""
raise NotImplementedError
def erc_weights(cov, budgets=None, tol: float = 1e-12, max_iter: int = 200) -> np.ndarray:
"""Long-only risk-budget weights summing to 1 with RC_i / sigma = b_i (equal b_i = 1/n if None).
Suggested method (Proposition 14.8): minimise the strictly convex
f(y) = 0.5 y'S y - sum_i b_i log y_i over y > 0
by damped Newton (gradient S y - b/y, Hessian S + diag(b/y^2), backtrack to stay positive),
then return y / sum(y). Results must satisfy the tests to ~1e-8.
"""
raise NotImplementedError
def combined_sharpe_optimal(s1: float, s2: float, rho: float) -> float:
"""Max Sharpe of two strategies combined: sqrt((s1^2 + s2^2 - 2 rho s1 s2)/(1 - rho^2)) (Prop. 14.12)."""
raise NotImplementedError
def combined_sharpe_equal_risk(s1: float, s2: float, rho: float) -> float:
"""Sharpe of an equal-volatility 50/50 mix: (s1 + s2)/sqrt(2(1 + rho))."""
raise NotImplementedError
def ledoit_wolf_identity(returns) -> tuple[np.ndarray, float]:
"""Ledoit-Wolf (2004) shrinkage toward m I (notes eq. 14.5). Return (shrunk covariance, delta).
X = demeaned (T, n) returns; S = X'X/T; m = tr(S)/n; F = m I;
d2 = ||S - F||_F^2 / n; b2_bar = (1/T^2) sum_t ||x_t x_t' - S||_F^2 / n;
b2 = min(b2_bar, d2); delta = b2/d2; shrunk = delta F + (1 - delta) S.
"""
raise NotImplementedError
def vol_target_leverage(sigma_hat: float, target: float, cap: float = 2.0) -> float:
"""Exposure multiplier min(target/sigma_hat, cap)."""
raise NotImplementedError
No runs yet
The stub's functions raise
NotImplementedError until you write them. Fill in one, press
Run tests, and watch its tests turn green.Reference solution
Available after your first run, with an account approved for code runs.