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 mixMuchos 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)) # TruePosiciones 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))- 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 es1 / (1' cov^-1 1); usenp.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