diff --git a/pyfolio/plotting.py b/pyfolio/plotting.py index 37a5300a..58cd72ae 100644 --- a/pyfolio/plotting.py +++ b/pyfolio/plotting.py @@ -44,6 +44,7 @@ def customize(func): """ Decorator to set plotting context and axes style during function call. """ + @wraps(func) def call_w_context(*args, **kwargs): set_context = kwargs.pop('set_context', True) @@ -52,6 +53,7 @@ def call_w_context(*args, **kwargs): return func(*args, **kwargs) else: return func(*args, **kwargs) + return call_w_context @@ -436,7 +438,7 @@ def plot_drawdown_periods(returns, top=10, ax=None, **kwargs): lim = ax.get_ylim() colors = sns.cubehelix_palette(len(df_drawdowns))[::-1] for i, (peak, recovery) in df_drawdowns[ - ['Peak date', 'Recovery date']].iterrows(): + ['Peak date', 'Recovery date']].iterrows(): if pd.isnull(recovery): recovery = returns.index[-1] ax.fill_between((peak, recovery), @@ -888,6 +890,58 @@ def plot_rolling_beta(returns, factor_returns, legend_loc='best', return ax +def plot_rolling_alpha(returns, factor_returns, legend_loc='best', + ax=None, **kwargs): + """ + Plots the rolling 6-month and 12-month alpha versus date. + + Parameters + ---------- + returns : pd.Series + Daily returns of the strategy, noncumulative. + - See full explanation in tears.create_full_tear_sheet. + factor_returns : pd.Series + Daily noncumulative returns of the benchmark factor to which alpha are + computed. Usually a benchmark such as market returns. + - This is in the same style as returns. + legend_loc : matplotlib.loc, optional + The location of the legend on the plot. + ax : matplotlib.Axes, optional + Axes upon which to plot. + **kwargs, optional + Passed to plotting function. + + Returns + ------- + ax : matplotlib.Axes + The axes that were plotted on. + """ + + if ax is None: + ax = plt.gca() + + y_axis_formatter = FuncFormatter(utils.two_dec_places) + ax.yaxis.set_major_formatter(FuncFormatter(y_axis_formatter)) + + ax.set_title("Rolling portfolio alpha to " + str(factor_returns.name)) + ax.set_ylabel('Alpha') + rb_1 = timeseries.rolling_alpha( + returns, factor_returns, rolling_window=APPROX_BDAYS_PER_MONTH * 6) + rb_1.plot(color='steelblue', lw=3, alpha=0.6, ax=ax, **kwargs) + rb_2 = timeseries.rolling_alpha( + returns, factor_returns, rolling_window=APPROX_BDAYS_PER_MONTH * 12) + rb_2.plot(color='grey', lw=3, alpha=0.4, ax=ax, **kwargs) + ax.axhline(rb_1.mean(), color='steelblue', linestyle='--', lw=3) + ax.axhline(0.0, color='black', linestyle='-', lw=2) + + ax.set_xlabel('') + ax.legend(['6-mo', + '12-mo'], + loc=legend_loc, frameon=True, framealpha=0.5) + ax.set_ylim((-1.0, 1.0)) + return ax + + def plot_rolling_volatility(returns, factor_returns=None, rolling_window=APPROX_BDAYS_PER_MONTH * 6, legend_loc='best', ax=None, **kwargs): diff --git a/pyfolio/timeseries.py b/pyfolio/timeseries.py index 2f1cb686..4530be71 100644 --- a/pyfolio/timeseries.py +++ b/pyfolio/timeseries.py @@ -548,6 +548,50 @@ def rolling_beta(returns, factor_returns, return out +def rolling_alpha(returns, factor_returns, + rolling_window=APPROX_BDAYS_PER_MONTH * 6): + """ + Determines the rolling alpha of a strategy. + + Parameters + ---------- + returns : pd.Series + Daily returns of the strategy, noncumulative. + - See full explanation in tears.create_full_tear_sheet. + factor_returns : pd.Series or pd.DataFrame + Daily noncumulative returns of the benchmark factor to which alpha are + computed. Usually a benchmark such as market returns. + - If DataFrame is passed, computes rolling alpha for each column. + - This is in the same style as returns. + rolling_window : int, optional + The size of the rolling window, in days, over which to compute + alpha (default 6 months). + + Returns + ------- + pd.Series + Rolling alpha. + + Note + ----- + See https://en.wikipedia.org/wiki/Alpha_(finance) for more details. + """ + + if factor_returns.ndim > 1: + # Apply column-wise + return factor_returns.apply(partial(rolling_alpha, returns), + rolling_window=rolling_window) + else: + out = pd.Series(index=returns.index) + for beg, end in zip(returns.index[0:-rolling_window], + returns.index[rolling_window:]): + out.loc[end] = ep.alpha( + returns.loc[beg:end], + factor_returns.loc[beg:end]) + + return out + + def rolling_regression(returns, factor_returns, rolling_window=APPROX_BDAYS_PER_MONTH * 6, nan_threshold=0.1):