σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
σμimplied vol · KKmax(S−K,0)udβρᵢⱼfat tailsPnL · drawdownyield · TdSₜ = μSₜ dt + σSₜ dWₜC = S·N(d₁) − K·e⁻ʳᵀ·N(d₂)σₚ² = wᵀΣwd₁ = [ln(S/K) + (r + σ²⁄2)T] ⁄ σ√T∂V/∂t + ½σ²S²·∂²V/∂S² + rS·∂V/∂S − rV = 0Sharpe = (Rₚ − R_f) ⁄ σₚVaR₉₉ = μ − 2.33σρ = Cov(X,Y) ⁄ σₓσᵧE[Rₚ] = Σ wᵢ·E[Rᵢ]φ(x) = e^(−x²/2) ⁄ √2πE[Rᵢ] = R_f + βᵢ(E[Rₘ] − R_f)dXₜ = θ(μ − Xₜ)dt + σ dWₜσ²ₜ = ω + α·ε²ₜ₋₁ + β·σ²ₜ₋₁C − P = S − K·e⁻ʳᵀΔ = ∂V/∂S Γ = ∂²V/∂S²p = (e^(rΔt) − d) ⁄ (u − d)rₜ = ln(Pₜ ⁄ Pₜ₋₁)E[Xₜ₊₁ | ℱₜ] = Xₜf* = (bp − q) ⁄ bz = (x − μ) ⁄ σP(N=k) = λᵏe^(−λ) ⁄ k!Θ = −∂V/∂Tν = S·φ(d₁)·√Tmin wᵀΣw s.t. wᵀμ = μ*∫Σ∂∞λ
uantCraft AcademyRutas
Ctrl K
AO
+1,2 k estudiantes
Iniciar sesiónUnirse ↗
Rutas
AO
+1,2 k estudiantes
Iniciar sesión
  • English
  • Français
  • Español
  • العربية
  • Português
  • Deutsch
  • 日本語
  • 中文
Ruta de Python›Módulo 5 · Optimización de carteras›Tema 5.1·~45 minIntermedio

Matrices de covarianza y correlación

Posición en la rutaMódulo 5 de 11 · Tema 1 de 5
5.1 Matrices de covarianza y correlación
—Puntuación del lab— / 100XP total—Racha—

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)))     # True

La 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
Pocas observaciones, muchos activosUna matriz para 500 acciones tiene 125 250 entradas distintas, estimadas con quizá 250 días. Con menos días que activos, la matriz muestral es singular: alguna cartera parece tener varianza cero, algo que ningún optimizador debería creer. Los profesionales la encogen hacia un objetivo simple o usan un modelo de factores. La estimación además es un promedio del pasado: los periodos tranquilos y de crisis tienen covarianzas muy distintas.
  • La matriz de covarianzas de una tabla (days, assets) es centered.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 por periods, 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
Explorador · lab-82-1
▾ lab-82-1
…
bash · en el navegador
learner@quantcraft:~/lab-82-1$
Python aún no cargadoPythonLn 1, Col 1Spaces: 4UTF-8

¿Restablecer este tema?

Todo lo que hay en lab-82-1 vuelve a los archivos iniciales y se limpia el terminal. Tu solución actual se perderá: no se puede deshacer.

Se restablecerá
  • main.py → código inicial
  • Historial del terminal y salida de pruebas
  • Cambios sin enviar
Se conservará
  • Mejor puntuación del lab
  • Tu XP y tu racha
  • Progreso de la lección y notas

Inicia sesión para ejecutar tu código.

Todas las clases se pueden leer gratis. Crea una cuenta o inicia sesión para ejecutar código, lanzar las pruebas y enviar este lab.

  • IDE en el navegador con terminal bash — sin instalar nada
  • Corrección instantánea, XP y tu lugar en la ruta
Crear una cuentaIniciar sesión
O
Continuar con GitHub