ranaroussi / quantstats

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

stats.py: res = abs(total + 1.0) ** (1.0 / years) - 1 <-- ZeroDivisionErrorZeroDivisionError #334

Open normanlmfung opened 5 months ago

normanlmfung commented 5 months ago

hi Team,

stats.py: res = abs(total + 1.0) ** (1.0 / years) - 1 <-- ZeroDivisionError

 3 import quantstats as qs
      5 RISK_FREE_RATE = 0.05 # 5%
----> 7 qs.reports.full(
      8     returns=pd_total_equity['interval_return'], 
      9     rf=RISK_FREE_RATE,
     10     title="Tear Sheet"
     11     )

File ~\AppData\Roaming\Python\Python39\site-packages\quantstats\reports.py:839, in metrics(returns, benchmark, rf, display, mode, sep, compounded, periods_per_year, prepare_returns, match_dates, **kwargs)
    836 else:
    837     metrics["Total Return %"] = (df.sum() * pct).map("{:,.2f}".format)
--> 839 metrics["CAGR﹪%"] = _stats.cagr(df, rf, compounded) * pct
    841 metrics["~~~~~~~~~~~~~~"] = blank
    843 metrics["Sharpe"] = _stats.sharpe(df, rf, win_year, True)

File ~\AppData\Roaming\Python\Python39\site-packages\quantstats\stats.py:531, in cagr(returns, rf, compounded, periods)
    527     total = _np.sum(total)
    529 years = (returns.index[-1] - returns.index[0]).days / periods
--> 531 res = abs(total + 1.0) ** (1.0 / **years**) - 1
    533 if isinstance(returns, _pd.DataFrame):
    534     res = _pd.Series(res)

**ZeroDivisionError**: float division by zero

To reproduce, use this sample file: singlelegta_total_equity_cache_4.csv

from datetime import datetime
import arrow
import pandas as pd
import numpy as np
src_file = './total_equity/singlelegta_total_equity_cache_4.csv'
pd_total_equity = pd.read_csv(src_file)
pd_total_equity['datetime'] = pd.to_datetime(pd_total_equity['interval'])
pd_total_equity.set_index('datetime', inplace=True)
pd_total_equity['interval_return'] = pd_total_equity['total_equity'].pct_change()
pd_total_equity['cumulative_interval_return'] = (1 + pd_total_equity['interval_return']).cumprod()
pd_total_equity = pd_total_equity[~pd_total_equity.interval_return.isna()]
pd_total_equity

%matplotlib inline
import quantstats as qs

RISK_FREE_RATE = 0.05 # 5%

qs.reports.full(
    returns=pd_total_equity['interval_return'], 
    rf=RISK_FREE_RATE,
    title="Tear Sheet"
    )
normanlmfung commented 5 months ago

I temporarily fixed by setting 'res' to zero when year==0:

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)

    # Bug: https://github.com/ranaroussi/quantstats/issues/334
    years = (returns.index[-1] - returns.index[0]).days / periods

    res = abs(total + 1.0) ** (1.0 / years) - 1 if years!=0 else 0

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

    return res