Sharpe ratio - A deep dive
Sharpe Ratio¶
Primer¶
Sharpe Ratio is the most common metric used to measure risk in finance.
The formula is
(return on portfolio - risk free rate)/standard deviation of the excess return on the portfolio
There are tons of resources on the internet about sharpe ratio.
This investopedia page is a good introduction.
We assume the risk free rate to be zero, then the formula simply becomes mean returns divided by the standard deviation of returns.
import numpy as np
import pandas as pd
import empyrical as ep
import matplotlib.pyplot as plt
import seaborn as sns
sns.set()
Single stock¶
Let us create a set of 5 stocks with same monthly returns but with different standard deviation for a period of 20 years. Stocks are named a to e with a being the stock with least volatility e with the highest volatility
mu = 0.01 #monthly returns
# Express sigma in relation to mean
multipliers = (0.5,1,2,5,10)
collect = {}
for s,m in zip(list('abcde'), multipliers):
collect[s] = np.random.normal(loc=mu, scale=m*mu, size=240)
df = pd.DataFrame(collect)
axes = df.plot(kind='kde', title='Distribution of returns', subplots=True, figsize=(8,8))
for ax in axes:
ax.axvline(0.01, color='red')
ep.cum_returns(df).plot(title='Cumulative returns')
s = df.describe().round(4)
s = s.append(pd.Series(df.mean()/df.std(), name='sharpe'))
s = s.append(pd.Series((df.mean()/df.std())*np.sqrt(12), name='sharpe_annual'))
s
Although all values are generated from the normal distribution with a fixed mean of 0.01, we could see a stark difference.
The first plot shows the distribution or the spread of returns where a and b are close to the mean of 0.01 (the red line) while e is just nowhere. You can also look at the table where the minimum and maximum returns for a are much closer while for e they are much wider. This implies this is very difficult to infer the mean returns of e as they wildly swing from one extreme to other while on the other hand we could be fairly confident with our estimates for a. From a risk perspective, it is very difficult to differentiate the actual mean value of stock e due to its high volatility. A look at the sharpe ratio for the instruments also follow this pattern.
A look at the cumulative returns suggests that stock e has left everybody behind and despite wild swings, it is way above; it seems that volatility helps in the long run. But this is only a single simulation of how prices would behave for a given risk/return profile.
Multiple stocks¶
Let us now create a simulation of 1000 samples of 240 days for each of the stocks and see how they behave.
We assume 5 risk profiles in the market and draw 1000 samples from each of them, so 5000 instruments. We would like to see how well one would have fared had he invested in these instruments. Theroetically, we only have 5 different profiles but let us do the simulation
def draw_samples(mu=0.01,sigma=0.01,size=1000,n=240):
samples = np.random.normal(loc=mu,scale=sigma,size=(size,n))
return samples
mu = 0.01
multipliers = (0.5,1,2,5,10)
sharpe_ratios_dict = {}
returns_dict = {}
for s,m in zip(list('abcde'), multipliers):
samples = draw_samples(mu=mu, sigma=m*mu)
sharpe_ratios_dict[s] = (samples.mean(axis=1)/samples.std(axis=1))*np.sqrt(12)
returns_dict[s] = (samples+1).prod(axis=1)
sharpe_ratios = pd.DataFrame(sharpe_ratios_dict)
returns = pd.DataFrame(returns_dict)
axes = sns.boxplot(data=sharpe_ratios)
axes.set_ylabel('sharpe ratio annualized')
sharpe_ratios.describe()
axes = sns.boxplot(data=returns.loc[:,'a':'d'])
axes.set_title('Distribution of returns')
axes.set_ylabel('Cumulative returns')
plt.figure()
axes2 = sns.boxplot(data=returns.e)
axes.set_ylabel('Cumulative returns')
print('Returns table')
returns.describe()
Though we only have 5 risk profiles and a fixed mean, there is a massive difference in the results.
The sharpe ratios for stock a and b are closely clustered. So if you have invested in any of the stocks falling into the risk profile a, you would end up more or less the same way the others have ended. The difference between the minimum and maximum returns would just be 2 times, volatility would not have played a big role and this risk profile have performed better in all times. The box plot shows the sharpe ratio decreasing with an increase in volatility.
On the other end of the spectrum, the maximum and minimum returns in profile e is just crazy. You could have even ended up in a loss or could have become a millionare.
Despite the same returns, risk does some crazy things.
Probability of returns¶
The monthly return in our example is 1%. So, if you have invested for 20 years, your return should be
1.01**240 = ~10.9
From the returns table, you could straight away infer that for profiles a,b,c the mean and median or more or less close to this value. Let us calculate the number of portfolios that beat this benchmark in all of the risk profiles.
Also, half of the returns is 1.005**240 = ~3.3
Let us also calculate the number of portfolios that beat this benchmark too
def calculate_probability(returns, v):
"""
Given an array of returns and a value v, return the number of
times returns exceed v
"""
length = len(returns)
positive = (returns>v).sum()
return positive
full_returns = [calculate_probability(returns[col],1.01**240) for col in returns]
half_returns = [calculate_probability(returns[col],1.005**240) for col in returns]
prob = pd.DataFrame({'full_returns': full_returns, 'half_returns': half_returns},
index=list('abcde'))
prob
Profiles a,b,c got 1000 out of 1000 in gaining half the monthly returns while profile e can't even fulfil 50% returns. Thus, if 1000 people has got their investments in profile c, even half of them would not have got 3 times the money; they would have even fared worse.
Thus, an increase in volatility becomes more of luck and less of skill. Looking at only the returns in deciding on a portfolio is fraught with too much risk and may just turn out to be a lucky affair which may not work out for all the people.
Sharpe multipliers¶
When I generated the samples, I used a fixed monthly mean of 1% but for deviation, multipliers are used. The mean is multiplied by the deviation to get the numbers.
multipliers = (0.5,1,2,5,10)
These multipliers are just inverse for the original formula standard_deviation/mean
, something similar to the P/E ratio. The higher this number, then the portfolio is risky, the lower this number the better. These are not annualized and hence they could not be compared on a similar scale. Below are the tables for the multipliers and their annualized sharpe ratios.
Thus if you portfolio has a mean of 0.2% and a deviation of 0.6% on a weekly basis, then your sharpe multiple is 0.6/0.2 = 3 and since it is on a weekly basis, the annualized sharpe ratio is (0.2/0.6) x sqrt(52) = ~2.4
multipliers = np.arange(1,11)
periods = np.array([252,52,5,1]).reshape(4,1)
base = 1
index = ['daily', 'weekly', 'monthly', 'yearly']
print('Annualized sharpe ratios for sharpe multiples')
pd.DataFrame((1/multipliers)*np.sqrt(periods),
index=index, columns=multipliers).T.round(4)