Sharpe, Sortino and Calmar Ratios with Python

by John | October 17, 2020

Join the discussion

Share this post with your friends!

In this article we will calculate the a number of well know statistics related to risk and reward in equities. In order to provide examples on real data we will use the following stocks to illustrate the concepts shown. Since the statistics in question are usually calculated on a portfolio, we will add an equal weighted portfolio to the analysis also. 


  • Apple: Stock ticker = AAPL
  • Amazon: Stock ticker = AMZN
  • Facebook: Stock ticker = FB
  • Google: Stock ticker = GOOGL
  • Microsoft: Stock ticker = MSFT
  • Port: Equally weighted portfolio of the securities above. 


Statistics to be calculated

  • Sharpe ratio 
  • Sortino Ratio 
  • Max Drawdown
  • Calmar Ratio 


Getting the Data


In order to get the data necessary to complete this analysis we will make use of Pandas Datareader, which allows us to directly download stock data into Python. Execute the following code block in your editor:

import as web
import datetime as dt
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt'ggplot')

start = dt.datetime(2013, 1, 1)
end = dt.datetime(2020, 10, 1)

tickers = ['AAPL', 'AMZN', 'MSFT', 'GOOGL','FB']

stocks = web.DataReader(tickers,
                        'yahoo', start, end)['Adj Close']



Symbols          AAPL        AMZN       MSFT       GOOGL         FB
2013-01-02  17.094694  257.309998  23.241472  361.987000  28.000000
2013-01-03  16.878920  258.480011  22.930120  362.197205  27.770000
2013-01-04  16.408764  259.149994  22.500971  369.354340  28.760000
2013-01-07  16.312239  268.459991  22.458902  367.742737  29.420000
2013-01-08  16.356150  266.380005  22.341091  367.017029  29.059999


Change the dataframe to percentage change and create a column for the equally weighted portfolio. Plot the normalized stock prices for comparison. 

df = stocks.pct_change().dropna()
df['Port'] = df.mean(axis=1) # 20% apple, ... , 20% facebook



compare normalized stocks returns


The plot shows the growth of $1 invested on 1st Jan 2013 until 10th Oct 2020. For every $1 you invested in Apple in 2013 you would now have approximately $7 and so-forth. 

Symbols         AAPL       AMZN      MSFT     GOOGL      FB      Port
2020-10-01  6.831944  12.518985  9.141418  4.110369  9.5225  8.936468



 Sharpe Ratio


The Sharpe ratio is the most common ratio for comparing reward (return on investment) to risk (standard deviation). This allows us to adjust the returns on an investment by the amount of risk that was taken in order to achieve it. The Sharpe ratio also provides a useful metric to compare investments. The calculations are as follows:

\(\text{Sharpe ratio} = \frac{\bar{R}-R_f}{\sigma}\)


\(\bar{R} \):  annual expected return of the asset in question. 

\(R_f\) : annual risk-free rate. Think of this like a deposit in the bank earning x% per annum.  

\(\sigma\)  :  annualized standard deviation of returns


Since our data frequency is daily we need to annualize the expected return and standard deviation. This can be achieved by multiplying the daily average return by 255. And multiplying the daily standard deviation by \(\sqrt 255\). For simplicity we will assume that the risk-free rate \(R_f\) = 1% throughout the 7 year period. 


Python code to calculate Sharpe ratio:

def sharpe_ratio(return_series, N, rf):
    mean = return_series.mean() * N -rf
    sigma = return_series.std() * np.sqrt(N)
    return mean / sigma

N = 255 #255 trading days in a year
rf =0.01 #1% risk free rate
sharpes = df.apply(sharpe_ratio, args=(N,rf,),axis=0)



The interpretation of the Sharpe ratio is that higher numbers relate to better risk-adjusted investments. Recall the total return numbers we calculated previously, and notice that even though the majority of the individual stocks had a higher return, they also had a higher standard deviation.


Sortino Ratio

The Sortino ratio is very similar to the Sharpe ratio, the only difference being that where the Sharpe ratio uses all the observations for calculating the standard deviation the Sortino ratio only considers the harmful variance. So in the plot below, we are only considering the deviations colored red. The rationale for this is that we aren't too worried about positive deviations, however, the negative deviations are of great concern, since they represent loss of our money. 



 \(\text{Sortino ratio} = \frac{\bar{R}-R_f}{\sigma^-}\)


Everything in the ratio above is the same as the Sharpe ratio except \(\sigma^-\) represents the annualized down-side standard deviation. 

Python code to calculate and plot the results. 

def sortino_ratio(series, N,rf):
    mean = series.mean() * N -rf
    std_neg = series[series<0].std()*np.sqrt(N)
    return mean/std_neg

sortinos = df.apply(sortino_ratio, args=(N,rf,), axis=0 )
plt.ylabel('Sortino Ratio')


sortino ratio with python



It looks like Amazon and the portfolio are almost neck-and-neck in terms of performance for this ratio. As with the Sharpe ratio, higher values are preferable.


Max Drawdown

Max drawdown quantifies the steepest decline from peak to trough observed for an investment. This is useful for a number of reasons, mainly the fact that it doesn't rely on the underlying returns being normally distributed. It also gives us an indication of conditionality amongst the returns increments. Whereas in the previous ratios, we only considered the overall reward relative to risk, however, it may be that consecutive returns are not independent leading to unacceptably high losses of a given period of time. 


To calculate max drawdown first we need to calculate a series of drawdowns as follows:

\(\text{drawdowns} = \frac{\text{peak-trough}}{\text{peak}}\)


We then take the minimum of this value throughout the period of analysis. 


max drawdown visualized with python


Python code to calculate max drawdown for the stocks listed above. 

def max_drawdown(return_series):
    comp_ret = (return_series+1).cumprod()
    peak = comp_ret.expanding(min_periods=1).max()
    dd = (comp_ret/peak)-1
    return dd.min()

max_drawdowns = df.apply(max_drawdown,axis=0)
plt.yabel('Max Drawdown')


max drawdowns comparison python


I believe max drawdown is usually presented as an absolute value, however, I will leave it the way it is for the time being. It looks like the portfolio performed the best according to max drawdown during the 7 year period we analyzed. The max drawdown should be interpreted as; numbers closer to zero are preferable. 




 Calmar Ratio

The final risk/reward ratio we will consider is the Calmar ratio. This is similar to the other ratios, with the key difference being that the Calmar ratio uses max drawdown in the denominator as opposed to standard deviation. 


\(\text{Calmar ratio} = \frac{\bar{R}}{\text{max drawdown}}\)


calmars = df.mean()*255/abs(max_drawdowns)
plt.ylabel('Calmar ratio')


calmar ratios comparison python



It appears that Microsoft performs the best according to this ratio. As with the Sharpe and Sortino, higher values are preferable.




Wrapping up


If you would like to see these ratios applied to a more realistic backtest you can take a look at this crypto-algo trading example


Let's combine all the ratios we have calculated and put them in a pandas dataframe.


btstats = pd.DataFrame()
btstats['sortino'] = sortinos
btstats['sharpe'] = sharpes
btstats['maxdd'] = max_drawdowns
btstats['calmar'] = calmars

         sortino    sharpe     maxdd    calmar
AAPL     1.286527  0.988260 -0.385159  0.758557
AMZN     1.678367  1.194467 -0.341038  1.107348
MSFT     1.549506  1.181460 -0.280393  1.158764
GOOGL    1.127802  0.808867 -0.308708  0.704477
FB       1.413423  0.994674 -0.429609  0.823078
Port     1.676891  1.311674 -0.274688  1.140057



Plotting dataframe as table


plt.table(cellText=np.round(btstats.values,2), colLabels=btstats.columns,


plot pandas dataframe as table