De un activo a muchos
Todo lo anterior miraba una sola serie de rendimientos. Una cartera tiene muchos activos y su riesgo depende no solo de lo arriesgado que sea cada uno, sino de cómo se mueven *juntos*. La covarianza de dos activos es el producto medio de sus desviaciones respecto a la media: positiva si tienden a subir y bajar juntos, negativa si uno sube cuando el otro baja. Para n activos hay n * n covarianzas, dispuestas en una matriz de covarianzas, simétrica y con las varianzas en la 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))) # TrueLa matriz de covarianzas muestral
Con una tabla de rendimientos de forma (days, assets), toda la matriz es una línea de álgebra lineal. Se resta la media de cada columna (axis=0, media sobre los días) y se multiplica la traspuesta de la tabla centrada por sí misma: la entrada (i, j) de centered.T @ centered es la suma sobre los días de x_i * x_j. Se divide por days - 1, como en una varianza muestral. Eso calculan np.cov(returns, rowvar=False) y DataFrame.cov(); ojo con rowvar=False, pues np.cov espera los activos en las filas.
La correlación: la covarianza sin unidades
Una covarianza de 0.0004 dice poco: depende de cuán volátiles sean los dos activos. Dividir por el producto de las dos desviaciones típicas quita la escala y da la correlación, siempre entre -1 y 1: corr[i, j] = cov[i, j] / (std[i] * std[j]), con std la raíz de la diagonal. En código, np.outer(std, std) construye la tabla de productos y una división reescala toda la matriz, con unos en la diagonal. La correlación dice cuán unidos se mueven dos activos; la covarianza, que conserva la escala, es lo que necesitan las fórmulas de riesgo.
# 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)¿Puede ser real esta matriz?
Una tabla de números cualquiera no es una matriz de covarianzas. Debe ser simétrica y semidefinida positiva: para cualquier vector de pesos w, el número w' C w es la varianza de una cartera, así que no puede ser negativo. Equivalentemente, ningún autovalor es negativo. Las matrices estimadas pasan, pero las editadas a mano, o un mosaico de correlaciones de fuentes distintas (por ejemplo A~B = 0.9, B~C = 0.9, A~C = -0.9), fallan. Se comprueba con np.linalg.eigvalsh, para matrices simétricas, que devuelve autovalores reales, con una pequeña tolerancia por el redondeo.
# 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- La matriz de covarianzas de una tabla
(days, assets)escentered.T @ centered / (days - 1): simétrica, con las varianzas en la diagonal. - La correlación es
cov / np.outer(std, std): sin unidades, entre -1 y 1, unos en la diagonal; las varianzas se anualizan porperiods, las volatilidades por su raíz. - Una matriz de covarianzas válida es simétrica y sin autovalores negativos (
np.linalg.eigvalsh); las estimaciones con muy pocos días, o retocadas a mano, pueden incumplirlo.
A continuación: el rendimiento y la varianza de una cartera, convertir los pesos y esta matriz en el riesgo de la cartera.
Lab: qué se califica
- Construir la tabla de covarianzas con
cov_matrix25 pts - Reescalarla a correlaciones con
corr_from_cov25 pts - Leer la volatilidad de cada activo con
annualised_vols25 pts - Comprobar si puede ser real con
is_valid_cov25 pts