Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix CAGR calculation and add periods parameter to relevant functions #330

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
124 changes: 81 additions & 43 deletions quantstats/reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,13 @@


def _get_trading_periods(periods_per_year=252):
"""returns trading periods per year and half year"""
half_year = _ceil(periods_per_year / 2)
return periods_per_year, half_year


def _match_dates(returns, benchmark):
"""match dates of returns and benchmark"""
if isinstance(returns, _pd.DataFrame):
loc = max(returns[returns.columns[0]].ne(0).idxmax(), benchmark.ne(0).idxmax())
else:
Expand All @@ -66,6 +68,7 @@ def html(
match_dates=True,
**kwargs,
):
"""generates full HTML tear sheet report"""

if output is None and not _utils._in_notebook():
raise ValueError("`output` must be specified")
Expand All @@ -86,9 +89,10 @@ def html(
returns = _utils._prepare_returns(returns)

strategy_title = kwargs.get("strategy_title", "Strategy")
if isinstance(returns, _pd.DataFrame):
if len(returns.columns) > 1 and isinstance(strategy_title, str):
strategy_title = list(returns.columns)
if isinstance(returns, _pd.DataFrame) and \
len(returns.columns) > 1 and \
isinstance(strategy_title, str):
strategy_title = list(returns.columns)

if benchmark is not None:
benchmark_title = kwargs.get("benchmark_title", "Benchmark")
Expand Down Expand Up @@ -501,6 +505,7 @@ def full(
match_dates=True,
**kwargs,
):
"""calculates and plots full performance metrics"""

# prepare timeseries
if match_dates:
Expand All @@ -517,9 +522,10 @@ def full(
strategy_title = kwargs.get("strategy_title", "Strategy")
active = kwargs.get("active_returns", "False")

if isinstance(returns, _pd.DataFrame):
if len(returns.columns) > 1 and isinstance(strategy_title, str):
strategy_title = list(returns.columns)
if isinstance(returns, _pd.DataFrame) and \
len(returns.columns) > 1 and \
isinstance(strategy_title, str):
strategy_title = list(returns.columns)

if benchmark is not None:
benchmark.name = benchmark_title
Expand Down Expand Up @@ -651,6 +657,7 @@ def basic(
match_dates=True,
**kwargs,
):
"""calculates and plots basic performance metrics"""

# prepare timeseries
if match_dates:
Expand All @@ -667,9 +674,10 @@ def basic(
strategy_title = kwargs.get("strategy_title", "Strategy")
active = kwargs.get("active_returns", "False")

if isinstance(returns, _pd.DataFrame):
if len(returns.columns) > 1 and isinstance(strategy_title, str):
strategy_title = list(returns.columns)
if isinstance(returns, _pd.DataFrame) and \
len(returns.columns) > 1 and \
isinstance(strategy_title, str):
strategy_title = list(returns.columns)

if _utils._in_notebook():
iDisplay(iHTML("<h4>Performance Metrics</h4>"))
Expand Down Expand Up @@ -731,6 +739,7 @@ def metrics(
match_dates=True,
**kwargs,
):
"""calculates and displays various performance metrics"""

if match_dates:
returns = returns.dropna()
Expand All @@ -757,11 +766,6 @@ def metrics(
else:
blank = [""]

# if isinstance(returns, _pd.DataFrame):
# if len(returns.columns) > 1:
# raise ValueError("`returns` needs to be a Pandas Series or one column DataFrame. multi colums DataFrame was passed")
# returns = returns[returns.columns[0]]

if prepare_returns:
df = _utils._prepare_returns(returns)

Expand Down Expand Up @@ -836,7 +840,7 @@ def metrics(
else:
metrics["Total Return %"] = (df.sum() * pct).map("{:,.2f}".format)

metrics["CAGR﹪%"] = _stats.cagr(df, rf, compounded) * pct
metrics["CAGR﹪%"] = _stats.cagr(df, rf, compounded, win_year) * pct

metrics["~~~~~~~~~~~~~~"] = blank

Expand All @@ -846,19 +850,14 @@ def metrics(
)
if mode.lower() == "full":
metrics["Smart Sharpe"] = _stats.smart_sharpe(df, rf, win_year, True)
# metrics['Prob. Smart Sharpe Ratio %'] = _stats.probabilistic_sharpe_ratio(df, rf, win_year, False, True) * pct

metrics["Sortino"] = _stats.sortino(df, rf, win_year, True)
if mode.lower() == "full":
# metrics['Prob. Sortino Ratio %'] = _stats.probabilistic_sortino_ratio(df, rf, win_year, False) * pct
metrics["Smart Sortino"] = _stats.smart_sortino(df, rf, win_year, True)
# metrics['Prob. Smart Sortino Ratio %'] = _stats.probabilistic_sortino_ratio(df, rf, win_year, False, True) * pct

metrics["Sortino/√2"] = metrics["Sortino"] / _sqrt(2)
if mode.lower() == "full":
# metrics['Prob. Sortino/√2 Ratio %'] = _stats.probabilistic_adjusted_sortino_ratio(df, rf, win_year, False) * pct
metrics["Smart Sortino/√2"] = metrics["Smart Sortino"] / _sqrt(2)
# metrics['Prob. Smart Sortino/√2 Ratio %'] = _stats.probabilistic_adjusted_sortino_ratio(df, rf, win_year, False, True) * pct
metrics["Omega"] = _stats.omega(df, rf, 0.0, win_year)

metrics["~~~~~~~~"] = blank
Expand Down Expand Up @@ -923,20 +922,28 @@ def metrics(
elif isinstance(returns, _pd.DataFrame):
metrics["Volatility (ann.) %"] = ret_vol

metrics["Calmar"] = _stats.calmar(df, prepare_returns=False)
metrics["Calmar"] = _stats.calmar(df, prepare_returns=False, periods=win_year)
metrics["Skew"] = _stats.skew(df, prepare_returns=False)
metrics["Kurtosis"] = _stats.kurtosis(df, prepare_returns=False)

metrics["~~~~~~~~~~"] = blank

metrics["Expected Daily %%"] = (
_stats.expected_return(df, compounded=compounded, prepare_returns=False) * pct
_stats.expected_return(df,
compounded=compounded,
prepare_returns=False) * pct
)
metrics["Expected Monthly %%"] = (
_stats.expected_return(df, compounded=compounded, aggregate="M", prepare_returns=False) * pct
_stats.expected_return(df,
compounded=compounded,
aggregate="M",
prepare_returns=False) * pct
)
metrics["Expected Yearly %%"] = (
_stats.expected_return(df, compounded=compounded, aggregate="A", prepare_returns=False) * pct
_stats.expected_return(df,
compounded=compounded,
aggregate="A",
prepare_returns=False) * pct
)
metrics["Kelly Criterion %"] = (
_stats.kelly_criterion(df, prepare_returns=False) * pct
Expand All @@ -958,10 +965,6 @@ def metrics(

metrics["Gain/Pain Ratio"] = _stats.gain_to_pain_ratio(df, rf)
metrics["Gain/Pain (1M)"] = _stats.gain_to_pain_ratio(df, rf, "M")
# if mode.lower() == 'full':
# metrics['GPR (3M)'] = _stats.gain_to_pain_ratio(df, rf, "Q")
# metrics['GPR (6M)'] = _stats.gain_to_pain_ratio(df, rf, "2Q")
# metrics['GPR (1Y)'] = _stats.gain_to_pain_ratio(df, rf, "A")
metrics["~~~~~~~"] = blank

metrics["Payoff Ratio"] = _stats.payoff_ratio(df, prepare_returns=False)
Expand Down Expand Up @@ -991,32 +994,52 @@ def metrics(
metrics["1Y %"] = comp_func(df[df.index >= d]) * pct

d = today - relativedelta(months=35)
metrics["3Y (ann.) %"] = _stats.cagr(df[df.index >= d], 0.0, compounded) * pct
metrics["3Y (ann.) %"] = _stats.cagr(df[df.index >= d],
0.0,
compounded,
win_year) * pct

d = today - relativedelta(months=59)
metrics["5Y (ann.) %"] = _stats.cagr(df[df.index >= d], 0.0, compounded) * pct
metrics["5Y (ann.) %"] = _stats.cagr(df[df.index >= d],
0.0,
compounded,
win_year) * pct

d = today - relativedelta(years=10)
metrics["10Y (ann.) %"] = _stats.cagr(df[df.index >= d], 0.0, compounded) * pct
metrics["10Y (ann.) %"] = _stats.cagr(df[df.index >= d],
0.0,
compounded,
win_year) * pct

metrics["All-time (ann.) %"] = _stats.cagr(df, 0.0, compounded) * pct
metrics["All-time (ann.) %"] = _stats.cagr(df, 0.0, compounded, win_year) * pct

# best/worst
if mode.lower() == "full":
metrics["~~~"] = blank
metrics["Best Day %"] = _stats.best(df, compounded=compounded, prepare_returns=False) * pct
metrics["Best Day %"] = _stats.best(df,
compounded=compounded,
prepare_returns=False) * pct
metrics["Worst Day %"] = _stats.worst(df, prepare_returns=False) * pct
metrics["Best Month %"] = (
_stats.best(df, compounded=compounded, aggregate="M", prepare_returns=False) * pct
_stats.best(df,
compounded=compounded,
aggregate="M",
prepare_returns=False) * pct
)
metrics["Worst Month %"] = (
_stats.worst(df, aggregate="M", prepare_returns=False) * pct
)
metrics["Best Year %"] = (
_stats.best(df, compounded=compounded, aggregate="A", prepare_returns=False) * pct
_stats.best(df,
compounded=compounded,
aggregate="A",
prepare_returns=False) * pct
)
metrics["Worst Year %"] = (
_stats.worst(df, compounded=compounded, aggregate="A", prepare_returns=False) * pct
_stats.worst(df,
compounded=compounded,
aggregate="A",
prepare_returns=False) * pct
)

# dd
Expand All @@ -1031,14 +1054,23 @@ def metrics(
if mode.lower() == "full":
metrics["~~~~~"] = blank
metrics["Avg. Up Month %"] = (
_stats.avg_win(df, compounded=compounded, aggregate="M", prepare_returns=False) * pct
_stats.avg_win(df,
compounded=compounded,
aggregate="M",
prepare_returns=False) * pct
)
metrics["Avg. Down Month %"] = (
_stats.avg_loss(df, compounded=compounded, aggregate="M", prepare_returns=False) * pct
_stats.avg_loss(df,
compounded=compounded,
aggregate="M",
prepare_returns=False) * pct
)
metrics["Win Days %%"] = _stats.win_rate(df, prepare_returns=False) * pct
metrics["Win Month %%"] = (
_stats.win_rate(df, compounded=compounded, aggregate="M", prepare_returns=False) * pct
_stats.win_rate(df,
compounded=compounded,
aggregate="M",
prepare_returns=False) * pct
)
metrics["Win Quarter %%"] = (
_stats.win_rate(df, compounded=compounded, aggregate="Q", prepare_returns=False) * pct
Expand Down Expand Up @@ -1212,15 +1244,16 @@ def plots(
match_dates=True,
**kwargs,
):
"""Plots for strategy performance"""

benchmark_colname = kwargs.get("benchmark_title", "Benchmark")
strategy_colname = kwargs.get("strategy_title", "Strategy")
active = kwargs.get("active", "False")

if isinstance(returns, _pd.DataFrame):
if len(returns.columns) > 1:
if isinstance(strategy_colname, str):
strategy_colname = list(returns.columns)
if isinstance(returns, _pd.DataFrame) and \
len(returns.columns) > 1 and \
isinstance(strategy_colname, str):
strategy_colname = list(returns.columns)

win_year, win_half_year = _get_trading_periods(periods_per_year)

Expand Down Expand Up @@ -1471,6 +1504,7 @@ def plots(


def _calc_dd(df, display=True, as_pct=False):
"""Returns drawdown stats"""
dd = _stats.to_drawdown_series(df)
dd_info = _stats.drawdown_details(dd)

Expand Down Expand Up @@ -1555,6 +1589,7 @@ def _calc_dd(df, display=True, as_pct=False):


def _html_table(obj, showindex="default"):
"""Returns HTML table"""
obj = _tabulate(
obj, headers="keys", tablefmt="html", floatfmt=".2f", showindex=showindex
)
Expand All @@ -1569,6 +1604,7 @@ def _html_table(obj, showindex="default"):


def _download_html(html, filename="quantstats-tearsheet.html"):
"""Downloads HTML report"""
jscode = _regex.sub(
" +",
" ",
Expand All @@ -1589,6 +1625,7 @@ def _download_html(html, filename="quantstats-tearsheet.html"):


def _open_html(html):
"""Opens HTML in a new tab"""
jscode = _regex.sub(
" +",
" ",
Expand All @@ -1604,6 +1641,7 @@ def _open_html(html):


def _embed_figure(figfiles, figfmt):
"""Embeds the figure bytes in the html output"""
if isinstance(figfiles, list):
embed_string = "\n"
for figfile in figfiles:
Expand Down
Loading