from getfactormodels import CarhartFactors
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
sns.set_theme()The Momentum Factor
Theoretical Background
While Fama and French (1993) established that size and value captured important dimensions of expected returns, subsequent research showed that additional patterns in the cross-section of returns remained unexplained.
Jegadeesh and Titman (1993) document that a portfolio that buys past winners and sells past losers generates positive alpha even after controlling for other factors. Momentum refers to the tendency of stocks that have performed well (or poorly) in the recent past to continue to perform well (or poorly) in the future.
Carhart (1997) expanded on the momentum phenomenon documented by Jegadeesh and Titman (1993) by incorporating it into a four-factor asset pricing model. In addition to the market risk factor, Carhart (1997) added three additional factors: size (SMB), book-to-market (HML), and momentum (MOM).
The momentum factor is constructed by using six value-weighted portfolios formed on size and prior (2-12) returns. The portfolios, which are formed monthly, are the intersections of 2 portfolios formed on size (market equity, ME) and 3 portfolios formed on prior (2-12) return. The monthly size breakpoint is the median NYSE market equity. The monthly prior (2-12) return breakpoints are the 30th and 70th NYSE percentiles.
According to Kenneth French, \(\textit{MOM}\) is the average return on the two high prior return portfolios minus the average return on the two low prior return portfolios, \[ \mathit{MOM} = \frac{1}{2} \left( \text{Small High} + \text{Big High} \right) - \frac{1}{2} \left( \text{Small Low} + \text{Big Low} \right). \]
Detailed information about how these portfolios are constructed can be found in Kenneth French’s Website, where we can also download the momentum factor. Therefore, the factor is accessible using getfactormodels.
The momentum factor can be used to augment the Fama-French three-factor model as in Carhart (1997), \[ R_{i} = a_{i} + b_{i} R_{m} + s_{i} \mathit{SMB} + h_{i} \mathit{HML} + m_{i} \mathit{MOM} + e_{i}, \] where \(R_{i} = r_{i} - r_{f}\) represents the monthly excess returns for security \(i\) over the risk-free rate, \(R_{m} = r_{m} - r_{f}\) represents the monthly excess returns of the market, and \(m_{i}\) is the factor loading on momentum.
Python Packages
We use the following packages:
Getting the Momentum Factor
start_date='1963-01-01'We download the momentum factor from Kenneth French’s website. We also retain the market excess return \(\mathit{RMRF}\) as a benchmark for comparison.
mom = (CarhartFactors(frequency='m', start_date=start_date)
.load()
.to_pandas()
.rename(columns={'Mkt-RF': 'RMRF'})
[['RMRF', 'MOM']]
)display(mom)| RMRF | MOM | |
|---|---|---|
| date | ||
| 1963-01-31 | 0.0494 | -0.0213 |
| 1963-02-28 | -0.0240 | 0.0252 |
| 1963-03-31 | 0.0308 | 0.0158 |
| 1963-04-30 | 0.0451 | -0.0010 |
| 1963-05-31 | 0.0176 | 0.0029 |
| ... | ... | ... |
| 2025-09-30 | 0.0339 | 0.0463 |
| 2025-10-31 | 0.0196 | 0.0027 |
| 2025-11-30 | -0.0013 | -0.0180 |
| 2025-12-31 | -0.0036 | -0.0240 |
| 2026-01-31 | 0.0102 | 0.0495 |
757 rows × 2 columns
Graphing the Momentum Factor
We can now plot the monthly returns of the momentum factor alongside the market:
axes = mom.plot(figsize=(12,5), subplots=True)
for c in axes:
c.axhline(y=0, lw=1)
plt.show()And the 7-year rolling average to assess the persistence of the momentum premium over time:
axes = mom.rolling(84).mean().plot(figsize=(12,5), subplots=True, sharey=True)
for c in axes:
c.axhline(y=0, lw=1)
plt.show()The rolling average reveals that the US momentum premium was strong and fairly stable through the 1990s and early 2000s, but has been notably weak — and at times negative — since around 2009. This decline is largely attributable to the momentum crash of 2009, documented by Daniel and Moskowitz (2016): following extreme market downturns, the stocks that had been performing the worst (and were therefore heavily shorted by momentum strategies) rebounded sharply, inflicting severe losses on momentum portfolios. More broadly, the post-2009 period has been characterized by low return dispersion and multiple sharp reversals, conditions that are particularly unfavorable for trend-following strategies.
International Comparison
A natural question is whether the weakness of US momentum since 2009 is a global phenomenon or specific to the US market. Kenneth French’s data library provides momentum factors for international markets, allowing us to compare. We download the momentum factor for Europe and Emerging Markets using the same CarhartFactors class with the region argument. Note that for international factors the momentum column is named WML (Winners Minus Losers) rather than MOM.
europe = (CarhartFactors(frequency='m', region='europe', start_date=start_date)
.load()
.to_pandas()
[['WML']]
.rename(columns={'WML': 'Europe'})
)
emerging = (CarhartFactors(frequency='m', region='emerging', start_date=start_date)
.load()
.to_pandas()
[['WML']]
.rename(columns={'WML': 'Emerging'})
)
mom_intl = (mom[['MOM']]
.rename(columns={'MOM': 'US'})
.join([europe, emerging], how='inner')
)display(mom_intl)| US | Europe | Emerging | |
|---|---|---|---|
| date | |||
| 1991-04-30 | -0.0232 | 0.0156 | 0.0469 |
| 1991-05-31 | -0.0009 | -0.0064 | 0.0364 |
| 1991-06-30 | 0.0042 | 0.0054 | 0.0128 |
| 1991-07-31 | 0.0426 | 0.0506 | 0.0009 |
| 1991-08-31 | 0.0158 | 0.0377 | -0.0015 |
| ... | ... | ... | ... |
| 2025-09-30 | 0.0463 | 0.0288 | 0.0152 |
| 2025-10-31 | 0.0027 | -0.0160 | 0.0099 |
| 2025-11-30 | -0.0180 | 0.0087 | -0.0045 |
| 2025-12-31 | -0.0240 | 0.0269 | 0.0383 |
| 2026-01-31 | 0.0495 | 0.0670 | 0.0802 |
418 rows × 3 columns
Note that Kenneth French’s European factor data begins in July 1990 and the Emerging Markets factor data in January 1988 — both later than the US series, which starts in 1963. The inner join above restricts mom_intl to the period where all three series overlap, so the chart below covers a shorter sample than the US-only plots above.
We plot the 7-year rolling average for each region to compare the persistence of the momentum premium across markets:
ax = mom_intl.rolling(84).mean().plot(figsize=(12,5))
ax.axhline(y=0, lw=1)
ax.set_ylabel('Monthly Return')
plt.show()The graph shows that while the US momentum premium has weakened considerably since 2009, momentum in Europe has been more resilient and has remained positive for most of the sample period. Emerging Markets momentum is more volatile and its rolling average has fluctuated more widely, reflecting the greater macroeconomic uncertainty and lower market efficiency that characterize these economies. The fact that momentum persists across multiple geographically and institutionally distinct markets lends support to the view that it reflects a fundamental feature of investor behavior rather than a US-specific data artifact.
Practice Problems
Problem 1 All factors are zero-cost portfolios, that is, they are long-short strategies in which the long position is exactly financed by the short position. Compute the cumulative sum of RMRF and MOM from January 1970 until May 2023.
Hint: For a given dataframe df, the function df.cumsum() computes the cumulative sum of all columns.
Solution
mom = (CarhartFactors(frequency='m', start_date='1970-01-01', end_date='2023-05-01')
.load()
.to_pandas()
.rename(columns={'Mkt-RF': 'RMRF'})
[['RMRF', 'MOM']]
)
mom.cumsum().plot()
plt.show()