Sortino Ratio
Downside-deviation-adjusted Sharpe variant. It only penalizes negative returns (returns below target_return), using the same zero-volatility guard as Sharpe. Edge-first; wired to more than one Price Factor it fans out to per-ticker by_symbol results.
What (One-liner)
A Layer-1 transform: consume a returns series, emit a risk-adjusted return that penalizes only downside (sub-target) volatility — the asymmetric correction of Sharpe. Zero downside clamps to 0.0, not inf.
Inputs
returns(array, required) — return series.target_return(number, optional) — minimum acceptable return, default 0.annualization_factor(number, optional) — default 252.
Outputs
sortino_ratio— annualized Sortino.by_symbol— per-ticker map when fed by multiple Price Factors.
How to use
- Provide a
returnsarray. - Optionally set
target_return(default 0) andannualization_factor.
Core formulas
excess = returns - rf
downside = [min(x - target, 0) for x in excess]
sortino = mean(excess) / std(downside, ddof=0) × √annualization_factorZero downside → 0.0.
Assumptions & applicability
Assumptions: annualization_factor matches input frequency (252 daily) + MAR = target_return (default 0) + downside std right-truncated + zero downside → 0.0.
Applicability: fat-tailed / skewed / hedge-fund-style strategies, n ≥ 30.
Out of scope: intraday, all-positive return sequences, short series n<30.
Known limitations
- Undefined / unstable when there are very few sub-target observations.
target_returnchoice materially shifts the result.- Zero downside clamped to 0.0 (BUG-GOLDEN-001 fix approach).
- Shares annualization assumptions with Sharpe.
References
Sortino & Price (1994) + Rollinger-Hoffman (2013) calculation pitfalls guide.
Golden Test
tests/golden/fixtures/tier1/sortino_ratio/, 1e-12 tolerance, passing as of 2026-05-25. Reference: independent numpy 2.2.6 reimplementation of Sortino-Price (1994).
Changelog
- 1.1.0 (2026-05-25) — Promoted to Active as Layer-1 transform; merged rich governance fields from the superseded Layer-0
sortino_ratiocard. - 1.0.0 (2026-05-23) — Initial edge-first Layer-1 Draft card.

