Arbitrage Pricing Theory and Multi-Factor Models › Working with Factor Data

Michael BrenndoerferDecember 15, 20254 min read
Quantitative FinanceAll chapters
Part 9 of 15Portfolio Theory and Investment Analysis
Chapter 48Arbitrage Pricing Theory and Multi-Factor Models › Working with Factor Data
Reading section 7 of 15 · Working with Factor Data · 4 minSection 7 of 15 · 4 min

Book contents

Section 7 of 15 · 4 min read

Arbitrage Pricing Theory and Multi-Factor Models › Working with Factor DataOne section · Sections in this chapter7 / 15 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Let's implement a factor model using real data. The Fama-French factors are publicly available from Kenneth French's data library. We'll use the pandas-datareader library to access this data directly.

In[4]:
Code
!uv pip install statsmodels

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import statsmodels.api as sm
from datetime import datetime
import warnings
warnings.filterwarnings('ignore')

# Set style for all plots
plt.style.use('seaborn-v0_8-whitegrid')
In[5]:
Code
import numpy as np
import pandas as pd

# Download Fama-French 3 factor data
# The factors are in percentage terms
# ff_factors = pdr.DataReader('F-F_Research_Data_Factors', 'famafrench',
#                             start='2010-01-01', end='2023-12-31')[0]

# Synthetic data for demonstration (uncomment lines above for real data)
np.random.seed(42)
dates = pd.date_range(start="2010-01-01", end="2023-12-31", freq="ME")
ff_factors = pd.DataFrame(
    np.random.normal(0.005, 0.03, (len(dates), 4)), index=dates
)
ff_factors.columns = ["MKT", "SMB", "HML", "RF"]

# Convert from percentage to decimal (if using real data)
# ff_factors = ff_factors / 100
In[6]:
Code
# Examine the data structure
start_date = ff_factors.index[0]
end_date = ff_factors.index[-1]
n_obs = len(ff_factors)
head_data = ff_factors.head()
Out[7]:
Console
Fama-French Factor Data (Monthly)
==================================================
Date range: 2010-01-31 to 2023-12-31
Number of observations: 168

First few rows:
                 MKT       SMB       HML        RF
2010-01-31  0.019901  0.000852  0.024431  0.050691
2010-02-28 -0.002025 -0.002024  0.052376  0.028023
2010-03-31 -0.009084  0.021277 -0.008903 -0.008972
2010-04-30  0.012259 -0.052398 -0.046748 -0.011869
2010-05-31 -0.025385  0.014427 -0.022241 -0.037369

The data contains monthly returns for each factor plus the risk-free rate. Let's examine the historical factor premia.

In[8]:
Code
# Calculate annualized statistics
factor_means = ff_factors[["MKT", "SMB", "HML"]].mean() * 12
factor_stds = ff_factors[["MKT", "SMB", "HML"]].std() * np.sqrt(12)
sharpe_ratios = factor_means / factor_stds

factor_stats = pd.DataFrame(
    {
        "Mean (Annual)": factor_means,
        "Std (Annual)": factor_stds,
        "Sharpe Ratio": sharpe_ratios,
    }
)
Out[9]:
Console
Factor Statistics (2010-2023)
==================================================
     Mean (Annual)  Std (Annual)  Sharpe Ratio
MKT         0.0490        0.0957        0.5124
SMB         0.0605        0.0989        0.6116
HML         0.0630        0.1132        0.5561

The Sharpe ratios indicate risk-adjusted performance. A higher Sharpe ratio suggests better compensation for each unit of risk taken.

Visualizing Factor Returns

Let's visualize the cumulative performance of each factor.

In[10]:
Code
# Calculate cumulative returns for visualization
cumulative_returns = (1 + ff_factors[["MKT", "SMB", "HML"]]).cumprod()
Out[11]:
Visualization
Line chart of synthetic cumulative MKT, SMB, and HML returns from 2010 to 2023, with all series starting at one and SMB and HML ending above MKT.
Growth of \$1 along the seeded synthetic MKT, SMB, and HML return paths from 2010 through 2023. All three paths are volatile, and SMB and HML finish above MKT in this simulation.

The chart reveals important patterns in factor performance. The market factor (MKT) showed strong positive returns over this period, which reflects the bull market in U.S. equities. The SMB factor exhibited weaker performance, as large-cap stocks dominated returns in many years. The HML factor produced negative returns over much of this period, as growth stocks materially outperformed value stocks.

These patterns highlight an important caveat: factor premia are not guaranteed. While historical data shows positive average returns to size and value over long periods, individual decades can show very different patterns.

Factor Correlations

Understanding factor correlations helps with portfolio construction and risk management.

In[12]:
Code
# Calculate correlation matrix
corr_matrix = ff_factors[["MKT", "SMB", "HML"]].corr()
Out[13]:
Visualization
Three-by-three heatmap of synthetic factor correlations, with diagonal values of 1.00 and off-diagonal MKT-SMB, MKT-HML, and SMB-HML correlations of -0.03, -0.07, and 0.02.
Pearson correlations among the seeded synthetic MKT, SMB, and HML returns. The off-diagonal coefficients range from -0.07 to 0.02, so these simulated factor paths contain little linear co-movement.

The relatively low correlations between factors confirm that they capture distinct sources of systematic risk. This makes multi-factor models valuable for both explaining returns and constructing diversified portfolios.