σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft AcademySkills
Ctrl K
AO
+1.2k Learners
LoginJoin ↗
Skills
AO
+1.2k Learners
Login
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Python skill›Module 5 · Portfolio optimisation›Topic 5.2·~45 minIntermediate

Portfolio return and variance

Skill positionModule 5 of 11 · Topic 2 of 5
5.2 Portfolio return and variance
—Lab score— / 100Total XP—Streak—

A portfolio is a vector of weights

A portfolio is described by a weight per asset: the fraction of its value invested there. The weights sum to 1, and a negative weight is a short position that finances the others. Its return over a period is the weighted average of the asset returns, so the expected return is a dot product w @ mu. Return is the easy half: it is linear, with no interaction between assets. Risk is not.

import numpy as np

weights = np.array([0.6, 0.4])             # 60% in asset A, 40% in asset B
mu = np.array([0.08, 0.05])                # expected annual returns

print(weights.sum())                       # 1.0
print(weights @ mu)                        # 0.068   = 0.6 * 0.08 + 0.4 * 0.05

Variance is a quadratic form

For two assets the variance of w1 * A + w2 * B is w1**2 * var(A) + w2**2 * var(B) + 2 * w1 * w2 * cov(A, B): each asset's own variance, plus a cross term. With n assets there are n * n terms, w_i * w_j * cov[i, j], and the whole sum is the quadratic form w @ cov @ w, one line in NumPy. The cross terms are the point: when two assets are not perfectly correlated they partly cancel, so the portfolio is less risky than the weighted average of the risks.

# The variance two ways: the explicit formula and the matrix form
import numpy as np

weights = np.array([0.6, 0.4])
cov = np.array([[0.04, 0.006], [0.006, 0.01]])      # variances 0.04 and 0.01 (volatilities 20% and 10%), covariance 0.006

explicit = weights[0] ** 2 * cov[0, 0] + weights[1] ** 2 * cov[1, 1] + 2 * weights[0] * weights[1] * cov[0, 1]
matrix_form = weights @ cov @ weights
print(round(explicit, 6), round(matrix_form, 6))     # 0.01888 0.01888
print(round(float(np.sqrt(matrix_form)), 4))         # 0.1374   a 13.7% volatility, below 0.6*20% + 0.4*10% = 16%
# Diversification: 50/50 in two assets of 20% volatility, for several correlations
import numpy as np

weights = np.array([0.5, 0.5])
for rho in (1.0, 0.5, 0.0, -0.5):
    cov = 0.2 * 0.2 * np.array([[1.0, rho], [rho, 1.0]])
    print(rho, round(float(np.sqrt(weights @ cov @ weights)), 4))      # 1.0: 0.2, 0.5: 0.1732, 0.0: 0.1414, -0.5: 0.1

Where does the risk come from?

A manager wants to know which positions drive the risk. The risk contribution of asset i is w_i * (cov @ w)_i / sigma_p: its weight times how strongly it moves with the portfolio as a whole, scaled by the portfolio volatility sigma_p. The contributions add up exactly to sigma_p, so they split the total into parts. An asset with a small weight but a high covariance with the rest can contribute far more than its weight suggests, and a hedge can contribute negatively.

# Risk contributions of a three-asset portfolio: they add up to the volatility
import numpy as np

weights = np.array([0.5, 0.3, 0.2])
cov = np.array([[0.0400, 0.0060, 0.0100],
                [0.0060, 0.0100, 0.0020],
                [0.0100, 0.0020, 0.0225]])

variance = weights @ cov @ weights
contributions = weights * (cov @ weights) / np.sqrt(variance)
print(np.round(contributions, 4), round(float(contributions.sum()), 4))
print(round(float(np.sqrt(variance)), 4))
Inputs matter more than the formulaThe formulas are exact, but mu and cov are estimates, and the variance is the more reliable of the two: expected returns are very noisy, covariances far less so. The weights, mu and cov must also share a period: a daily covariance with annual returns is a common bug; scale the variance by the number of periods, the volatility by its square root.
  • Portfolio return is linear: w @ mu. Variance is the quadratic form w @ cov @ w, with n * n terms and cross terms that let correlation below 1 reduce risk.
  • Volatility is sqrt(variance * periods); the annualisation must match the period of cov.
  • Risk contributions w_i * (cov @ w)_i / sigma_p split the volatility exactly by asset; a hedge can contribute a negative amount.

Next up: the minimum-variance portfolio, choosing the weights that make this risk as small as possible.

Lab · what's graded

  • Add up the expected return with portfolio_return25 pts
  • Measure the risk with portfolio_variance25 pts
  • Quote the volatility with portfolio_vol25 pts
  • Split the risk by asset with risk_contributions25 pts
Explorer · lab-83-1
▾ lab-83-1
…
bash · in-browser
learner@quantcraft:~/lab-83-1$
Python not loaded yetPythonLn 1, Col 1Spaces: 4UTF-8

Reset this topic?

Everything in lab-83-1 goes back to the starter files and the terminal is cleared. Your current solution will be lost — this can't be undone.

Will be reset
  • main.py → starter code
  • Terminal history and test output
  • Unsubmitted changes
Will be kept
  • Best lab score
  • Your XP and streak
  • Lecture progress and notes

Log in to run your code.

Every lecture is free to read. Create an account or log in to run code, run tests and submit this lab.

  • In-browser IDE with a bash terminal — nothing to install
  • Instant grading, XP and your place on the skill
Create an accountLog in
OR
Continue with GitHub