Mais de um motivo para se mover
O CAPM tem um fator, o mercado. Em 1992, Fama e French mostraram que outros dois explicam grande parte das diferenças de retorno entre ações: o tamanho (empresas pequenas renderam mais que grandes, fator SMB, small minus big) e o valor (ações baratas pela relação valor contábil/mercado renderam mais que as caras, fator HML, high minus low). Modelos posteriores acrescentaram momentum, rentabilidade e investimento. Cada fator é uma série de retornos, e a exposição de um ativo a ela diz o quanto ele se move com essa 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 SMBMínimos quadrados em forma matricial
Com vários fatores a regressão é asset = alpha + b1 * f1 + ... + bk * fk + erro. Empilham-se os fatores ao lado de uma coluna de uns numa matriz X, e os coeficientes de mínimos quadrados resolvem as equações normais (X'X) c = X'y. Em NumPy: np.linalg.solve(X.T @ X, X.T @ y). É a mesma mecânica da regressão de um fator, agora com k + 1 incógnitas. O primeiro coeficiente é o alfa, os demais são as exposições.
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))Quanto os fatores explicam?
O R quadrado responde: a parcela da variação do ativo que os fatores explicam, 1 - SSres / SStot, entre 0 (nada) e 1 (tudo). Uma carteira de ações diversificada costuma ter R quadrado acima de 0,9 nos fatores de Fama–French: seu risco é quase todo risco fatorial. Uma ação isolada tem menos, e o resto, a variação residual, é específico da empresa. Acrescentar um fator nunca reduz o R quadrado na amostra, então um número maior sozinho não prova que um fator importa.
# 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- Um modelo fatorial regride os retornos excedentes sobre vários fatores:
X = [uns | fatores], coeficientes[alpha, beta_1..beta_k]. - Resolva as equações normais
np.linalg.solve(X.T @ X, X.T @ y); o R quadrado é1 - SSres / SStote nunca cai ao acrescentar um fator. - O modelo implica
rf + sum(betas * premia); exposições e prêmios são estimativas, e um prêmio de fator pode desaparecer.
A seguir: o coeficiente de informação, medir se um sinal prevê os retornos.
Lab: o que é avaliado
- Montar a regressão com
design_matrix25 pts - Estimar as exposições com
fit_factor_model25 pts - Pontuar o ajuste com
r_squared25 pts - Precificar as exposições com
factor_expected_return25 pts