Case Study: CAPM as a Regression Model¶
Dr. Andrés GarcÃa Medina¶
Email: andgarm.n@gmaiñ.com¶
Site: https://sites.google.com/view/andresgm/home¶
Model Definition
In multiple linear regression, the general model is:
$ y = \beta_0 + \beta_1 x_1 + \cdots + \beta_p x_p + \varepsilon $
where:
- $y$: dependent variable
- $x_i$: independent variables
- $\beta_i$: parameters to estimate
- $\varepsilon$: error term
The CAPM (Capital Asset Pricing Model) can be written as a linear regression model:
$ R_i - R_f = \alpha + \beta (R_m - R_f) + \varepsilon $
where:
- $R_i$: return of the asset (GameStop)
- $R_m$: return of the market (S&P 500)
- $R_f$: risk-free rate
- $\beta$: systematic risk (sensitivity to the market)
- $\alpha$: abnormal return
Note: Sharpe, Markowitz and Merton Miller jointly received the 1990 Nobel Memorial Prize in Economic Sciences for this contribution to the field of financial economics.
Beta (β)¶
Measures how sensitive the asset is to market movements:
- $ \beta = 1 $: asset moves with the market
- $ \beta > 1 $: more volatile than the market (higher risk)
- $ 0 < \beta < 1 $: less volatile than the market
- $ \beta < 0 $: moves opposite to the market
Alpha (α)¶
Represents abnormal returns not explained by the market:
- $ \alpha = 0 $: asset is correctly priced according to CAPM
- $ \alpha > 0 $: asset outperforms CAPM prediction (positive abnormal return)
- $ \alpha < 0 $: asset underperforms
Libraries¶
In [1]:
import numpy as np
import pandas as pd
import yfinance as yf
import statsmodels.api as sm
import matplotlib.pyplot as plt
Data
In [8]:
tickers = ["GME", "^GSPC"]
data = yf.download(tickers, start="2023-01-01", end="2026-03-17")
data.head()
Out[8]:
| Price | Close | High | Low | Open | Volume | |||||
|---|---|---|---|---|---|---|---|---|---|---|
| Ticker | GME | ^GSPC | GME | ^GSPC | GME | ^GSPC | GME | ^GSPC | GME | ^GSPC |
| Date | ||||||||||
| 2023-01-03 | 17.200001 | 3824.139893 | 19.260000 | 3878.459961 | 17.090000 | 3794.330078 | 18.639999 | 3853.290039 | 5135200 | 3959140000 |
| 2023-01-04 | 17.320000 | 3852.969971 | 17.930000 | 3873.159912 | 16.900000 | 3815.770020 | 17.250000 | 3840.360107 | 3939300 | 4414080000 |
| 2023-01-05 | 16.219999 | 3808.100098 | 17.260000 | 3839.739990 | 15.890000 | 3802.419922 | 17.059999 | 3839.739990 | 6066200 | 3893450000 |
| 2023-01-06 | 16.459999 | 3895.080078 | 16.570000 | 3906.189941 | 15.410000 | 3809.560059 | 16.000000 | 3823.370117 | 4823400 | 3923560000 |
| 2023-01-09 | 16.379999 | 3892.090088 | 17.129999 | 3950.570068 | 16.360001 | 3890.419922 | 16.650000 | 3910.820068 | 3522600 | 4311770000 |
In [4]:
data.to_excel("stock_data.xlsx")
print("Data saved to stock_data.xlsx")
Data saved to stock_data.xlsx
Compute log returns¶
In [7]:
returns = np.log(data / data.shift(1)).dropna()
returns.head()
Out[7]:
| Price | Close | High | Low | Open | Volume | |||||
|---|---|---|---|---|---|---|---|---|---|---|
| Ticker | GME | ^GSPC | GME | ^GSPC | GME | ^GSPC | GME | ^GSPC | GME | ^GSPC |
| Date | ||||||||||
| 2023-01-04 | 0.006952 | 0.007511 | -0.071555 | -0.001367 | -0.011180 | 0.005635 | -0.077498 | -0.003361 | -0.265116 | 0.108773 |
| 2023-01-05 | -0.065617 | -0.011714 | -0.038084 | -0.008666 | -0.061624 | -0.003505 | -0.011076 | -0.000161 | 0.431729 | -0.125504 |
| 2023-01-06 | 0.014688 | 0.022584 | -0.040798 | 0.017158 | -0.030673 | 0.001876 | -0.064148 | -0.004272 | -0.229253 | 0.007704 |
| 2023-01-09 | -0.004872 | -0.000768 | 0.033237 | 0.011297 | 0.059823 | 0.021003 | 0.039821 | 0.022615 | -0.314280 | 0.094349 |
| 2023-01-10 | 0.081451 | 0.006954 | 0.054528 | -0.007812 | -0.006746 | -0.003381 | -0.021245 | -0.005706 | 0.223041 | -0.113008 |
Define CAPM variables¶
In [9]:
R_i = returns['Close']['GME']
R_m = returns['Close']['^GSPC']
# Risk-free rate (approx. constant daily)
Rf = 0.02 / 252 # 2% annually
# Excess returns
Y = R_i - Rf
X = R_m - Rf
Estimate regression (OLS)¶
In [10]:
# Add constant (intercept)
X = sm.add_constant(X)
model = sm.OLS(Y, X).fit()
# Results
print(model.summary())
OLS Regression Results
==============================================================================
Dep. Variable: GME R-squared: 0.033
Model: OLS Adj. R-squared: 0.031
Method: Least Squares F-statistic: 26.84
Date: Thu, 17 Sep 2026 Prob (F-statistic): 2.80e-07
Time: 08:31:53 Log-Likelihood: 1143.8
No. Observations: 801 AIC: -2284.
Df Residuals: 799 BIC: -2274.
Df Model: 1
Covariance Type: nonrobust
==============================================================================
coef std err t P>|t| [0.025 0.975]
------------------------------------------------------------------------------
const -0.0004 0.002 -0.197 0.843 -0.004 0.004
^GSPC 1.1356 0.219 5.181 0.000 0.705 1.566
==============================================================================
Omnibus: 370.290 Durbin-Watson: 1.943
Prob(Omnibus): 0.000 Jarque-Bera (JB): 28150.260
Skew: 1.192 Prob(JB): 0.00
Kurtosis: 31.944 Cond. No. 107.
==============================================================================
Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.
Visualization¶
In [17]:
plt.figure(figsize=(8,6))
plt.scatter(X.iloc[:,1], Y, alpha=0.5)
# Regression line
beta = model.params[1]
alpha = model.params[0]
x_vals = np.linspace(X.iloc[:,1].min(), X.iloc[:,1].max(), 100)
y_vals = alpha + beta * x_vals
plt.plot(x_vals, y_vals)
plt.xlabel("Market excess return")
plt.ylabel("GME excess return")
plt.title("CAPM: GameStop vs S&P 500")
plt.grid()
plt.show()