Arbitrage Pricing Theory and Multi-Factor Models › Estimating Factor Loadings for a Stock

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 › Estimating Factor Loadings for a Stock
Reading section 8 of 15 · Estimating Factor Loadings for a Stock · 4 minSection 8 of 15 · 4 min

Book contents

Section 8 of 15 · 4 min read

Arbitrage Pricing Theory and Multi-Factor Models › Estimating Factor Loadings for a StockOne section · Sections in this chapter8 / 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

Now let's estimate factor loadings for a specific stock. We'll create synthetic return data that mimics a real stock's behavior, then estimate its factor exposures using regression.

In[14]:
Code
# Generate synthetic stock returns with known factor exposures
np.random.seed(42)

# True factor loadings for our synthetic stock
true_betas = {
    "MKT": 1.2,  # Higher market exposure than average
    "SMB": 0.3,  # Slight small-cap tilt
    "HML": -0.4,  # Growth stock characteristics
}
true_alpha = 0.001  # Small positive alpha (0.1% monthly)

# Generate returns using the factor model
n_obs = len(ff_factors)
idiosyncratic_vol = 0.03  # 3% monthly idiosyncratic volatility
epsilon = np.random.normal(0, idiosyncratic_vol, n_obs)

stock_excess_returns = (
    true_alpha
    + true_betas["MKT"] * ff_factors["MKT"].values
    + true_betas["SMB"] * ff_factors["SMB"].values
    + true_betas["HML"] * ff_factors["HML"].values
    + epsilon
)

# Create a DataFrame
stock_data = pd.DataFrame(
    {"excess_return": stock_excess_returns}, index=ff_factors.index
)
In[15]:
Code
import statsmodels.api as sm

# Estimate factor loadings via OLS regression
X = ff_factors[["MKT", "SMB", "HML"]]
X = sm.add_constant(X)
y = stock_data["excess_return"]

model = sm.OLS(y, X).fit()

# Extract results for display
r_squared = model.rsquared
adj_r_squared = model.rsquared_adj
params = model.params
std_errs = model.bse
t_stats = model.tvalues
p_values = model.pvalues
Out[16]:
Console
Factor Model Regression Results
==================================================

R-squared: 0.6820
Adjusted R-squared: 0.6762

Estimated Factor Loadings:
--------------------------------------------------
Parameter      Estimate  Std Error     t-stat    p-value
--------------------------------------------------
const           -0.0005     0.0023      -0.20     0.8432
MKT              1.3249     0.0795      16.66     0.0000
SMB              0.3070     0.0768       4.00     0.0001
HML             -0.4512     0.0672      -6.71     0.0000

The regression successfully recovers the true factor loadings. The R-squared indicates what fraction of return variance is explained by the factors. The remaining variance represents idiosyncratic risk that could be diversified away in a portfolio.

Interpreting the Results

Let's compare our estimates to the true values and interpret the stock's factor profile.

In[17]:
Code
# Compare estimated vs true parameters
comparison_data = []

# Alpha comparison
alpha_est = model.params["const"]
comparison_data.append(
    {
        "Factor": "Alpha",
        "True": true_alpha,
        "Estimated": alpha_est,
        "Difference": alpha_est - true_alpha,
    }
)

# Factor betas comparison
for factor in ["MKT", "SMB", "HML"]:
    beta_est = model.params[factor]
    beta_true = true_betas[factor]
    comparison_data.append(
        {
            "Factor": factor,
            "True": beta_true,
            "Estimated": beta_est,
            "Difference": beta_est - beta_true,
        }
    )

comparison_df = pd.DataFrame(comparison_data).set_index("Factor")
Out[18]:
Console
Comparison: Estimated vs True Factor Loadings
==================================================
         True  Estimated  Difference
Factor                              
Alpha   0.001    -0.0005     -0.0015
MKT     1.200     1.3249      0.1249
SMB     0.300     0.3070      0.0070
HML    -0.400    -0.4512     -0.0512
Out[19]:
Visualization
Two-bar chart comparing true and OLS-estimated monthly alpha in percent.
True and OLS-estimated monthly alpha shown separately from the dimensionless factor betas so the small intercept remains visible.
Grouped bar chart comparing true and OLS-estimated MKT, SMB, and HML factor betas.
True and OLS-estimated MKT, SMB, and HML betas. The estimates closely recover both the magnitude and sign of the seeded exposures.

The estimated loadings closely match the true values. The small differences arise from estimation error due to finite sample size and idiosyncratic noise. This stock has:

  • High market beta (1.2): More volatile than the market, amplifying gains in bull markets and losses in bear markets
  • Positive SMB loading (0.3): Behaves somewhat like a small-cap stock, gaining when small stocks outperform
  • Negative HML loading (-0.4): Behaves like a growth stock, gaining when growth outperforms value

This profile is typical of a technology growth stock: high market sensitivity, modest small-cap characteristics, and strong growth orientation.