---
title: "2.2.1 Hipótesis de ventaja: de dónde sale un edge"
description: "Riesgo, comportamiento, estructura e información: cuatro fuentes de ventaja, medidas sobre datos con efectos plantados y con su perfil de riesgo."
url: "https://pythonparatrading.com/curso-python-trading/nivel-2/hipotesis-de-ventaja-edge/"
lang: "es"
image: "https://pythonparatrading.com/og/curso-python-trading/nivel-2/hipotesis-de-ventaja-edge.png"
---

# 2.2.1 · Hipótesis de ventaja: riesgo, comportamiento, estructura e información

> Un sistema de trading gana dinero solo si alguien, al otro lado, acepta perderlo o pagarlo. Esta lección ordena las cuatro razones por las que eso ocurre y mide cuatro ventajas plantadas en los datos del nivel, con su rentabilidad, su asimetría, sus crashes y su tamaño.

- Curso: [Nivel 2: Estrategias sistemáticas y backtesting riguroso](https://pythonparatrading.com/curso-python-trading/nivel-2/)
- Módulo: 2.2 · Anatomía de un sistema de trading
- Duración: 60 min
- Requisitos: [1.6.2 · Hechos estilizados: colas gruesas, agrupamiento, autocorrelación y apalancamiento](https://pythonparatrading.com/curso-python-trading/nivel-1/hechos-estilizados-de-los-mercados/), [1.4.4 · Universos sin sesgo de supervivencia: deslistados, fusiones y cambios de símbolo](https://pythonparatrading.com/curso-python-trading/nivel-1/sesgo-de-supervivencia-y-point-in-time/), [0.7.2 · Probabilidad y distribuciones, correlación frente a causalidad y regresión simple](https://pythonparatrading.com/curso-python-trading/nivel-0/probabilidad-distribuciones-y-regresion/)

## Al terminar esta lección

- Formular una hipótesis de ventaja con su mecanismo, su contraparte y las condiciones en que deja de funcionar.
- Clasificar una ventaja por su fuente (prima de riesgo, sesgo de comportamiento, restricción estructural o difusión lenta de información).
- Medir en Python cuatro ventajas plantadas (carry de divisas, momentum, rebalanceos de índice y deriva tras resultados) y un placebo sin efecto.
- Comparar su perfil de riesgo: Sharpe, asimetría, curtosis, peor mes, número de apuestas al año y liquidez de lo que se negocia.

## Un edge es una hipótesis, no un patrón

Una **ventaja** (*edge*) es una razón para esperar que una regla de trading gane dinero en el futuro, después de
costes. La palabra importante es «razón». Un patrón encontrado en un backtest («las acciones que suben el martes
siguen subiendo el miércoles») no es una ventaja: es una observación, y la mayoría de las observaciones de un
backtest son azar (lecciones 2.1.8 y 2.2.4). Para convertirla en hipótesis hay que contestar tres preguntas:

1. **¿Quién está al otro lado?** Toda operación tiene contraparte. Si el sistema gana, alguien gana menos o pierde.
2. **¿Por qué lo acepta?** Porque cobra algo a cambio (se quita un riesgo de encima), porque se equivoca, porque está
   obligado o porque todavía no sabe lo que el sistema ya ha descontado.
3. **¿Por qué no lo ha corregido nadie?** Arbitrarlo tiene un riesgo, un coste o un límite de capital que los demás no
   quieren o no pueden asumir.

Las respuestas a la segunda pregunta dan una taxonomía de cuatro fuentes:

```mermaid
flowchart TB
  E[Ventaja] --> R[Riesgo]
  E --> C[Comportamiento]
  E --> S[Estructura]
  E --> I[Información]
  R --> R1[Carry]
  C --> C1[Momentum]
  S --> S1[Rebalanceos]
  I --> I1[Deriva PEAD]
```

| Fuente | Por qué paga la contraparte | Ejemplo en los datos del nivel | Qué predice la hipótesis |
|---|---|---|---|
| Prima de riesgo | Se quita un riesgo que no quiere soportar y paga por ello | Carry de divisas: largo en tipos altos, corto en tipos bajos | Persiste, pero con pérdidas grandes en las crisis (asimetría negativa) |
| Comportamiento | Reacciona tarde o de más a las noticias, o vende lo que pierde demasiado pronto | Momentum 12-1 de acciones | Mayor donde arbitrar cuesta; puede menguar; crashes en los rebotes |
| Estructura | Una norma o un mandato le obliga a operar en una fecha o a un precio | Altas y bajas de un índice | Concentrada en fechas conocidas; poca capacidad; compite con quien conoce el calendario |
| Información | Aún no ha incorporado una noticia pública al precio | Deriva tras resultados (PEAD) | Decae con el tiempo tras la noticia y con la velocidad del mercado |

La clasificación no es rígida. El momentum tiene explicaciones de comportamiento (infrarreacción a las noticias) y de
riesgo (sus crashes llegan cuando el mercado rebota tras una crisis, el peor momento); el carry, además de prima, puede
leerse como una restricción de los inversores que no pueden endeudarse en divisas baratas. Lo que importa es que cada
explicación **predice algo distinto y comprobable**: dónde debería ser mayor la ventaja, cuándo debería fallar y cómo
debería ser la distribución de sus rentabilidades.

## Los datos: efectos plantados y un placebo

Los datos del nivel 2 son sintéticos y llevan efectos con parámetros conocidos (`/curso/datos/nivel-2/README.md`). Esta
lección mide cuatro de ellos y un control:

- **Carry de divisas** (`fx2_carry.parquet`): la divisa de tipo alto solo devuelve el 30 % del diferencial en el tipo de
  cambio, y en cinco tramos de estrés las divisas de tipo alto caen de golpe.
- **Momentum** (`universo2_caracteristicas.parquet`): prima mensual de `0,0025 × z` sobre la rentabilidad 12-1, con un
  coeficiente negativo en los tres meses tras los suelos de 2009 y de 2020.
- **Rebalanceos de índice** (`eventos2_rebalanceos.csv`): las altas suben un 3 % entre el anuncio y la fecha efectiva y
  las bajas caen otro tanto; en las 20 sesiones siguientes revierten la mitad.
- **Deriva tras resultados** (`eventos2_resultados.csv`): durante 60 sesiones tras la reacción, el precio deriva en la
  dirección de la sorpresa.
- **Placebo**: la `puntuacion_esg` es un AR(1) independiente de todo. Su prima verdadera es cero.

<aside class="callout" data-kind="aviso">

Los efectos de estos datos los ha puesto el generador. Que el código de la lección los recupere demuestra que el
método funciona, no que las estrategias funcionen en un mercado real. Para eso está la literatura citada en los
recursos, con datos reales, y aun así con las cautelas de la lección 2.5.1.

</aside>

Todas las ventajas se comparan con la misma vara: una serie de rentabilidades mensuales de una cartera, y un mismo
resumen de su perfil.

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

def perfil(m):
    """m: rentabilidades mensuales (en tanto por uno)."""
    sr = m.mean() / m.std() * np.sqrt(12)
    return pd.Series({
        "meses": len(m),
        "rent_anual": 12 * m.mean(),
        "vol_anual": np.sqrt(12) * m.std(),
        "sharpe": sr,
        "t": sr * np.sqrt(len(m) / 12),
        "asimetria": m.skew(),
        "curtosis": m.kurt(),
        "peor_mes": m.min(),
        "meses_pos": (m > 0).mean(),
    })

def a_mensual(r_diaria):
    return (1 + r_diaria).resample("ME").prod() - 1
```

La `t` es el Sharpe anual por la raíz del número de años: la t de la media de las rentabilidades. La `curtosis` es la
curtosis en exceso (0 en una normal) y la `asimetria`, la de los meses.

## Riesgo: el carry de divisas

La hipótesis del carry es una **prima de riesgo**. Quien presta en la divisa de tipo alto y se endeuda en la de tipo
bajo cobra el diferencial de tipos. La paridad descubierta de intereses diría que la divisa de tipo alto se deprecia
exactamente lo necesario para anularlo; si solo lo hace en parte, queda una ganancia media. La explicación de por qué
dura es que el carry pierde mucho justo cuando todo pierde: en las crisis, los inversores deshacen sus posiciones a la
vez y las divisas de tipo alto se desploman[^1].

La cartera es la clásica: cada fin de mes, largo en las tres divisas de tipo más alto y corto en las tres de tipo más
bajo, con pesos iguales. La rentabilidad diaria de estar largo en una divisa es la variación del tipo de cambio más el
diferencial de tipos devengado (README, sección d).

```python
fx = pd.read_parquet("fx2_carry.parquet")
spot = fx.pivot(index="fecha", columns="divisa", values="spot")
tipo = fx.pivot(index="fecha", columns="divisa", values="tipo_divisa")
usd = fx.groupby("fecha")["tipo_usd"].first()
dif = tipo.sub(usd, axis=0)                     # % anual

dias = spot.index.to_series().diff().dt.days
interes = dif.shift(1).div(100).mul(dias / 360, axis=0)
r_fx = spot.pct_change() + interes              # largo en la divisa

# última sesión de cada mes: largo 3 de tipo alto, corto 3 de bajo
fin_mes = dif.groupby(dif.index.to_period("M")).tail(1)
rango = fin_mes.rank(axis=1)
peso = (rango >= 8).astype(float) / 3 - (rango <= 3) / 3
peso_d = peso.reindex(r_fx.index).ffill().shift(1)
carry_d = (peso_d * r_fx).sum(axis=1).loc["2005-02":]
carry = a_mensual(carry_d)
perfil(carry).round(3)
```
```output
meses         251.000
rent_anual      0.044
vol_anual       0.108
sharpe          0.409
t               1.871
asimetria      -1.384
curtosis        7.389
peor_mes       -0.199
meses_pos       0.574
dtype: float64
```

El `shift(1)` final hace que el peso decidido con los tipos del cierre de un mes se aplique desde la sesión
siguiente: sin él, la cartera cobraría el día de la decisión una rentabilidad que aún no podía tener.

¿Dónde están las pérdidas? El README fija cinco tramos de crash. La suma de lo que gana o pierde la cartera dentro de
cada tramo, y fuera de todos ellos:

```python
tramos = [("2008-09-15", "2008-12-31"), ("2010-05-03", "2010-05-31"),
          ("2011-08-01", "2011-09-30"), ("2015-08-17", "2015-09-30"),
          ("2020-02-24", "2020-03-31")]
en_crash = pd.Series(False, index=carry_d.index)
for ini, fin in tramos:
    en_crash.loc[ini:fin] = True
    print(ini[:7], f"{carry_d.loc[ini:fin].sum():+.1%}")
print(f"sesiones de crash: {en_crash.mean():.1%} del total")
print(f"suma en crash: {carry_d[en_crash].sum():+.1%}")
print(f"suma fuera: {carry_d[~en_crash].sum():+.1%}")
carry.nsmallest(4).round(3)
```
```output
2008-09 -23.4%
2010-05 -7.5%
2011-08 -7.8%
2015-08 -4.3%
2020-02 -15.5%
sesiones de crash: 3.7% del total
suma en crash: -58.6%
suma fuera: +147.2%
fecha
2009-02-28   -0.199
2020-03-31   -0.124
2008-11-30   -0.083
2022-08-31   -0.077
dtype: float64
```

*Fig. — Carry de divisas de los datos del nivel 2, 2005–2025: valor de 1 USD invertido, con los cinco tramos de crash del generador sombreados.* Curva mensual que empieza en 1 en 2005, sube hasta 2008, cae con fuerza en el tramo de crash de otoño de 2008 y toca su peor mes, destacado, en febrero de 2009 (−19,9 %), ya fuera del tramo, Después recupera con caídas menores en 2010, 2011 y 2015 y otra fuerte en marzo de 2020; termina en 2,22.

La cartera gana un 4,4 % anual con un 10,8 % de volatilidad: Sharpe de 0,41 y t de 1,87, en el límite de lo que
distingue una prima del azar con 21 años. La asimetría mensual es de −1,38 y la curtosis en exceso, de 7,4. Las sesiones
de los cinco tramos de crash son el 3,7 % del total y en ellas la cartera suma −58,6 puntos; fuera, +147,2. Solo el
tramo de 2008 se lleva 23,4 puntos y el de 2020, 15,5.

El perfil es el de quien vende un seguro: gana poco y casi siempre, y devuelve una parte grande de golpe. La figura
lo enseña sobre la curva de la cartera. Dos detalles del resultado importan para un sistema real:

- El peor mes, febrero de 2009 (−19,9 %), no cae dentro de un tramo plantado, sino después: la volatilidad GARCH de
  las divisas de tipo alto sigue disparada tras el crash. Las crisis no terminan cuando termina la caída; el riesgo de cola dura más.
- Un backtest de cinco años sin ninguna crisis dentro enseñaría una curva casi recta y un Sharpe alto. La prima solo
  se ve completa con muestras que incluyen los episodios por los que se paga.

## Comportamiento (o riesgo): el momentum

El momentum transversal compra las acciones que más han subido en los últimos doce meses, sin el último, y vende las
que más han caído[^2]. Las explicaciones de comportamiento dicen que los inversores infrarreaccionan a las noticias y
que el precio tarda meses en reflejarlas; las de riesgo, que la cartera larga-corta pierde mucho en los rebotes tras
una crisis, cuando los perdedores (muy castigados, con beta alta) suben más que nadie[^3].

En los datos del nivel la señal es la de la primera hipótesis y el crash, el de la segunda. Con las rentabilidades
mensuales point-in-time de `universo2_caracteristicas.parquet`:

```python
car = pd.read_parquet("universo2_caracteristicas.parquet")
rm = car.pivot(index="fecha", columns="ticker",
               values="rentabilidad_mes")
hay = car.pivot(index="fecha", columns="ticker",
                values="cierre").notna()    # False: deslistado
# rentabilidad de m-11 a m-1 (se salta el último mes), solo
# donde hay precio al cierre de m
mom = np.expm1(np.log1p(rm.where(hay)).shift(1)
               .rolling(11).sum()).where(hay)
sig_mes = rm.shift(-1)          # m+1, con el deslistado incluido

ic = mom.rank(axis=1).corrwith(sig_mes.rank(axis=1), axis=1).dropna()
print(f"IC de rangos medio {ic.mean():.3f}, "
      f"t = {ic.mean() / ic.std() * np.sqrt(len(ic)):.2f}")

def quintiles(senal, fut, excluir=()):
    s, f = senal.drop(columns=list(excluir)), fut.drop(
        columns=list(excluir))
    rk = s.rank(axis=1, pct=True)
    m = (f[rk > 0.8].mean(axis=1) - f[rk <= 0.2].mean(axis=1))
    m = m.dropna()
    # etiqueta: fin del mes del rendimiento (el siguiente)
    sig = m.index.to_period("M") + 1
    m.index = sig.to_timestamp() + pd.offsets.MonthEnd(0)
    return m

momentum = quintiles(mom, sig_mes)
perfil(momentum).round(3)
```
```output
IC de rangos medio 0.028, t = 2.96
meses         227.000
rent_anual      0.101
vol_anual       0.120
sharpe          0.838
t               3.644
asimetria       0.136
curtosis        0.082
peor_mes       -0.090
meses_pos       0.573
dtype: float64
```

La señal y la rentabilidad futura no usan las mismas filas. El fichero tiene, para cada acción que deja de cotizar,
una última fila sin precio en el mes de su salida, con una `rentabilidad_mes` que encadena lo que va de mes con la
rentabilidad de deslistado (−100 % en las cuatro quiebras). Esa fila no sirve para formar una señal (ese día la acción
ya no se puede comprar), y por eso `mom` se calcula solo donde `hay` precio. Pero sí tiene que entrar en `sig_mes`: es
lo que gana o pierde quien tenía la acción en cartera el mes anterior. Un backtest que la descartara no vería ninguna
quiebra.

El IC de rangos (la correlación de Spearman entre la señal de un mes y la rentabilidad del siguiente, lección 2.3.5)
mide la relación sin dejarse arrastrar por unos pocos valores extremos. La cartera de quintiles sí se deja: el README
avisa de que cuatro quiebras guiadas caen durante meses hasta casi cero y acaban siempre en el quintil perdedor, del
lado corto, donde el −100 % del deslistado es una ganancia del 100 %. Su aportación se mide quitándolas:

```python
quiebras = ["QXQT", "QXKV", "QXZT", "QXYX"]
sin_q = quintiles(mom, sig_mes, excluir=quiebras)
print(f"con quiebras {12 * momentum.mean():.1%} anual, "
      f"sin ellas {12 * sin_q.mean():.1%}")
crash = ["2009-04", "2009-05", "2009-06",
         "2020-04", "2020-05", "2020-06"]
momentum.loc[momentum.index.strftime("%Y-%m").isin(crash)].round(3)
```
```output
con quiebras 10.1% anual, sin ellas 6.9%
fecha
2009-04-30   -0.060
2009-05-31   -0.031
2009-06-30   -0.058
2020-04-30   -0.044
2020-05-31   -0.006
2020-06-30   -0.059
dtype: float64
```

El IC medio es de 0,028 con t de 2,96, cerca del +0,03 esperado. La cartera de quintiles gana un 10,1 % anual con
Sharpe de 0,84; sin las quiebras, un 6,9 %: casi un tercio de la rentabilidad (3,2 puntos) viene de cuatro empresas,
que en su último mes con precio están en el percentil 1 de la señal. Con datos reales pasa lo mismo
con un signo peligroso: sin las rentabilidades de deslistado, o con un universo de supervivientes (lección 1.4.4), el
lado corto parece peor de lo que fue, o desaparece.

Los seis meses plantados son negativos, pero ninguno es el peor de la serie (−9,0 %, en febrero de 2021). Junio de
2009 se queda en −5,8 % por una razón que no tiene que ver con la señal: ese mes deja de cotizar QXKV, una de las
quiebras, y el lado corto cobra su −100 % mientras los perdedores rebotan. El crash de estos datos es moderado: la
asimetría mensual de la cartera es de 0,14, casi nula, y la curtosis en exceso, de 0,08. En el momentum real los
crashes han sido más violentos que aquí[^3]; un modelo de datos lleva el riesgo de cola que su autor decidió ponerle.

## Estructura: los rebalanceos del índice

Una **ventaja estructural** no depende de que nadie se equivoque: depende de que alguien está obligado a operar. Un
fondo que replica un índice tiene que comprar las acciones que entran y vender las que salen al cierre de la fecha
efectiva, y esa fecha se anuncia días antes. Quien compra entre el anuncio y la fecha efectiva vende después al
indexador; si la demanda de acciones no es perfectamente elástica, el precio sube con la compra obligada y revierte
en parte después[^4].

Cada evento se mide como la rentabilidad del valor desde el cierre del día del anuncio hasta el de la fecha efectiva,
con signo (largo en altas, corto en bajas) y en exceso sobre la media del universo. Las bajas por deslistado no tienen
efecto plantado y se quitan:

```python
aj = pd.read_parquet("universo2_ajustados.parquet")
px = aj.pivot(index="fecha", columns="ticker",
              values="cierre_ajustado")
r = px.pct_change(fill_method=None)
mercado = r.mean(axis=1)
sesiones = px.index

rb = pd.read_csv("eventos2_rebalanceos.csv",
                 parse_dates=["fecha_anuncio", "fecha_efectiva"])
rb = rb[rb["motivo"] != "deslistado"]

def evento(e):
    ia = sesiones.searchsorted(e.fecha_anuncio)
    ie = sesiones.searchsorted(e.fecha_efectiva)
    s = 1 if e.tipo == "alta" else -1
    p, m = px[e.ticker], (1 + mercado.iloc[ia + 1:ie + 1]).prod()
    return pd.Series({
        "sesiones": ie - ia,
        "exceso": s * (p.iloc[ie] / p.iloc[ia] - m),
        "despues_20": s * (p.iloc[ie + 20] / p.iloc[ie] - 1),
    })

ev = pd.concat([rb, rb.apply(evento, axis=1)], axis=1)
ev.groupby("tipo")[["sesiones", "exceso", "despues_20"]].agg(
    ["mean", "count"]).round(3)
```
```output
     sesiones       exceso       despues_20
         mean count   mean count       mean count
tipo
alta    4.741    27  0.022    27     -0.012    27
baja    5.000    20  0.040    20     -0.003    20
```

Las altas ganan en exceso un 2,2 % de media en 4,7 sesiones y las bajas, cortas, un 4,0 % en 5, frente al 3 %
plantado: con 27 y 20 eventos y una desviación típica de 3-4 puntos por evento, el error de cada media es de 0,7 y
0,9 puntos. La reversión de las 20 sesiones siguientes sale con el signo esperado (−1,2 % y −0,3 % para la posición
del evento), pero con un error de 2 puntos: con tan pocos eventos no se distingue de cero. Medir bien una ventaja de
eventos exige muchos eventos.

Para compararla con las demás, la ventaja se convierte en una cartera diaria: largo en las altas y corto en las bajas
mientras dura cada ventana, y en liquidez el resto del tiempo.

```python
pos = pd.DataFrame(0.0, index=sesiones, columns=px.columns)
for e in ev.itertuples():
    ia = sesiones.searchsorted(e.fecha_anuncio)
    ie = sesiones.searchsorted(e.fecha_efectiva)
    pos.iloc[ia + 1:ie + 1, px.columns.get_loc(e.ticker)] = (
        1 if e.tipo == "alta" else -1)
n_pos = pos.abs().sum(axis=1)
r_exc = r.sub(mercado, axis=0).fillna(0)
rebal_d = (pos * r_exc).sum(axis=1) / n_pos.where(n_pos > 0, 1)
rebal = a_mensual(rebal_d)
print(f"sesiones con posición: {(n_pos > 0).mean():.1%}")
print(f"eventos al año: {len(ev) / 20:.1f}")
perfil(rebal).round(3)
```
```output
sesiones con posición: 2.4%
eventos al año: 2.4
meses         240.000
rent_anual      0.035
vol_anual       0.046
sharpe          0.755
t               3.375
asimetria       3.223
curtosis       14.768
peor_mes       -0.046
meses_pos       0.092
dtype: float64
```

El perfil es el contrario del carry: solo el 9,2 % de los meses son positivos (casi todos los demás valen cero), unos
pocos ganan mucho y la asimetría es de +3,2. El Sharpe, de 0,75, engaña por otro lado: la cartera solo tiene posición
en el 2,4 % de las sesiones, una de cada cuarenta. Esa es la **capacidad**
de una ventaja estructural: pocas oportunidades, cortas y conocidas por todos los que leen el mismo calendario. Hay
más capital persiguiéndolas cuanto más se publica el efecto, y su precio de entrada sube.

## Información: la deriva tras resultados

La **deriva tras resultados** (PEAD, *post-earnings-announcement drift*) es la tendencia del precio a seguir moviéndose,
semanas después de la presentación, en la dirección de la sorpresa[^5]. La hipótesis es de información: el mercado
incorpora despacio una noticia pública. Predice que la deriva es mayor cuanto mayor la sorpresa y que decae con el
tiempo.

La sorpresa se tipifica con la de los trimestres anteriores (SUE: sorpresa dividida por la desviación típica de las 8
anteriores, con al menos 4). La reacción es la misma sesión si se publica antes de la apertura y la siguiente si es
tras el cierre; la cartera entra al cierre de la reacción, para no contar el salto del día, y mide las 60 sesiones
siguientes en exceso sobre el universo:

```python
res = pd.read_csv("eventos2_resultados.csv",
                  parse_dates=["periodo_fin", "fecha_anuncio"])
res = res.sort_values(["ticker", "fecha_anuncio"])
previa = res.groupby("ticker")["sorpresa"].transform(
    lambda s: s.shift(1).rolling(8, min_periods=4).std())
res["sue"] = res["sorpresa"] / previa
tras = (res["momento"] == "tras_cierre").to_numpy()
res["i"] = sesiones.searchsorted(res["fecha_anuncio"]) + tras
res = res.dropna(subset=["sue"])
res = res[res["i"] + 61 < len(sesiones)]
res["decil"] = res.groupby("periodo_fin")["sue"].transform(
    lambda s: pd.qcut(s.rank(method="first"), 10, labels=False))

log_exc = np.log1p(r_exc).cumsum().to_numpy()
j = px.columns.get_indexer(res["ticker"])
i = res["i"].to_numpy()
res["deriva_60"] = np.expm1(log_exc[i + 60, j] - log_exc[i, j])
(100 * res.groupby("decil")["deriva_60"].mean()).round(2)
```
```output
decil
0   -1.05
1   -0.20
2   -0.31
3    0.54
4   -0.53
5    0.02
6    0.29
7   -0.11
8    0.97
9    1.41
Name: deriva_60, dtype: float64
```

Los deciles extremos se separan: el de sorpresas más positivas deriva un 1,41 % al alza y el de las más negativas, un
1,05 % a la baja; la diferencia, 2,5 puntos, está dentro de los 2-3 del README.
En medio la relación es ruidosa: la deriva plantada es pequeña por unidad de sorpresa, y la volatilidad de 60
sesiones de una acción, grande. Como cartera, largo en el decil alto y corto en el bajo durante sus 60 sesiones:

```python
L = np.zeros(px.shape)
S = np.zeros(px.shape)
for ii, jj, d in zip(i, j, res["decil"]):
    if d == 9:
        L[ii + 1:ii + 61, jj] = 1
    elif d == 0:
        S[ii + 1:ii + 61, jj] = 1
R = r.fillna(0).to_numpy()
largo = (L * R).sum(1) / np.maximum(L.sum(1), 1)
corto = (S * R).sum(1) / np.maximum(S.sum(1), 1)
pead = a_mensual(pd.Series(largo - corto, index=sesiones))
print(f"eventos al año: {(res['decil'].isin([0, 9])).sum() / 20:.0f}")
perfil(pead.loc["2006-04":]).round(3)
```
```output
eventos al año: 82
meses         237.000
rent_anual      0.089
vol_anual       0.117
sharpe          0.768
t               3.412
asimetria      -0.149
curtosis        0.296
peor_mes       -0.100
meses_pos       0.595
dtype: float64
```

La cartera gana un 8,9 % anual con Sharpe de 0,77, por detrás solo del momentum (0,84, que debe una parte a las
quiebras), y es de las más diversificadas: 82 eventos al año, repartidos en fechas distintas, con una asimetría casi
nula. Su riesgo no es un crash, sino que desaparezca: si la deriva es
información que se incorpora despacio, cuanto más rápido y más vigilado está el mercado, menos deriva queda.

## El perfil de riesgo de cada ventaja

Falta el control. La cartera de quintiles de la `puntuacion_esg`, una variable sin ningún efecto:

```python
esg = car.pivot(index="fecha", columns="ticker",
                values="puntuacion_esg").where(hay)
placebo = quintiles(esg, sig_mes)

ventajas = {"carry_fx": carry, "momentum": momentum,
            "rebalanceos": rebal, "pead": pead.loc["2006-04":],
            "placebo_esg": placebo}
tabla = pd.DataFrame({k: perfil(v) for k, v in ventajas.items()}).T
tabla[["rent_anual", "vol_anual", "sharpe", "t", "asimetria",
       "curtosis", "peor_mes"]].round(2)
```
```output
             rent_anual  vol_anual  sharpe     t  asimetria  curtosis  peor_mes
carry_fx           0.04       0.11    0.41  1.87      -1.38      7.39     -0.20
momentum           0.10       0.12    0.84  3.64       0.14      0.08     -0.09
rebalanceos        0.03       0.05    0.75  3.37       3.22     14.77     -0.05
pead               0.09       0.12    0.77  3.41      -0.15      0.30     -0.10
placebo_esg        0.03       0.10    0.33  1.46      -0.42      1.31     -0.12
```

La figura de la cabecera pone las cinco en un plano: Sharpe en horizontal, asimetría de los meses en vertical. Tres
lecturas:

- **El Sharpe no ordena el riesgo.** El carry (0,41) y el placebo (0,33) tienen un Sharpe parecido; el primero esconde
  un mes de −20 % y el segundo, uno de −12 %. El del placebo no es una cola de la señal: es julio de 2020, cuando
  QXYX quiebra estando, por azar, en su quintil alto. El del carry es la prima misma. Un Sharpe de 0,4 con asimetría
  −1,4 no es el mismo producto que uno de 0,3 con asimetría −0,4.
- **El placebo pasa por una ventaja pequeña.** Gana un 3,1 % anual con t de 1,46, sin ninguna prima detrás. Sin una
  hipótesis previa, una t así no distingue nada: con veinte variables sin efecto e independientes, la probabilidad de
  que alguna dé |t| > 2 ronda el 60 % (lección 2.1.8).
- **La curtosis delata a la de eventos.** Los rebalanceos tienen la curtosis más alta (14,8): muchos meses en cero y
  unos pocos grandes. Es un perfil distinto del carry aunque las dos tengan colas: una cola a favor y otra en contra.

La comparación de las distribuciones completas, con las rentabilidades mensuales tipificadas (divididas por su
desviación típica) para que se vean en la misma escala:

```python
q = [0.01, 0.05, 0.5, 0.95, 0.99]
z = {k: (v - v.mean()) / v.std() for k, v in ventajas.items()}
cuant = pd.DataFrame({k: v.quantile(q) for k, v in z.items()}).T
cuant["min"] = [v.min() for v in z.values()]
cuant["max"] = [v.max() for v in z.values()]
cuant.round(2)
```
```output
             0.01  0.05   0.5  0.95  0.99   min   max
carry_fx    -2.69 -1.51 -0.01  1.46  1.89 -6.49  2.48
momentum    -2.09 -1.61 -0.06  1.72  2.52 -2.85  2.75
rebalanceos -1.18 -0.22 -0.22  1.84  4.39 -3.67  6.01
pead        -2.54 -1.62  0.01  1.67  2.23 -3.20  2.77
placebo_esg -2.43 -1.69  0.05  1.55  2.29 -4.49  2.58
```

*Fig. — Distribución de los meses de cada cartera, tipificados: del peor al mejor mes (línea fina), del percentil 5 al 95 (barra) y mediana (marca).* Cinco filas en escala de desviaciones típicas de −7 a +7. El carry de divisas tiene su peor mes, destacado, a −6,5 y su mejor a +2,5; el momentum va de −2,85 a +2,75; la deriva tras resultados, de −3,2 a +2,8; los rebalanceos, de −3,7 a +6,0, con la barra del 5 al 95 % entre −0,2 y +1,8; el placebo ESG, de −4,5 (el mes de una quiebra en su lado largo) a +2,6.

El peor mes del carry está a 6,5 desviaciones típicas por debajo de su media; el de la deriva tras resultados, a 3,2,
y el del momentum, a 2,85. El placebo tiene uno a 4,5: el de la quiebra de QXYX. Una cartera sin ventaja también
cobra o paga los deslistados que le tocan, y quitarlos de los datos la haría parecer más limpia de lo que es. En una normal, un mes a −6,5σ tiene una probabilidad de $4 \times 10^{-11}$: menos de una vez en mil
millones de años. En el carry de estos datos ha ocurrido una vez en 21. Los rebalanceos son la imagen especular: su
cola larga está a la derecha (+6,0σ). Una regla de sizing que use solo la volatilidad (lección 2.2.2) asigna el mismo riesgo a
las dos y se equivoca con la primera.

La última dimensión es el **tamaño**. Un Sharpe alto sobre poco volumen negociable no admite capital. Con el volumen
medio de 21 sesiones en dólares de lo que negocia cada cartera de acciones:

```python
car["adv_musd"] = car["volumen_medio_21d"] * car["cierre"] / 1e6
adv = car.pivot(index="fecha", columns="ticker", values="adv_musd")

def adv_eventos(fechas, tickers):
    """ADV del último fin de mes anterior a cada evento."""
    previo = adv.reindex(fechas, method="ffill")
    return np.nanmedian([previo.iloc[k][t]
                      for k, t in enumerate(tickers)])

rk = mom.rank(axis=1, pct=True)
ext = res[res["decil"].isin([0, 9])]
liq = pd.DataFrame({
    "apuestas_año": [12 * ((rk > 0.8) | (rk <= 0.2)).sum(axis=1)
                     .mean(), len(ev) / 20, len(ext) / 20],
    "sesiones": [21, ev["sesiones"].mean(), 60],
    "adv_musd": [adv[(rk > 0.8) | (rk <= 0.2)].stack().median(),
                 adv_eventos(ev["fecha_anuncio"], ev["ticker"]),
                 adv_eventos(ext["fecha_anuncio"], ext["ticker"])],
}, index=["momentum", "rebalanceos", "pead"])
liq.round(1)
```
```output
             apuestas_año  sesiones  adv_musd
momentum            491.8      21.0     111.0
rebalanceos           2.4       4.9      20.0
pead                 82.5      60.0     118.7
```

El momentum reparte su riesgo entre unas 41 acciones cada mes (492 posiciones al año, aunque solapadas y no
independientes) con un volumen mediano de 111 millones de USD al día. La deriva tras resultados abre 82 posiciones al
año en acciones parecidas (119 millones). Los rebalanceos, 2,4 al año en acciones cinco veces menos líquidas (20
millones): las que entran en el índice vienen de fuera, de entre las pequeñas. Cuánto capital admite cada ventaja con
estas cifras es el tema de la lección 2.2.3.

## Cómo escribir una hipótesis de ventaja

Antes de abrir un backtest, la hipótesis se escribe. Una plantilla que obliga a contestar lo importante:

```mermaid
flowchart TB
  M[Mecanismo] --> C[Contraparte]
  C --> P[Predicciones]
  P --> T[Prueba y control]
  T --> F[Cuándo falla]
  F --> M
```

| Campo | Ejemplo: carry de divisas |
|---|---|
| Mecanismo | Prima por soportar el riesgo de crash de las divisas de tipo alto |
| Contraparte | Quien se cubre o no quiere ese riesgo; quien se endeuda en la divisa de tipo alto |
| Por qué persiste | Arbitrarlo exige soportar el mismo crash; el capital se retira justo cuando más rinde |
| Predicciones | Ganancia media positiva; asimetría negativa; pérdidas concentradas en las crisis; mayor con diferenciales mayores |
| Prueba | Cartera larga-corta por diferencial, rentabilidad dentro y fuera de los tramos de estrés |
| Control | Misma cartera con una variable sin relación (placebo) o con los diferenciales barajados |
| Cuándo falla | Crisis de liquidez; convergencia de tipos (sin diferencial no hay prima) |
| Capacidad | Mercados de divisas muy líquidos; el límite lo pone el riesgo de cola, no el volumen |

La plantilla tiene dos usos. El primero es filtrar antes de probar: una idea que no tiene contraparte plausible ni
razón para persistir no merece las horas de backtest, y cada prueba que se ahorra es una prueba menos en la cuenta de
las pruebas múltiples (lección 2.2.4). El segundo es leer el resultado: si la hipótesis predice asimetría negativa y
el backtest sale con asimetría positiva, algo no cuadra, aunque el Sharpe sea bonito.

<aside class="callout" data-kind="riesgo">

Las cifras de esta lección salen de datos sintéticos con efectos plantados. En un mercado real las ventajas
publicadas suelen menguar después de publicarse, los costes se comen una parte grande de las de alta rotación y
cualquier backtest sobrevalora lo que se obtendrá en real. Ninguna cifra de aquí es una expectativa de rentabilidad.

</aside>

## Ejercicios

1. Repite la cartera de carry con dos divisas por lado en lugar de tres. ¿Sube o baja la rentabilidad? ¿Y la
   asimetría y el peor mes? Explica el resultado con la hipótesis de prima de riesgo.

**Solución.**

```python
peso2 = (rango >= 9).astype(float) / 2 - (rango <= 2) / 2
carry2_d = (peso2.reindex(r_fx.index).ffill().shift(1)
            * r_fx).sum(axis=1).loc["2005-02":]
pd.DataFrame({"3 por lado": perfil(carry),
              "2 por lado": perfil(a_mensual(carry2_d))}).round(3)
```
```output
            3 por lado  2 por lado
meses          251.000     251.000
rent_anual       0.044       0.055
vol_anual        0.108       0.139
sharpe           0.409       0.393
t                1.871       1.798
asimetria       -1.384      -1.011
curtosis         7.389       5.388
peor_mes        -0.199      -0.238
meses_pos        0.574       0.542
```

Con dos por lado la cartera se concentra en los extremos del diferencial. La rentabilidad sube del 4,4 % al 5,5 % y
el peor mes empeora del −19,9 % al −23,8 %: más diferencial, más prima y más crash, como predice la hipótesis de
prima de riesgo. La volatilidad sube más que la rentabilidad (Sharpe de 0,39 frente a 0,41) y la asimetría es menos
negativa (−1,01): con dos divisas por lado, la volatilidad de cada una pesa más en los meses normales y diluye la
cola en términos relativos. La predicción de la hipótesis se cumple en la rentabilidad y en la pérdida máxima; la
asimetría, como cociente, depende también del ruido de la cartera.

2. Mide la deriva tras resultados por separado en la primera mitad de la muestra (hasta 2015) y en la segunda. Si la
   ventaja fuera información que el mercado aprende a descontar, ¿qué esperarías? ¿Qué encuentras en estos datos y por
   qué?

**Solución.**

```python
mitades = {"2006-2015": pead.loc["2006-04":"2015-12"],
           "2016-2025": pead.loc["2016-01":]}
pd.DataFrame({k: perfil(v) for k, v in mitades.items()}).T[
    ["rent_anual", "sharpe", "t"]].round(2)
```
```output
           rent_anual  sharpe     t
2006-2015        0.07    0.56  1.75
2016-2025        0.11    0.99  3.13
```

Si fuera una ineficiencia que se arbitra, la segunda mitad debería ser más débil. Aquí sale al revés: Sharpe de 0,56
hasta 2015 y de 0,99 después. En estos datos la deriva está plantada con el mismo coeficiente en toda la muestra, así
que la diferencia es ruido de muestreo: con diez años por mitad, el error típico de cada Sharpe es de unos 0,3 y el de
la diferencia, de unos 0,45. Con datos reales, una caída entre mitades sería
compatible con la hipótesis de información, pero tampoco la probaría por sí sola.

3. Escribe la hipótesis de ventaja del momentum con la plantilla de la lección. ¿Qué predicción la distinguiría de una
   explicación de riesgo puro?

**Solución.**

Mecanismo: infrarreacción a las noticias (comportamiento) o prima por el riesgo de crash en los rebotes (riesgo).
Contraparte: quien vende pronto lo que gana y mantiene lo que pierde, o quien no quiere el riesgo de rebote.
Predicciones comunes: rentabilidad media positiva de la cartera 12-1 y crashes tras los suelos de mercado. Una
predicción que las separa: si es infrarreacción, la ventaja debe ser mayor en acciones poco seguidas (pequeñas, con
pocos analistas) y en noticias graduales, y menguar con el tiempo; si es prima de riesgo, debe estar ligada a la
exposición a esos rebotes y no a la cobertura de analistas. Control: la misma cartera con una señal placebo y con la
rentabilidad del último mes (que el 12-1 se salta a propósito).

[^1]: Brunnermeier, Nagel y Pedersen (2008), en los recursos, documentan la asimetría negativa del carry y la
atribuyen a que las posiciones se deshacen de golpe cuando caen el apetito por el riesgo y la liquidez de financiación.

[^2]: Jegadeesh y Titman (1993), en los recursos, es el estudio clásico de las carteras de ganadoras y perdedoras.

[^3]: Daniel y Moskowitz (2016), en los recursos, estudian los crashes del momentum en los rebotes tras las caídas.

[^4]: Shleifer (1986), en los recursos, usa las inclusiones en el S&P 500 para contrastar si la demanda de acciones
tiene pendiente negativa.

[^5]: Bernard y Thomas (1989), en los recursos, contrastan si la deriva es una respuesta retardada o una prima de
riesgo.

## Autoevaluación

1. Un backtest encuentra que comprar las acciones que suben el martes da beneficio. ¿Qué le falta para ser una hipótesis de ventaja?

   - A. Más años de datos
   - B. Un mecanismo: quién está al otro lado, por qué acepta perder y por qué no lo ha corregido nadie; sin eso es un patrón, no una hipótesis
   - C. Un Sharpe mayor que 1
   - D. Probarlo en otro mercado

   *Respuesta B: Un mecanismo: quién está al otro lado, por qué acepta perder y por qué no lo ha corregido nadie; sin eso es un patrón, no una hipótesis.* Más datos y otros mercados ayudan a contrastarla, pero primero hay que tener algo que contrastar. Una hipótesis explica de dónde sale el dinero y predice cosas que se pueden comprobar (en qué activos, en qué épocas y con qué riesgo debe aparecer).

2. El carry de divisas de la lección tiene Sharpe positivo y una asimetría mensual muy negativa. ¿Qué encaja mejor con ese perfil?

   - A. Un sesgo de comportamiento que el mercado tardará en corregir
   - B. Una prima de riesgo: cobra un poco casi todos los meses a cambio de pérdidas grandes en las crisis, cuando el dinero vale más
   - C. Un error de los datos
   - D. Una ventaja de información

   *Respuesta B: Una prima de riesgo: cobra un poco casi todos los meses a cambio de pérdidas grandes en las crisis, cuando el dinero vale más.* Ganar poco y a menudo y perder mucho de golpe, justo en las crisis, es el perfil de quien vende un seguro. Esa es la explicación clásica del carry (Brunnermeier, Nagel y Pedersen, 2008), y en los datos del nivel está plantada así: los crashes de las divisas de tipo alto caen en los tramos de estrés.

3. ¿Por qué la ventaja de los rebalanceos de índice tiene poca capacidad aunque su rentabilidad por evento sea alta?

   - A. Porque es un efecto falso
   - B. Porque hay pocos eventos al año, cada uno dura unas pocas sesiones y en esas sesiones compiten por las mismas acciones todos los que conocen el calendario
   - C. Porque las acciones del índice son ilíquidas
   - D. Porque la rentabilidad es negativa

   *Respuesta B: Porque hay pocos eventos al año, cada uno dura unas pocas sesiones y en esas sesiones compiten por las mismas acciones todos los que conocen el calendario.* En los datos hay 47 eventos con efecto en 20 años, unos 2,4 al año, de unas 5 sesiones cada uno. Aunque cada evento rinda entre un 2 % y un 4 %, el capital que se puede desplegar está limitado por el volumen de esas pocas sesiones. Es una ventaja estructural: existe porque los fondos indexados tienen que operar en esas fechas.

4. La puntuación ESG de los datos es un placebo sin efecto y su cartera larga-corta gana un 3 % anual con t ≈ 1,5. ¿Qué enseña?

   - A. Que el ESG funciona
   - B. Que una cartera sin ninguna ventaja puede dar cifras de apariencia razonable por azar; sin un mecanismo y sin corregir por el número de pruebas, no se distingue de una ventaja pequeña
   - C. Que los datos están mal
   - D. Que hay que quitar el ESG de los datos

   *Respuesta B: Que una cartera sin ninguna ventaja puede dar cifras de apariencia razonable por azar; sin un mecanismo y sin corregir por el número de pruebas, no se distingue de una ventaja pequeña.* Con 20 años de datos mensuales, el error típico de la media anual de una cartera con casi un 10 % de volatilidad es de unos 2 puntos. Una t de 1,5 está dentro de lo que da el azar; con muchas variables probadas, aparecerán varias así (lección 2.1.8).

5. ¿Qué distingue una ventaja de información, como la deriva tras resultados, de una prima de riesgo?

   - A. Que la de información no tiene riesgo
   - B. Que existe porque el precio incorpora la noticia despacio, así que debería menguar cuando hay más gente que la explota o la información llega más rápido; una prima de riesgo es la compensación por soportar algo que nadie quiere y puede durar
   - C. Que la de información solo funciona en cripto
   - D. Ninguna: son lo mismo

   *Respuesta B: Que existe porque el precio incorpora la noticia despacio, así que debería menguar cuando hay más gente que la explota o la información llega más rápido; una prima de riesgo es la compensación por soportar algo que nadie quiere y puede durar.* La hipótesis predice cosas distintas. Si la ventaja es una prima, persiste pero con crashes; si es una ineficiencia (comportamiento o información lenta), debería ser mayor donde arbitrar cuesta más (acciones pequeñas, poco seguidas) y menguar con el tiempo.

## Ideas clave

- Una ventaja es una hipótesis con mecanismo: quién está al otro lado, por qué acepta perder y por qué no lo ha arbitrado nadie. Un patrón sin mecanismo es candidato a falso positivo.
- Cuatro fuentes: prima por soportar un riesgo, sesgo de comportamiento de otros participantes, restricción estructural que obliga a alguien a operar, y difusión lenta de la información.
- Cada fuente deja un perfil: el carry de los datos gana un 4,4 % anual con asimetría mensual de −1,4 y su peor mes cae un 19,9 %; la deriva tras resultados tiene un Sharpe de 0,77 y una asimetría casi nula.
- El rendimiento no basta: la capacidad (apuestas al año, liquidez y duración) decide cuánto capital admite. Los rebalanceos rinden entre un 2 % y un 4 % por evento, pero hay 2,4 al año.
- El placebo ESG gana un 3,1 % anual (t = 1,46) sin efecto alguno. Los datos del nivel son sintéticos y sus efectos están plantados: enseñan a medir, no prueban que nada funcione en un mercado real.

## Recursos

### Research

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

### Libros

- [Brunnermeier, Nagel y Pedersen (2008): Carry Trades and Currency Crashes. NBER Macroeconomics Annual 23, 313-348](https://doi.org/10.1086/593088) · doi.org
- [Jegadeesh y Titman (1993): Returns to Buying Winners and Selling Losers. Journal of Finance 48(1), 65-91](https://doi.org/10.1111/j.1540-6261.1993.tb04702.x) · doi.org
- [Daniel y Moskowitz (2016): Momentum crashes. Journal of Financial Economics 122(2), 221-247](https://doi.org/10.1016/j.jfineco.2015.12.002) · doi.org
- [Bernard y Thomas (1989): Post-Earnings-Announcement Drift: Delayed Price Response or Risk Premium? Journal of Accounting Research 27](https://doi.org/10.2307/2491062) · doi.org
- [Shleifer (1986): Do Demand Curves for Stocks Slope Down? Journal of Finance 41(3), 579-590](https://doi.org/10.1111/j.1540-6261.1986.tb04518.x) · doi.org

### Datos

- [Datos del nivel 2: divisas con carry (Parquet)](https://pythonparatrading.com/curso/datos/nivel-2/fx2_carry.parquet) · Descarga · fx2_carry.parquet

Lección anterior: [2.1.8 · El problema de las pruebas múltiples: Bonferroni, FDR y el «factor zoo»](https://pythonparatrading.com/curso-python-trading/nivel-2/pruebas-multiples-bonferroni-fdr-y-factor-zoo/)

Lección siguiente: [2.2.2 · Universo, señal, filtro, entrada, salida, sizing y gestión del riesgo](https://pythonparatrading.com/curso-python-trading/nivel-2/universo-senal-filtro-entrada-salida-y-sizing/)
