Más de una razón para moverse
El CAPM tiene un factor, el mercado. En 1992 Fama y French mostraron que otros dos explican gran parte de las diferencias de rendimiento entre acciones: el tamaño (las empresas pequeñas ganaron más que las grandes, factor SMB, small minus big) y el valor (las acciones baratas por valor contable/mercado ganaron más que las caras, factor HML, high minus low). Modelos posteriores añadieron momentum, rentabilidad e inversión. Cada factor es una serie de rendimientos, y la exposición de un activo a ella dice cuánto se mueve con esa serie.
import numpy as np
rng = np.random.default_rng(1)
n = 250
market = rng.normal(0.0004, 0.010, size=n) # excess returns of three factors
smb = rng.normal(0.0001, 0.005, size=n)
hml = rng.normal(0.0001, 0.005, size=n)
factors = np.column_stack([market, smb, hml]) # shape (n, k) = (250, 3)
asset = 0.0002 + factors @ np.array([1.1, 0.5, -0.3]) + rng.normal(0, 0.004, size=n)
X = np.column_stack([np.ones(n), factors]) # design matrix: a column of ones for alpha
print(X.shape) # (250, 4)# The design matrix of three days and two factors
import numpy as np
factors = np.array([[0.010, -0.002], [-0.004, 0.001], [0.006, 0.003]])
X = np.column_stack([np.ones(len(factors)), factors])
print(X) # a column of ones, then market and SMBMínimos cuadrados en forma matricial
Con varios factores la regresión es asset = alpha + b1 * f1 + ... + bk * fk + error. Se colocan los factores junto a una columna de unos en una matriz X, y los coeficientes de mínimos cuadrados resuelven las ecuaciones normales (X'X) c = X'y. En NumPy: np.linalg.solve(X.T @ X, X.T @ y). Es la misma maquinaria que la regresión de un solo factor, ahora con k + 1 incógnitas. El primer coeficiente es alfa, el resto son las exposiciones.
import numpy as np
rng = np.random.default_rng(1)
n = 250
factors = np.column_stack([rng.normal(0.0004, 0.010, size=n), rng.normal(0.0001, 0.005, size=n), rng.normal(0.0001, 0.005, size=n)])
asset = 0.0002 + factors @ np.array([1.1, 0.5, -0.3]) + rng.normal(0, 0.004, size=n)
X = np.column_stack([np.ones(n), factors])
coef = np.linalg.solve(X.T @ X, X.T @ asset)
print(np.round(coef, 4)) # [alpha, market, SMB, HML]: the loadings come out near [1.1, 0.5, -0.3]
residual = asset - X @ coef
r2 = 1 - np.sum(residual ** 2) / np.sum((asset - asset.mean()) ** 2)
print(round(float(r2), 4))¿Cuánto explican los factores?
El R cuadrado lo responde: la parte de la variación del activo que explican los factores, 1 - SSres / SStot, entre 0 (nada) y 1 (todo). Una cartera de acciones diversificada suele tener un R cuadrado superior a 0,9 con los factores de Fama–French: su riesgo es casi todo riesgo factorial. Una acción individual tiene uno menor, y el resto, la variación residual, es propia de la empresa. Añadir un factor nunca baja el R cuadrado en la muestra, así que un número mayor no prueba por sí solo que un factor importe.
# What the model implies: the expected return from the loadings and the factor premia
import numpy as np
betas = np.array([1.1, 0.5, -0.3]) # market, SMB, HML loadings
premia = np.array([0.0006, 0.0002, 0.0002]) # expected daily factor returns
rf = 0.0001
expected = rf + np.sum(betas * premia)
print(round(float(expected), 6)) # 0.0008 per day, about 20% a year on 250 days- Un modelo factorial regresa los rendimientos en exceso sobre varios factores:
X = [unos | factores], coeficientes[alpha, beta_1..beta_k]. - Resuelva las ecuaciones normales
np.linalg.solve(X.T @ X, X.T @ y); el R cuadrado es1 - SSres / SStoty nunca baja al añadir un factor. - El modelo implica
rf + sum(betas * premia); exposiciones y primas son estimaciones, y una prima de factor puede desaparecer.
A continuación: el coeficiente de información, medir si una señal predice los rendimientos.
Lab: qué se califica
- Preparar la regresión con
design_matrix25 pts - Estimar las exposiciones con
fit_factor_model25 pts - Puntuar el ajuste con
r_squared25 pts - Poner precio a las exposiciones con
factor_expected_return25 pts