Plus d'une raison de bouger
Le MEDAF n'a qu'un facteur, le marché. En 1992, Fama et French ont montré que deux autres expliquent une grande part des différences de rendement entre actions : la taille (les petites sociétés ont rapporté plus que les grandes, facteur SMB, small minus big) et la valeur (les actions bon marché selon le rapport valeur comptable/marché ont rapporté plus que les chères, facteur HML, high minus low). Des modèles ultérieurs ont ajouté le momentum, la rentabilité et l'investissement. Chaque facteur est lui-même une série de rendements, et l'exposition d'un actif à ce facteur dit à quel point il évolue avec cette série.
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 SMBLes moindres carrés sous forme matricielle
Avec plusieurs facteurs, la régression est asset = alpha + b1 * f1 + ... + bk * fk + erreur. On place les facteurs à côté d'une colonne de uns dans une matrice X, et les coefficients des moindres carrés résolvent les équations normales (X'X) c = X'y. En NumPy : np.linalg.solve(X.T @ X, X.T @ y). C'est la même mécanique que la régression à un actif, avec maintenant k + 1 inconnues. Le premier coefficient est l'alpha, les autres sont les expositions.
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))Que expliquent les facteurs ?
Le R carré y répond : la part de la variation de l'actif que les facteurs expliquent, 1 - SSres / SStot, entre 0 (rien) et 1 (tout). Un portefeuille d'actions diversifié a souvent un R carré supérieur à 0,9 sur les facteurs de Fama–French : son risque est presque entièrement du risque factoriel. Une action seule a un R carré plus faible, et le reste, la variation résiduelle, est propre à l'entreprise. Ajouter un facteur ne peut jamais baisser le R carré dans l'échantillon, donc un nombre plus élevé ne prouve pas à lui seul qu'un facteur compte.
# 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 modèle factoriel régresse les rendements excédentaires sur plusieurs facteurs :
X = [uns | facteurs], coefficients[alpha, beta_1..beta_k]. - Résolvez les équations normales
np.linalg.solve(X.T @ X, X.T @ y); le R carré est1 - SSres / SStotet ne baisse jamais quand on ajoute un facteur. - Le modèle implique
rf + sum(betas * premia); expositions et primes sont des estimations, et une prime de facteur peut disparaître.
À suivre : le coefficient d'information, mesurer si un signal prédit les rendements.
Lab : ce qui est noté
- Préparer la régression avec
design_matrix25 pts - Estimer les expositions avec
fit_factor_model25 pts - Noter l'ajustement avec
r_squared25 pts - Chiffrer les expositions avec
factor_expected_return25 pts