pmorissette / ffn

ffn - a financial function library for Python
pmorissette.github.io/ffn
MIT License
1.97k stars 294 forks source link

Include risk-free rate in Sharpe ratio calculation #4

Closed vfilimonov closed 9 years ago

vfilimonov commented 9 years ago

So far Sharpe ratio was calculated implying rf=0%. Added functionality to change rf both for individual object and "system-wide": for entire class.

TODO later: extent to possibility of having time-series of risk-free ratios

P.S. As I understand from this, daily and monthly returns are annualized? I have _monthly_rf and _daily_rf annualized as well: Phil, please check.

pmorissette commented 9 years ago

Hey Vlad,

Another good idea - the implementation looks good to me.

My only question/concern is the annualization of returns & rates. Since we are currently annualizing the return component as such

daily_mean = r.mean() * 252
monthly_mean = mr.mean() * 12

should we not be doing something similar for the risk-free rate

_daily_rf  = _yearly_rf / 252.
_monthly_rf = _yearly_rf / 12.

for sake of consistency? Or alternatively, maybe we should be using geometric compounding to annualize the daily and monthly returns?

The reason I initially used simple returns return * n is because this is how the Sharpe ratio is typically annualized mean(r) * std(r) * sqrt(252). I am aware that this method is not perfect (for example Andrew Lo's paper http://www.edge-fund.com/Lo02.pdf) but it is still the most commonly used way to annualize the Sharpe ratio.

What do you think?

vfilimonov commented 9 years ago

My logic was the following: if we're given the yearly rate _yearly_rf, then monthly rate should be calculated from the compounding rule P0 * (1+_yearly_rf) = P0 * (1+_monthly_rf) ** 12, i.e:

_monthly_rf = np.power(1+_yearly_rf, 1./12.) - 1.

Here _yearly_rf / 12. gives indeed a first order approximation. When it is calculated in this way, _monthly_rf is consistent with monthly returns that are calculated via resampled price series (price.resample('M', 'last').to_returns().mean()).

Now, since we have annualized returns and volatility:

self.monthly_mean = mr.mean() * 12
self.monthly_vol = mr.std() * np.sqrt(12)
self.monthly_sharpe = self.monthly_mean / self.monthly_vol

then we need to subtract from monthly_mean value, which is annualized in the same way:

_monthly_rf = (np.power(1+_yearly_rf, 1./12.) - 1.) * 12

I think it's OK to keep both average returns and rates annualized as long as it is documented - then daily/monthly/yearly returns, volatilities and Sharpe ratios could be directly compared.

pmorissette commented 9 years ago

Hey Vlad,

The more I think about it, the more I think your approach was fine to begin with. Thanks again for the pull request!