AA1.03's risk-adjusted measures section derives why the Sharpe ratio is excess return per unit of total risk, when it is the right measure (the portfolio is the investor's whole book), and its two structural blind spots (meaningless with a negative numerator; blind to skew and fat tails). Re-read that before this section: nothing here re-derives it. What this section owns is getting the same number out of code, on purpose, every time.
How is the annualized Sharpe ratio calculated?
An annualized Sharpe ratio built from T per-period returns is:
Sharpe = (mean(r - r_f) / std(r - r_f, ddof)) × sqrt(periods_per_year)
Three choices hide inside that one line, and each is a place a solution can be quietly wrong. First, r_f is almost always quoted as an annual rate; you must divide it by periods_per_year before subtracting it from a per-period return series, or every Sharpe ratio you compute will be biased toward zero by roughly the entire risk-free rate. Second, the sqrt(periods_per_year) scaling assumes returns are i.i.d. across periods, real return series show some autocorrelection, which this scaling silently ignores; it is a convention, not a law of nature, and worth stating as one. Third, and the one AA1.03 already flagged as a Modern Data Analysis callout in the time-weighted vs money-weighted return section: ddof. numpy.std() defaults to ddof=0 (population standard deviation, dividing by T); pandas.Series.std() defaults to ddof=1 (sample standard deviation, dividing by T-1). On a modest sample the two give different Sharpe ratios from identical data, and the difference is large enough to reorder a manager ranking: AA1.03 quotes exactly this, a Sharpe of 0.512 versus 0.535 from the same twelve quarters, from a keyword nobody typed. The fix used throughout this module and its code pack: ddof is always an explicit parameter with a stated default, never inherited silently from whichever library function happened to get called. Exercise QD2.03-e02 grades sharpe_ratio(returns, rf, periods_per_year, ddof) with hidden tests that specifically probe whether periods_per_year and ddof are actually wired through to the calculation, not merely accepted as unused arguments.
Advertisement
How does the Sortino ratio differ?
Sortino's refinement. Sharpe charges a return series for all of its volatility, upside included, which is exactly backwards from what most investors mean by "risk." Sortino and van der Meer's answer (1991) keeps the same numerator (excess return over a minimum acceptable return, MAR) but replaces total standard deviation with downside deviation: only period-by-period shortfalls below the MAR contribute.
downside_i = min(r_i - MAR_period, 0), downside\_deviation = sqrt( Σ downside_i² / (n - ddof) ), Sortino = mean(r - MAR_period) / downside\_deviation × sqrt(periods\_per\_year)
Two managers with identical Sharpe ratios can have meaningfully different Sortino ratios: one earns its volatility from a handful of very good months (Sharpe punishes this, Sortino does not), the other from a handful of very bad ones (both punish this, but Sortino more precisely). A widening gap between the two (Sortino noticeably above Sharpe) is itself informative: it says the manager's volatility skews toward the upside, information Sharpe alone destroys by treating both tails identically. Exercise QD2.03-e03 grades sortino_ratio(returns, mar, periods_per_year, ddof), and one hidden test specifically checks that shrinking a downside move (while leaving every upside month untouched) strictly raises Sortino, a direct, executable test of the property that makes Sortino a genuinely different statistic, not merely Sharpe with extra steps.