ranaroussi / quantstats

Portfolio analytics for quants, written in Python
Apache License 2.0
4.91k stars 851 forks source link

CAGR and Sharpe ratio should have the same sign #230

Open kasparthommen opened 1 year ago

kasparthommen commented 1 year ago

Hi,

Please consider the following snippet:

import datetime as dt
import pandas as pd
import quantstats as qs

returns = [0.5, -0.4, 0.5, -0.4]  # i.e., +50%, -40%, +50%, -40%
years = [dt.date(2000 + i, 1, 1) for i in range(len(returns))]

returns = pd.Series(data=returns, index=years)
cagr = qs.stats.cagr(returns)
sharpe = qs.stats.sharpe(returns, periods=1)
print(f"CAGR = {cagr:.2f}, Sharpe = {sharpe:.2f}")

This will print:

CAGR = -0.07, Sharpe = 0.10

Now, an investment that has a negative cumulative return cannot possibly have a positive Sharpe ratio, and vice-versa. I have written about this problem in my blog, and I also propose a solution there:

https://kasparthommen.github.io/posts/sharpe-ratio.html

TL;DR: Use log returns for Sharpe/Sortino/CALMAR/... ratio computations, not simple returns.

lbsm2017 commented 1 year ago

I think in this case, Sharpe is computed as per : ( returns_mean - rfr) / stdDev(returns)

np.mean([0.50, -0.4, +0.5, -0.4]) 
0.04999999999999999

std = np.std([0.50, -0.4, 0.5, -0.4])
0.45

Assuming rfr=0.00 , we should get a Sharpe = 0.11 , or 0.096 if we use ddof=1 for stdDev

CAGR might be negative because it's the compounded average growth rate, which is not indeed the mean of individual returns. In fact +50% and -50% have a mean of 0% , but CAGR is 86% , and 75% cumulative return.

I agree with you this can be counterintuitive (and potentially misleading) , and I also think your suggestion to use LogReturns instead, might be solving this issue, on the other hand, Log returns are an approximation of returns, and because of the nature of Log function, the approximation is only acceptable for small values, in your case, with fairly big values (~ grater than 10%) , this is the comparison:

np.array([1.50, 1-0.4, 1+0.5, 1-0.4]).prod() -1 
= -0.19

ret = np.array([0.50, -0.4, 0.5, -0.4])
np.log1p(ret).sum()
= -21%

Log Returns are excellent for intraday prices, daily returns or up to weekly maybe, I would not personally use that for yearly returns, as the results can be quite off.

In any case, a very nice improvement to QuantStats would be to offer the option to use LogReturns or Returns for Sharpe. 100% agree with you for that.

Looking forward to hearing from you. Thanks

kasparthommen commented 1 year ago

Hi @lbsm2017,

Thanks for the detailed answer. You are right that log returns "compress" returns that are large in absolute value. However, when using log returns in the Sharpe ratio computation, the fact that it's a ratio cancels out that effect to some extent:

Try it on real-world data - you'll see that the difference between a Sharpe ratio computed using log returns vs. simple returns is quite small, but the former comes with the additional benefit of always having the same sign as the CAGR.