The Sortino ratio is average return above a minimum acceptable return (MAR), divided by downside deviation below that target. Unlike the Sharpe ratio, it uses a target-based lower partial moment instead of symmetric standard deviation. Returns above the target do not enter the downside denominator, although they still affect the average return in the numerator.
The ratio is useful when falling short of a stated goal is the relevant risk. It is not a complete tail-risk, drawdown, or loss-probability measure. A result is comparable only when the target return, observation period, denominator convention, annualization, costs, and sample window match.
Frank Sortino and Robert van der Meer presented this downside-risk framework in their 1991 paper. Sortino and Lee Price then discussed performance measurement in a 1994 downside-risk framework.
Sortino ratio formula
For periodic simple returns (R_t), a periodic minimum acceptable return (M), and (T) valid observations:
shortfall_t = min(R_t - M, 0)
downside_deviation = sqrt(sum(shortfall_t^2) / T)
Sortino_period = (mean(R_t) - M) / downside_deviation
- (R_t) is the strategy's net return in period (t).
- (M) is the MAR over the same period and in the same return convention.
- (T) counts all valid observations, including periods at or above the target.
shortfall_tis zero when the target is met and negative when it is missed.downside_deviationis the square root of a second-order lower partial moment.
The MAR can represent zero return, a liability growth rate, an investor goal, or a benchmark. It is not automatically the risk-free rate. Choosing the target after seeing the results creates another degree of research freedom.
Convert an annual effective target to the observation period before subtracting it:
periodic_mar = (1 + annual_mar)^(1 / periods_per_year) - 1
The common historical-sample convention annualizes the periodic ratio as:
Sortino_annual = sqrt(periods_per_year) * Sortino_period
That square-root rule is a convention, not a general identity. Downside deviation contains a nonlinear threshold. The downside deviation of an annual return is not generally the monthly downside deviation times sqrt(12), even with independent monthly observations. Serial dependence makes the relationship less defensible. A CFA Institute review of Sortino variations also warns that the chosen downside-deviation and annualization methods materially affect the result. When the decision concerns annual target shortfall, calculate non-overlapping annual returns against the annual target when the sample is long enough, or state the periodic approximation explicitly.
Python example
This hand-checkable example uses 12 monthly returns and a 6% annual effective MAR. It calculates the full-sample lower partial moment, then shows two superficially similar denominators that answer different questions.
import numpy as np
returns = np.array(
[0.030, 0.020, -0.010, 0.040, -0.030, 0.010,
0.000, 0.025, -0.015, 0.005, 0.020, -0.020]
)
periods_per_year = 12
annual_mar = 0.06
monthly_mar = (1 + annual_mar) ** (1 / periods_per_year) - 1
excess = returns - monthly_mar
shortfall = np.minimum(excess, 0.0)
downside_deviation = np.sqrt(np.mean(shortfall**2))
sortino = (
np.sqrt(periods_per_year)
* excess.mean()
/ downside_deviation
)
below_target = shortfall[shortfall < 0]
conditional_rms = np.sqrt(np.mean(below_target**2))
filtered_std = below_target.std(ddof=1)
print(f"Monthly MAR: {monthly_mar:.4%}")
print(f"Mean monthly return: {returns.mean():.4%}")
print(f"Downside deviation: {downside_deviation:.4%}")
print(f"Annualized Sortino (sqrt(12) convention): {sortino:.3f}")
print(f"Below-target observations: {len(below_target)} of {len(returns)}")
print(f"Conditional downside RMS: {conditional_rms:.4%}")
print(f"Filtered downside standard deviation: {filtered_std:.4%}")
Monthly MAR: 0.4868%
Mean monthly return: 0.6250%
Downside deviation: 1.4357%
Annualized Sortino (sqrt(12) convention): 0.334
Below-target observations: 5 of 12
Conditional downside RMS: 2.2242%
Filtered downside standard deviation: 1.1180%
The formula divides squared shortfalls by all 12 observations. Dividing by only the five misses produces a conditional root mean square of 2.2242%, which is a valid descriptive statistic when labeled but is not the same lower partial moment. Taking the standard deviation of only the misses is a different error. It recenters those shortfalls around their own mean and produces 1.1180%, even though downside deviation measures distance from the target.
The annualized ratio of 0.334 uses the stated square-root convention. Twelve observations are far too few for a stable performance conclusion, and the constructed sequence is not evidence of an investment edge.
Why the target and denominator must be reported
A zero-MAR Sortino and a 6%-target Sortino can differ substantially on the same returns. Changing from daily to monthly returns can also change which observations fall below the target. Report:
- the exact MAR and why it matches the decision,
- whether the target is constant or time varying,
- simple or log returns and gross or net performance,
- the observation frequency and annualization method,
- the number of valid and below-target observations,
- whether the denominator uses all observations or only shortfalls,
- missing-value, zero-denominator, and external-cash-flow policies, and
- the sample period, strategy search, and out-of-sample status.
A time-varying target must be aligned observation by observation. For example, a liability benchmark or cash rate can change through time. Subtracting one current annual rate from years of historical daily returns mixes information and units.
How Sortino and Sharpe differ
Sharpe measures average differential return relative to total differential-return variability. Sortino measures average return above a target relative to squared target shortfalls. The ratios can rank strategies differently because they use different targets and denominators.
Neither ranking is universally preferable. Upside volatility may represent desirable convexity, unstable exposure, a one-off windfall, stale marks, or estimation noise. The decision context determines whether excluding it from the risk denominator helps. Negative skew and rare crashes remain dangerous because a finite sample may contain too few tail events to estimate downside risk. A strategy with no observed target miss has zero sample downside deviation. That does not establish zero economic risk.
The Sortino ratio also ignores loss order and recovery. A clustered drawdown and scattered losses can produce the same lower partial moment. Inspect maximum drawdown, expected shortfall, stress scenarios, exposure, liquidity, and portfolio correlation alongside the ratio.
Common mistakes in backtests
Downside deviation can be understated by stale or model-based prices, return smoothing, survivorship bias, missing delisted assets, infrequent valuation, and ignored intraperiod losses. Overlapping returns and serial correlation reduce the effective information in the sample. Fees, spread, slippage, financing, funding, borrow costs, and market impact belong in the return series before the calculation.
Optimizing parameters for the highest Sortino introduces the same selection problem as optimizing Sharpe. The ratio has no built-in correction for the number of tried strategies. Preserve the full search, use untouched evaluation data, and apply multiple-testing controls. No universal threshold such as 1 or 2 can replace that evidence.
Python library conventions
Community VectorBT 1.1.0 subtracts a constant per-period required_return, averages squared shortfalls over all non-missing observations, multiplies downside deviation by the square root of its annualization factor, and annualizes mean excess return linearly. Its returns accessor also provides rolling downside risk and rolling Sortino. Set year_freq explicitly because the current global default is 365 days.
PyBroker 2.0.1 computes downside deviation from negative per-bar returns around a fixed zero target and divides by the full bar count. When StrategyConfig.bars_per_year is set, it scales the ratio by that value's square root. Its current evaluation source returns infinity for a positive mean with no negative bar. PyBroker's backtest metrics are therefore not a configurable-MAR implementation.
Zero-denominator behavior differs across libraries. Some return infinity, some return NaN, and some return zero. Treat that output as a prompt to inspect the observations and conventions, not as a performance claim.