Sharpe Ratio for Algorithmic Trading Performance Measurement

Sharpe Ratio for Algorithmic Trading Performance Measurement

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.