De um ativo para muitos
Tudo até aqui olhava uma única série de retornos. Uma carteira tem muitos ativos, e seu risco depende não só do risco de cada um, mas de como eles se movem *juntos*. A covariância de dois ativos é o produto médio de seus desvios em relação à média: positiva se tendem a subir e cair juntos, negativa se um sobe quando o outro cai. Para n ativos há n * n covariâncias, dispostas numa matriz de covariância, simétrica e com as variâncias na diagonal.
import numpy as np
# Five days of returns for two assets: shape (days, assets)
returns = np.array([[0.01, 0.02], [-0.02, -0.01], [0.03, 0.01], [0.00, -0.02], [0.01, 0.03]])
centered = returns - returns.mean(axis=0) # subtract each column's mean
cov = centered.T @ centered / (len(returns) - 1) # (assets x days) @ (days x assets) -> (assets x assets)
print(np.round(cov, 6))
print(np.allclose(cov, np.cov(returns, rowvar=False))) # TrueA matriz de covariância amostral
Com uma tabela de retornos de forma (days, assets), a matriz inteira é uma linha de álgebra linear. Subtrai-se a média de cada coluna (axis=0, média sobre os dias) e multiplica-se a transposta da tabela centrada por ela mesma: a entrada (i, j) de centered.T @ centered é a soma sobre os dias de x_i * x_j. Divide-se por days - 1, como numa variância amostral. É isso que np.cov(returns, rowvar=False) e DataFrame.cov() calculam; atenção ao rowvar=False, pois np.cov espera os ativos nas linhas.
A correlação: a covariância sem unidades
Uma covariância de 0.0004 diz pouco: depende de quão voláteis são os dois ativos. Dividir pelo produto dos dois desvios-padrão tira a escala e dá a correlação, sempre entre -1 e 1: corr[i, j] = cov[i, j] / (std[i] * std[j]), com std a raiz da diagonal. Em código, np.outer(std, std) constrói a tabela de produtos e uma divisão reescala a matriz toda, com uns na diagonal. A correlação diz quão unidos dois ativos se movem; a covariância, que mantém a escala, é o que as fórmulas de risco precisam.
# From covariance to correlation and annualised volatilities
import numpy as np
returns = np.array([[0.01, 0.02], [-0.02, -0.01], [0.03, 0.01], [0.00, -0.02], [0.01, 0.03]])
cov = np.cov(returns, rowvar=False)
std = np.sqrt(np.diag(cov))
corr = cov / np.outer(std, std)
print(np.round(corr, 4))
print(np.round(np.sqrt(np.diag(cov) * 252), 4)) # daily variances scaled to a year: volatility grows with sqrt(252)Esta matriz pode ser real?
Uma tabela qualquer de números não é uma matriz de covariância. Ela deve ser simétrica e semidefinida positiva: para quaisquer pesos w, o número w' C w é a variância de uma carteira, então não pode ser negativo. De forma equivalente, nenhum autovalor é negativo. Matrizes estimadas passam, mas as editadas à mão, ou um mosaico de correlações de fontes diferentes (por exemplo A~B = 0.9, B~C = 0.9, A~C = -0.9), falham. Verifica-se com np.linalg.eigvalsh, para matrizes simétricas, que devolve autovalores reais, com uma pequena tolerância para arredondamentos.
# A validity check: symmetry and no negative eigenvalue
import numpy as np
def is_valid_cov(matrix, tol=1e-10):
symmetric = bool(np.all(np.abs(matrix - matrix.T) <= tol))
return symmetric and bool(np.linalg.eigvalsh(matrix).min() >= -tol)
good = np.array([[1.0, 0.5, 0.2], [0.5, 1.0, 0.3], [0.2, 0.3, 1.0]])
bad = np.array([[1.0, 0.9, -0.9], [0.9, 1.0, 0.9], [-0.9, 0.9, 1.0]])
print(is_valid_cov(good), np.round(np.linalg.eigvalsh(good), 3))
print(is_valid_cov(bad), np.round(np.linalg.eigvalsh(bad), 3))# Few days, many assets: the sample covariance matrix is singular
import numpy as np
rng = np.random.default_rng(0)
returns = rng.normal(0, 0.01, size=(5, 8)) # 5 days, 8 assets
cov = np.cov(returns, rowvar=False)
print(np.linalg.matrix_rank(cov), "of", cov.shape[0]) # 4 of 8: some portfolios seem to have zero variance- A matriz de covariância de uma tabela
(days, assets)écentered.T @ centered / (days - 1): simétrica, com as variâncias na diagonal. - A correlação é
cov / np.outer(std, std): sem unidade, entre -1 e 1, uns na diagonal; anualizam-se as variâncias porperiods, as volatilidades pela sua raiz. - Uma matriz de covariância válida é simétrica e sem autovalores negativos (
np.linalg.eigvalsh); estimativas com poucos dias, ou retocadas à mão, podem violar isso.
A seguir: o retorno e a variância de uma carteira, transformar os pesos e esta matriz no risco da carteira.
Lab: o que é avaliado
- Construir a tabela de covariâncias com
cov_matrix25 pts - Reescalá-la em correlações com
corr_from_cov25 pts - Ler a volatilidade de cada ativo com
annualised_vols25 pts - Testar se pode ser real com
is_valid_cov25 pts