Researching the Momentum Factor

Momentum investing says that excess returns can be generated by buying recent winners and selling recent losers. In this notebook we will research the momentum factor on our universe of demo stocks. This will help us determine whether we have a profitable idea before turning to a full backtest.

First, load your historical data into pandas.

In [1]:
from quantrocket import get_prices
prices = get_prices("usstock-free-1d", universes="usstock-free", start_date="2017-01-01", fields=["Close"])
prices.head()
Out[1]:
SidFIBBG000B9XRY4FIBBG000BFWKC0FIBBG000BKZB36FIBBG000BMHYD1FIBBG000BPH459FIBBG000GZQ728FIBBG00B3T3HD3
FieldDate
Close2017-01-03110.6912102.3847124.1493106.233759.133379.818028.83
2017-01-04110.5673102.6583125.2493106.059458.868778.939830.26
2017-01-05111.1296102.8146123.7703107.169158.868777.763030.65
2017-01-06112.3685105.6682123.4283106.655559.379077.719130.68
2017-01-09113.3977106.4695124.1493106.637259.190076.437029.48

Next, we use closing prices to calculate our momentum factor. We calculate momentum using a twelve-month window but excluding the most recent month, as commonly recommended by academic papers.

In [2]:
closes = prices.loc["Close"]

MOMENTUM_WINDOW = 252 # 12 months = 252 trading days
RANKING_PERIOD_GAP = 22 # 1 month = 22 trading days
earlier_closes = closes.shift(MOMENTUM_WINDOW)
later_closes = closes.shift(RANKING_PERIOD_GAP)
momentum_returns = (later_closes - earlier_closes) / earlier_closes

Now that we have the twelve-month returns, we calculate the next day returns:

In [3]:
next_day_returns = closes.pct_change().shift(-1)

To see if the twelve-month returns predict next-day returns, we will split the twelve-month returns into bins and look at the mean next-day return of each bin. To do this, we first need to stack our wide-form DataFrames into Series.

In [4]:
momentum_returns = momentum_returns.stack(dropna=False)
next_day_returns = next_day_returns.stack(dropna=False)

Use pandas' qcut function to create the bins:

In [5]:
import pandas as pd

# For a very small demo universe, you might only want 2 quantiles 
num_bins = 2
bins = pd.qcut(momentum_returns, num_bins)

Now group the next day returns by momentum bin and plot the mean return:

In [6]:
next_day_returns.groupby(bins).mean().plot(kind="bar", title="Next-day return by 12-month momentum bin")
Out[6]:
<matplotlib.axes._subplots.AxesSubplot at 0x7f2ca0e31b90>

For a predictive factor, the higher quantiles should perform better than the lower quantiles.