σμ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 3 · Probability & Monte Carlo simulation›Topic 3.7·~40 minIntermediate

The Sharpe ratio

Skill positionModule 3 of 11 · Topic 7 of 12
3.7 The Sharpe ratio
—Lab score— / 100Total XP—Streak—

Reward per unit of risk

A strategy that earns 12% with 20% volatility is not better than one that earns 6% with 5%. The Sharpe ratio puts them on one scale: the average return above the risk-free rate divided by the volatility, (mean - rf) / std, annualised by sqrt(periods). You met the formula in the topic on mean and variance. This topic is about its use: how to read a Sharpe ratio, improve on it, and above all how much to trust it. A Sharpe of 0.5 is mediocre, 1 is good, 2 is excellent and suspicious.

Volatility counts upside and downside equally, yet nobody complains about a surprise gain. The Sortino ratio keeps only the bad side: it replaces the standard deviation with the downside deviation, sqrt(mean(min(r - mar, 0)²)), where mar is the minimum acceptable return. Days above mar contribute zero, but they still count in the average. A strategy with big positive jumps and small losses scores much better on Sortino than on Sharpe.

import math
import numpy as np
import pandas as pd

returns = np.array([0.004, -0.002, 0.001, 0.003, -0.004, 0.002, 0.006, -0.001, -0.010, 0.004, 0.002, -0.003])

sharpe = returns.mean() / returns.std(ddof=1) * math.sqrt(252)
print(round(sharpe, 3))                                  # 0.598

mar = 0.0                                                # a day below zero is a bad day
downside = math.sqrt(np.mean(np.minimum(returns - mar, 0) ** 2))
sortino = (returns.mean() - mar) / downside * math.sqrt(252)
print(round(downside, 5), round(sortino, 3))             # 0.00329 0.804   the up days no longer count as risk

Watching it move

One Sharpe ratio for the whole history hides the story. A rolling Sharpe ratio recomputes it on the last window returns, so you see the strategy's good and bad regimes. With pandas it is the rolling mean divided by the rolling standard deviation, annualised. Notice how wildly short windows jump: a month of data is nowhere near enough to rank strategies.

series = pd.Series(returns)
rolling = series.rolling(5)
rolling_sharpe = rolling.mean() / rolling.std() * math.sqrt(252)
print(rolling_sharpe.round(2).tolist())
# [nan, nan, nan, nan, 1.89, 0.0, 6.96, 4.97, -3.66, 0.51, 0.51, -4.69]   a 5-day Sharpe ratio is mostly noise

How much of it is luck?

A Sharpe ratio is an estimate, and estimates have error. For a per-period Sharpe ratio sr measured on n returns, the standard error is about sqrt((1 + 0.5 * sr²) / n). With our 12 days it is 0.29 per day against a Sharpe of 0.04: the error is seven times the estimate. Annualised, the same 12 days give 0.6 ± 4.6. For an annual Sharpe S observed over years, the t statistic is S * sqrt(years), so you need (1.96 / S)² years to be 95% sure it is not zero: 15 years for a Sharpe of 0.5, 4 years for a Sharpe of 1. Short backtests of modest strategies prove nothing.

per_period = returns.mean() / returns.std(ddof=1)            # the per-day Sharpe ratio
std_error = math.sqrt((1 + 0.5 * per_period ** 2) / len(returns))
print(round(per_period, 4), round(std_error, 4))            # 0.0376 0.2888   the error is 7 times the estimate
print(round(std_error * math.sqrt(252), 3))                 # 4.584           annualised: 0.6 +/- 4.6

# Years needed for an annual Sharpe ratio to reach t = 1.96
for annual in (0.5, 1.0, 2.0):
    print(annual, round((1.96 / annual) ** 2, 1))
# 0.5 15.4
# 1.0 3.8
# 2.0 1.0
# A yearly Sharpe of 1.0 observed over 4 years (1,008 days): how precise is it?
per_day = 1.0 / math.sqrt(252)
error_per_day = math.sqrt((1 + 0.5 * per_day ** 2) / 1008)
print(round(per_day, 4), round(error_per_day, 4), round(error_per_day * math.sqrt(252), 3))   # 0.063 0.0315 0.5   so 1.0 +/- 0.5
The best backtest is the least trustworthyIf you try 100 variants of a strategy and keep the one with the highest Sharpe ratio, the winner's number is inflated by selection: even strategies with no edge produce a Sharpe of 1 or more by luck. Divide your enthusiasm by the number of variants you tried, test on data the selection never saw, and prefer ideas with an economic reason.
  • Sharpe = (mean - rf) / std * sqrt(periods); Sortino replaces the std by the downside deviation sqrt(mean(min(r - mar, 0)²)).
  • A rolling Sharpe (rolling.mean() / rolling.std()) shows how the ratio changes; short windows are mostly noise.
  • Its standard error is sqrt((1 + 0.5 * sr²) / n) per period; the t statistic of an annual Sharpe is S * sqrt(years): a Sharpe of 1 needs about 4 years.

Next up: drawdowns, the losses from a peak that investors actually feel.

Lab · what's graded

  • Penalise only the bad days with sortino_ratio25 pts
  • Watch the ratio change over time with rolling_sharpe25 pts
  • Size the luck in a Sharpe ratio with sharpe_standard_error25 pts
  • Ask whether it is real with sharpe_t_stat25 pts
Explorer · lab-60-1
▾ lab-60-1
…
bash · in-browser
learner@quantcraft:~/lab-60-1$
Python not loaded yetPythonLn 1, Col 1Spaces: 4UTF-8

Reset this topic?

Everything in lab-60-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