A comprehensive list of quantitative finance portfolio/strategy performance measures.
The total return measures the overall performance of an investment over a specific period from t=0 to t=T.
Where:
-
$r_t$ is the return at time$t$ -
$T$ is the total number of periods
import numpy as np
def total_return(returns):
return np.prod(1 + returns) - 1
The annualized return normalizes the total return to a one-year period, allowing for comparison of investments held for different lengths of time.
Where:
- 252 is the typical number of trading days in a year (for daily returns)
-
$T$ is the total number of periods
def annualized_return(returns, periods_per_year):
total_return = np.prod(1 + returns) - 1
return (1 + total_return) ** (periods_per_year / len(returns)) - 1
The Sharpe ratio measures the excess return (or risk premium) per unit of deviation in an investment asset or a trading strategy. Keep in mind that Sharpe Ratio is always annulized.
Where:
-
$R_p$ is the return of the portfolio -
$R_f$ is the risk-free rate -
$E[R_p - R_f]$ is the expected value of the excess return -
$\sigma_p$ is the standard deviation of the portfolio's excess return
def sharpe_ratio(returns, risk_free_rate, periods_per_year):
excess_returns = returns - risk_free_rate
return np.sqrt(periods_per_year) * excess_returns.mean() / excess_returns.std()
The Sortino ratio is a variation of the Sharpe ratio that differentiates harmful volatility from total overall volatility by using the asset's standard deviation of negative portfolio returns.
Where:
-
$R_p$ is the return of the portfolio -
$R_f$ is the risk-free rate -
$\sigma_d$ is the standard deviation of negative portfolio returns
def sortino_ratio(returns, risk_free_rate, periods_per_year):
excess_returns = returns - risk_free_rate
downside_returns = np.minimum(excess_returns, 0)
return np.sqrt(periods_per_year) * excess_returns.mean() / np.std(downside_returns)
Maximum Drawdown (MDD) measures the largest peak-to-trough decline in the cumulative returns of a portfolio.
Where:
-
$T$ is the total number of periods - Peak Value is the highest cumulative return achieved
- Trough Value is the lowest cumulative return after the peak
def maximum_drawdown(returns):
cum_returns = np.cumprod(1 + returns)
peak = np.maximum.accumulate(cum_returns)
drawdown = (cum_returns - peak) / peak
return np.min(drawdown)
The Calmar ratio is a risk-adjusted performance measure that relates the average annual compounded rate of return to the maximum drawdown.
def calmar_ratio(returns, periods_per_year):
return annualized_return(returns, periods_per_year) / abs(maximum_drawdown(returns))
The Information Ratio measures a portfolio manager's ability to generate excess returns relative to a benchmark, but also attempts to identify the consistency of the investor.
Where:
-
$R_p$ is the return of the portfolio -
$R_b$ is the return of the benchmark -
$\sigma_{R_p - R_b}$ is the standard deviation of the excess return
def information_ratio(returns, benchmark_returns, periods_per_year):
active_returns = returns - benchmark_returns
return np.sqrt(periods_per_year) * active_returns.mean() / active_returns.std()
Alpha represents the active return on an investment, gauging the performance of an investment against a market index or benchmark that is considered to represent the market's movement as a whole.
Where:
-
$R_p$ is the return of the portfolio -
$R_f$ is the risk-free rate -
$\beta$ is the beta of the portfolio -
$R_m$ is the return of the market
def alpha(returns, benchmark_returns, risk_free_rate):
excess_returns = returns - risk_free_rate
excess_benchmark_returns = benchmark_returns - risk_free_rate
beta = np.cov(excess_returns, excess_benchmark_returns)[0, 1] / np.var(excess_benchmark_returns)
return excess_returns.mean() - beta * excess_benchmark_returns.mean()
Beta is a measure of the volatility, or systematic risk, of a security or portfolio in comparison to the market as a whole.
Where:
-
$R_p$ is the return of the portfolio -
$R_m$ is the return of the market
def beta(returns, benchmark_returns):
return np.cov(returns, benchmark_returns)[0, 1] / np.var(benchmark_returns)
The Treynor ratio, also known as the reward-to-volatility ratio, measures the returns earned in excess of that which could have been earned on a riskless investment per unit of market risk assumed.
Where:
-
$R_p$ is the return of the portfolio -
$R_f$ is the risk-free rate -
$\beta$ is the beta of the portfolio
def treynor_ratio(returns, benchmark_returns, risk_free_rate, periods_per_year):
excess_returns = returns - risk_free_rate
beta_val = beta(returns, benchmark_returns)
return np.sqrt(periods_per_year) * excess_returns.mean() / beta_val
VaR estimates how much a set of investments might lose, given normal market conditions, in a set time period such as a day.
Where:
-
$\alpha$ is the confidence level -
$L$ is the loss of the portfolio
def value_at_risk(returns, confidence_level=0.95):
return np.percentile(returns, 100 * (1 - confidence_level))
CVaR, also known as Expected Shortfall, measures the expected loss given that the loss is greater than the VaR.
$$\text{CVaR}\alpha = E[L|L \geq \text{VaR}\alpha]$$
Where:
-
$\alpha$ is the confidence level -
$L$ is the loss of the portfolio
def conditional_value_at_risk(returns, confidence_level=0.95):
var = value_at_risk(returns, confidence_level)
return returns[returns <= var].mean()
The Omega ratio is a risk-return performance measure of an investment asset, portfolio, or strategy.
Where:
-
$r$ is the threshold return -
$F(x)$ is the cumulative distribution function of the returns
def omega_ratio(returns, threshold):
return np.mean(np.maximum(returns - threshold, 0)) / np.mean(np.maximum(threshold - returns, 0))
The Kappa ratio is a generalization of the Sortino ratio for higher-order moments.
Where:
-
$R_p$ is the return of the portfolio -
$r$ is the threshold return -
$LPM_n(r)$ is the Lower Partial Moment of order$n$
def kappa_ratio(returns, threshold, n):
lower_partial_moment = np.mean(np.maximum(threshold - returns, 0) ** n)
return (np.mean(returns) - threshold) / (lower_partial_moment ** (1 / n))
The Upside Potential Ratio measures upside performance relative to downside risk.
Where:
-
$R$ is the return -
$r$ is the threshold return -
$LPM_2(r)$ is the Lower Partial Moment of order 2
def upside_potential_ratio(returns, threshold):
upside = np.maximum(returns - threshold, 0)
downside = np.maximum(threshold - returns, 0)
return np.mean(upside) / np.sqrt(np.mean(downside ** 2))
The Gain-Loss Ratio is the ratio between the average gain and the average loss.
Where:
-
$R$ represents the returns
def gain_loss_ratio(returns):
gains = returns[returns > 0]
losses = returns[returns < 0]
return np.mean(gains) / abs(np.mean(losses))
The Ulcer Index measures downside risk in terms of both depth and duration of price declines.
Where:
-
$R_i$ is the percentage drawdown from previous peak -
$n$ is the number of periods
def ulcer_index(returns):
cum_returns = np.cumprod(1 + returns)
drawdowns = 1 - cum_returns / np.maximum.accumulate(cum_returns)
return np.sqrt(np.mean(drawdowns ** 2))
The Pain Index is the average percentage drawdown over the period.
Where:
-
$D_i$ is the drawdown at time$i$ -
$n$ is the number of periods
def pain_index(returns):
cum_returns = np.cumprod(1 + returns)
drawdowns = 1 - cum_returns / np.maximum.accumulate(cum_returns)
return np.mean(drawdowns)
The Pain Ratio relates the excess return over the risk-free rate to the Pain Index.
Where:
-
$R_p$ is the return of the portfolio -
$R_f$ is the risk-free rate
def pain_ratio(returns, risk_free_rate, periods_per_year):
return (annualized_return(returns, periods_per_year) - risk_free_rate) / pain_index(returns)
The Martin Ratio is similar to the Pain Ratio but uses the Ulcer Index instead of the Pain Index.
Where:
-
$R_p$ is the return of the portfolio -
$R_f$ is the risk-free rate
def martin_ratio(returns, risk_free_rate, periods_per_year):
return (annualized_return(returns, periods_per_year) - risk_free_rate) / ulcer_index(returns)
Skewness measures the asymmetry of the probability distribution of returns about its mean.
Where:
-
$R$ is the return -
$\mu$ is the mean of the returns -
$\sigma$ is the standard deviation of the returns
from scipy import stats
def skewness(returns):
return stats.skew(returns)
Kurtosis measures the "tailedness" of the probability distribution of returns.
Where:
-
$R$ is the return -
$\mu$ is the mean of the returns -
$\sigma$ is the standard deviation of the returns
def kurtosis(returns):
return stats.kurtosis(returns)
The Jarque-Bera test is a statistical test of the hypothesis that sample data have the skewness and kurtosis matching a normal distribution.
Where:
-
$n$ is the number of observations -
$S$ is the sample skewness -
$K$ is the sample kurtosis
def jarque_bera_test(returns):
return stats.jarque_bera(returns)
The Hurst exponent measures the long-term memory of a time series. It relates to the autocorrelations of the time series and the rate at which these decrease as the lag between pairs of values increases.
Where:
-
$R/S$ is the rescaled range -
$T$ is the duration of the sample of data -
$H$ is the Hurst exponent
def hurst_exponent(returns, lags=range(2, 100)):
tau = [np.sqrt(np.std(np.subtract(returns[lag:], returns[:-lag]))) for lag in lags]
poly = np.polyfit(np.log(lags), np.log(tau), 1)
return poly[0] * 2.0
Autocorrelation measures the correlation between a time series and a lagged version of itself.
Where:
-
$R_t$ is the return at time$t$ -
$\mu$ is the mean of the returns -
$\sigma^2$ is the variance of the returns -
$k$ is the lag
import pandas as pd
def autocorrelation(returns, lag=1):
return pd.Series(returns).autocorr(lag)
This metric measures the longest streak of consecutive positive returns (wins) and negative returns (losses).
def max_consecutive_wins_losses(returns):
streaks = np.diff(np.where(np.diff(returns >= 0))[0])
return max(streaks), min(streaks)
The Win Rate is the proportion of positive returns to total returns.
def win_rate(returns):
return np.sum(returns > 0) / len(returns)
The Profit Factor is the ratio of the sum of all profits over the sum of all losses.
def profit_factor(returns):
gains = returns[returns > 0]
losses = returns[returns < 0]
return np.sum(gains) / abs(np.sum(losses))
The Tail Ratio measures the ratio of right tail returns to left tail returns.
def tail_ratio(returns, percentile=5):
return abs(np.percentile(returns, 100 - percentile)) / abs(np.percentile(returns, percentile))
Downside Deviation is similar to standard deviation but only for returns below a threshold.
Where:
-
$R_i$ is the return at time$i$ -
$T$ is the target return (often 0 or the risk-free rate) -
$n$ is the number of returns
def downside_deviation(returns, threshold=0):
downside_returns = np.minimum(returns - threshold, 0)
return np.sqrt(np.mean(downside_returns ** 2))
Here's an example of how you might use these functions:
import numpy as np
# Generate some random returns
np.random.seed(42)
returns = np.random.normal(0.001, 0.02, 1000)
benchmark_returns = np.random.normal(0.0005, 0.01, 1000)
risk_free_rate = 0.02 / 252 # Assuming daily returns and 2% annual risk-free rate
periods_per_year = 252 # Assuming daily returns
# Calculate various metrics
print(f"Total Return: {total_return(returns):.4f}")
print(f"Annualized Return: {annualized_return(returns, periods_per_year):.4f}")
print(f"Sharpe Ratio: {sharpe_ratio(returns, risk_free_rate, periods_per_year):.4f}")
print(f"Maximum Drawdown: {maximum_drawdown(returns):.4f}")
print(f"Calmar Ratio: {calmar_ratio(returns, periods_per_year):.4f}")
print(f"Information Ratio: {information_ratio(returns, benchmark_returns, periods_per_year):.4f}")
print(f"Sortino Ratio: {sortino_ratio(returns, risk_free_rate, periods_per_year):.4f}")
print(f"Omega Ratio: {omega_ratio(returns, risk_free_rate):.4f}")
print(f"Kappa Ratio: {kappa_ratio(returns, risk_free_rate, 3):.4f}")
print(f"Ulcer Index: {ulcer_index(returns):.4f}")
print(f"Hurst Exponent: {hurst_exponent(returns):.4f}")
print(f"Autocorrelation: {autocorrelation(returns):.4f}")
print(f"Win Rate: {win_rate(returns):.4f}")
print(f"Profit Factor: {profit_factor(returns):.4f}")
print(f"Tail Ratio: {tail_ratio(returns):.4f}")
print(f"Downside Deviation: {downside_deviation(returns):.4f}")
This comprehensive set of performance measures provides a thorough analysis of quantitative finance strategies and portfolios. Each measure offers unique insights into different aspects of performance, risk, and return characteristics. By using these measures in combination, analysts can gain a well-rounded understanding of an investment strategy's behavior and effectiveness.