diff --git a/quantstats/_plotting/core.py b/quantstats/_plotting/core.py index 818aca55..74a759e6 100644 --- a/quantstats/_plotting/core.py +++ b/quantstats/_plotting/core.py @@ -50,9 +50,10 @@ 'ytick.color': '#666666' }) -_FLATUI_COLORS = ["#fedd78", "#348dc1", "#af4b64", - "#4fa487", "#9b59b6", "#808080"] -_GRAYSCALE_COLORS = ['silver', '#222222', 'gray'] * 3 +_FLATUI_COLORS = ['#FEDD78', '#348DC1', '#BA516B', '#4FA487', '#9B59B6', + '#613F66', '#84B082', '#DC136C', '#559CAD', '#4A5899'] +_GRAYSCALE_COLORS = ['#000000', '#222222', '#555555', '#888888', '#AAAAAA', + '#CCCCCC', '#EEEEEE', '#333333', '#666666', '#999999'] def _get_colors(grayscale): @@ -108,11 +109,11 @@ def plot_returns_bars(returns, benchmark=None, ax.spines['left'].set_visible(False) # use a more precise date string for the x axis locations in the toolbar - fig.suptitle(title+"\n", y=.99, fontweight="bold", fontname=fontname, + fig.suptitle(title, y=.94, fontweight="bold", fontname=fontname, fontsize=14, color="black") if subtitle: - ax.set_title("\n%s - %s " % ( + ax.set_title("%s - %s \n" % ( df.index.date[:1][0].strftime('%Y'), df.index.date[-1:][0].strftime('%Y') ), fontsize=12, color='gray') @@ -151,7 +152,7 @@ def plot_returns_bars(returns, benchmark=None, ax.axhline(0, ls="--", lw=1, color="#000000", zorder=2) # if isinstance(benchmark, _pd.Series) or hline: - ax.legend(fontsize=12) + ax.legend(fontsize=11) _plt.yscale("symlog" if log_scale else "linear") @@ -163,6 +164,9 @@ def plot_returns_bars(returns, benchmark=None, ax.yaxis.set_major_formatter(_FuncFormatter(format_pct_axis)) + if benchmark is None and len(_pd.DataFrame(returns).columns) == 1: + ax.get_legend().remove() + try: _plt.subplots_adjust(hspace=0, bottom=0, top=1) except Exception: @@ -238,11 +242,11 @@ def plot_timeseries(returns, benchmark=None, ax.spines['bottom'].set_visible(False) ax.spines['left'].set_visible(False) - fig.suptitle(title+"\n", y=.99, fontweight="bold", fontname=fontname, + fig.suptitle(title, y=.94, fontweight="bold", fontname=fontname, fontsize=14, color="black") if subtitle: - ax.set_title("\n%s - %s " % ( + ax.set_title("%s - %s \n" % ( returns.index.date[:1][0].strftime('%e %b \'%y'), returns.index.date[-1:][0].strftime('%e %b \'%y') ), fontsize=12, color='gray') @@ -258,8 +262,8 @@ def plot_timeseries(returns, benchmark=None, ax.plot(returns, lw=lw, label=returns.name, color=colors[1], alpha=alpha) elif isinstance(returns, _pd.DataFrame): # color_dict = {col: colors[i+1] for i, col in enumerate(returns.columns)} - for col in returns.columns: - ax.plot(returns[col], lw=lw, label=col, alpha=alpha) + for i, col in enumerate(returns.columns): + ax.plot(returns[col], lw=lw, label=col, alpha=alpha, color=colors[i+1]) if fill: if isinstance(returns, _pd.Series): @@ -287,8 +291,7 @@ def plot_timeseries(returns, benchmark=None, color='white' if grayscale else 'black', zorder=2) # if isinstance(benchmark, _pd.Series) or hline is not None: - # ax.legend(fontsize=12) - ax.legend(fontsize=12) + ax.legend(fontsize=11) _plt.yscale("symlog" if log_scale else "linear") @@ -303,6 +306,9 @@ def plot_timeseries(returns, benchmark=None, fontweight='bold', fontsize=12, color="black") ax.yaxis.set_label_coords(-.1, .5) + if benchmark is None and len(_pd.DataFrame(returns).columns) == 1: + ax.get_legend().remove() + try: _plt.subplots_adjust(hspace=0, bottom=0, top=1) except Exception: @@ -336,9 +342,11 @@ def plot_histogram(returns, benchmark, resample="M", bins=20, ylabel=True, subtitle=True, compounded=True, savefig=None, show=True): - colors = ['#348dc1', '#003366', 'red'] - if grayscale: - colors = ['silver', 'gray', 'black'] + # colors = ['#348dc1', '#003366', 'red'] + # if grayscale: + # colors = ['silver', 'gray', 'black'] + + colors, _, _ = _get_colors(grayscale) apply_fnc = _stats.comp if compounded else _np.sum if benchmark is not None: @@ -348,17 +356,19 @@ def plot_histogram(returns, benchmark, resample="M", bins=20, returns = returns.fillna(0).resample(resample).apply( apply_fnc).resample(resample).last() + figsize = (0.995 * figsize[0], figsize[1]) fig, ax = _plt.subplots(figsize=figsize) + ax.spines['top'].set_visible(False) ax.spines['right'].set_visible(False) ax.spines['bottom'].set_visible(False) ax.spines['left'].set_visible(False) - fig.suptitle(title+"\n", y=.99, fontweight="bold", fontname=fontname, + fig.suptitle(title, y=.94, fontweight="bold", fontname=fontname, fontsize=14, color="black") if subtitle: - ax.set_title("\n%s - %s " % ( + ax.set_title("%s - %s \n" % ( returns.index.date[:1][0].strftime('%Y'), returns.index.date[-1:][0].strftime('%Y') ), fontsize=12, color='gray') @@ -366,40 +376,60 @@ def plot_histogram(returns, benchmark, resample="M", bins=20, fig.set_facecolor('white') ax.set_facecolor('white') - # Why do we need average? - # ax.axvline(returns.mean(), ls="--", lw=1.5, - # zorder=2, label="Average") + if isinstance(returns, _pd.DataFrame) and len(returns.columns) == 1: + returns = returns[returns.columns[0]] + + pallete = colors[1:2] if benchmark is None else colors[:2] + alpha = 0.7 + if isinstance(returns, _pd.DataFrame): + pallete = colors[1:len(returns.columns)+1] if benchmark is None else colors[:len(returns.columns)+1] + if len(returns.columns) > 1: + alpha = 0.5 if benchmark is not None: if isinstance(returns, _pd.Series): combined_returns = benchmark.to_frame().join(returns.to_frame()) \ .stack().reset_index() \ - .rename(columns={'level_1': 'Portfolio', 0: 'Returns'}) + .rename(columns={'level_1': '', 0: 'Returns'}) elif isinstance(returns, _pd.DataFrame): combined_returns = benchmark.to_frame().join(returns) \ .stack().reset_index() \ - .rename(columns={'level_1': 'Portfolio', 0: 'Returns'}) + .rename(columns={'level_1': '', 0: 'Returns'}) + x = _sns.histplot(data=combined_returns, x='Returns', + bins=bins, alpha=alpha, kde=kde, + stat="density", hue='', + palette=pallete, + ax=ax) - _sns.histplot(data=combined_returns, x='Returns', - bins=bins, alpha=0.4, kde=kde, - stat="density", hue='Portfolio', - ax=ax) else: if isinstance(returns, _pd.Series): combined_returns = returns.copy() - _sns.histplot(data=combined_returns, bins=bins, - alpha=0.4, kde=kde, - stat="density", - ax=ax) - ax.legend(fontsize=12) + if kde: + _sns.kdeplot(data=combined_returns, color='black', ax=ax) + x = _sns.histplot(data=combined_returns, bins=bins, + alpha=alpha, + kde=False, + stat="density", + color=colors[1], + ax=ax) + elif isinstance(returns, _pd.DataFrame): combined_returns = returns.stack().reset_index() \ - .rename(columns={'level_1': 'Portfolio', 0: 'Returns'}) - _sns.histplot(data=combined_returns, x='Returns', - bins=bins, alpha=0.4, kde=kde, - stat="density", hue='Portfolio', - ax=ax) + .rename(columns={'level_1': '', 0: 'Returns'}) + # _sns.kdeplot(data=combined_returns, color='black', ax=ax) + x = _sns.histplot(data=combined_returns, x='Returns', + bins=bins, alpha=alpha, kde=kde, + stat="density", hue='', + palette=pallete, + ax=ax) + + # Why do we need average? + if isinstance(combined_returns, _pd.Series) or len(combined_returns.columns) == 1: + ax.axvline(combined_returns.mean(), ls="--", lw=1.5, + zorder=2, label="Average", color="red") + + # _plt.setp(x.get_legend().get_texts(), fontsize=11) ax.xaxis.set_major_formatter(_plt.FuncFormatter( lambda x, loc: "{:,}%".format(int(x*100)))) @@ -408,10 +438,9 @@ def plot_histogram(returns, benchmark, resample="M", bins=20, # ax.axvline(0, lw=1, color="#000000", zorder=2) ax.set_xlabel('') - if ylabel: - ax.set_ylabel("Occurrences", fontname=fontname, - fontweight='bold', fontsize=12, color="black") - ax.yaxis.set_label_coords(-.1, .5) + ax.set_ylabel("Occurrences", fontname=fontname, + fontweight='bold', fontsize=12, color="black") + ax.yaxis.set_label_coords(-.1, .5) # fig.autofmt_xdate() @@ -473,9 +502,8 @@ def plot_rolling_stats(returns, benchmark=None, title="", elif isinstance(returns, _pd.DataFrame): col_names = ['Benchmark', returns_label] df = df[list(_pd.core.common.flatten(col_names))].dropna() - for col in returns_label: - ax.plot(df[col], lw=lw, - label=col) + for i, col in enumerate(returns_label): + ax.plot(df[col], lw=lw, label=col, color=colors[i+1]) ax.plot(df['Benchmark'], lw=lw, label=benchmark.name, color=colors[0], alpha=.8) else: @@ -485,20 +513,19 @@ def plot_rolling_stats(returns, benchmark=None, title="", label=returns.name, color=colors[1]) elif isinstance(returns, _pd.DataFrame): df = df[returns_label].dropna() - for col in returns_label: - ax.plot(df[col], lw=lw, - label=col) + for i, col in enumerate(returns_label): + ax.plot(df[col], lw=lw, label=col, color=colors[i+1]) # rotate and align the tick labels so they look better fig.autofmt_xdate() # use a more precise date string for the x axis locations in the toolbar # ax.fmt_xdata = _mdates.DateFormatter('%Y-%m-%d')\ - fig.suptitle(title+"\n", y=.99, fontweight="bold", fontname=fontname, + fig.suptitle(title, y=.94, fontweight="bold", fontname=fontname, fontsize=14, color="black") if subtitle: - ax.set_title("\n%s - %s " % ( + ax.set_title("%s - %s \n" % ( df.index.date[:1][0].strftime('%e %b \'%y'), df.index.date[-1:][0].strftime('%e %b \'%y') ), fontsize=12, color='gray') @@ -519,7 +546,10 @@ def plot_rolling_stats(returns, benchmark=None, title="", ax.yaxis.set_major_formatter(_FormatStrFormatter('%.2f')) - ax.legend(fontsize=12) + ax.legend(fontsize=11) + + if benchmark is None and len(_pd.DataFrame(returns).columns) == 1: + ax.get_legend().remove() try: _plt.subplots_adjust(hspace=0, bottom=0, top=1) @@ -562,15 +592,16 @@ def plot_rolling_beta(returns, benchmark, ax.spines['bottom'].set_visible(False) ax.spines['left'].set_visible(False) - fig.suptitle(title+"\n", y=.99, fontweight="bold", fontname=fontname, + fig.suptitle(title, y=.94, fontweight="bold", fontname=fontname, fontsize=14, color="black") if subtitle: - ax.set_title("\n%s - %s " % ( + ax.set_title("%s - %s \n" % ( returns.index.date[:1][0].strftime('%e %b \'%y'), returns.index.date[-1:][0].strftime('%e %b \'%y') ), fontsize=12, color='gray') + i = 1 if isinstance(returns, _pd.Series): beta = _stats.rolling_greeks(returns, benchmark, window1)['beta'].fillna(0) ax.plot(beta, lw=lw, label=window1_label, color=colors[1]) @@ -578,9 +609,12 @@ def plot_rolling_beta(returns, benchmark, beta = ({col: _stats.rolling_greeks(returns[col], benchmark, window1)['beta'].fillna(0) for col in returns.columns}) for name, b in beta.items(): - ax.plot(b, lw=lw, label=name + " " + f"({window1_label})") + ax.plot(b, lw=lw, label=name + " " + f"({window1_label})", color=colors[i]) + i += 1 + i = 1 if window2: + lw = lw - 0.5 if isinstance(returns, _pd.Series): ax.plot(_stats.rolling_greeks(returns, benchmark, window2)['beta'], lw=lw, label=window2_label, color="gray", alpha=0.8) @@ -588,9 +622,8 @@ def plot_rolling_beta(returns, benchmark, betas_w2 = ({col: _stats.rolling_greeks(returns[col], benchmark, window2)['beta'] for col in returns.columns}) for name, beta_w2 in betas_w2.items(): - ax.plot(beta_w2, lw=lw, ls='--', label=name + " " + f"({window2_label})", alpha=0.8) - # ax.plot(_stats.rolling_greeks(returns, benchmark, window2)['beta'], - # lw=lw, label=window2_label, color="gray", alpha=0.8) + ax.plot(beta_w2, lw=lw, ls='--', label=name + " " + f"({window2_label})", alpha=0.5, color=colors[i]) + i += 1 beta_min = beta.min() if isinstance(returns, _pd.Series) else min([b.min() for b in beta.values()]) beta_max = beta.max() if isinstance(returns, _pd.Series) else max([b.max() for b in beta.values()]) @@ -616,7 +649,10 @@ def plot_rolling_beta(returns, benchmark, fontweight='bold', fontsize=12, color="black") ax.yaxis.set_label_coords(-.1, .5) - ax.legend(fontsize=12) + ax.legend(fontsize=11) + if benchmark is None and len(_pd.DataFrame(returns).columns) == 1: + ax.get_legend().remove() + try: _plt.subplots_adjust(hspace=0, bottom=0, top=1) except Exception: @@ -665,11 +701,11 @@ def plot_longest_drawdowns(returns, periods=5, lw=1.5, ax.spines['bottom'].set_visible(False) ax.spines['left'].set_visible(False) - fig.suptitle(f"{title} - Worst %.0f Drawdown Periods\n" % - periods, y=.99, fontweight="bold", fontname=fontname, + fig.suptitle(f"{title} - Worst %.0f Drawdown Periods" % + periods, y=.94, fontweight="bold", fontname=fontname, fontsize=14, color="black") if subtitle: - ax.set_title("\n%s - %s " % ( + ax.set_title("%s - %s \n" % ( returns.index.date[:1][0].strftime('%e %b \'%y'), returns.index.date[-1:][0].strftime('%e %b \'%y') ), fontsize=12, color='gray') @@ -767,12 +803,16 @@ def plot_distribution(returns, figsize=(10, 6), ax.spines['bottom'].set_visible(False) ax.spines['left'].set_visible(False) - fig.suptitle(f"{title} - Return Quantiles\n", y=.99, + if title: + title = f"{title} - Return Quantiles" + else: + title = "Return Quantiles" + fig.suptitle(title, y=.94, fontweight="bold", fontname=fontname, fontsize=14, color="black") if subtitle: - ax.set_title("\n%s - %s " % ( + ax.set_title("%s - %s \n" % ( returns.index.date[:1][0].strftime('%e %b \'%y'), returns.index.date[-1:][0].strftime('%e %b \'%y') ), fontsize=12, color='gray') @@ -780,7 +820,14 @@ def plot_distribution(returns, figsize=(10, 6), fig.set_facecolor('white') ax.set_facecolor('white') - port.boxplot(ax=ax)#, colo=tuple(colors[:5])) + _sns.boxplot(data=port, ax=ax, + palette={ + 'Daily': colors[0], + 'Weekly': colors[1], + 'Monthly': colors[2], + 'Quarterly': colors[3], + 'Yearly': colors[4] + }) ax.yaxis.set_major_formatter(_plt.FuncFormatter( lambda x, loc: "{:,}%".format(int(x*100)))) diff --git a/quantstats/_plotting/wrappers.py b/quantstats/_plotting/wrappers.py index 889b6f50..6f595220 100644 --- a/quantstats/_plotting/wrappers.py +++ b/quantstats/_plotting/wrappers.py @@ -64,16 +64,23 @@ def snapshot(returns, grayscale=False, figsize=(10, 8), mode="comp", subtitle=True, savefig=None, show=True, log_scale=False, **kwargs): - strategy_colname = kwargs.get("strategy_title", "Strategy") + strategy_colname = kwargs.get("strategy_col", "Strategy") + multi_column = False if isinstance(returns, _pd.Series): returns.name = strategy_colname elif isinstance(returns, _pd.DataFrame): + if len(returns.columns) > 1: + if strategy_colname in returns.columns: + returns = returns[strategy_colname] + else: + multi_column = True + returns = returns.mean(axis=1) + title = title + " (daily equal-weighted*)" returns.columns = strategy_colname colors = _GRAYSCALE_COLORS if grayscale else _FLATUI_COLORS - - returns = _utils.make_portfolio(returns, 1, mode).pct_change().fillna(0) + returns = _utils.make_portfolio(returns.dropna(), 1, mode).pct_change().fillna(0) if figsize is None: size = list(_plt.gcf().get_size_inches()) @@ -82,20 +89,27 @@ def snapshot(returns, grayscale=False, figsize=(10, 8), fig, axes = _plt.subplots(3, 1, sharex=True, figsize=figsize, gridspec_kw={'height_ratios': [3, 1, 1]}) + if multi_column: + _plt.figtext( + 0, -.05, + " * When a multi-column DataFrame is passed, the mean of all columns will be used as returns.\n" + " To change this behavior, use a pandas Series or pass the column name in the `strategy_col` parameter.", + ha="left", fontsize=11, color='black', alpha=0.6, linespacing=1.5) + for ax in axes: ax.spines['top'].set_visible(False) ax.spines['right'].set_visible(False) ax.spines['bottom'].set_visible(False) ax.spines['left'].set_visible(False) - fig.suptitle(title, fontsize=14, y=.995, + fig.suptitle(title, fontsize=14, y=.97, fontname=fontname, fontweight='bold', color='black') fig.set_facecolor('white') if subtitle: if isinstance(returns, _pd.Series): - axes[0].set_title("\n%s - %s ; Sharpe: %.2f " % ( + axes[0].set_title("%s - %s ; Sharpe: %.2f \n" % ( returns.index.date[:1][0].strftime('%e %b \'%y'), returns.index.date[-1:][0].strftime('%e %b \'%y'), _stats.sharpe(returns) @@ -118,10 +132,10 @@ def snapshot(returns, grayscale=False, figsize=(10, 8), axes[0].axhline(0, color='silver', lw=1, zorder=0) axes[0].set_yscale("symlog" if log_scale else "linear") - axes[0].legend(fontsize=12) + # axes[0].legend(fontsize=12) dd = _stats.to_drawdown_series(returns) * 100 - ddmin = min(_utils._round_to_closest(abs(dd.min()), 5)) + ddmin = _utils._round_to_closest(abs(dd.min()), 5) ddmin_ticks = 5 if ddmin > 50: ddmin_ticks = ddmin / 4 @@ -141,13 +155,13 @@ def snapshot(returns, grayscale=False, figsize=(10, 8), axes[1].axhline(0, color='silver', lw=1, zorder=0) if not grayscale: if isinstance(dd, _pd.Series): - axes[1].fill_between(dd.index, 0, dd, color=colors[1], alpha=.25) + axes[1].fill_between(dd.index, 0, dd, color=colors[2], alpha=.25) elif isinstance(dd, _pd.DataFrame): for i, col in enumerate(dd.columns): axes[1].fill_between(dd[col].index, 0, dd[col], color=colors[i + 1], alpha=.25) axes[1].set_yscale("symlog" if log_scale else "linear") - axes[1].legend(fontsize=12) + # axes[1].legend(fontsize=12) axes[2].set_ylabel('Daily Return', fontname=fontname, fontweight='bold', fontsize=12) @@ -160,17 +174,17 @@ def snapshot(returns, grayscale=False, figsize=(10, 8), axes[2].axhline(0, color=colors[-1], linestyle='--', lw=1, zorder=2) axes[2].set_yscale("symlog" if log_scale else "linear") - axes[2].legend(fontsize=12) + # axes[2].legend(fontsize=12) - retmax = max(_utils._round_to_closest(returns.max() * 100, 5)) - retmin = min(_utils._round_to_closest(returns.min() * 100, 5)) + retmax = _utils._round_to_closest(returns.max() * 100, 5) + retmin = _utils._round_to_closest(returns.min() * 100, 5) retdiff = (retmax - retmin) steps = 5 if retdiff > 50: steps = retdiff / 5 elif retdiff > 30: steps = retdiff / 4 - steps = int(_utils._round_to_closest(steps, 5)) + steps = _utils._round_to_closest(steps, 5) axes[2].set_yticks(_np.arange(retmin, retmax, step=steps)) for ax in axes: @@ -463,7 +477,7 @@ def distribution(returns, fontname='Arial', grayscale=False, ylabel=True, return fig -def histogram(returns, benchmark, resample='M', fontname='Arial', +def histogram(returns, benchmark=None, resample='M', fontname='Arial', grayscale=False, figsize=(10, 5), ylabel=True, subtitle=True, compounded=True, savefig=None, show=True, prepare_returns=True): @@ -673,7 +687,7 @@ def monthly_heatmap(returns, benchmark, annot_size=10, figsize=(10, 5), returns = _stats.monthly_returns(returns, eoy=eoy, compounded=compounded) * 100 - fig_height = len(returns) / 3 + fig_height = len(returns) / 2.5 if figsize is None: size = list(_plt.gcf().get_size_inches()) @@ -682,7 +696,7 @@ def monthly_heatmap(returns, benchmark, annot_size=10, figsize=(10, 5), figsize = (figsize[0], max([fig_height, figsize[1]])) if cbar: - figsize = (figsize[0]*1.04, max([fig_height, figsize[1]])) + figsize = (figsize[0]*1.051, max([fig_height, figsize[1]])) fig, ax = _plt.subplots(figsize=figsize) ax.spines['top'].set_visible(False) diff --git a/quantstats/reports.py b/quantstats/reports.py index 58b624d9..a8fd0462 100644 --- a/quantstats/reports.py +++ b/quantstats/reports.py @@ -26,7 +26,6 @@ ) from base64 import b64encode as _b64encode import re as _regex - from tabulate import tabulate as _tabulate from . import ( __version__, stats as _stats, @@ -34,7 +33,6 @@ ) from dateutil.relativedelta import relativedelta from io import StringIO - try: from IPython.display import ( display as iDisplay, HTML as iHTML @@ -51,10 +49,12 @@ def _get_trading_periods(periods_per_year=252): def _match_dates(returns, benchmark): - returns = returns.loc[ - max(returns.ne(0).idxmax(), benchmark.ne(0).idxmax()):] - benchmark = benchmark.loc[ - max(returns.ne(0).idxmax(), benchmark.ne(0).idxmax()):] + if isinstance(returns, _pd.DataFrame): + loc = max(returns[returns.columns[0]].ne(0).idxmax(), benchmark.ne(0).idxmax()) + else: + loc = max(returns.ne(0).idxmax(), benchmark.ne(0).idxmax()) + returns = returns.loc[loc:] + benchmark = benchmark.loc[loc:] return returns, benchmark @@ -62,11 +62,14 @@ def _match_dates(returns, benchmark): def html(returns, benchmark=None, rf=0., grayscale=False, title='Strategy Tearsheet', output=None, compounded=True, periods_per_year=252, download_filename='quantstats-tearsheet.html', - figfmt='svg', template_path=None, match_dates=False, **kwargs): + figfmt='svg', template_path=None, match_dates=True, **kwargs): if output is None and not _utils._in_notebook(): raise ValueError("`output` must be specified") + if match_dates: + returns = returns.dropna() + win_year, win_half_year = _get_trading_periods(periods_per_year) tpl = "" @@ -75,9 +78,15 @@ def html(returns, benchmark=None, rf=0., grayscale=False, f.close() # prepare timeseries + if match_dates: + returns = returns.dropna() 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 benchmark is not None: benchmark_title = kwargs.get('benchmark_title', 'Benchmark') if kwargs.get('benchmark_title') is None: @@ -220,7 +229,7 @@ def html(returns, benchmark=None, rf=0., grayscale=False, figfile = _utils._file_stream() _plots.histogram(returns, benchmark, grayscale=grayscale, - figsize=(8, 4), subtitle=False, + figsize=(7, 4), subtitle=False, savefig={'fname': figfile, 'format': figfmt}, show=False, ylabel=False, compounded=compounded, prepare_returns=False) @@ -345,9 +354,11 @@ def html(returns, benchmark=None, rf=0., grayscale=False, def full(returns, benchmark=None, rf=0., grayscale=False, figsize=(8, 5), display=True, compounded=True, - periods_per_year=252, match_dates=False, **kwargs): + periods_per_year=252, match_dates=True, **kwargs): # prepare timeseries + if match_dates: + returns = returns.dropna() returns = _utils._prepare_returns(returns) if benchmark is not None: benchmark = _utils._prepare_benchmark(benchmark, returns.index, rf) @@ -360,6 +371,10 @@ def full(returns, benchmark=None, rf=0., grayscale=False, 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 benchmark is not None: benchmark.name = benchmark_title if isinstance(returns, _pd.Series): @@ -396,19 +411,19 @@ def full(returns, benchmark=None, rf=0., grayscale=False, prepare_returns=False, benchmark_title=benchmark_title, strategy_title=strategy_title)) - iDisplay(iHTML('
(no drawdowns)
")) else: iDisplay(dd_info) elif isinstance(dd, _pd.DataFrame): for ptf, dd_info in dd_info_dict.items(): + iDisplay(iHTML('(no drawdowns)
")) else: - iDisplay(iHTML("