σμ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.3·~50 minIntermedio

Cartera de mínima varianza

Posición en la rutaMódulo 5 de 11 · Tema 3 de 5
5.3 Cartera de mínima varianza
—Puntuación del lab— / 100XP total—Racha—

La mezcla más segura

Combinar activos reduce el riesgo, pero cuánto depende de los pesos. La cartera de mínima varianza es la elección de pesos, de suma 1, con la menor varianza posible. No necesita ninguna previsión de rendimientos, solo la matriz de covarianzas, lo que la hace popular: los rendimientos esperados son muy difíciles de estimar y las covarianzas mucho menos. Para dos activos la respuesta es una fórmula; para muchos, la solución de un sistema lineal.

# Two assets: volatility 20% and 10%, correlation 0.3
def variance(w, vol1, vol2, rho):
    return w * w * vol1 ** 2 + (1 - w) ** 2 * vol2 ** 2 + 2 * w * (1 - w) * rho * vol1 * vol2

vol1, vol2, rho = 0.20, 0.10, 0.3
w = (vol2 ** 2 - rho * vol1 * vol2) / (vol1 ** 2 + vol2 ** 2 - 2 * rho * vol1 * vol2)
print(round(w, 4), round(variance(w, vol1, vol2, rho) ** 0.5, 4))        # 0.1053 0.0979
print(round(variance(0.0, vol1, vol2, rho) ** 0.5, 4))                   # 0.1      all in the safe asset: riskier than the mix

Muchos activos: un sistema lineal

Para minimizar w @ cov @ w sujeto a sum(w) = 1, se anula la derivada del lagrangiano. El resultado es w = cov^-1 * 1 / (1' cov^-1 1): se resuelve el sistema cov @ x = ones y se reescala x para que los pesos sumen 1. La varianza mínima en sí es 1 / (1' cov^-1 1). En código se usa np.linalg.solve(cov, ones), no np.linalg.inv(cov) @ ones: es más rápido y preciso, porque nunca forma la inversa.

# Minimum-variance weights of three assets
import numpy as np

cov = np.array([[0.0400, 0.0060, 0.0100],
                [0.0060, 0.0100, 0.0020],
                [0.0100, 0.0020, 0.0225]])

x = np.linalg.solve(cov, np.ones(3))
weights = x / x.sum()
print(np.round(weights, 4), round(float(weights.sum()), 4))        # [0.0192 0.7082 0.2726] 1.0
print(round(float(weights @ cov @ weights), 6), round(1 / float(x.sum()), 6))      # 0.007742 twice: the same number
# No other mix with weights summing to 1 does better: try 5000 random ones
import numpy as np

cov = np.array([[0.0400, 0.0060, 0.0100],
                [0.0060, 0.0100, 0.0020],
                [0.0100, 0.0020, 0.0225]])
x = np.linalg.solve(cov, np.ones(3))
best = x / x.sum()

rng = np.random.default_rng(0)
trial = rng.normal(size=(5000, 3))
trial = trial / trial.sum(axis=1, keepdims=True)                  # each row sums to 1
variances = np.einsum("ij,jk,ik->i", trial, cov, trial)            # w @ cov @ w for every row
print(round(float(variances.min()), 6), round(float(best @ cov @ best), 6))
print(bool(variances.min() >= best @ cov @ best))                  # True

Posiciones cortas y restricciones

La fórmula permite pesos negativos: puede vender en corto un activo para cubrir otro. Muchos inversores no pueden o no quieren, y desean pesos solo largos. Llevar a cero los pesos negativos y reescalar es un arreglo rápido que mantiene pesos válidos, pero no es el verdadero óptimo sin cortos, que requiere un solucionador numérico (programación cuadrática) porque la restricción w >= 0 hace que el problema tenga restricciones de desigualdad. La cartera recortada al menos se puede mantener y es fácil de comparar con la libre.

# The unconstrained solution shorts an asset; clip it and compare the risks
import numpy as np

cov = np.array([[0.04, 0.0342, 0.0],
                [0.0342, 0.0324, 0.0],
                [0.0, 0.0, 0.0225]])        # assets 1 and 2: volatility 20% and 18%, correlation 0.95

x = np.linalg.solve(cov, np.ones(3))
free = x / x.sum()
clipped = np.clip(free, 0, None)
clipped = clipped / clipped.sum()
print(np.round(free, 4), round(float(free @ cov @ free), 6))
print(np.round(clipped, 4), round(float(clipped @ cov @ clipped), 6))
Óptimo en teoría, frágil en la prácticaLos pesos usan la inversa de la matriz de covarianzas, que amplifica los errores: los activos que parecen algo menos correlacionados o menos volátiles en la muestra reciben pesos grandes, a menudo con grandes cortos, y los pesos saltan cuando se mueve la ventana de datos. Los profesionales encogen la estimación de la covarianza, acotan los pesos o añaden una penalización. Una cartera de mínima varianza que luce genial en los datos con que se ajustó se prueba con datos posteriores, no con la misma muestra.
  • La mínima varianza solo necesita la matriz de covarianzas: para dos activos, (vol2**2 - rho * vol1 * vol2) / (vol1**2 + vol2**2 - 2 * rho * vol1 * vol2) en el activo 1.
  • En general w = solve(cov, ones) / sum(...) y la varianza mínima es 1 / (1' cov^-1 1); use np.linalg.solve, no la inversa.
  • La respuesta libre puede vender activos en corto; recortar a cero y reescalar es un arreglo rápido, no el verdadero óptimo, y los pesos son frágiles ante el error de estimación.

A continuación: la optimización media-varianza, añadir los rendimientos esperados para hallar el mejor equilibrio entre rendimiento y riesgo.

Lab: qué se califica

  • Mezclar dos activos con el menor riesgo con two_asset_weight25 pts
  • Resolver los pesos más seguros con min_variance_weights25 pts
  • Informar el riesgo más bajo con min_variance25 pts
  • Descartar los cortos con long_only_weights25 pts
Explorador · lab-84-1
▾ lab-84-1
…
bash · en el navegador
learner@quantcraft:~/lab-84-1$
Python aún no cargadoPythonLn 1, Col 1Spaces: 4UTF-8

¿Restablecer este tema?

Todo lo que hay en lab-84-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