Skip to content

fix: use target downside deviation in Sortino ratio#285

Merged
timkpaine merged 1 commit into
pmorissette:masterfrom
Bortlesboat:fix-sortino-ratio
Mar 4, 2026
Merged

fix: use target downside deviation in Sortino ratio#285
timkpaine merged 1 commit into
pmorissette:masterfrom
Bortlesboat:fix-sortino-ratio

Conversation

@Bortlesboat

Copy link
Copy Markdown
Contributor

Summary

Fixes the downside deviation calculation in calc_sortino_ratio to use the standard Target Downside Deviation (TDD) formula instead of sample standard deviation.

The bug: The previous implementation used .std(ddof=1) (sample standard deviation) on the clipped return series. This is incorrect because:

  1. .std() subtracts the mean of the clipped series (a negative number) before squaring, so zero entries from positive returns contribute mean^2 each to the variance — they don't actually "zero out"
  2. It uses Bessel's correction (N-1 denominator) rather than N

The fix: Replace .std(ddof=1) with sqrt(mean(min(r, 0)^2)), which is the standard Target Downside Deviation per Sortino, van der Meer & Plantinga (1999). This is the root mean square of the clipped (at zero) excess returns, where positive returns correctly contribute zero to the sum.

The .clip(upper=0.0) logic is unchanged and correct — zeros from positive-return periods belong in the formula (they increase N in the denominator of the mean, reflecting that calm periods reduce downside risk).

Closes #254

Note on #169: Issue #169 suggests using geometric mean instead of arithmetic mean for the numerator. After review, the current arithmetic mean approach is the standard convention for the Sortino ratio (consistent with this library's Sharpe ratio implementation and with Investopedia/academic references). No change made for that issue.

Test plan

  • Updated test_calc_sortino_ratio to verify against the corrected TDD formula
  • Full test suite passes (51/51 tests)

Generated with Claude Code

Replace std(ddof=1) with sqrt(mean(min(r,0)^2)) for the downside
deviation in calc_sortino_ratio. The previous implementation used
pandas .std() (sample standard deviation) on the clipped returns,
which subtracts the mean of the clipped series before squaring and
uses Bessel's correction (N-1). The standard Sortino ratio uses
Target Downside Deviation (TDD), defined as sqrt(1/N * sum(min(r,0)^2)),
which is the root mean square of the clipped returns.

Fixes #254

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@timkpaine timkpaine merged commit cf9a0c8 into pmorissette:master Mar 4, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Sortino ratio incorrectly uses np.minimum

2 participants