---
title: "4.1.3 Black-Litterman: equilibrio y visiones"
description: "Black-Litterman desde cero: equilibrio implícito, confianza de Idzorek, seis visiones con su verdad publicada (tres correctas) y visiones de modelos de ML."
url: "https://pythonparatrading.com/curso-python-trading/nivel-4/black-litterman/"
lang: "es"
image: "https://pythonparatrading.com/og/curso-python-trading/nivel-4/black-litterman.png"
---

# 4.1.3 · Black-Litterman: del equilibrio del mercado a las visiones

> Black-Litterman sustituye la media muestral, la entrada más frágil de Markowitz, por los rendimientos que hacen óptima la cartera de mercado, y solo se separa de ellos donde el gestor tiene una opinión y en la medida de su confianza. Esta lección lo construye desde cero, lo contrasta con PyPortfolioOpt y lo prueba con seis visiones cuya verdad está publicada: tres correctas y tres equivocadas, una de ellas de un modelo de machine learning.

- Curso: [Nivel 4: Carteras, riesgo, ejecución y trading en real](https://pythonparatrading.com/curso-python-trading/nivel-4/)
- Módulo: 4.1 · Teoría y optimización de carteras
- Duración: 70 min
- Requisitos: [4.1.1 · Markowitz: frontera eficiente y restricciones reales](https://pythonparatrading.com/curso-python-trading/nivel-4/markowitz-frontera-eficiente-y-restricciones/), [4.1.2 · Estimación de covarianzas: shrinkage, modelos factoriales y denoising](https://pythonparatrading.com/curso-python-trading/nivel-4/estimacion-de-covarianzas-shrinkage-y-denoising/)

## Al terminar esta lección

- Calcular los rendimientos de equilibrio por optimización inversa a partir de los pesos de mercado y entender qué supone ese equilibrio.
- Escribir visiones absolutas y relativas con las matrices P, Q y Ω, y obtener la distribución posterior de Black-Litterman con una implementación propia contrastada con PyPortfolioOpt.
- Convertir una confianza porcentual en Ω con el método de Idzorek y comprobar que la versión cerrada coincide con la original.
- Medir con la verdad publicada cuándo una visión mejora la cartera: no basta con que sea correcta, tiene que estar más cerca de la verdad que el equilibrio.
- Tratar una visión de un modelo de machine learning: Q de su predicción y Ω de su error fuera de muestra.

## El problema que resuelve

La lección 4.1.1 dejó dos resultados. El primero, que la media es la entrada más frágil de Markowitz: con 20 años, su
error típico ronda los 5 puntos en un índice de acciones. El segundo, que el optimizador amplifica ese error: busca
las diferencias de rendimiento estimadas y apuesta por ellas. Basta ver qué hace con la media muestral de 20 años de
los 38 activos del universo, sin restricciones:

```python
import numpy as np
import pandas as pd

precios = pd.read_parquet("universo4_precios.parquet")
precios = precios.set_index("fecha")
fac = pd.read_parquet("universo4_factores_diarios.parquet")
r = precios.pct_change()
r.iloc[0] = precios.iloc[0] / 100 - 1
exceso = r.sub(fac.set_index("fecha")["rf"], axis=0)
activos = list(exceso.columns)

info = pd.read_csv("universo4_activos.csv", index_col="activo")
verdad = pd.read_csv("universo4_verdad.csv", index_col="activo")
cov_v = pd.read_csv("universo4_cov_verdad.csv")
S = cov_v[cov_v["regimen"] == "total"].set_index("activo")
S = S.loc[activos, activos].to_numpy()
w_mkt = info.loc[activos, "peso_mercado"].to_numpy()
mu_v = verdad.loc[activos, "mu_verdadero"].to_numpy()
mu_m = exceso.mean().to_numpy() * 252

delta = 2.5
w_m = np.linalg.solve(delta * S, mu_m)
print(f"apalancamiento bruto: {np.abs(w_m).sum():.0f} veces")
print((100 * pd.Series(w_m, index=activos)).round(0)
      .sort_values().iloc[[0, 1, -2, -1]])
```
```output
apalancamiento bruto: 138 veces
MONET     -2077.0
RF_T710   -1991.0
RF_CIG     1214.0
RF_T37     2395.0
dtype: float64
```

Para aislar el problema de la media, la lección usa la covarianza verdadera total del universo (la 4.1.2 trató la
covarianza; el ejercicio 1 repite el cálculo con la muestral). Aun con la covarianza exacta, la cartera óptima para
una aversión al riesgo de 2,5 tiene un apalancamiento bruto de 138 veces: vende un 2.077 % del monetario y un 1.991 % del bono de 7-10 años para
comprar un 2.395 % del de 3-7 años, activos casi idénticos cuyas medias muestrales difieren en décimas.
Ningún gestor lo haría. Black y Litterman (1992), en Goldman Sachs, propusieron otro punto de partida.

## El equilibrio: optimización inversa

La condición de primer orden de un inversor de media-varianza con aversión al riesgo $\delta$ es
$w = (\delta \Sigma)^{-1} \mu$. Si se conocen los pesos y no la media, se despeja al revés:

$$
\pi = \delta\, \Sigma\, w_{\text{mercado}} .
$$

$\pi$ son los rendimientos esperados en exceso que harían óptima la cartera de mercado. Si todos los inversores
fueran de media-varianza y tuvieran la misma información, la cartera de mercado sería la tangente (el CAPM) y $\pi$
sería la verdad. La aversión al riesgo $\delta$ se puede calibrar con el Sharpe del mercado:
$\delta = (\mu_m / \sigma_m) / \sigma_m$.

```mermaid
flowchart TB
  W[Pesos de mercado] --> PI["π = δΣw"]
  S[Covarianza Σ] --> PI
  V["Visiones P, Q"] --> BL[Black-Litterman]
  O["Confianza Ω"] --> BL
  PI --> BL
  BL --> M[Media posterior]
  M --> OP[Optimizador]
```

El universo publica los pesos de la cartera de mercado (`peso_mercado` en `universo4_activos.csv`: los 18 bloques
sin solapamiento, por capitalización a 2025) y su propio $\pi$ con $\delta = 2{,}5$:

```python
pi = delta * S @ w_mkt
pub = verdad.loc[activos, "pi_equilibrio"].to_numpy()
print("igual que el publicado:", np.allclose(pi, pub, atol=1e-4))

mu_mkt, vol_mkt = w_mkt @ mu_v, np.sqrt(w_mkt @ S @ w_mkt)
print(f"mercado: exceso {mu_mkt:.2%}, vol {vol_mkt:.2%}, "
      f"Sharpe {mu_mkt / vol_mkt:.2f}")
print(f"δ implícito con la verdad: {mu_mkt / vol_mkt**2:.2f}")

r_mkt = exceso.to_numpy() @ w_mkt
print(f"δ implícito con la muestra: "
      f"{r_mkt.mean() / r_mkt.var():.2f}")
w_tan = np.linalg.solve(S, mu_v)
print("Sharpe de la tangente verdadera:",
      (w_tan @ mu_v / np.sqrt(w_tan @ S @ w_tan)).round(2))
```
```output
igual que el publicado: True
mercado: exceso 5.77%, vol 12.95%, Sharpe 0.45
δ implícito con la verdad: 3.44
δ implícito con la muestra: 2.94
Sharpe de la tangente verdadera: 1.35
```

El $\delta$ que implican los datos (2,94 con la muestra, 3,44 con la verdad) es algo mayor que el 2,5 con el que se
publica $\pi$ en los datos. Cambiar $\delta$ escala todo $\pi$ por igual; lo que interesa es
su forma. Y la forma tiene dos rasgos de este universo que conviene ver antes de seguir:

```python
ventana = exceso.loc["2021":"2025"].mean().to_numpy() * 252
def ecm(x):
    return np.sqrt(np.mean((x - mu_v) ** 2))

print(f"error cuadrático medio frente a la verdad: "
      f"equilibrio {ecm(pi):.2%}, muestra 20 años {ecm(mu_m):.2%}, "
      f"muestra 2021-2025 {ecm(ventana):.2%}")
sel = ["RV_EEUU", "RV_EMG", "RF_T20", "RF_CIG", "MP_ORO", "MP_ENE"]
tabla = pd.DataFrame({"equilibrio": pi, "verdad": mu_v,
                      "muestra_20": mu_m, "muestra_5": ventana},
                     index=activos).loc[sel]
print((100 * tabla).round(2))
```
```output
error cuadrático medio frente a la verdad: equilibrio 3.45%, muestra 20 años 1.71%, muestra 2021-2025 6.32%
         equilibrio  verdad  muestra_20  muestra_5
RV_EEUU        6.46    9.62        8.79      10.51
RV_EMG         7.96    3.80        5.46       7.22
RF_T20        -0.52    3.68        1.54     -11.60
RF_CIG         0.41    2.76        2.13      -2.06
MP_ORO         0.77    9.98       10.03      13.72
MP_ENE         3.32   -4.62       -5.46      10.38
```

*Fig. — Rendimiento de equilibrio de Black-Litterman frente a la media verdadera de los 38 activos del universo.* Nube de 38 puntos con la diagonal μ = π. Las acciones quedan por encima de la diagonal (la verdad supera al equilibrio, tecnología 13,9 % frente a 7,3 %), salvo emergentes, por debajo. Los bonos del Tesoro tienen equilibrio negativo y verdad positiva (más de 20 años: −0,5 % frente a 3,7 %). Las materias primas quedan muy por debajo (energía: 3,3 % frente a −4,6 %). El oro, destacado, es el caso extremo: equilibrio 0,8 % y verdad 10,0 %.

El equilibrio da primas casi nulas o negativas a los bonos del Tesoro (−0,52 % al de más de 20 años, frente a un
3,68 % verdadero) porque en la calma, el régimen dominante, los bonos tienen correlación negativa con las acciones: en
una cartera de mercado dominada por acciones, cubren, y lo que cubre «merece» una prima baja. Y da primas positivas a
las materias primas (3,32 % a la energía), que en el guion de estos 20 años pierden (−4,62 %). El oro es el caso
extremo: equilibrio 0,77 %, verdad 9,98 %. En este universo la cartera de mercado no es eficiente: su Sharpe verdadero
es 0,45, y el de la cartera tangente verdadera, sin restricciones, 1,35.

En error cuadrático medio, el equilibrio (3,45 puntos) queda peor que la media muestral de 20 años (1,71 puntos) y
mejor que la de cinco años (6,32). La muestra de 20 años es inusualmente buena: las semillas del generador se
eligieron para que las medias realizadas quedaran cerca de las verdaderas. Lo normal, y lo que tiene un gestor que
mira cinco años, se parece más a la última columna. La ventaja del equilibrio no es acertar más que una buena muestra,
sino no producir carteras absurdas: un $\pi$ coherente con la cartera de mercado lleva a la cartera de mercado.

## Visiones: P, Q y Ω

Una **visión** es una afirmación sobre el rendimiento esperado de una cartera. Las absolutas hablan de un activo
(«emergentes rinde un 5 % anual en exceso»); las relativas, de una diferencia («momentum bate a valor en un 3 %»).
Con $k$ visiones se escriben como

$$
P \mu = Q + \varepsilon, \qquad \varepsilon \sim N(0, \Omega),
$$

con $P$ una matriz $k \times n$ (una fila por visión, con los pesos de su cartera), $Q$ los rendimientos que afirman y
$\Omega$ su incertidumbre, normalmente diagonal. El fichero `universo4_visiones.csv` trae seis, con la respuesta
conocida: `verdad` es $P \mu$ con la media verdadera y `correcta` marca las que están a menos de un punto de ella.

```python
vis = pd.read_csv("universo4_visiones.csv")
P = np.zeros((len(vis), len(activos)))
for i, fila in enumerate(vis["activos"]):
    for parte in fila.split(";"):
        activo, peso = parte.split(":")
        P[i, activos.index(activo)] = float(peso)
Q = vis["q"].to_numpy()
conf = vis["confianza"].to_numpy()
print(vis[["vision", "q", "confianza", "pi_equilibrio", "verdad",
           "correcta"]].round(4).to_string(index=False))
print("P·π y P·μ coinciden con el fichero:",
      np.allclose(P @ pi, vis["pi_equilibrio"], atol=1e-4),
      np.allclose(P @ mu_v, vis["verdad"], atol=1e-4))
```
```output
vision     q  confianza  pi_equilibrio  verdad  correcta
    V1  0.05       0.50         0.0796  0.0380     False
    V2  0.02       0.40        -0.0055 -0.0330     False
    V3  0.03       0.60         0.0027  0.0328      True
    V4  0.02       0.30         0.0206  0.0220      True
    V5 -0.01       0.50        -0.0200 -0.0173      True
    V6  0.08       0.25         0.0077  0.0998     False
P·π y P·μ coinciden con el fichero: True True
```

Las visiones V3, V4 y V5 son correctas; V1, V2 y V6 no. La V6 viene «de un modelo de ML» y afirma que el oro rinde un
8 % anual en exceso: es incorrecta por el criterio del punto (la verdad es 9,98 %), pero está mucho más cerca de la
verdad que el equilibrio. Esa diferencia va a importar.

## La posterior de Black-Litterman

Black-Litterman trata $\pi$ como un prior sobre la media verdadera, $\mu \sim N(\pi, \tau\Sigma)$, con un escalar
$\tau$ pequeño (la incertidumbre de la media es mucho menor que la de los rendimientos), y lo combina con las visiones
como en una regresión bayesiana. La media posterior es

$$
\mu_{BL} = \pi + \tau\Sigma P^\top \left(P \tau\Sigma P^\top + \Omega\right)^{-1} (Q - P\pi) .
$$

El último factor es la «sorpresa» de cada visión frente al equilibrio, $Q - P\pi$. Se reparte entre todos los
activos según sus covarianzas con las carteras de las visiones, así que una visión sobre emergentes mueve también la
media de las bolsas y de la deuda emergente.

Falta $\Omega$. Idzorek propuso fijarla con una **confianza** $c$ entre 0 y 1, más intuitiva: con confianza
del 100 % la visión se cumple exactamente; con el 0 %, se ignora; con $c$, los pesos de la cartera recorren una
fracción $c$ del camino entre la de mercado y la de confianza total. Su método original busca cada $\omega_k$
numéricamente; Walters, en su revisión del modelo, mostró que sin restricciones la solución es cerrada:

$$
\omega_k = \tau \, \frac{1 - c_k}{c_k} \, p_k \Sigma p_k^\top .
$$

```python
tau = 0.05
def omega_idzorek(P, conf):
    return np.diag([tau * (1 - c) / c * p @ S @ p
                    for p, c in zip(P, conf)])

def black_litterman(P, Q, Om):
    tS = tau * S
    sorpresa = np.linalg.solve(P @ tS @ P.T + Om, Q - P @ pi)
    return pi + tS @ P.T @ sorpresa

Om = omega_idzorek(P, conf)
mu_bl = black_litterman(P, Q, Om)

from pypfopt.black_litterman import BlackLittermanModel
blm = BlackLittermanModel(
    pd.DataFrame(S, index=activos, columns=activos),
    pi=pd.Series(pi, index=activos), P=P, Q=Q, tau=tau,
    omega="idzorek", view_confidences=conf)
print("igual que PyPortfolioOpt:",
      np.allclose(blm.bl_returns().to_numpy(), mu_bl, atol=1e-12))
post = pd.DataFrame({"q": Q, "equilibrio": P @ pi,
                     "posterior": P @ mu_bl, "verdad": P @ mu_v},
                    index=vis["vision"])
print((100 * post).round(2))
```
```output
igual que PyPortfolioOpt: True
          q  equilibrio  posterior  verdad
vision
V1      5.0        7.96       6.21    3.80
V2      2.0       -0.55       0.43   -3.30
V3      3.0        0.27       1.76    3.28
V4      2.0        2.06       1.46    2.20
V5     -1.0       -2.00      -1.36   -1.72
V6      8.0        0.77       2.46    9.98
```

La figura de la cabecera dibuja esta tabla. La posterior de cada visión queda entre el equilibrio y lo que afirma la
visión, más cerca de lo segundo cuanto mayor es la confianza y menor la varianza de su cartera. La V6, con confianza
del 25 %, mueve el oro del 0,77 % al 2,46 %: menos de lo que dice el modelo y muy lejos de la verdad. La V4 se mueve
del 2,06 % al 1,46 %, en dirección contraria a su propia $Q$: las visiones interactúan, porque el crédito de alto
rendimiento está correlado con las acciones y otras visiones (V1, V2) mueven las acciones.

Que la forma cerrada reproduce el método original de Idzorek se comprueba buscando cada $\omega_k$ como él: la que
deja los pesos sin restricciones a una fracción $c_k$ del camino.

```python
from scipy.optimize import minimize_scalar

def pesos(m):
    return np.linalg.solve(delta * S, m)

for k in range(len(Q)):
    p, q = P[[k]], Q[[k]]
    w_100 = pesos(black_litterman(p, q, np.array([[1e-12]])))
    objetivo = w_mkt + conf[k] * (w_100 - w_mkt)
    def distancia(log_om):
        m = black_litterman(p, q, np.array([[np.exp(log_om)]]))
        return np.sum((pesos(m) - objetivo) ** 2)
    res = minimize_scalar(distancia, bounds=(-20, 5),
                          method="bounded",
                          options={"xatol": 1e-10})
    print(vis["vision"][k], f"{np.exp(res.x) / Om[k, k]:.6f}")
```
```output
V1 1.000000
V2 1.000000
V3 1.000000
V4 1.000000
V5 1.000000
V6 1.000000
```

El cociente entre el $\omega$ numérico y el cerrado es 1 en las seis visiones. Con restricciones (solo largos, por
ejemplo) ya no hay forma cerrada y el método numérico es el único correcto.

## ¿Ayuda cada visión?

La pregunta práctica no es si una visión es correcta, sino si la cartera mejora con ella. Para cada visión sola, se
calcula la media posterior, la cartera de máximo Sharpe de solo largos (la misma función que en la lección 4.1.1) y
su Sharpe con la media y la covarianza verdaderas:

```python
import cvxpy as cp

def max_sharpe(m):
    y = cp.Variable(len(m))
    pr = cp.Problem(cp.Minimize(cp.quad_form(y, cp.psd_wrap(S))),
                    [m @ y == 1, y >= 0])
    pr.solve(solver="CLARABEL")
    return y.value / y.value.sum()

def sharpe(w):
    return w @ mu_v / np.sqrt(w @ S @ w)

filas = []
for k in range(len(Q)):
    m = black_litterman(P[[k]], Q[[k]], Om[[k]][:, [k]])
    filas.append({
        "vision": vis["vision"][k],
        "correcta": vis["correcta"][k],
        "error_equilibrio": abs(P[k] @ pi - P[k] @ mu_v),
        "error_q": abs(Q[k] - P[k] @ mu_v),
        "error_posterior": abs(P[k] @ m - P[k] @ mu_v),
        "sharpe": sharpe(max_sharpe(m))})
cada = pd.DataFrame(filas).set_index("vision")
print(cada.round(3))
print("Sharpe de la cartera de mercado:", sharpe(w_mkt).round(3))
```
```output
        correcta  error_equilibrio  error_q  error_posterior  sharpe
vision
V1         False             0.042    0.012            0.027   0.492
V2         False             0.028    0.053            0.038   0.357
V3          True             0.030    0.003            0.014   0.583
V4          True             0.001    0.002            0.002   0.446
V5          True             0.003    0.007            0.002   0.473
V6         False             0.092    0.020            0.074   0.627
Sharpe de la cartera de mercado: 0.446
```

*Fig. — Sharpe verdadero de la cartera de solo largos de Black-Litterman con cada visión por separado, frente a la cartera de mercado.* Barras horizontales. V1, incorrecta: 0,49. V2, incorrecta: 0,36, destacada, la única por debajo del mercado (0,45, línea discontinua). V3, correcta: 0,58. V4, correcta: 0,45. V5, correcta: 0,47. V6, incorrecta: 0,63, la mayor.

La tabla separa dos ideas que se suelen confundir:

- **V1 y V6, «equivocadas», mejoran la cartera.** Sus $Q$ no aciertan por un punto, pero el equilibrio estaba mucho
  más lejos: 4,2 puntos en emergentes y 9,2 en el oro. La visión del oro, con solo un 25 % de confianza, sube el
  Sharpe de 0,45 a 0,63.
- **V4 y V5, «correctas», casi no cambian nada.** El equilibrio ya estaba a 0,1 y 0,3 puntos de la verdad; la
  visión no añade información y el Sharpe queda en 0,45 y 0,47.
- **V2 es la única dañina.** Afirma que Europa cubierta bate a EE. UU. en un 2 %, cuando la verdad es −3,30 % y el
  equilibrio decía −0,55 %: aleja la media de la verdad y el Sharpe cae a 0,36, por debajo del mercado.
- **V3, correcta y lejos del equilibrio**, es el caso de manual: 3,0 puntos de error en el equilibrio, 0,3 en la
  visión, y el Sharpe sube a 0,58.

Una visión vale lo que su distancia a la verdad tiene de menos que la del equilibrio. Por eso no basta con contar
aciertos de un analista o de un modelo: hay que medir si acierta más que el mercado.

## La cartera de Black-Litterman

He y Litterman (1999) mostraron una propiedad que hace al modelo fácil de explicar a un comité: sin restricciones, la
cartera óptima con la media posterior es la cartera de mercado más una posición en la cartera de cada visión. Los
activos que no aparecen en ninguna visión conservan su peso de mercado, aunque su media haya cambiado:

```python
w_bl = pesos(mu_bl)
dif = pd.Series(w_bl - w_mkt, index=activos)
en_vistas = np.abs(P).sum(axis=0) > 0
print("activos con cambio > 0,01 %:", list(dif[dif.abs() > 1e-4].index))
cambian = set(dif[dif.abs() > 1e-4].index)
print("todos en alguna visión:",
      cambian <= set(np.array(activos)[en_vistas]))
print((100 * dif[dif.abs() > 1e-4]).round(1))
```
```output
activos con cambio > 0,01 %: ['RV_EEUU', 'RV_EUR_C', 'RV_EMG', 'ES_VAL', 'ES_MOM', 'ES_MIN', 'RF_CIG', 'RF_CHY', 'MP_ORO']
todos en alguna visión: True
RV_EEUU    -96.4
RV_EUR_C    71.8
RV_EMG      -6.9
ES_VAL     -55.7
ES_MOM      55.7
ES_MIN      24.7
RF_CIG     -10.3
RF_CHY      10.3
MP_ORO      34.7
dtype: float64
```

La cartera sin restricciones lleva posiciones cortas en EE. UU. (su peso baja 96 puntos, por V2 y V5), en valor
(V3) y en grado de inversión (V4): son las carteras de las visiones relativas, que son largas-cortas por definición. Un fondo de solo largos
aplica el optimizador con la media posterior y sus restricciones. La comparación final, con el Sharpe evaluado con la
verdad:

```python
grupos = {"mercado": None, "solo correctas": [2, 3, 4],
          "solo incorrectas": [0, 1, 5], "las seis": list(range(6))}
res = {}
for nombre, idx in grupos.items():
    if idx is None:
        res[nombre] = sharpe(w_mkt)
        continue
    m = black_litterman(P[idx], Q[idx], Om[np.ix_(idx, idx)])
    res[nombre] = sharpe(max_sharpe(m))
res["media 2021-2025"] = sharpe(max_sharpe(ventana))
res["media 20 años"] = sharpe(max_sharpe(mu_m))
print(pd.Series(res).round(3))
```
```output
mercado             0.446
solo correctas      0.595
solo incorrectas    0.597
las seis            0.700
media 2021-2025     0.668
media 20 años       0.901
dtype: float64
```

Con las seis visiones, el Sharpe verdadero pasa de 0,45 (mercado) a 0,70. Las tres correctas solas dan 0,60; las tres
incorrectas, otro 0,60, por V1 y V6. La media de los últimos cinco años da 0,67 con una cartera elegida por el azar
de la ventana (lección 4.1.1), y la de 20 años, 0,90: con datos tan buenos como los de este generador, la media
muestral con restricciones de solo largos gana. Black-Litterman no promete la mejor cartera; promete partir de una
razonable y separarse de ella solo donde se sabe algo.

## Visiones de modelos de machine learning

Un modelo de ML (los del nivel 5 del curso) produce predicciones, y una predicción se puede convertir en visión:
$Q$ es la predicción y $\Omega$, la incertidumbre de esa predicción. El error típico es tomar la confianza del ajuste
dentro de la muestra de entrenamiento. Lo correcto es medirla **fuera de muestra** y frente al prior: si el modelo no
predice mejor que el equilibrio, su visión no debe mover nada.

```mermaid
flowchart TB
  D[Datos hasta t] --> M[Modelo]
  M --> Q["Predicción Q"]
  M --> E[Errores fuera]
  E --> O["Ω"]
  Q --> BL[Black-Litterman]
  O --> BL
```

Un ejemplo sencillo con el oro: un «modelo» que predice el exceso del año siguiente con la media de los tres
anteriores, evaluado año a año de 2009 a 2025 (cada predicción usa solo datos pasados). Su error cuadrático se compara
con el del equilibrio, y lo que el modelo gana sobre el ruido del propio rendimiento anual es lo que justifica
$\Omega$:

```python
oro = exceso["MP_ORO"]
anual = (1 + oro).groupby(oro.index.year).prod() - 1
pred = {a: oro.loc[str(a - 3):str(a - 1)].mean() * 252
        for a in range(2009, 2026)}
pred = pd.Series(pred)
real = anual.loc[pred.index]
i_oro = activos.index("MP_ORO")
ecm_mod = np.mean((pred - real) ** 2)
ecm_eq = np.mean((pi[i_oro] - real) ** 2)
print(f"error cuadrático: modelo {np.sqrt(ecm_mod):.1%}, "
      f"equilibrio {np.sqrt(ecm_eq):.1%}, "
      f"R² fuera de muestra {1 - ecm_mod / ecm_eq:.3f}")

ruido = S[i_oro, i_oro]               # varianza de un año de oro
omega_ml = max(ecm_mod - ruido, 1e-6)  # error del modelo en la media
p6 = P[5]
c_ml = 1 / (1 + omega_ml / (tau * p6 @ S @ p6))
print(f"ω del modelo {omega_ml:.4f} frente al publicado {Om[5, 5]:.4f}")
print(f"confianza implícita: {c_ml:.1%} (publicada {conf[5]:.0%})")
```
```output
error cuadrático: modelo 20.8%, equilibrio 21.5%, R² fuera de muestra 0.068
ω del modelo 0.0218 frente al publicado 0.0032
confianza implícita: 4.7% (publicada 25%)
```

El modelo bate al equilibrio por muy poco (un R² fuera de muestra de 0,07, con 17 años, indistinguible de cero). Su
error cuadrático es el doble de la varianza de un año de oro: la mitad del error es ruido del propio rendimiento
anual, que ningún modelo puede quitar, y la otra mitad, $\omega = 0{,}0218$, es error del modelo sobre la media, casi
siete veces la $\omega$ publicada. La confianza que eso justifica es de alrededor del 5 %, no del 25 %. En este universo, la visión V6 está cerca de la verdad, pero un gestor no
podría saberlo con este historial: su confianza tiene que salir de los datos y no del resultado que se espera.

Cuatro precauciones con visiones de ML:

- **Fuera de muestra de verdad**: predicciones hechas con datos anteriores a cada fecha, con la purga de la lección
  2.5.3 si las etiquetas se solapan.
- **Contra el prior, no contra cero**: un modelo que predice «las acciones suben» acierta más que cero, pero no más
  que el equilibrio.
- **No contar dos veces**: si el modelo usa los mismos datos que estiman $\Sigma$ o la media, la visión repite
  información que el prior ya tiene.
- **Horizonte coherente**: $Q$ y $\Omega$ en exceso anual, como $\pi$; una predicción a un mes se anualiza, y su
  error también.

## Errores típicos

- **Mezclar rendimientos totales y en exceso** entre $\pi$ (en exceso) y $Q$.
- **Visiones relativas mal escritas**: la fila de $P$ de «A bate a B» es $+1$ en A y $-1$ en B; con varios activos por
  lado, los pesos de cada lado suman 1 y −1.
- **Confundir τ con la confianza**: con la forma cerrada de Idzorek, τ no cambia la media posterior (ejercicio 3);
  sí cambia la covarianza posterior.
- **Pesos de mercado que no son de mercado**: $\pi$ hereda lo que se ponga en $w$. Con un universo que incluye
  sectores y estilos que se solapan con el índice, hay que usar solo los bloques sin solapamiento, como hace el
  fichero.
- **Creer que la posterior es la verdad**: es un compromiso entre dos fuentes con error, y en este universo el
  equilibrio tiene errores grandes en bonos y materias primas.

## Ejercicios

1. Repite el equilibrio y la posterior con la covarianza muestral de los 20 años en lugar de la verdadera. ¿Cuánto
   cambian $\pi$ y la posterior de cada visión?

**Solución.**

```python
S_m = exceso.cov().to_numpy() * 252
pi_m = delta * S_m @ w_mkt
print(f"diferencia máxima de π: {np.abs(pi_m - pi).max():.2%}")
tS_m = tau * S_m
Om_m = np.diag([tau * (1 - c) / c * p @ S_m @ p
                for p, c in zip(P, conf)])
mu_bl_m = pi_m + tS_m @ P.T @ np.linalg.solve(
    P @ tS_m @ P.T + Om_m, Q - P @ pi_m)
print((100 * pd.DataFrame({"verdadera": P @ mu_bl,
                           "muestral": P @ mu_bl_m},
                          index=vis["vision"])).round(2))
```
```output
diferencia máxima de π: 0.33%
        verdadera  muestral
vision
V1           6.21      6.44
V2           0.43      0.43
V3           1.76      1.83
V4           1.46      1.43
V5          -1.36     -1.37
V6           2.46      2.45
```

Con 20 años, la covarianza muestral está muy cerca de la verdadera (lección 4.1.2), y $\pi$ cambia como mucho un
tercio de punto. Las posteriores se mueven unas décimas. Black-Litterman es poco sensible al error de la covarianza y
muy sensible a las visiones: el riesgo del modelo está en $Q$ y en la confianza, no en $\Sigma$.

2. Quita la visión V2 de las seis. ¿Cuánto mejora el Sharpe verdadero de la cartera de solo largos? ¿Podría un
   gestor haber sabido de antemano que era la visión que había que quitar?

**Solución.**

```python
idx = [0, 2, 3, 4, 5]
m = black_litterman(P[idx], Q[idx], Om[np.ix_(idx, idx)])
print("sin V2:", sharpe(max_sharpe(m)).round(3),
      "| las seis:", res["las seis"].round(3))
```
```output
sin V2: 0.798 | las seis: 0.7
```

Sin la V2, el Sharpe sube. Pero solo se sabe porque la verdad está publicada. Con datos reales, la V2 era tan
defendible como las demás. Lo que sí se puede hacer es llevar un registro de las visiones y de su resultado fuera de
muestra frente al equilibrio, y ajustar la confianza de cada fuente (analista o modelo) con ese historial.

3. Comprueba que, con la Ω de Idzorek en forma cerrada, la media posterior no cambia si τ pasa de 0,05 a 0,5. Explica
   por qué.

**Solución.**

```python
def bl_tau(t):
    Om_t = np.diag([t * (1 - c) / c * p @ S @ p
                    for p, c in zip(P, conf)])
    tS = t * S
    return pi + tS @ P.T @ np.linalg.solve(P @ tS @ P.T + Om_t,
                                            Q - P @ pi)

print("misma media:", np.allclose(bl_tau(0.05), bl_tau(0.5)))
```
```output
misma media: True
```

En la fórmula, la media posterior depende de $\tau\Sigma$ (el peso del prior) frente a $\Omega$ (el de las visiones).
Si $\Omega$ es proporcional a $\tau$, como en la forma cerrada de Idzorek, los dos crecen juntos y $\tau$ se cancela.
Importa cuando $\Omega$ se fija por fuera, como en la sección de ML, donde sale del error del modelo: ahí un $\tau$
mayor da más peso a la visión.

## Autoevaluación

1. ¿Qué rendimientos esperados devuelve la optimización inversa de Black-Litterman?

   - A. Las medias históricas de cada activo
   - B. Los que harían óptima la cartera de mercado para un inversor de media-varianza con aversión al riesgo δ: π = δΣw_mercado
   - C. Los del CAPM con la beta de cada activo frente al S&P 500
   - D. Rendimientos nulos para todos los activos

   *Respuesta B: Los que harían óptima la cartera de mercado para un inversor de media-varianza con aversión al riesgo δ: π = δΣw_mercado.* Se parte de la condición de primer orden de Markowitz (los pesos óptimos son la inversa de δΣ por μ) y se despeja μ con los pesos de mercado. Si el mercado fuera eficiente en media-varianza, π serían los rendimientos esperados verdaderos; es un supuesto, no un hecho.

2. La visión V4 (alto rendimiento bate a grado de inversión en un 2 %) es correcta según el criterio de los datos y la V1 (emergentes rinde un 5 %) no. Sin embargo, sola, la V1 mejora la cartera y la V4 no. ¿Por qué?

   - A. Por azar
   - B. Porque la V1 tiene más confianza
   - C. Porque las visiones absolutas pesan más que las relativas
   - D. Porque lo que importa es si la visión está más cerca de la verdad que el equilibrio: en V4 el equilibrio ya decía 2,06 % (verdad 2,21 %), y en V1 decía 7,96 % frente a una verdad de 3,81 %, así que un 5 % acerca

   *Respuesta D: Porque lo que importa es si la visión está más cerca de la verdad que el equilibrio: en V4 el equilibrio ya decía 2,06 % (verdad 2,21 %), y en V1 decía 7,96 % frente a una verdad de 3,81 %, así que un 5 % acerca.* Black-Litterman mueve la posterior desde π hacia Q. Si π ya estaba bien, una visión correcta no aporta; si π estaba lejos, una visión imperfecta en la dirección buena ayuda. La V2 es la única de las seis que empeora: afirma un +2 % donde la verdad es −3,30 % y el equilibrio, −0,55 %.

3. Con el método de Idzorek en su forma cerrada, Ω de una visión es τ·(1 − c)/c·pΣp′. ¿Qué ocurre con la media posterior si se cambia τ de 0,05 a 0,5?

   - A. Nada: τ aparece en el peso del prior y en Ω, y se cancela
   - B. Se acerca a las visiones
   - C. Se acerca al equilibrio
   - D. Se vuelve inestable

   *Respuesta A: Nada: τ aparece en el peso del prior y en Ω, y se cancela.* La posterior depende del cociente entre la incertidumbre del prior (τΣ) y la de las visiones (Ω). Si Ω se define proporcional a τ, el cociente no cambia. τ sí importa si Ω se fija por otro camino, por ejemplo con el error de un modelo.

4. Una visión de un modelo de ML predice el rendimiento del oro. ¿De dónde debe salir su Ω?

   - A. Del R² del modelo en la muestra de entrenamiento
   - B. De la confianza que le tenga el equipo que lo desarrolló
   - C. Del error de sus predicciones fuera de muestra, comparado con el del equilibrio: si no lo bate, la visión no debe mover la cartera
   - D. De la volatilidad del oro

   *Respuesta C: Del error de sus predicciones fuera de muestra, comparado con el del equilibrio: si no lo bate, la visión no debe mover la cartera.* El ajuste en la muestra de entrenamiento siempre es optimista. En la lección, un modelo sencillo que bate al equilibrio por muy poco fuera de muestra justifica una confianza de alrededor del 5 %, muy por debajo del 25 % publicado.

5. La cartera de Black-Litterman sin restricciones difiere de la de mercado...

   - A. En todos los activos, porque cambia la media de todos
   - B. Solo en los activos que aparecen en alguna visión: es la cartera de mercado más una posición en cada cartera de visión
   - C. Solo en los activos con más peso de mercado
   - D. En nada, si las visiones son correctas

   *Respuesta B: Solo en los activos que aparecen en alguna visión: es la cartera de mercado más una posición en cada cartera de visión.* Con los pesos óptimos sin restricciones (la inversa de δΣ por μ_BL), la media posterior cambia en todos los activos correlacionados, pero la inversa de Σ deshace esa propagación y los pesos solo se mueven en las filas de P (He y Litterman, 1999). Es una de las propiedades que hacen a Black-Litterman intuitivo.

## Ideas clave

- El equilibrio π = δΣw_mercado da unos rendimientos esperados coherentes con la cartera de mercado y estables. Es un supuesto: en el universo del curso, la cartera de mercado no es eficiente y π se equivoca con los bonos y las materias primas.
- Black-Litterman combina el prior N(π, τΣ) con visiones P·μ = Q + ruido de varianza Ω. La implementación propia coincide con PyPortfolioOpt, y la confianza de Idzorek en forma cerrada coincide con su método original.
- Lo que decide si una visión ayuda no es si es correcta, sino si está más cerca de la verdad que el equilibrio: V1 y V6, «equivocadas», mejoran la cartera; V4 y V5, «correctas», casi no cambian nada; solo V2 empeora.
- Con las seis visiones, la cartera de solo largos pasa de un Sharpe verdadero de 0,45 (mercado) a 0,70, con pesos razonables. Con la media muestral sin restricciones, la cartera necesitaría un apalancamiento bruto de 138 veces.
- Una visión de ML se trata como cualquier otra: Q de su predicción y Ω de su error fuera de muestra frente al equilibrio. Un modelo que apenas lo bate merece una confianza cercana a cero.

## Recursos

### Research

- [Alpha Factors y Alpha Trading en Python](https://pythonparatrading.com/alpha-factors-y-alpha-trading-en-python/) · Research-0007 · 5 min

### Documentación

- [PyPortfolioOpt: Black-Litterman](https://pyportfolioopt.readthedocs.io/en/latest/BlackLitterman.html) · pyportfolioopt.readthedocs.io

### Libros

- [Black, F. y Litterman, R. (1992): Global Portfolio Optimization. Financial Analysts Journal 48(5), 28-43](https://doi.org/10.2469/faj.v48.n5.28) · doi.org
- [He, G. y Litterman, R. (1999): The Intuition Behind Black-Litterman Model Portfolios. Goldman Sachs (SSRN)](https://doi.org/10.2139/ssrn.334304) · doi.org
- [Walters, J.: The Black-Litterman Model in Detail (SSRN 1314585)](https://doi.org/10.2139/ssrn.1314585) · doi.org

### Datos

- [Datos del nivel 4: visiones con su verdad (CSV)](https://pythonparatrading.com/curso/datos/nivel-4/universo4_visiones.csv) · Descarga · universo4_visiones.csv
- [Datos del nivel 4: pesos de mercado de los 38 activos (CSV)](https://pythonparatrading.com/curso/datos/nivel-4/universo4_activos.csv) · Descarga · universo4_activos.csv

Lección anterior: [4.1.2 · Estimación de covarianzas: shrinkage, modelos factoriales y denoising](https://pythonparatrading.com/curso-python-trading/nivel-4/estimacion-de-covarianzas-shrinkage-y-denoising/)

Lección siguiente: [4.1.4 · Risk parity, mínima varianza, HRP y HERC](https://pythonparatrading.com/curso-python-trading/nivel-4/risk-parity-minima-varianza-hrp-y-herc/)
