Sharpe Ratio for Algorithmic Trading Performance Measurement

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**.

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:

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.

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.

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.

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.

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. In the article on securities master implementation in Python and MySQL I created a system for achieving this. Alternatively, we can make use of this simpler code to grab Yahoo Finance data directly and put it straight into a pandas DataFrame. At the bottom of this script I have created a function to calculate the annualised Sharpe ratio based on a time-period returns stream:

import datetime import numpy as np import pandas as pd import urllib2 def get_historic_data(ticker, start_date=(2000,1,1), end_date=datetime.date.today().timetuple()[0:3]): """ Obtains data from Yahoo Finance and adds it to a pandas DataFrame object. ticker: Yahoo Finance ticker symbol, e.g. "GOOG" for Google, Inc. start_date: Start date in (YYYY, M, D) format end_date: End date in (YYYY, M, D) format """ # Construct the Yahoo URL with the correct integer query parameters # for start and end dates. Note that some parameters are zero-based! yahoo_url = "http://ichart.finance.yahoo.com/table.csv?s=%s&a=%s&b=%s&c=%s&d=%s&e=%s&f=%s" % \ (ticker, start_date[1] - 1, start_date[2], start_date[0], end_date[1] - 1, end_date[2], end_date[0]) # Try connecting to Yahoo Finance and obtaining the data # On failure, print an error message try: yf_data = urllib2.urlopen(yahoo_url).readlines() except Exception, e: print "Could not download Yahoo data: %s" % e # Create the (temporary) Python data structures to store # the historical data date_list = [] hist_data = [[] for i in range(6)] # Format and copy the raw text data into datetime objects # and floating point values (still in native Python lists) for day in yf_data[1:]: # Avoid the header line in the CSV headers = day.rstrip().split(',') date_list.append(datetime.datetime.strptime(headers[0],'%Y-%m-%d')) for i, header in enumerate(headers[1:]): hist_data[i].append(float(header)) # Create a Python dictionary of the lists and then use that to # form a sorted Pandas DataFrame of the historical data hist_data = dict(zip(['open', 'high', 'low', 'close', 'volume', 'adj_close'], hist_data)) pdf = pd.DataFrame(hist_data, index=pd.Index(date_list)).sort() return pdf 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 have the ability to obtain data from Yahoo Finance and straightforwardly calculate the annualised Sharpe ratio, we can test out a buy and hold strategy for two equities. We will use Google (GOOG) and Goldman Sachs (GS) from Jan 1st 2000 to May 29th 2013 (when I wrote this article!).

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 Yahoo Finance. The dates have been hardcoded here for the QuantStart article on Sharpe ratios. """ # Obtain the equities daily historic data for the desired time period # and add to a pandas DataFrame pdf = get_historic_data(ticker, start_date=(2000,1,1), end_date=(2013,5,29)) # Use the percentage change method to easily calculate daily returns pdf['daily_ret'] = pdf['adj_close'].pct_change() # Assume an average annual risk-free rate over the period of 5% pdf['excess_daily_ret'] = pdf['daily_ret'] - 0.05/252 # Return the annualised Sharpe ratio based on the excess daily returns return annualised_sharpe(pdf['excess_daily_ret'])

For Google, the Sharpe ratio for buying and holding is `0.7501`

. For Goldman Sachs it is `0.2178`

:

>>> equity_sharpe('GOOG') 0.75013831274645904 >>> equity_sharpe('GS') 0.21777027767830823

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:

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'. """ # Get historic data for both a symbol/ticker and a benchmark ticker # The dates have been hardcoded, but you can modify them as you see fit! tick = get_historic_data(ticker, start_date=(2000,1,1), end_date=(2013,5,29)) bench = get_historic_data(benchmark, start_date=(2000,1,1), end_date=(2013,5,29)) # Calculate the percentage returns on each of the time series tick['daily_ret'] = tick['adj_close'].pct_change() bench['daily_ret'] = bench['adj_close'].pct_change() # Create a new DataFrame to store the strategy information # The net returns are (long - short)/2, since there is twice # trading capital for this strategy strat = pd.DataFrame(index=tick.index) strat['net_ret'] = (tick['daily_ret'] - bench['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.7597`

. For Goldman Sachs it is `0.2999`

:

>>> market_neutral_sharpe('GOOG', 'SPY') 0.75966612163452329 >>> market_neutral_sharpe('GS', 'SPY') 0.29991401047248328

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.

comments powered by Disqus