Notebook

Alphalens + Quantopian | How To

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

In [81]:
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 [82]:
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 [83]:
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 [84]:
# universe = StaticAssets(symbols(['CVX', 'AAPL'])) # works
universe = StaticAssets(symbols(['CVX'])) # Does not work

Create the Pipeline

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

Run the Pipeline

In [86]:
results = run_pipeline(pipe_low_vol, '2015-06-30', '2016-06-30')
In [87]:
results.head()
Out[87]:
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 [88]:
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 [89]:
pricing.head()
Out[89]:
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 [90]:
results.head()
Out[90]:
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 [91]:
results.loc[:,'LowVolRank'] = results.loc[:,'LowVol'].rank(method='dense')
In [92]:
results[ results['LowVolRank'].isin([225.0, 226.0, 227.0])]
Out[92]:
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 [93]:
type(results['LowVol'].index)
results['LowVol'].index[0]
Out[93]:
(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 [94]:
import alphalens

factor_data = alphalens.utils.get_clean_factor_and_forward_returns(results['LowVol'],
                                                                   pricing,
                                                                   quantiles=2,
                                                                   periods=(1,5,10))

alphalens.tears.create_full_tear_sheet(factor_data)

ValueErrorTraceback (most recent call last)
<ipython-input-94-d2697e15dc66> in <module>()
      4                                                                    pricing,
      5                                                                    quantiles=2,
----> 6                                                                    periods=(1,5,10))
      7 
      8 alphalens.tears.create_full_tear_sheet(factor_data)

/usr/local/lib/python2.7/dist-packages/alphalens/utils.pyc in get_clean_factor_and_forward_returns(factor, prices, groupby, by_group, quantiles, bins, periods, filter_zscore, groupby_labels)
    339                                                      quantiles,
    340                                                      bins,
--> 341                                                      by_group)
    342 
    343     merged_data = merged_data.dropna()

/usr/local/lib/python2.7/dist-packages/alphalens/utils.pyc in quantize_factor(factor_data, quantiles, bins, by_group)
     94         grouper.append('group')
     95 
---> 96     factor_quantile = factor_data.groupby(grouper)['factor'].apply(quantile_calc, quantiles, bins)
     97     factor_quantile.name = 'factor_quantile'
     98 

/usr/local/lib/python2.7/dist-packages/pandas/core/groupby.pyc in apply(self, func, *args, **kwargs)
    649         # ignore SettingWithCopy here in case the user mutates
    650         with option_context('mode.chained_assignment', None):
--> 651             return self._python_apply_general(f)
    652 
    653     def _python_apply_general(self, f):

/usr/local/lib/python2.7/dist-packages/pandas/core/groupby.pyc in _python_apply_general(self, f)
    653     def _python_apply_general(self, f):
    654         keys, values, mutated = self.grouper.apply(f, self._selected_obj,
--> 655                                                    self.axis)
    656 
    657         return self._wrap_applied_output(

/usr/local/lib/python2.7/dist-packages/pandas/core/groupby.pyc in apply(self, f, data, axis)
   1525             # group might be modified
   1526             group_axes = _get_axes(group)
-> 1527             res = f(group)
   1528             if not _is_indexed_like(res, group_axes):
   1529                 mutated = True

/usr/local/lib/python2.7/dist-packages/pandas/core/groupby.pyc in f(g)
    645         @wraps(func)
    646         def f(g):
--> 647             return func(g, *args, **kwargs)
    648 
    649         # ignore SettingWithCopy here in case the user mutates

/usr/local/lib/python2.7/dist-packages/alphalens/utils.pyc in quantile_calc(x, _quantiles, _bins)
     85     def quantile_calc(x, _quantiles, _bins):
     86         if _quantiles is not None:
---> 87             return pd.qcut(x, _quantiles, labels=False) + 1
     88         elif _bins is not None:
     89             return pd.cut(x, _bins, labels=False) + 1

/usr/local/lib/python2.7/dist-packages/pandas/tools/tile.pyc in qcut(x, q, labels, retbins, precision)
    171     bins = algos.quantile(x, quantiles)
    172     return _bins_to_cuts(x, bins, labels=labels, retbins=retbins,
--> 173                          precision=precision, include_lowest=True)
    174 
    175 

/usr/local/lib/python2.7/dist-packages/pandas/tools/tile.pyc in _bins_to_cuts(x, bins, right, labels, retbins, precision, name, include_lowest)
    190 
    191     if len(algos.unique(bins)) < len(bins):
--> 192         raise ValueError('Bin edges must be unique: %s' % repr(bins))
    193 
    194     if include_lowest:

ValueError: Bin edges must be unique: array([-0.00748695, -0.00748695, -0.00748695])
In [ ]: