March cohort is now open: How to secure your spot:

How to use the information coefficient to measure your alpha

How to use the information coefficient to measure your alpha. It’s a metric used to evaluate the effectiveness of an alpha factor in generating returns.

During my master’s program, my professors gave me a lot of theories I couldn’t use.

I remember my interest rate derivatives professor telling me:

“None of what I’m going to teach you actually works in practice.”

Now the context behind that statement was negative interest rates during the Great Financial Crisis.

But the point remains.

Of all the theories I couldn’t use, there were a few bright spots of practical wisdom.

One of those bright spots was factor investing and associated performance monitoring with the information coefficient.

One of my professors was a portfolio manager at a large hedge fund and he spent all his time building factor models and measuring their performance.

He spent the entire semester showing us exactly how.

It took me many months and several hundred lines of MATLAB code to get it right.

Fortunately, there is an open-source tool that does it in a few lines of Python.

How to use the information coefficient to measure your alpha

The information coefficient measures the correlation between a stock’s returns and the predicted returns from an alpha factor.

It’s a metric used to evaluate the effectiveness of an alpha factor in generating returns.

The information coefficient was first introduced by Fama and French in 1992, and it’s been widely used in quantitative finance ever since.

The information coefficient is important for retail traders, algorithm developers, and data analysts because it helps them determine which alpha factors are worth using in their trading strategies. Quants use the information coefficient to measure the predictive power of an alpha factor and to optimize their trading strategies.

Now you can too.

You will extend the momentum strategy you built last week by adding short positions to the portfolio and tracking the factor’s rank. These changes will let you use AlphaLens to assess the performance of the alpha factor.

Start with the imports. Define two constants, N_LONGS and N_SHORTS, which will be the number of securities the strategy will go long and short in the portfolio.

import pandas as pd
from scipy import stats
import matplotlib.pyplot as plt

from zipline import run_algorithm
from zipline.pipeline import Pipeline, CustomFactor
from zipline.pipeline.data import USEquityPricing
from zipline.api import (
    attach_pipeline,
    calendars,
    pipeline_output,
    date_rules,
    time_rules,
    set_commission,
    set_slippage,
    record,
    order_target_percent,
    get_open_orders,
    schedule_function
)

from alphalens.utils import (
    get_clean_factor_and_forward_returns,
    get_forward_returns_columns
)
from alphalens.plotting import plot_ic_ts
from alphalens.performance import factor_information_coefficient

import warnings
warnings.filterwarnings("ignore")

N_LONGS = N_SHORTS = 10

After the imports, load the Zipline extension and download data. You’ll need a free API from Nasdaq Data Link, which you can get here.

%load_ext zipline
! zipline ingest -b quandl

Create a custom momentum factor and build the pipeline

Now, create a custom factor to calculate the momentum and define the pipeline for our trading algorithm.

class Momentum(CustomFactor):
    # Default inputs
    inputs = [USEquityPricing.close]

    # Compute momentum
    def compute(self, today, assets, out, close):
        out[:] = close[-1] / close[0]

def make_pipeline():

    twenty_day_momentum = Momentum(window_length=20)
    thirty_day_momentum = Momentum(window_length=30)

    positive_momentum = (
        (twenty_day_momentum > 1) & 
        (thirty_day_momentum > 1)
    )

    return Pipeline(
        columns={
            'longs': thirty_day_momentum.top(N_LONGS),
            'shorts': thirty_day_momentum.top(N_SHORTS),
            'ranking': thirty_day_momentum.rank(ascending=False)
        },
        screen=positive_momentum
    )

The factor divides the first price in the window by the last price. In other words, if the last price in the window is greater than the first price, there is momentum.

Define a function that creates a pipeline with two momentum calculations: one for 20-day momentum and another for 30-day momentum.

The Pipeline returns the top and bottom trending stocks that have positive momentum. It also returns their factor ranking.

Implement the trading strategy

Implement the trading strategy by initializing the pipeline, scheduling the rebalancing function, and executing the trades.

def before_trading_start(context, data):
    context.factor_data = pipeline_output("factor_pipeline")

def initialize(context):
    attach_pipeline(make_pipeline(), "factor_pipeline")
    schedule_function(
        rebalance,
        date_rules.week_start(),
        time_rules.market_open(),
        calendar=calendars.US_EQUITIES,
    )

def rebalance(context, data):

    factor_data = context.factor_data
    record(factor_data=factor_data.ranking)

    assets = factor_data.index
    record(prices=data.current(assets, 'price'))

    longs = assets[factor_data.longs]
    shorts = assets[factor_data.shorts]
    divest = set(context.portfolio.positions.keys()) - set(longs.union(shorts))

    exec_trades(data, assets=divest, target_percent=0)
    exec_trades(data, assets=longs, target_percent=1 / N_LONGS)
    exec_trades(data, assets=shorts, target_percent=-1 / N_SHORTS)

def exec_trades(data, assets, target_percent):
    # Loop through every asset...
    for asset in assets:
        # ...if the asset is tradeable and there are no open orders...
        if data.can_trade(asset) and not get_open_orders(asset):
            # ...execute the order against the target percent
            order_target_percent(asset, target_percent)

The before_trading_start function gets the pipeline output before every trading session. The initialize function attaches the pipeline and schedules the rebalancing function to run at the start of each week at market open.

The rebalance function gets the pipeline output, filters the assets to go long and short, and figures out which assets to divest. It then executes the trades using the exec_trades function.

In the exec_trades function, loop through the assets and check if they are tradable and if there are no open orders. If both conditions are met, place an order targeting the specified percentage of the portfolio.

And finally, run the algorithm.

start = pd.Timestamp('2015')
end = pd.Timestamp('2018')
perf = run_algorithm(
    start=start,
    end=end,
    initialize=initialize,
    before_trading_start=before_trading_start,
    capital_base=100_000,
    bundle="quandl",
)

Assess the factor performance with AlphaLens

Manipulate the price and factor data so AlphaLens can read them.

prices = pd.concat(
    [df.to_frame(d) for d, df in perf.prices.dropna().items()], 
    axis=1
).T

prices.columns = [col.symbol for col in prices.columns]

prices.index = prices.index.normalize()

Construct a DataFrame where each column is a stock and each row is a date. Do this by concatenating the DataFrames, each of which corresponds to a stock. Then convert the column names to strings and normalize the dates to midnight, UTC.

Now, do the same for the factor data.

factor_data = pd.concat(
    [df.to_frame(d) for d, df in perf.factor_data.dropna().items()],
    axis=1
).T

factor_data.columns = [col.symbol for col in factor_data.columns]

factor_data.index = factor_data.index.normalize()

factor_data = factor_data.stack()

factor_data.index.names = ['date', 'asset']

This time, create a MultiIndex DataFrame with the date as the first index and the asset as the index column. The column has the factor ranking.

Finally, you can analyze the factor returns using AlphaLens.

alphalens_data = get_clean_factor_and_forward_returns(
    factor=factor_data, 
    prices=prices, 
    periods=(5, 10, 21, 63), 
    quantiles=5
)

This is a utility function that creates holding period returns of 5, 10, 21, and 63 days. It includes the factor ranking and the factor quantile.

Now, you can get the information coefficient for each lagged return. For example, this is the historic information coefficient for a 5 day lag.

ic = factor_information_coefficient(alphalens_data)

plot_ic_ts(ic[["5D"]])
plt.tight_layout()
How to use the information coefficient to measure your alpha. It’s a metric used to evaluate the effectiveness of an alpha factor in generating returns.

This chart shows how the information coefficient changes over time but is generally positive. This implies the momentum factor predicts returns.

You can also see the mean information coefficient of the factor for each holding period for each year.

ic_by_year = ic.resample('A').mean()
ic_by_year.index = ic_by_year.index.year
ic_by_year.plot.bar(figsize=(14, 6))
plt.tight_layout();
How to use the information coefficient to measure your alpha. It’s a metric used to evaluate the effectiveness of an alpha factor in generating returns.

Throughout the backtest period, the momentum factor performed best on shorter time periods.

Professional money managers from Blackrock to Vanguard use factors to invest. Building factors and measuring their performance used to be reserved for the quants at these institutions. Now, you can use the same techniques they do.