Article Updated for Python 3.10, February 2023
When carrying out an algorithmic trading strategy it is tempting to consider the annualised return as the most useful performance metric. However, there are many flaws with using this measure in isolation. The calculation of returns for certain strategies is not completely straightforward. This is especially true for strategies that aren't directional such as market-neutral variants or strategies which make use of leverage. These factors make it hard to compare two strategies based solely upon their returns.
In addition, if we are presented with two strategies possessing identical returns how do we know which one contains more risk? Further, what do we even mean by "more risk"? In finance, we are often concerned with volatility of returns and periods of drawdown. Thus if one of these strategies has a significantly higher volatility of returns we would likely find it less attractive, despite the fact that its historical returns might be similar if not identical.
These problems of strategy comparison and risk assessment motivate the use of the Sharpe Ratio.
Definition of the Sharpe Ratio
William Forsyth Sharpe is a Nobel-prize winning economist, who helped create the Capital Asset Pricing Model (CAPM) and developed the Sharpe Ratio in 1966 (later updated in 1994).
The Sharpe Ratio $S$ is defined by the following relation:
\begin{eqnarray} S = \frac{\mathbb{E}(R_a - R_b)}{\sqrt{\text{Var} (R_a - R_b)}} \end{eqnarray}Where $R_a$ is the period return of the asset or strategy and $R_b$ is the period return of a suitable benchmark.
The ratio compares the mean average of the excess returns of the asset or strategy with the standard deviation of those returns. Thus a lower volatility of returns will lead to a greater Sharpe ratio, assuming identical returns.
The "Sharpe Ratio" often quoted by those carrying out trading strategies is the annualised Sharpe, the calculation of which depends upon the trading period of which the returns are measured. Assuming there are $N$ trading periods in a year, the annualised Sharpe is calculated as follows:
\begin{eqnarray*} S_A = \sqrt{N} \frac{\mathbb{E}(R_a - R_b)}{\sqrt{\text{Var} (R_a - R_b)}} \end{eqnarray*}Note that the Sharpe ratio itself MUST be calculated based on the Sharpe of that particular time period type. For a strategy based on trading period of days, $N = 252$ (as there are 252 trading days in a year, not 365), and $R_a$, $R_b$ must be the daily returns. Similarly for hours $N = 252 \times 6.5 = 1638$, not $N = 252 \times 24 = 6048$, since there are only 6.5 hours in a trading day.
Benchmark Inclusion
The formula for the Sharpe ratio above alludes to the use of a benchmark. A benchmark is used as a "yardstick" or a "hurdle" that a particular strategy must overcome for it to worth considering. For instance, a simple long-only strategy using US large-cap equities should hope to beat the S&P500 index on average, or match it for less volatility.
The choice of benchmark can sometimes be unclear. For instance, should a sector Exhange Traded Fund (ETF) be utilised as a performance benchmark for individual equities, or the S&P500 itself? Why not the Russell 3000? Equally should a hedge fund strategy be benchmarking itself against a market index or an index of other hedge funds? There is also the complication of the "risk free rate". Should domestic government bonds be used? A basket of international bonds? Short-term or long-term bills? A mixture? Clearly there are plenty of ways to choose a benchmark! The Sharpe ratio generally utilises the risk-free rate and often, for US equities strategies, this is based on 10-year government Treasury bills.
In one particular instance, for market-neutral strategies, there is a particular complication regarding whether to make use of the risk-free rate or zero as the benchmark. The market index itself should not be utilised as the strategy is, by design, market-neutral. The correct choice for a market-neutral portfolio is not to substract the risk-free rate because it is self-financing. Since you gain a credit interest, $R_f$, from holding a margin, the actual calculation for returns is: $(R_a + R_f) - R_f = R_a$. Hence there is no actual subtraction of the risk-free rate for dollar neutral strategies.
Limitations
Despite the prevalence of the Sharpe ratio within quantitative finance, it does suffer from some limitations.
Firstly, the Sharpe ratio is backward looking. It only accounts for historical returns distribution and volatility, not those occuring in the future. When making judgements based on the Sharpe ratio there is an implicit assumption that the past will be similar to the future. This is evidently not always the case, particular under market regime changes.
The Sharpe ratio calculation assumes that the returns being used are normally distributed (i.e. Gaussian). Unfortunately, markets often suffer from kurtosis above that of a normal distribution. Essentially the distribution of returns has "fatter tails" and thus extreme events are more likely to occur than a Gaussian distribution would lead us to believe. Hence, the Sharpe ratio is poor at characterising tail risk.
This can be clearly seen in strategies which are highly prone to such risks. For instance, the sale of call options (aka "pennies under a steam roller"). A steady stream of option premia are generated by the sale of call options over time, leading to a low volatility of returns, with a strong excess above a benchmark. In this instance the strategy would possess a high Sharpe ratio (based on historical data). However, it does not take into account that such options may be called, leading to significant and sudden drawdowns (or even wipeout) in the equity curve. Hence, as with any measure of algorithmic trading strategy performance, the Sharpe ratio cannot be used in isolation.
Although this point might seem obvious to some, transaction costs MUST be included in the calculation of Sharpe ratio in order for it to be realistic. There are countless examples of trading strategies that have high Sharpes (and thus a likelihood of great profitability) only to be reduced to low Sharpe, low profitability strategies once realistic costs have been factored in. This means making use of the net returns when calculating in excess of the benchmark. Hence, transaction costs must be factored in upstream of the Sharpe ratio calculation.
Practical Usage and Examples
One obvious question that has remained unanswered thus far in this article is "What is a good Sharpe Ratio for a strategy?". Pragmatically, you should ignore any strategy that possesses an annualised Sharpe ratio $S < 1$ after transaction costs. Quantitative hedge funds tend to ignore any strategies that possess Sharpe ratios $S < 2$. One prominent quantitative hedge fund that I am familiar with wouldn't even consider strategies that had Sharpe ratios $S < 3$ while in research. As a retail algorithmic trader, if you can achieve a Sharpe ratio $S>2$ then you are doing very well.
The Sharpe ratio will often increase with trading frequency. Some high frequency strategies will have high single (and sometimes low double) digit Sharpe ratios, as they can be profitable almost every day and certainly every month. These strategies rarely suffer from catastrophic risk and thus minimise their volatility of returns, which leads to such high Sharpe ratios.
Examples of Sharpe Ratios
This has been quite a theoretical article up to this point. Now we will turn our attention to some actual examples. We will start simply, by considering a long-only buy-and-hold of an individual equity then consider a market-neutral strategy. Both of these examples have been carried out in the Python pandas data analysis library.
The first task is to actually obtain the data and put it into a pandas DataFrame object. You will need to obtain a CSV file for GOOGL and a CSV file for GS (Goldman Sachs PLC) both starting from January 3rd 2005 and ending January 1st 2013. You can make use of any data vendor you wish as long as the data has the following format:
Date Open High Low Close Adj Close Volume
0 2005-01-03 4.939940 5.096096 4.891391 5.072823 5.072823 633134232
1 2005-01-04 5.040040 5.078328 4.841842 4.867367 4.867367 549685764
2 2005-01-05 4.841091 4.927427 4.810561 4.842593 4.842593 329134536
3 2005-01-06 4.881882 4.902402 4.697698 4.718468 4.718468 415068516
4 2005-01-07 4.770771 4.861111 4.724224 4.851101 4.851101 386129484
We begin by defining our imports, for this code we will only need Pandas and Numpy. Then we create our DataFrames from our CSV files and set the Index as a DatetimeIndex using the Date column. We then call the two functions in the __main__
function.
# sharpe.py
import numpy as np
import pandas as pd
def create_stock_df(csv):
csv_df = pd.read_csv(csv)
csv_df = csv_df.set_index(pd.DatetimeIndex(csv_df['Date']))
return csv_df
if __name__ == "__main__":
goog_csv = "PATH/TO/YOUR/GOOGL/CSV"
gs_csv = "PATH/TO/YOUR/GS/CSV"
goog = create_stock_df(goog_csv)
gs = create_stock_df(gs_csv)
Next we need to calculate our annualised Sharpe ratio.
def annualised_sharpe(returns, N=252):
"""
Calculate the annualised Sharpe ratio of a returns stream
based on a number of trading periods, N. N defaults to 252,
which then assumes a stream of daily returns.
The function assumes that the returns are the excess of
those compared to a benchmark.
"""
return np.sqrt(N) * returns.mean() / returns.std()
Now that we can calculate the annualised Sharpe ratio we can test out a buy and hold strategy for two equities using Google (GOOG) and Goldman Sachs (GS). We can create an additional helper function that allows us to quickly see buy-and-hold Sharpe across multiple equities for the same (hardcoded) period:
def equity_sharpe(ticker):
"""
Calculates the annualised Sharpe ratio based on the daily
returns of an equity ticker symbol listed in AlphaVantage.
"""
# Use the percentage change method to easily calculate daily returns
ticker['daily_ret'] = ticker['Adj Close'].pct_change()
# Assume an average annual risk-free rate over the period of 5%
ticker['excess_daily_ret'] = ticker['daily_ret'] - 0.05/252
# Return the annualised Sharpe ratio based on the excess daily returns
return annualised_sharpe(ticker['excess_daily_ret'])
For Google, the Sharpe ratio for buying and holding is 0.4844
. For Goldman Sachs it is 0.1814
:
>>> equity_sharpe('GOOG')
0.4843921300482182
>>> equity_sharpe('GS')
0.18140815797044094
Now we can try the same calculation for a market-neutral strategy. The goal of this strategy is to fully isolate a particular equity's performance from the market in general. The simplest way to achieve this is to go short an equal amount (in dollars) of an Exchange Traded Fund (ETF) that is designed to track such a market. The most ovious choice for the US large-cap equities market is the S&P500 index, which is tracked by the SPDR ETF, with the ticker of SPY.
To calculate the annualised Sharpe ratio of such a strategy we will obtain the historical prices for SPY and calculate the percentage returns in a similar manner to the previous stocks, with the exception that we will not use the risk-free benchmark. We will calculate the net daily returns which requires subtracting the difference between the long and the short returns and then dividing by 2, as we now have twice as much trading capital. Here is the Python/pandas code to carry this out. First we update our main function to read in the SPY csv and create our benchmark DataFrame.
if __name__ == "__main__":
goog_csv = "PATH/TO/YOUR/GOOGL/CSV"
gs_csv = "PATH/TO/YOUR/GS/CSV"
spy_csv = "PATH/TO/YOUR/SPY/CSV"
goog = create_stock_df(goog_csv)
gs = create_stock_df(gs_csv)
benchmark = create_stock_df(spy_csv)
Then we can create the market neutral sharpe.
def market_neutral_sharpe(ticker, benchmark):
"""
Calculates the annualised Sharpe ratio of a market
neutral long/short strategy inolving the long of 'ticker'
with a corresponding short of the 'benchmark'.
"""
# Calculate the percentage returns on each of the time series
ticker['daily_ret'] = ticker['Adj Close'].pct_change()
benchmark['daily_ret'] = benchmark['Adj Close'].pct_change()
# Create a new DataFrame to store the strategy information
# The net returns are (long - short)/2, since there is twice
# the trading capital for this strategy
strat = pd.DataFrame(index=ticker.index)
strat['net_ret'] = (ticker['daily_ret'] - benchmark['daily_ret'])/2.0
# Return the annualised Sharpe ratio for this strategy
return annualised_sharpe(strat['net_ret'])
For Google, the Sharpe ratio for the long/short market-neutral strategy is 0.5423
. For Goldman Sachs it is 0.2016
:
>>> market_neutral_sharpe('GOOG', 'SPY')
0.5422999606160066
>>> market_neutral_sharpe('GS', 'SPY')
0.2015836069300835
Despite the Sharpe ratio being used almost everywhere in algorithmic trading, we need to consider other metrics of performance and risk. In later articles we will discuss drawdowns and how they affect the decision to run a strategy or not.