You've learned the basics of using Alphalens. This lesson explores the following advanced Alphalens concepts:
All sections of this lesson will use the data produced by the Pipeline created in the following cell. Please run it.
Important note: Until this lesson, we passed the output of run_pipeline()
to get_clean_factor_and_forward_returns()
without any changes. This was possible because the previous lessons' Pipelines only returned one column. This lesson's Pipeline returns two columns, which means we need to specify the column we're passing as factor data. Look for commented code near get_clean_factor_and_forward_returns()
in the following cell to see how to do this.
In [ ]:
from quantopian.pipeline import Pipeline
from quantopian.pipeline.data import factset
from quantopian.research import run_pipeline
from quantopian.pipeline.filters import QTradableStocksUS
from quantopian.pipeline.classifiers.fundamentals import Sector
from alphalens.utils import get_clean_factor_and_forward_returns
def make_pipeline():
change_in_working_capital = factset.Fundamentals.wkcap_chg_qf.latest
ciwc_processed = change_in_working_capital.winsorize(.2, .98).zscore()
sales_per_working_capital = factset.Fundamentals.sales_wkcap_qf.latest
spwc_processed = sales_per_working_capital.winsorize(.2, .98).zscore()
factor_to_analyze = (ciwc_processed + spwc_processed).zscore()
sector = Sector()
return Pipeline(
columns = {
'factor_to_analyze': factor_to_analyze,
'sector': sector,
},
screen = (
QTradableStocksUS()
& factor_to_analyze.notnull()
& sector.notnull()
)
)
pipeline_output = run_pipeline(make_pipeline(), '2013-1-1', '2014-1-1')
pricing_data = get_pricing(pipeline_output.index.levels[1], '2013-1-1', '2014-3-1', fields='open_price')
factor_data = get_clean_factor_and_forward_returns(
pipeline_output['factor_to_analyze'], # How to analyze a specific pipeline column with Alphalens
pricing_data,
periods=range(1,32,3)
)
Alphalens allows you to group assets using a classifier. A common use case for this is creating a classifier that specifies which sector each equity belongs to, then comparing your alpha factor's returns among sectors.
You can group assets by any classifier, but sector is most common. The Pipeline in the first cell of this lesson returns a column named sector
, whose values represent the corresponding Morningstar sector code. All we have to do now is pass that column to the groupby
argument of get_clean_factor_and_forward_returns()
Run the following cell, and notice the charts at the bottom of the tear sheet showing how our factor performs in different sectors.
In [ ]:
from alphalens.tears import create_returns_tear_sheet
sector_labels, sector_labels[-1] = dict(Sector.SECTOR_NAMES), "Unknown"
factor_data = get_clean_factor_and_forward_returns(
factor=pipeline_output['factor_to_analyze'],
prices=pricing_data,
groupby=pipeline_output['sector'],
groupby_labels=sector_labels,
)
create_returns_tear_sheet(factor_data=factor_data, by_group=True)
Not only does Alphalens allow us to simulate how our alpha factor would perform in a long/short trading strategy, it also allows us to simulate how it would do if we went long/short on every group!
Grouping by sector, and going long/short on each sector allows you to limit exposure to the overall movement of sectors. For example, you may have noticed in step three of this tutorial, that certain sectors had all positive returns, or all negative returns. That information isn't useful to us, because that just means the sector group outperformed (or underperformed) the market; it doesn't give us any insight into how our factor performs within that sector.
Since we grouped our assets by sector in the previous cell, going group neutral is easy; just make the two following changes:
binning_by_group=True
as an argument to get_clean_factor_and_forward_returns()
.group_neutral=True
as an argument to create_full_tear_sheet()
.The following cell has made the approriate changes. Try running it and notice how the results differ from the previous cell.
In [ ]:
factor_data = get_clean_factor_and_forward_returns(
pipeline_output['factor_to_analyze'],
prices=pricing_data,
groupby=pipeline_output['sector'],
groupby_labels=sector_labels,
binning_by_group=True,
)
create_returns_tear_sheet(factor_data, by_group=True, group_neutral=True)
A lot of fundamental data only comes out 4 times a year in quarterly reports. Because of this low frequency, it can be useful to increase the amount of time get_clean_factor_and_forward_returns()
looks into the future to calculate returns.
Tip: A month usually has 21 trading days, a quarter usually has 63 trading days, and a year usually has 252 trading days.
Let's say you're creating a strategy that buys stock in companies with rising profits (data that is released every 63 trading days). Would you only look 10 days into the future to analyze that factor? Probably not! But how do you decide how far to look forward?
Run the following cell to chart our alpha factor's IC mean over time. The point where the line dips below 0 represents when our alpha factor's predictions stop being useful.
In [ ]:
from alphalens.performance import mean_information_coefficient
mean_information_coefficient(factor_data).plot(title="IC Decay");
What do you think the chart will look like if we calculate the IC a full year into the future?
Hint: This is a setup for section two of this lesson.
In [ ]:
factor_data = get_clean_factor_and_forward_returns(
pipeline_output['factor_to_analyze'],
pricing_data,
periods=range(1,252,20) # The third argument to the range statement changes the "step" of the range
)
mean_information_coefficient(factor_data).plot()
Oh no! What does MaxLossExceededError
mean?
get_clean_factor_and_forward_returns()
looks at how alpha factor data affects pricing data in the future. This means we need our pricing data to go further into the future than our alpha factor data by at least as long as our forward looking period.
In this case, we'll change get_pricing()
's end_date
to be at least a year after run_pipeline()
's end_date
.
Run the following cell to make those changes. As you can see, this alpha factor's IC decays quickly after a quarter, but comes back even stronger six months into the future. Interesting!
In [ ]:
pipeline_output = run_pipeline(
make_pipeline(),
start_date='2013-1-1',
end_date='2014-1-1' # *** NOTE *** Our factor data ends in 2014
)
pricing_data = get_pricing(
pipeline_output.index.levels[1],
start_date='2013-1-1',
end_date='2015-2-1', # *** NOTE *** Our pricing data ends in 2015
fields='open_price'
)
factor_data = get_clean_factor_and_forward_returns(
pipeline_output['factor_to_analyze'],
pricing_data,
periods=range(1,252,20) # Change the step to 10 or more for long look forward periods to save time
)
mean_information_coefficient(factor_data).plot()
Note: MaxLossExceededError has two possible causes; forward returns computation and binning. We showed you how to fix forward returns computation here because it is much more common. Try passing quantiles=None
and bins=5
if you get MaxLossExceededError because of binning.
That's it! This tutorial got you started with Alphalens, but there's so much more to it. Check out our API docs to see the rest!
In [ ]: