ranaroussi / quantstats

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

Suggestion for Improving the CAGR Calculation in Your Package #359

Open wangyxDQPI opened 1 month ago

wangyxDQPI commented 1 month ago

Dear

I hope this email finds you well. I have been using your [package name] and find it incredibly useful for my work. However, I encountered an issue with the cagr function that I believe could be improved.

Currently, the calculation of years in the cagr function is performed as follows: years = (returns.index[-1] - returns.index[0]).days / periods This method assumes that the periods parameter represents the number of days in a year (e.g., 252 for trading days), but it doesn't account for cases where the number of returns may not span the entire period or if there are missing days in the time series. This can lead to inaccurate results, especially in time series with irregular data points.

I suggest modifying the calculation of years to: years = len(returns) / periods This approach uses the length of the returns series to calculate the total number of periods, which provides a more accurate estimation of the years, especially when dealing with incomplete data.

Here's the revised version of the cagr function: def cagr(returns, rf=0.0, compounded=True, periods=252): """ Calculates the communicative annualized growth return (CAGR%) of access returns

If rf is non-zero, you must specify periods.
In this case, rf is assumed to be expressed in yearly (annualized) terms
"""
total = _utils._prepare_returns(returns, rf)
if compounded:
    total = comp(total)
else:
    total = _np.sum(total)

years = len(returns) / periods

res = abs(total + 1.0) ** (1.0 / years) - 1

if isinstance(returns, _pd.DataFrame):
    res = _pd.Series(res)
    res.index = returns.columns

return res

I believe this adjustment will enhance the accuracy and robustness of the CAGR calculation in various scenarios.

Thank you for your time and for maintaining such a valuable package. I look forward to your thoughts on this suggestion.

Best regards,

yuxue wang wangyx0629@nepu.edu.cn

itsXactlY commented 1 month ago

Awesome! It took me days to weeks to debug my shit here inside my framework im building, and then out of the sudden;

Full traceback: Traceback (most recent call last): File "/home/alca/projects/backtrader-quant-main/backtest_cpp_mssql.py", line 84, in backtest strategy = cerebro.run()[0] ^^^^^^^^^^^^^ File "/home/alca/projects/.bt/lib/python3.12/site-packages/backtrader/cerebro.py", line 1131, in run runstrat = self.runstrategies(iterstrat) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/home/alca/projects/.bt/lib/python3.12/site-packages/backtrader/cerebro.py", line 1297, in runstrategies self._runonce(runstrats) File "/home/alca/projects/.bt/lib/python3.12/site-packages/backtrader/cerebro.py", line 1656, in _runonce strat._once() File "/home/alca/projects/.bt/lib/python3.12/site-packages/backtrader/lineiterator.py", line 297, in _once indicator._once() File "/home/alca/projects/.bt/lib/python3.12/site-packages/backtrader/lineiterator.py", line 297, in _once indicator._once() File "/home/alca/projects/.bt/lib/python3.12/site-packages/backtrader/linebuffer.py", line 631, in _once self.once(self._minperiod, self.buflen()) File "/home/alca/projects/.bt/lib/python3.12/site-packages/backtrader/linebuffer.py", line 755, in once self._once_op(start, end) File "/home/alca/projects/.bt/lib/python3.12/site-packages/backtrader/linebuffer.py", line 772, in _once_op dst[i] = op(srca[i], srcb[i]) ^^^^^^^^^^^^^^^^^^^^ ZeroDivisionError: float division by zero

Popped up over and over - i thrown everything apart, besides turn of Quantstats.

Then, out of sudden this happend;

Traceback (most recent call last):
  File "/home/alca/projects/backtrader-quant-main/backtest_cpp_mssql.py", line 103, in backtest
    quantstats.reports.html(returns, output=html_filepath, title=f'{current_date}_{startdate}_to_{enddate}_1m')
  File "/home/alca/projects/.bt/lib/python3.12/site-packages/quantstats/reports.py", line 124, in html
    mtrx = metrics(
           ^^^^^^^^
  File "/home/alca/projects/.bt/lib/python3.12/site-packages/quantstats/reports.py", line 839, in metrics
    metrics["CAGR﹪%"] = _stats.cagr(df, rf, compounded) * pct
                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/alca/projects/.bt/lib/python3.12/site-packages/quantstats/stats.py", line 531, in cagr
    res = abs(total + 1.0) ** (1.0 / years) - 1
                               ~~~~^~~~~~~
ZeroDivisionError: float division by zero

Your fix is just my lifesaver right now, over spend another couple hours figure this out. Many thanks! <3

sammybadd commented 1 month ago

Has this issue been fixed? I am still seeing incorrect CAGRs due to my data being weekly data and quantstats recognizing only daily data.

grzesir commented 4 weeks ago

Check out https://github.com/Lumiwealth/quantstats_lumi, which is being updated regularly. We are a fork of this library that is being maintained by Lumiwealth