Notebook

Alphalens + Quantopian | How To

For more information on how to read and understand the plots look at:

In [97]:
import numpy as np
from quantopian.research import run_pipeline
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data.builtin import USEquityPricing
from quantopian.pipeline.factors import CustomFactor, Returns, AverageDollarVolume
from quantopian.pipeline.classifiers.morningstar import Sector
from quantopian.pipeline.filters import Q500US
from quantopian.pipeline.filters import StaticAssets
In [98]:
MORNINGSTAR_SECTOR_CODES = {
     -1: 'Misc',
    101: 'Basic Materials',
    102: 'Consumer Cyclical',
    103: 'Financial Services',
    104: 'Real Estate',
    205: 'Consumer Defensive',
    206: 'Healthcare',
    207: 'Utilities',
    308: 'Communication Services',
    309: 'Energy',
    310: 'Industrials',
    311: 'Technology' ,    
}

Define our factor

In [99]:
class LowVol(CustomFactor):
    inputs = [Returns(window_length=2)]
    window_length = 25
    
    def compute(self, today, assets, out, close):
        out[:] = -np.nanstd(close, axis=0)

Define our universe

In [100]:
# universe = StaticAssets(symbols(['CVX', 'AAPL'])) # works
universe = StaticAssets(symbols(['CVX'])) # Does not work

Create the Pipeline

In [101]:
pipe_low_vol = Pipeline(
    columns={
        'LowVol' : LowVol(mask=universe),
        'Sector': Sector(mask=universe),
    },
    screen=universe
)

Run the Pipeline

In [102]:
results = run_pipeline(pipe_low_vol, '2015-06-30', '2016-06-30')
In [103]:
results.head()
Out[103]:
LowVol Sector
2015-06-30 00:00:00+00:00 Equity(23112 [CVX]) -0.007487 309
2015-07-01 00:00:00+00:00 Equity(23112 [CVX]) -0.007075 309
2015-07-02 00:00:00+00:00 Equity(23112 [CVX]) -0.007082 309
2015-07-06 00:00:00+00:00 Equity(23112 [CVX]) -0.007020 309
2015-07-07 00:00:00+00:00 Equity(23112 [CVX]) -0.007197 309

Get pricing

In [104]:
assets = results.index.levels[1].unique()
# We need to get a little more pricing data than the 
# length of our factor so we can compare forward returns.
# We'll tack on another month in this example.
pricing = get_pricing(assets, start_date='2015-06-30', end_date='2016-07-31', fields='open_price')
In [105]:
pricing.head()
Out[105]:
Equity(23112 [CVX])
2015-06-30 00:00:00+00:00 92.903
2015-07-01 00:00:00+00:00 91.807
2015-07-02 00:00:00+00:00 91.864
2015-07-06 00:00:00+00:00 90.624
2015-07-07 00:00:00+00:00 90.005

Use Alphalens to create a factor tear sheet

In [106]:
results.head()
Out[106]:
LowVol Sector
2015-06-30 00:00:00+00:00 Equity(23112 [CVX]) -0.007487 309
2015-07-01 00:00:00+00:00 Equity(23112 [CVX]) -0.007075 309
2015-07-02 00:00:00+00:00 Equity(23112 [CVX]) -0.007082 309
2015-07-06 00:00:00+00:00 Equity(23112 [CVX]) -0.007020 309
2015-07-07 00:00:00+00:00 Equity(23112 [CVX]) -0.007197 309
In [107]:
results.loc[:,'LowVolRank'] = results.loc[:,'LowVol'].rank(method='dense')
In [108]:
results[ results['LowVolRank'].isin([225.0, 226.0, 227.0])]
Out[108]:
LowVol Sector LowVolRank
2016-06-06 00:00:00+00:00 Equity(23112 [CVX]) -0.008977 309 225.0
2016-06-13 00:00:00+00:00 Equity(23112 [CVX]) -0.008959 309 226.0
2016-06-14 00:00:00+00:00 Equity(23112 [CVX]) -0.008927 309 227.0
In [109]:
type(results['LowVol'].index)
results['LowVol'].index[0]
Out[109]:
(Timestamp('2015-06-30 00:00:00+0000', tz='UTC'),
 Equity(23112, symbol=u'CVX', asset_name=u'CHEVRON CORPORATION', exchange=u'NYSE', start_date=Timestamp('2002-01-01 00:00:00+0000', tz='UTC'), end_date=Timestamp('2017-04-05 00:00:00+0000', tz='UTC'), first_traded=None, auto_close_date=Timestamp('2017-04-10 00:00:00+0000', tz='UTC'), exchange_full=u'NEW YORK STOCK EXCHANGE'))
In [110]:
import alphalens

bins=[-10000,100000] 
quantiles=None 

factor_data = alphalens.utils.get_clean_factor_and_forward_returns(results['LowVol'],
                                                                   pricing,
                                                                   quantiles=quantiles,
                                                                   bins=bins,
                                                                   periods=(1,5,10))
In [111]:
# please note long_short=False as we are not running a dollar neutral portfolio
alphalens.tears.create_event_returns_tear_sheet(factor_data, pricing, long_short=False)
<matplotlib.figure.Figure at 0x7f1860c51690>
In [112]:
# please note long_short=False as we are not running a dollar neutral portfolio
alphalens.tears.create_returns_tear_sheet(factor_data, long_short=False)
Returns Analysis
1 5 10
Ann. alpha NaN NaN NaN
beta NaN NaN NaN
Mean Period Wise Return Top Quantile (bps) 6.881 34.251 72.944
Mean Period Wise Return Bottom Quantile (bps) 6.881 34.251 72.944
Mean Period Wise Spread (bps) 0.000 0.000 0.000
<matplotlib.figure.Figure at 0x7f1852efa510>

ValueErrorTraceback (most recent call last)
/usr/local/lib/python2.7/dist-packages/IPython/core/formatters.pyc in __call__(self, obj)
    305                 pass
    306             else:
--> 307                 return printer(obj)
    308             # Finally look for special method names
    309             method = get_real_method(obj, self.print_method)

/usr/local/lib/python2.7/dist-packages/IPython/core/pylabtools.pyc in <lambda>(fig)
    238 
    239     if 'png' in formats:
--> 240         png_formatter.for_type(Figure, lambda fig: print_figure(fig, 'png', **kwargs))
    241     if 'retina' in formats or 'png2x' in formats:
    242         png_formatter.for_type(Figure, lambda fig: retina_figure(fig, **kwargs))

/usr/local/lib/python2.7/dist-packages/IPython/core/pylabtools.pyc in print_figure(fig, fmt, bbox_inches, **kwargs)
    122 
    123     bytes_io = BytesIO()
--> 124     fig.canvas.print_figure(bytes_io, **kw)
    125     data = bytes_io.getvalue()
    126     if fmt == 'svg':

/usr/local/lib/python2.7/dist-packages/matplotlib/backend_bases.pyc in print_figure(self, filename, dpi, facecolor, edgecolor, orientation, format, **kwargs)
   2178                     orientation=orientation,
   2179                     dryrun=True,
-> 2180                     **kwargs)
   2181                 renderer = self.figure._cachedRenderer
   2182                 bbox_inches = self.figure.get_tightbbox(renderer)

/usr/local/lib/python2.7/dist-packages/matplotlib/backends/backend_agg.pyc in print_png(self, filename_or_obj, *args, **kwargs)
    525 
    526     def print_png(self, filename_or_obj, *args, **kwargs):
--> 527         FigureCanvasAgg.draw(self)
    528         renderer = self.get_renderer()
    529         original_dpi = renderer.dpi

/usr/local/lib/python2.7/dist-packages/matplotlib/backends/backend_agg.pyc in draw(self)
    472 
    473         try:
--> 474             self.figure.draw(self.renderer)
    475         finally:
    476             RendererAgg.lock.release()

/usr/local/lib/python2.7/dist-packages/matplotlib/artist.pyc in draw_wrapper(artist, renderer, *args, **kwargs)
     59     def draw_wrapper(artist, renderer, *args, **kwargs):
     60         before(artist, renderer)
---> 61         draw(artist, renderer, *args, **kwargs)
     62         after(artist, renderer)
     63 

/usr/local/lib/python2.7/dist-packages/matplotlib/figure.pyc in draw(self, renderer)
   1157         dsu.sort(key=itemgetter(0))
   1158         for zorder, a, func, args in dsu:
-> 1159             func(*args)
   1160 
   1161         renderer.close_group('figure')

/usr/local/lib/python2.7/dist-packages/matplotlib/artist.pyc in draw_wrapper(artist, renderer, *args, **kwargs)
     59     def draw_wrapper(artist, renderer, *args, **kwargs):
     60         before(artist, renderer)
---> 61         draw(artist, renderer, *args, **kwargs)
     62         after(artist, renderer)
     63 

/usr/local/lib/python2.7/dist-packages/matplotlib/axes/_base.pyc in draw(self, renderer, inframe)
   2322 
   2323         for zorder, a in dsu:
-> 2324             a.draw(renderer)
   2325 
   2326         renderer.close_group('axes')

/usr/local/lib/python2.7/dist-packages/matplotlib/artist.pyc in draw_wrapper(artist, renderer, *args, **kwargs)
     59     def draw_wrapper(artist, renderer, *args, **kwargs):
     60         before(artist, renderer)
---> 61         draw(artist, renderer, *args, **kwargs)
     62         after(artist, renderer)
     63 

/usr/local/lib/python2.7/dist-packages/matplotlib/lines.pyc in draw(self, renderer)
    737                 self._set_gc_clip(gc)
    738 
--> 739                 ln_color_rgba = self._get_rgba_ln_color()
    740                 gc.set_foreground(ln_color_rgba, isRGBA=True)
    741                 gc.set_alpha(ln_color_rgba[3])

/usr/local/lib/python2.7/dist-packages/matplotlib/lines.pyc in _get_rgba_ln_color(self, alt)
   1249 
   1250     def _get_rgba_ln_color(self, alt=False):
-> 1251         return colorConverter.to_rgba(self._color, self._alpha)
   1252 
   1253     # some aliases....

/usr/local/lib/python2.7/dist-packages/matplotlib/colors.pyc in to_rgba(self, arg, alpha)
    374         except (TypeError, ValueError) as exc:
    375             raise ValueError(
--> 376                 'to_rgba: Invalid rgba arg "%s"\n%s' % (str(arg), exc))
    377 
    378     def to_rgba_array(self, c, alpha=None):

ValueError: to_rgba: Invalid rgba arg "[[ 0.          0.40784314  0.21568628  1.        ]]"
length of rgba sequence should be either 3 or 4
<matplotlib.figure.Figure at 0x7f186336cb10>

Because of bug https://github.com/quantopian/alphalens/commit/bccedc9f743c054b6ee93727accf4f215c2e9ed0 Alphalens crashes if there is only one quantile/bin. So, until Alphalens is updated to the latest version on Q, we have to use the following workaround instead of calling alphalens.tears.create_returns_tear_sheet directly

In [113]:
import alphalens.performance as perf 
import alphalens.utils as utils
import alphalens.plotting as plotting

def create_returns_tear_sheet(factor_data, long_short=True, by_group=False):
    """
    Creates a tear sheet for returns analysis of a factor.
    Parameters
    ----------
    factor_data : pd.DataFrame - MultiIndex
        A MultiIndex DataFrame indexed by date (level 0) and asset (level 1),
        containing the values for a single alpha factor, forward returns for each period,
        The factor quantile/bin that factor value belongs too, and (optionally) the group the
        asset belongs to.
    long_short : bool
        Should this computation happen on a long short portfolio?
    by_group : bool
        If True, perform calcuations, and display graphs separately for
        each group.
    """

    factor_returns = perf.factor_returns(factor_data, long_short)

    mean_ret_quantile, std_quantile = perf.mean_return_by_quantile(factor_data,
                                                                   by_group=False,
                                                                   demeaned=long_short)

    mean_compret_quantile = mean_ret_quantile.apply(utils.rate_of_return, axis=0)

    mean_ret_quant_daily, std_quant_daily = perf.mean_return_by_quantile(factor_data,
                                                                         by_date=True,
                                                                         by_group=False,
                                                                         demeaned=long_short)

    mean_compret_quant_daily = mean_ret_quant_daily.apply(utils.rate_of_return, axis=0)

    plotting.plot_quantile_returns_bar(mean_compret_quantile,
                                       by_group=False,
                                       ylim_percentiles=None)

    plotting.plot_quantile_returns_violin(mean_compret_quant_daily,
                                          ylim_percentiles=(1, 99))

    for p in factor_returns:

        plotting.plot_cumulative_returns(factor_returns[p],
                                         period=p)

#        plotting.plot_cumulative_returns_by_quantile(mean_ret_quant_daily[p],
#                                                     period=p)
In [114]:
create_returns_tear_sheet(factor_data, long_short=False)