From df7f6c64e57c60d5005855e9bcaa54c469a125a3 Mon Sep 17 00:00:00 2001 From: sgao Date: Fri, 23 Aug 2019 17:56:59 +0800 Subject: [PATCH 1/4] :sparkle: return figure and tables --- alphalens/plotting.py | 224 +++++++++++++++++++++++++++++++----------- alphalens/tears.py | 137 +++++++++++++++++++------- 2 files changed, 266 insertions(+), 95 deletions(-) diff --git a/alphalens/plotting.py b/alphalens/plotting.py index 96cf7a04..d340761b 100644 --- a/alphalens/plotting.py +++ b/alphalens/plotting.py @@ -35,6 +35,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) @@ -45,6 +46,7 @@ def call_w_context(*args, **kwargs): return func(*args, **kwargs) else: return func(*args, **kwargs) + return call_w_context @@ -133,7 +135,8 @@ def axes_style(style='darkgrid', rc=None): def plot_returns_table(alpha_beta, mean_ret_quantile, - mean_ret_spread_quantile): + mean_ret_spread_quantile, + return_table=False): returns_table = pd.DataFrame() returns_table = returns_table.append(alpha_beta) returns_table.loc["Mean Period Wise Return Top Quantile (bps)"] = \ @@ -145,9 +148,12 @@ def plot_returns_table(alpha_beta, print("Returns Analysis") utils.print_table(returns_table.apply(lambda x: x.round(3))) + if return_table: + return returns_table -def plot_turnover_table(autocorrelation_data, quantile_turnover): +def plot_turnover_table(autocorrelation_data, quantile_turnover, + return_table=False): turnover_table = pd.DataFrame() for period in sorted(quantile_turnover.keys()): for quantile, p_data in quantile_turnover[period].iteritems(): @@ -161,9 +167,12 @@ def plot_turnover_table(autocorrelation_data, quantile_turnover): print("Turnover Analysis") utils.print_table(turnover_table.apply(lambda x: x.round(3))) utils.print_table(auto_corr.apply(lambda x: x.round(3))) + if return_table: + return [turnover_table, auto_corr] -def plot_information_table(ic_data): +def plot_information_table(ic_data, + return_table=False): ic_summary_table = pd.DataFrame() ic_summary_table["IC Mean"] = ic_data.mean() ic_summary_table["IC Std."] = ic_data.std() @@ -177,19 +186,25 @@ def plot_information_table(ic_data): print("Information Analysis") utils.print_table(ic_summary_table.apply(lambda x: x.round(3)).T) + if return_table: + return ic_summary_table -def plot_quantile_statistics_table(factor_data): +def plot_quantile_statistics_table(factor_data, + return_table=False): quantile_stats = factor_data.groupby('factor_quantile') \ .agg(['min', 'max', 'mean', 'std', 'count'])['factor'] quantile_stats['count %'] = quantile_stats['count'] \ - / quantile_stats['count'].sum() * 100. + / quantile_stats['count'].sum() * 100. print("Quantiles Statistics") utils.print_table(quantile_stats) + if return_table: + return quantile_stats -def plot_ic_ts(ic, ax=None): +def plot_ic_ts(ic, ax=None, + return_fig=False): """ Plots Spearman Rank Information Coefficient and IC moving average for a given factor. @@ -209,6 +224,7 @@ def plot_ic_ts(ic, ax=None): ic = ic.copy() num_plots = len(ic.columns) + f = None if ax is None: f, ax = plt.subplots(num_plots, 1, figsize=(18, num_plots * 7)) ax = np.asarray([ax]).flatten() @@ -226,7 +242,7 @@ def plot_ic_ts(ic, ax=None): a.set(ylabel='IC', xlabel="") a.set_title( "{} Period Forward Return Information Coefficient (IC)" - .format(period_num)) + .format(period_num)) a.axhline(0.0, linestyle='-', color='black', lw=1, alpha=0.8) a.legend(['IC', '1 month moving avg'], loc='upper right') a.text(.05, .95, "Mean %.3f \n Std. %.3f" % (ic.mean(), ic.std()), @@ -241,11 +257,14 @@ def plot_ic_ts(ic, ax=None): for a in ax: a.set_ylim([ymin, ymax]) - - return ax + if return_fig: + return f, ax + else: + return ax -def plot_ic_hist(ic, ax=None): +def plot_ic_hist(ic, ax=None, + return_fig=False): """ Plots Spearman Rank Information Coefficient histogram for a given factor. @@ -267,7 +286,7 @@ def plot_ic_hist(ic, ax=None): num_plots = len(ic.columns) v_spaces = ((num_plots - 1) // 3) + 1 - + f = None if ax is None: f, ax = plt.subplots(v_spaces, 3, figsize=(18, v_spaces * 6)) ax = ax.flatten() @@ -286,10 +305,14 @@ def plot_ic_hist(ic, ax=None): if num_plots < len(ax): ax[-1].set_visible(False) - return ax + if return_fig: + return f, ax + else: + return ax -def plot_ic_qq(ic, theoretical_dist=stats.norm, ax=None): +def plot_ic_qq(ic, theoretical_dist=stats.norm, ax=None, + return_fig=False): """ Plots Spearman Rank Information Coefficient "Q-Q" plot relative to a theoretical distribution. @@ -315,7 +338,7 @@ def plot_ic_qq(ic, theoretical_dist=stats.norm, ax=None): num_plots = len(ic.columns) v_spaces = ((num_plots - 1) // 3) + 1 - + f = None if ax is None: f, ax = plt.subplots(v_spaces, 3, figsize=(18, v_spaces * 6)) ax = ax.flatten() @@ -331,17 +354,21 @@ def plot_ic_qq(ic, theoretical_dist=stats.norm, ax=None): sm.qqplot(ic.replace(np.nan, 0.).values, theoretical_dist, fit=True, line='45', ax=a) a.set(title="{} Period IC {} Dist. Q-Q".format( - period_num, dist_name), - ylabel='Observed Quantile', - xlabel='{} Distribution Quantile'.format(dist_name)) + period_num, dist_name), + ylabel='Observed Quantile', + xlabel='{} Distribution Quantile'.format(dist_name)) - return ax + if return_fig: + return f, ax + else: + return ax def plot_quantile_returns_bar(mean_ret_by_q, by_group=False, ylim_percentiles=None, - ax=None): + ax=None, + return_fig=False): """ Plots mean period wise returns for factor quantiles. @@ -361,7 +388,9 @@ def plot_quantile_returns_bar(mean_ret_by_q, ax : matplotlib.Axes The axes that were plotted on. """ - + if ax is not None and return_fig: + print("Error argument combination: ax should be None if return_fig") + return mean_ret_by_q = mean_ret_by_q.copy() if ylim_percentiles is not None: @@ -372,11 +401,10 @@ def plot_quantile_returns_bar(mean_ret_by_q, else: ymin = None ymax = None - + f = None if by_group: num_group = len( mean_ret_by_q.index.get_level_values('group').unique()) - if ax is None: v_spaces = ((num_group - 1) // 2) + 1 f, ax = plt.subplots(v_spaces, 2, sharex=False, @@ -385,8 +413,8 @@ def plot_quantile_returns_bar(mean_ret_by_q, for a, (sc, cor) in zip(ax, mean_ret_by_q.groupby(level='group')): (cor.xs(sc, level='group') - .multiply(DECIMAL_TO_BPS) - .plot(kind='bar', title=sc, ax=a)) + .multiply(DECIMAL_TO_BPS) + .plot(kind='bar', title=sc, ax=a)) a.set(xlabel='', ylabel='Mean Return (bps)', ylim=(ymin, ymax)) @@ -394,24 +422,31 @@ def plot_quantile_returns_bar(mean_ret_by_q, if num_group < len(ax): ax[-1].set_visible(False) - return ax + if return_fig: + return f, ax + else: + return ax else: if ax is None: f, ax = plt.subplots(1, 1, figsize=(18, 6)) (mean_ret_by_q.multiply(DECIMAL_TO_BPS) - .plot(kind='bar', - title="Mean Period Wise Return By Factor Quantile", ax=ax)) + .plot(kind='bar', + title="Mean Period Wise Return By Factor Quantile", ax=ax)) ax.set(xlabel='', ylabel='Mean Return (bps)', ylim=(ymin, ymax)) - return ax + if return_fig: + return f, ax + else: + return ax def plot_quantile_returns_violin(return_by_q, ylim_percentiles=None, - ax=None): + ax=None, + return_fig=False): """ Plots a violin box plot of period wise returns for factor quantiles. @@ -430,7 +465,9 @@ def plot_quantile_returns_violin(return_by_q, ax : matplotlib.Axes The axes that were plotted on. """ - + if ax is not None and return_fig: + print("Error argument combination: ax should be None if return_fig") + return return_by_q = return_by_q.copy() if ylim_percentiles is not None: @@ -441,7 +478,7 @@ def plot_quantile_returns_violin(return_by_q, else: ymin = None ymax = None - + f = None if ax is None: f, ax = plt.subplots(1, 1, figsize=(18, 6)) @@ -466,13 +503,17 @@ def plot_quantile_returns_violin(return_by_q, ax.axhline(0.0, linestyle='-', color='black', lw=0.7, alpha=0.6) - return ax + if return_fig: + return f, ax + else: + return ax def plot_mean_quantile_returns_spread_time_series(mean_returns_spread, std_err=None, bandwidth=1, - ax=None): + ax=None, + return_fig=False): """ Plots mean period wise returns for factor quantiles. @@ -493,7 +534,10 @@ def plot_mean_quantile_returns_spread_time_series(mean_returns_spread, ax : matplotlib.Axes The axes that were plotted on. """ - + if ax is not None and return_fig: + print("Error argument combination: ax should be None if return_fig") + return + f = None if isinstance(mean_returns_spread, pd.DataFrame): if ax is None: ax = [None for a in mean_returns_spread.columns] @@ -552,10 +596,14 @@ def plot_mean_quantile_returns_spread_time_series(mean_returns_spread, ylim=(-ylim, ylim)) ax.axhline(0.0, linestyle='-', color='black', lw=1, alpha=0.8) - return ax + if return_fig: + return f, ax + else: + return ax -def plot_ic_by_group(ic_group, ax=None): +def plot_ic_by_group(ic_group, ax=None, + return_fig=False): """ Plots Spearman Rank Information Coefficient for a given factor over provided forward returns. Separates by group. @@ -572,6 +620,10 @@ def plot_ic_by_group(ic_group, ax=None): ax : matplotlib.Axes The axes that were plotted on. """ + if ax is not None and return_fig: + print("Error argument combination: ax should be None if return_fig") + return + f = None if ax is None: f, ax = plt.subplots(1, 1, figsize=(18, 6)) ic_group.plot(kind='bar', ax=ax) @@ -579,12 +631,16 @@ def plot_ic_by_group(ic_group, ax=None): ax.set(title="Information Coefficient By Group", xlabel="") ax.set_xticklabels(ic_group.index, rotation=45) - return ax + if return_fig: + return f, ax + else: + return ax def plot_factor_rank_auto_correlation(factor_autocorrelation, period=1, - ax=None): + ax=None, + return_fig=False): """ Plots factor rank autocorrelation over time. See factor_rank_autocorrelation for more details. @@ -604,6 +660,10 @@ def plot_factor_rank_auto_correlation(factor_autocorrelation, ax : matplotlib.Axes The axes that were plotted on. """ + if ax is not None and return_fig: + print("Error argument combination: ax should be None if return_fig") + return + f = None if ax is None: f, ax = plt.subplots(1, 1, figsize=(18, 6)) @@ -617,10 +677,14 @@ def plot_factor_rank_auto_correlation(factor_autocorrelation, transform=ax.transAxes, verticalalignment='top') - return ax + if return_fig: + return f, ax + else: + return ax -def plot_top_bottom_quantile_turnover(quantile_turnover, period=1, ax=None): +def plot_top_bottom_quantile_turnover(quantile_turnover, period=1, ax=None, + return_fig=False): """ Plots period wise top and bottom quantile factor turnover. @@ -638,6 +702,10 @@ def plot_top_bottom_quantile_turnover(quantile_turnover, period=1, ax=None): ax : matplotlib.Axes The axes that were plotted on. """ + if ax is not None and return_fig: + print("Error argument combination: ax should be None if return_fig") + return + f = None if ax is None: f, ax = plt.subplots(1, 1, figsize=(18, 6)) @@ -650,10 +718,14 @@ def plot_top_bottom_quantile_turnover(quantile_turnover, period=1, ax=None): .format(period), ax=ax, alpha=0.6, lw=0.8) ax.set(ylabel='Proportion Of Names New To Quantile', xlabel="") - return ax + if return_fig: + return f, ax + else: + return ax -def plot_monthly_ic_heatmap(mean_monthly_ic, ax=None): +def plot_monthly_ic_heatmap(mean_monthly_ic, ax=None, + return_fig=False): """ Plots a heatmap of the information coefficient or returns by month. @@ -667,13 +739,15 @@ def plot_monthly_ic_heatmap(mean_monthly_ic, ax=None): ax : matplotlib.Axes The axes that were plotted on. """ - + if ax is not None and return_fig: + print("Error argument combination: ax should be None if return_fig") + return mean_monthly_ic = mean_monthly_ic.copy() num_plots = len(mean_monthly_ic.columns) v_spaces = ((num_plots - 1) // 3) + 1 - + f = None if ax is None: f, ax = plt.subplots(v_spaces, 3, figsize=(18, v_spaces * 6)) ax = ax.flatten() @@ -689,7 +763,6 @@ def plot_monthly_ic_heatmap(mean_monthly_ic, ax=None): names=["year", "month"]) for a, (periods_num, ic) in zip(ax, mean_monthly_ic.iteritems()): - sns.heatmap( ic.unstack(), annot=True, @@ -708,10 +781,14 @@ def plot_monthly_ic_heatmap(mean_monthly_ic, ax=None): if num_plots < len(ax): ax[-1].set_visible(False) - return ax + if return_fig: + return f, ax + else: + return ax -def plot_cumulative_returns(factor_returns, period, freq, title=None, ax=None): +def plot_cumulative_returns(factor_returns, period, freq, title=None, ax=None, + return_fig=False): """ Plots the cumulative returns of the returns series passed in. @@ -739,6 +816,10 @@ def plot_cumulative_returns(factor_returns, period, freq, title=None, ax=None): ax : matplotlib.Axes The axes that were plotted on. """ + if ax is not None and return_fig: + print("Error argument combination: ax should be None if return_fig") + return + f = None if ax is None: f, ax = plt.subplots(1, 1, figsize=(18, 6)) @@ -751,13 +832,17 @@ def plot_cumulative_returns(factor_returns, period, freq, title=None, ax=None): xlabel='') ax.axhline(1.0, linestyle='-', color='black', lw=1) - return ax + if return_fig: + return f, ax + else: + return ax def plot_cumulative_returns_by_quantile(quantile_returns, period, freq, - ax=None): + ax=None, + return_fig=False): """ Plots the cumulative returns of various factor quantiles. @@ -781,7 +866,10 @@ def plot_cumulative_returns_by_quantile(quantile_returns, ------- ax : matplotlib.Axes """ - + if ax is not None and return_fig: + print("Error argument combination: ax should be None if return_fig") + return + f = None if ax is None: f, ax = plt.subplots(1, 1, figsize=(18, 6)) @@ -804,14 +892,18 @@ def plot_cumulative_returns_by_quantile(quantile_returns, ax.yaxis.set_major_formatter(ScalarFormatter()) ax.axhline(1.0, linestyle='-', color='black', lw=1) - return ax + if return_fig: + return f, ax + else: + return ax def plot_quantile_average_cumulative_return(avg_cumulative_returns, by_quantile=False, std_bar=False, title=None, - ax=None): + ax=None, + return_fig=False): """ Plots sector-wise mean daily returns for factor quantiles across provided forward price movement columns. @@ -834,12 +926,14 @@ def plot_quantile_average_cumulative_return(avg_cumulative_returns, ------- ax : matplotlib.Axes """ - + if ax is not None and return_fig: + print("Error argument combination: ax should be None if return_fig") + return avg_cumulative_returns = avg_cumulative_returns.multiply(DECIMAL_TO_BPS) quantiles = len(avg_cumulative_returns.index.levels[0].unique()) palette = [cm.coolwarm(i) for i in np.linspace(0, 1, quantiles)] palette = palette[::-1] # we want negative quantiles as 'red' - + f = None if by_quantile: if ax is None: @@ -849,7 +943,7 @@ def plot_quantile_average_cumulative_return(avg_cumulative_returns, ax = ax.flatten() for i, (quantile, q_ret) in enumerate(avg_cumulative_returns - .groupby(level='factor_quantile') + .groupby(level='factor_quantile') ): mean = q_ret.loc[(quantile, 'mean')] @@ -872,7 +966,7 @@ def plot_quantile_average_cumulative_return(avg_cumulative_returns, f, ax = plt.subplots(1, 1, figsize=(18, 6)) for i, (quantile, q_ret) in enumerate(avg_cumulative_returns - .groupby(level='factor_quantile') + .groupby(level='factor_quantile') ): mean = q_ret.loc[(quantile, 'mean')] @@ -892,10 +986,14 @@ def plot_quantile_average_cumulative_return(avg_cumulative_returns, if title is None else title), xlabel='Periods') - return ax + if return_fig: + return f, ax + else: + return ax -def plot_events_distribution(events, num_bars=50, ax=None): +def plot_events_distribution(events, num_bars=50, ax=None, + return_fig=False): """ Plots the distribution of events in time. @@ -912,7 +1010,10 @@ def plot_events_distribution(events, num_bars=50, ax=None): ------- ax : matplotlib.Axes """ - + if ax is not None and return_fig: + print("Error argument combination: ax should be None if return_fig") + return + f = None if ax is None: f, ax = plt.subplots(1, 1, figsize=(18, 6)) @@ -925,4 +1026,7 @@ def plot_events_distribution(events, num_bars=50, ax=None): title='Distribution of events in time', xlabel='Date') - return ax + if return_fig: + return f, ax + else: + return ax diff --git a/alphalens/tears.py b/alphalens/tears.py index a193b37c..ac6ef033 100644 --- a/alphalens/tears.py +++ b/alphalens/tears.py @@ -62,7 +62,8 @@ def close(self): @plotting.customize def create_summary_tear_sheet(factor_data, long_short=True, - group_neutral=False): + group_neutral=False, + return_tf=False): """ Creates a small summary tear sheet with returns, information, and turnover analysis. @@ -128,21 +129,28 @@ def create_summary_tear_sheet(factor_data, vertical_sections = 2 + fr_cols * 3 gf = GridFigure(rows=vertical_sections, cols=1) - plotting.plot_quantile_statistics_table(factor_data) - - plotting.plot_returns_table(alpha_beta, - mean_quant_rateret, - mean_ret_spread_quant) - - plotting.plot_quantile_returns_bar(mean_quant_rateret, - by_group=False, - ylim_percentiles=None, - ax=gf.next_row()) + result_tables = {} + result_figs = [] + quantile_statistics_table = plotting.plot_quantile_statistics_table(factor_data, return_table=return_tf) + if quantile_statistics_table is not None: + result_tables["quantile_statistics_table"] = quantile_statistics_table + returns_table = plotting.plot_returns_table(alpha_beta, + mean_quant_rateret, + mean_ret_spread_quant, + return_table=return_tf) + if returns_table is not None: + result_tables["returns_table"] = returns_table + + ax = plotting.plot_quantile_returns_bar(mean_quant_rateret, + by_group=False, + ylim_percentiles=None, + ax=gf.next_row()) # Information Analysis ic = perf.factor_information_coefficient(factor_data) - plotting.plot_information_table(ic) - + ic_summary_table = plotting.plot_information_table(ic, return_table=return_tf) + if ic_summary_table is not None: + result_tables["summary_ic_summary_table"] = ic_summary_table # Turnover Analysis quantile_factor = factor_data['factor_quantile'] @@ -150,23 +158,32 @@ def create_summary_tear_sheet(factor_data, {p: pd.concat([perf.quantile_turnover(quantile_factor, q, p) for q in range(1, int(quantile_factor.max()) + 1)], axis=1) - for p in periods} + for p in periods} autocorrelation = pd.concat( [perf.factor_rank_autocorrelation(factor_data, period) for period in periods], axis=1) - plotting.plot_turnover_table(autocorrelation, quantile_turnover) + turnover_table_results = plotting.plot_turnover_table(autocorrelation, quantile_turnover, return_table=return_tf) + if turnover_table_results is not None: + result_tables["summary_turnover_table"] = turnover_table_results[0] + result_tables["summary_auto_corr"] = turnover_table_results[1] plt.show() + if return_tf: + fig = gf.fig + result_figs.append(fig) gf.close() + if return_tf: + return result_tables, result_figs @plotting.customize def create_returns_tear_sheet(factor_data, long_short=True, group_neutral=False, - by_group=False): + by_group=False, + return_tf=False): """ Creates a tear sheet for returns analysis of a factor. @@ -237,9 +254,14 @@ def create_returns_tear_sheet(factor_data, vertical_sections = 2 + fr_cols * 3 gf = GridFigure(rows=vertical_sections, cols=1) - plotting.plot_returns_table(alpha_beta, + result_tables = {} + result_figs = [] + returns_table = plotting.plot_returns_table(alpha_beta, mean_quant_rateret, - mean_ret_spread_quant) + mean_ret_spread_quant, + return_table=return_tf) + if returns_table: + result_tables["returns_table"] = returns_table plotting.plot_quantile_returns_bar(mean_quant_rateret, by_group=False, @@ -259,7 +281,6 @@ def create_returns_tear_sheet(factor_data, ) for p in factor_returns: - title = ('Factor Weighted ' + ('Group Neutral ' if group_neutral else '') + ('Long/Short ' if long_short else '') @@ -290,6 +311,9 @@ def create_returns_tear_sheet(factor_data, ) plt.show() + if return_tf: + res_fig = gf.fig + result_figs.append(res_fig) gf.close() if by_group: @@ -318,13 +342,19 @@ def create_returns_tear_sheet(factor_data, ylim_percentiles=(5, 95), ax=ax_quantile_returns_bar_by_group) plt.show() + if return_tf: + res_group_fig = gf.fig + result_figs.append(res_group_fig) gf.close() + if return_tf: + return result_tables, result_figs @plotting.customize def create_information_tear_sheet(factor_data, group_neutral=False, - by_group=False): + by_group=False, + return_tf=False): """ Creates a tear sheet for information analysis of a factor. @@ -341,10 +371,13 @@ def create_information_tear_sheet(factor_data, by_group : bool If True, display graphs separately for each group. """ - + result_tables = {} + result_figs = [] ic = perf.factor_information_coefficient(factor_data, group_neutral) - plotting.plot_information_table(ic) + ic_summary_table = plotting.plot_information_table(ic, return_table=return_tf) + if ic_summary_table: + result_tables["information_ic_summary_table"] = ic_summary_table columns_wide = 2 fr_cols = len(ic.columns) @@ -360,7 +393,6 @@ def create_information_tear_sheet(factor_data, plotting.plot_ic_qq(ic, ax=ax_ic_hqq[1::2]) if not by_group: - mean_monthly_ic = \ perf.mean_information_coefficient(factor_data, group_adjust=group_neutral, @@ -379,11 +411,17 @@ def create_information_tear_sheet(factor_data, plotting.plot_ic_by_group(mean_group_ic, ax=gf.next_row()) plt.show() + if return_tf: + fig = gf.fig + result_figs.append(fig) gf.close() + if return_tf: + return result_tables, result_figs @plotting.customize -def create_turnover_tear_sheet(factor_data, turnover_periods=None): +def create_turnover_tear_sheet(factor_data, turnover_periods=None, + return_tf=False): """ Creates a tear sheet for analyzing the turnover properties of a factor. @@ -403,7 +441,8 @@ def create_turnover_tear_sheet(factor_data, turnover_periods=None): are 2h and 4h and the factor is computed daily and so values like ['1D', '2D'] could be used instead """ - + result_tables = {} + result_figs = [] if turnover_periods is None: turnover_periods = utils.get_forward_returns_columns( factor_data.columns) @@ -414,13 +453,16 @@ def create_turnover_tear_sheet(factor_data, turnover_periods=None): {p: pd.concat([perf.quantile_turnover(quantile_factor, q, p) for q in range(1, int(quantile_factor.max()) + 1)], axis=1) - for p in turnover_periods} + for p in turnover_periods} autocorrelation = pd.concat( [perf.factor_rank_autocorrelation(factor_data, period) for period in turnover_periods], axis=1) - plotting.plot_turnover_table(autocorrelation, quantile_turnover) + turnover_tables = plotting.plot_turnover_table(autocorrelation, quantile_turnover, return_table=return_tf) + if turnover_tables: + result_tables["turnover_turnover_table"] = turnover_tables[0] + result_tables["turnover_auto_corr"] = turnover_tables[1] fr_cols = len(turnover_periods) columns_wide = 1 @@ -443,14 +485,20 @@ def create_turnover_tear_sheet(factor_data, turnover_periods=None): ax=gf.next_row()) plt.show() + if return_tf: + fig = gf.fig + result_figs.append(fig) gf.close() + if return_tf: + return result_tables, result_figs @plotting.customize def create_full_tear_sheet(factor_data, long_short=True, group_neutral=False, - by_group=False): + by_group=False, + return_tf=False): """ Creates a full tear sheet for analysis and evaluating single return predicting (alpha) factor. @@ -476,7 +524,8 @@ def create_full_tear_sheet(factor_data, by_group : bool If True, display graphs separately for each group. """ - + result_tables = {} + result_figs = [] plotting.plot_quantile_statistics_table(factor_data) create_returns_tear_sheet(factor_data, long_short, @@ -497,7 +546,8 @@ def create_event_returns_tear_sheet(factor_data, long_short=True, group_neutral=False, std_bar=True, - by_group=False): + by_group=False, + return_tf=False): """ Creates a tear sheet to view the average cumulative returns for a factor within a window (pre and post event). @@ -527,7 +577,8 @@ def create_event_returns_tear_sheet(factor_data, by_group : bool If True, display graphs separately for each group. """ - + result_tables = {} + result_figs = [] before, after = avgretplot avg_cumulative_returns = \ @@ -560,6 +611,9 @@ def create_event_returns_tear_sheet(factor_data, ax=ax_avg_cumulative_returns_by_q) plt.show() + if return_tf: + fig1 = gf.fig + result_figs.append(fig1) gf.close() if by_group: @@ -588,7 +642,12 @@ def create_event_returns_tear_sheet(factor_data, ax=gf.next_cell()) plt.show() + if return_tf: + fig2 = gf.fig + result_figs.append(fig2) gf.close() + if return_tf: + return result_tables, result_figs @plotting.customize @@ -596,7 +655,8 @@ def create_event_study_tear_sheet(factor_data, prices=None, avgretplot=(5, 15), rate_of_ret=True, - n_bars=50): + n_bars=50, + return_tf=False): """ Creates an event study tear sheet for analysis of a specific event. @@ -621,7 +681,8 @@ def create_event_study_tear_sheet(factor_data, n_bars : int, optional Number of bars in event distribution plot """ - + result_tables = {} + result_figs = [] long_short = False plotting.plot_quantile_statistics_table(factor_data) @@ -631,10 +692,12 @@ def create_event_study_tear_sheet(factor_data, num_bars=n_bars, ax=gf.next_row()) plt.show() + if return_tf: + fig = gf.fig + result_figs.append(fig) gf.close() if prices is not None and avgretplot is not None: - create_event_returns_tear_sheet(factor_data=factor_data, prices=prices, avgretplot=avgretplot, @@ -689,7 +752,6 @@ def create_event_study_tear_sheet(factor_data, ) for p in factor_returns: - plotting.plot_cumulative_returns( factor_returns[p], period=p, @@ -698,4 +760,9 @@ def create_event_study_tear_sheet(factor_data, ) plt.show() + if return_tf: + fig = gf.fig + result_figs.append(fig) gf.close() + if return_tf: + return result_tables, result_figs \ No newline at end of file From 924a6dcbf65af987dc78030c6c4f12b851e4d161 Mon Sep 17 00:00:00 2001 From: sgao Date: Mon, 26 Aug 2019 10:10:42 +0800 Subject: [PATCH 2/4] :bug: fix, show --- alphalens/tears.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/alphalens/tears.py b/alphalens/tears.py index ac6ef033..f8c9ac65 100644 --- a/alphalens/tears.py +++ b/alphalens/tears.py @@ -150,6 +150,7 @@ def create_summary_tear_sheet(factor_data, ic = perf.factor_information_coefficient(factor_data) ic_summary_table = plotting.plot_information_table(ic, return_table=return_tf) if ic_summary_table is not None: + result_tables["summary_ic_summary_table"] = ic_summary_table # Turnover Analysis quantile_factor = factor_data['factor_quantile'] @@ -354,7 +355,8 @@ def create_returns_tear_sheet(factor_data, def create_information_tear_sheet(factor_data, group_neutral=False, by_group=False, - return_tf=False): + return_tf=False, + show=True): """ Creates a tear sheet for information analysis of a factor. @@ -376,7 +378,7 @@ def create_information_tear_sheet(factor_data, ic = perf.factor_information_coefficient(factor_data, group_neutral) ic_summary_table = plotting.plot_information_table(ic, return_table=return_tf) - if ic_summary_table: + if ic_summary_table is not None: result_tables["information_ic_summary_table"] = ic_summary_table columns_wide = 2 @@ -409,8 +411,8 @@ def create_information_tear_sheet(factor_data, by_group=True) plotting.plot_ic_by_group(mean_group_ic, ax=gf.next_row()) - - plt.show() + if show: + plt.show() if return_tf: fig = gf.fig result_figs.append(fig) @@ -460,7 +462,7 @@ def create_turnover_tear_sheet(factor_data, turnover_periods=None, turnover_periods], axis=1) turnover_tables = plotting.plot_turnover_table(autocorrelation, quantile_turnover, return_table=return_tf) - if turnover_tables: + if turnover_tables is not None: result_tables["turnover_turnover_table"] = turnover_tables[0] result_tables["turnover_auto_corr"] = turnover_tables[1] From 878db5f2e143f08ab6ce372d58716229c99157c3 Mon Sep 17 00:00:00 2001 From: sgao Date: Mon, 26 Aug 2019 11:01:52 +0800 Subject: [PATCH 3/4] :sparkle: shows --- alphalens/tears.py | 57 +++++++++++++++++++++++----------------------- 1 file changed, 29 insertions(+), 28 deletions(-) diff --git a/alphalens/tears.py b/alphalens/tears.py index f8c9ac65..563bc7b1 100644 --- a/alphalens/tears.py +++ b/alphalens/tears.py @@ -63,7 +63,8 @@ def close(self): def create_summary_tear_sheet(factor_data, long_short=True, group_neutral=False, - return_tf=False): + return_tf=False, + show=True): """ Creates a small summary tear sheet with returns, information, and turnover analysis. @@ -150,7 +151,6 @@ def create_summary_tear_sheet(factor_data, ic = perf.factor_information_coefficient(factor_data) ic_summary_table = plotting.plot_information_table(ic, return_table=return_tf) if ic_summary_table is not None: - result_tables["summary_ic_summary_table"] = ic_summary_table # Turnover Analysis quantile_factor = factor_data['factor_quantile'] @@ -169,8 +169,8 @@ def create_summary_tear_sheet(factor_data, if turnover_table_results is not None: result_tables["summary_turnover_table"] = turnover_table_results[0] result_tables["summary_auto_corr"] = turnover_table_results[1] - - plt.show() + if show: + plt.show() if return_tf: fig = gf.fig result_figs.append(fig) @@ -184,7 +184,8 @@ def create_returns_tear_sheet(factor_data, long_short=True, group_neutral=False, by_group=False, - return_tf=False): + return_tf=False, + show=True): """ Creates a tear sheet for returns analysis of a factor. @@ -258,10 +259,10 @@ def create_returns_tear_sheet(factor_data, result_tables = {} result_figs = [] returns_table = plotting.plot_returns_table(alpha_beta, - mean_quant_rateret, - mean_ret_spread_quant, - return_table=return_tf) - if returns_table: + mean_quant_rateret, + mean_ret_spread_quant, + return_table=return_tf) + if returns_table is not None: result_tables["returns_table"] = returns_table plotting.plot_quantile_returns_bar(mean_quant_rateret, @@ -310,8 +311,8 @@ def create_returns_tear_sheet(factor_data, bandwidth=0.5, ax=ax_mean_quantile_returns_spread_ts ) - - plt.show() + if show: + plt.show() if return_tf: res_fig = gf.fig result_figs.append(res_fig) @@ -423,7 +424,7 @@ def create_information_tear_sheet(factor_data, @plotting.customize def create_turnover_tear_sheet(factor_data, turnover_periods=None, - return_tf=False): + return_tf=False, show=True): """ Creates a tear sheet for analyzing the turnover properties of a factor. @@ -485,8 +486,8 @@ def create_turnover_tear_sheet(factor_data, turnover_periods=None, plotting.plot_factor_rank_auto_correlation(autocorrelation[period], period=period, ax=gf.next_row()) - - plt.show() + if show: + plt.show() if return_tf: fig = gf.fig result_figs.append(fig) @@ -499,8 +500,7 @@ def create_turnover_tear_sheet(factor_data, turnover_periods=None, def create_full_tear_sheet(factor_data, long_short=True, group_neutral=False, - by_group=False, - return_tf=False): + by_group=False): """ Creates a full tear sheet for analysis and evaluating single return predicting (alpha) factor. @@ -526,8 +526,6 @@ def create_full_tear_sheet(factor_data, by_group : bool If True, display graphs separately for each group. """ - result_tables = {} - result_figs = [] plotting.plot_quantile_statistics_table(factor_data) create_returns_tear_sheet(factor_data, long_short, @@ -549,7 +547,8 @@ def create_event_returns_tear_sheet(factor_data, group_neutral=False, std_bar=True, by_group=False, - return_tf=False): + return_tf=False, + show=True): """ Creates a tear sheet to view the average cumulative returns for a factor within a window (pre and post event). @@ -611,8 +610,8 @@ def create_event_returns_tear_sheet(factor_data, by_quantile=True, std_bar=True, ax=ax_avg_cumulative_returns_by_q) - - plt.show() + if show: + plt.show() if return_tf: fig1 = gf.fig result_figs.append(fig1) @@ -642,8 +641,8 @@ def create_event_returns_tear_sheet(factor_data, std_bar=False, title=group, ax=gf.next_cell()) - - plt.show() + if show: + plt.show() if return_tf: fig2 = gf.fig result_figs.append(fig2) @@ -658,7 +657,8 @@ def create_event_study_tear_sheet(factor_data, avgretplot=(5, 15), rate_of_ret=True, n_bars=50, - return_tf=False): + return_tf=False, + show=True): """ Creates an event study tear sheet for analysis of a specific event. @@ -693,7 +693,8 @@ def create_event_study_tear_sheet(factor_data, plotting.plot_events_distribution(events=factor_data['factor'], num_bars=n_bars, ax=gf.next_row()) - plt.show() + if show: + plt.show() if return_tf: fig = gf.fig result_figs.append(fig) @@ -760,11 +761,11 @@ def create_event_study_tear_sheet(factor_data, freq=trading_calendar, ax=gf.next_row() ) - - plt.show() + if show: + plt.show() if return_tf: fig = gf.fig result_figs.append(fig) gf.close() if return_tf: - return result_tables, result_figs \ No newline at end of file + return result_tables, result_figs From f712d2890f96a8aa417516fb1b5af885d612b700 Mon Sep 17 00:00:00 2001 From: sgao Date: Tue, 27 Aug 2019 14:13:14 +0800 Subject: [PATCH 4/4] :art: --- alphalens/plotting.py | 20 +++++++++----------- alphalens/tears.py | 28 +++++++++++++++++++--------- 2 files changed, 28 insertions(+), 20 deletions(-) diff --git a/alphalens/plotting.py b/alphalens/plotting.py index d340761b..a69d0054 100644 --- a/alphalens/plotting.py +++ b/alphalens/plotting.py @@ -194,8 +194,8 @@ def plot_quantile_statistics_table(factor_data, return_table=False): quantile_stats = factor_data.groupby('factor_quantile') \ .agg(['min', 'max', 'mean', 'std', 'count'])['factor'] - quantile_stats['count %'] = quantile_stats['count'] \ - / quantile_stats['count'].sum() * 100. + quantile_stats['count %'] = \ + quantile_stats['count'] / quantile_stats['count'].sum() * 100. print("Quantiles Statistics") utils.print_table(quantile_stats) @@ -242,7 +242,7 @@ def plot_ic_ts(ic, ax=None, a.set(ylabel='IC', xlabel="") a.set_title( "{} Period Forward Return Information Coefficient (IC)" - .format(period_num)) + .format(period_num)) a.axhline(0.0, linestyle='-', color='black', lw=1, alpha=0.8) a.legend(['IC', '1 month moving avg'], loc='upper right') a.text(.05, .95, "Mean %.3f \n Std. %.3f" % (ic.mean(), ic.std()), @@ -942,10 +942,9 @@ def plot_quantile_average_cumulative_return(avg_cumulative_returns, sharey=False, figsize=(18, 6 * v_spaces)) ax = ax.flatten() - for i, (quantile, q_ret) in enumerate(avg_cumulative_returns - .groupby(level='factor_quantile') - ): - + for i, (quantile, q_ret) in \ + enumerate(avg_cumulative_returns + .groupby(level='factor_quantile')): mean = q_ret.loc[(quantile, 'mean')] mean.name = 'Quantile ' + str(quantile) mean.plot(ax=ax[i], color=palette[i]) @@ -965,10 +964,9 @@ def plot_quantile_average_cumulative_return(avg_cumulative_returns, if ax is None: f, ax = plt.subplots(1, 1, figsize=(18, 6)) - for i, (quantile, q_ret) in enumerate(avg_cumulative_returns - .groupby(level='factor_quantile') - ): - + for i, (quantile, q_ret) in \ + enumerate(avg_cumulative_returns + .groupby(level='factor_quantile')): mean = q_ret.loc[(quantile, 'mean')] mean.name = 'Quantile ' + str(quantile) mean.plot(ax=ax, color=palette[i]) diff --git a/alphalens/tears.py b/alphalens/tears.py index 563bc7b1..dcd0265c 100644 --- a/alphalens/tears.py +++ b/alphalens/tears.py @@ -132,7 +132,9 @@ def create_summary_tear_sheet(factor_data, result_tables = {} result_figs = [] - quantile_statistics_table = plotting.plot_quantile_statistics_table(factor_data, return_table=return_tf) + quantile_statistics_table = \ + plotting.plot_quantile_statistics_table(factor_data, + return_table=return_tf) if quantile_statistics_table is not None: result_tables["quantile_statistics_table"] = quantile_statistics_table returns_table = plotting.plot_returns_table(alpha_beta, @@ -142,14 +144,15 @@ def create_summary_tear_sheet(factor_data, if returns_table is not None: result_tables["returns_table"] = returns_table - ax = plotting.plot_quantile_returns_bar(mean_quant_rateret, - by_group=False, - ylim_percentiles=None, - ax=gf.next_row()) + plotting.plot_quantile_returns_bar(mean_quant_rateret, + by_group=False, + ylim_percentiles=None, + ax=gf.next_row()) # Information Analysis ic = perf.factor_information_coefficient(factor_data) - ic_summary_table = plotting.plot_information_table(ic, return_table=return_tf) + ic_summary_table =\ + plotting.plot_information_table(ic, return_table=return_tf) if ic_summary_table is not None: result_tables["summary_ic_summary_table"] = ic_summary_table # Turnover Analysis @@ -165,7 +168,10 @@ def create_summary_tear_sheet(factor_data, [perf.factor_rank_autocorrelation(factor_data, period) for period in periods], axis=1) - turnover_table_results = plotting.plot_turnover_table(autocorrelation, quantile_turnover, return_table=return_tf) + turnover_table_results =\ + plotting.plot_turnover_table(autocorrelation, + quantile_turnover, + return_table=return_tf) if turnover_table_results is not None: result_tables["summary_turnover_table"] = turnover_table_results[0] result_tables["summary_auto_corr"] = turnover_table_results[1] @@ -378,7 +384,8 @@ def create_information_tear_sheet(factor_data, result_figs = [] ic = perf.factor_information_coefficient(factor_data, group_neutral) - ic_summary_table = plotting.plot_information_table(ic, return_table=return_tf) + ic_summary_table = \ + plotting.plot_information_table(ic, return_table=return_tf) if ic_summary_table is not None: result_tables["information_ic_summary_table"] = ic_summary_table @@ -462,7 +469,10 @@ def create_turnover_tear_sheet(factor_data, turnover_periods=None, [perf.factor_rank_autocorrelation(factor_data, period) for period in turnover_periods], axis=1) - turnover_tables = plotting.plot_turnover_table(autocorrelation, quantile_turnover, return_table=return_tf) + turnover_tables =\ + plotting.plot_turnover_table(autocorrelation, + quantile_turnover, + return_table=return_tf) if turnover_tables is not None: result_tables["turnover_turnover_table"] = turnover_tables[0] result_tables["turnover_auto_corr"] = turnover_tables[1]