---
title: "2.5.5 Monte Carlo: trades, bloques y permutaciones"
description: "Remuestreo de operaciones, bootstrap de bloques y test de permutación de la señal sobre una cartera de tendencia con efecto plantado y sobre su control."
url: "https://pythonparatrading.com/curso-python-trading/nivel-2/monte-carlo-bootstrap-y-permutaciones/"
lang: "es"
image: "https://pythonparatrading.com/og/curso-python-trading/nivel-2/monte-carlo-bootstrap-y-permutaciones.png"
---

# 2.5.5 · Monte Carlo: remuestreo de trades, bootstrap de bloques y permutaciones de señales

> Un backtest produce una sola curva de capital, y esa curva es una muestra de tamaño uno. Esta lección genera miles de alternativas plausibles de tres formas distintas, cada una con su pregunta: qué drawdown cabe esperar, cuánto se fía uno del Sharpe y si la señal acierta el momento o solo recoge la exposición.

- Curso: [Nivel 2: Estrategias sistemáticas y backtesting riguroso](https://pythonparatrading.com/curso-python-trading/nivel-2/)
- Módulo: 2.5 · Sesgos y validación
- Duración: 70 min
- Requisitos: [2.1.1 · Contrastes de hipótesis, p-valores, bootstrap y regresión con errores robustos](https://pythonparatrading.com/curso-python-trading/nivel-2/contrastes-bootstrap-y-errores-robustos/), [2.3.1 · Seguimiento de tendencia y momentum](https://pythonparatrading.com/curso-python-trading/nivel-2/seguimiento-de-tendencia-y-momentum/), [2.5.4 · Deflated Sharpe Ratio y Probability of Backtest Overfitting (PBO)](https://pythonparatrading.com/curso-python-trading/nivel-2/deflated-sharpe-ratio-y-pbo/)

## Al terminar esta lección

- Remuestrear y desordenar las operaciones de una estrategia para estimar la distribución del drawdown y el riesgo de ruina según el apalancamiento.
- Aplicar el bootstrap estacionario a los rendimientos diarios, elegir la longitud de bloque (Politis y White) y construir un abanico de capital.
- Programar un test de permutación de la señal que conserve los rendimientos y la escala de riesgo, y calcular su p-valor.
- Comprobar cada método donde hay un efecto plantado y en controles sin efecto, y reconocer lo que ninguno corrige: la selección entre muchas estrategias.

## Una curva de capital es una muestra de tamaño uno

Un backtest devuelve una curva de capital, un Sharpe y un drawdown máximo. Son cifras de **una** historia: la que
ocurrió. Si el mercado hubiera repartido los mismos días buenos y malos en otro orden, o la estrategia hubiera
cerrado sus operaciones en otra secuencia, el drawdown sería otro. La lección 2.1.1 presentó el bootstrap para
estimar la incertidumbre de un estadístico; aquí se aplica a la pregunta práctica de un sistema de trading: qué
parte del resultado es estructura y qué parte es azar del orden.

«Monte Carlo» en este contexto agrupa tres familias de remuestreo. Cada una parte de un objeto distinto del backtest
y responde a una pregunta distinta:

```mermaid
flowchart TB
  B[Backtest] --> O[Operaciones]
  B --> R[Rendimientos]
  B --> S[Señal y mercado]
  O --> T[Remuestreo trades]
  R --> K[Bootstrap bloques]
  S --> P[Permutación]
  T --> T2[Drawdown y ruina]
  K --> K2[IC y abanico]
  P --> P2[p-valor timing]
```

| Método | Qué remuestrea | Qué conserva | Qué rompe | Pregunta |
|---|---|---|---|---|
| Desordenar trades | El orden de las operaciones | Cada operación y el resultado final | El agrupamiento temporal de las pérdidas | ¿Qué drawdown cabe con estas operaciones? |
| Remuestrear trades | Operaciones con reemplazo | La distribución de una operación | El orden y el solapamiento | ¿Qué resultado y qué ruina cabe esperar? |
| Bootstrap de bloques | Tramos de rendimientos diarios | Dependencia dentro de cada bloque | La dependencia entre bloques | ¿Cuánto me fío del Sharpe y del drawdown? |
| Permutar la señal | La dirección respecto a los rendimientos | Rendimientos, escala de riesgo, exposición media | La relación señal-rendimiento | ¿Acierta el momento o solo la exposición? |

Ninguno de los tres sabe cuántas estrategias se probaron antes de quedarse con esta. Esa pregunta, la del sesgo de
selección, es la de la lección 2.5.4 y vuelve al final de esta.

## La estrategia y su control

La estrategia es un **momentum de series temporales** clásico sobre los 24 futuros sintéticos del nivel 2: en cada
mercado, largo si la rentabilidad de las últimas 252 sesiones es positiva y corto si es negativa, con una posición
escalada por la inversa de la volatilidad (EWMA de 60 sesiones) para que cada mercado aporte un riesgo parecido. La
señal se calcula al cierre de cada sesión y se opera en la siguiente. La lección 2.3.1 la construye paso a paso; la
referencia empírica en mercados reales es Moskowitz, Ooi y Pedersen (2012)[^1].

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

Los datos son sintéticos y el efecto está **plantado**. El README del nivel 2 documenta la respuesta conocida: el
rendimiento esperado de cada futuro incluye `0,15·σ·signo(rendimiento de las 252 sesiones anteriores)`, un Sharpe de
momentum de unos 0,15 por mercado, más una parte del carry, la prima de la renta variable en los índices y la caída de
los bonos en 2022. Que la estrategia gane aquí
no dice nada de los mercados reales: sirve para comprobar si cada método **recupera** un efecto que se sabe que
existe y si **no se lo inventa** donde no existe.

</aside>

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

def sharpe(x):
    return np.mean(x) / np.std(x, ddof=1) * np.sqrt(252)

def tendencia(px, L=252, riesgo=0.30):
    """Signo de la rentabilidad de L sesiones, escalado por vol."""
    r = px.pct_change()
    vol = r.ewm(span=60, min_periods=60).std() * np.sqrt(252)
    signo = np.sign(px.pct_change(L))       # se conoce al cierre de t
    escala = riesgo / vol / px.shape[1]     # riesgo por mercado
    pnl = (signo * escala).shift(1) * r     # se opera en t + 1
    return signo, escala, r, pnl.loc["2001-01-02":]

fut = pd.read_parquet("futuros2_continuos.parquet")
px = fut.pivot(index="fecha", columns="mercado",
               values="cierre_ajustado")
signo, escala, r, pnl = tendencia(px)
cartera = pnl.sum(axis=1)
print(f"Sesiones: {len(cartera)}")
print(f"Volatilidad anual: {cartera.std() * np.sqrt(252):.1%}")
print(f"Sharpe: {sharpe(cartera):.2f}")
```
```output
Sesiones: 6287
Volatilidad anual: 9.0%
Sharpe: 1.00
```

Veinticinco años, una volatilidad del 9,0 % y un Sharpe de 1,00, sin costes (la lección 2.4.7 los añade; aquí no
cambian el método). El 1,00 es mayor que el 0,15 por mercado del README por dos razones: la diversificación entre 24
mercados poco correlados y lo que la regla captura de paso, sobre todo el carry (el README mide una correlación de 0,50
entre el carry y el signo de 252 sesiones) y la prima de los índices. El README da 0,98 para la misma regla con otra
ponderación (10 % entre la volatilidad de 63 sesiones): la cifra no depende de ese detalle.

Para cada método hace falta un **control**: algo con la misma forma que la estrategia y sin efecto. El más directo es
un gemelo de los propios futuros: los mismos rendimientos diarios, las mismas filas (cada sesión conserva los 24
mercados juntos, y con ellos sus correlaciones), pero en un orden de sesiones barajado. Así se conservan la
distribución de cada día y la estructura entre mercados y se destruye cualquier memoria temporal: en el gemelo, una
regla de tendencia no puede tener ventaja.

```python
rng = np.random.default_rng(2028)
orden = rng.permutation(len(r) - 1)
r_g = pd.DataFrame(r.to_numpy()[1:][orden], index=r.index[1:],
                   columns=r.columns)
px_g = (1 + r_g).cumprod()                  # mismos días, otro orden
signo_g, escala_g, r_g, pnl_g = tendencia(px_g)
gemelo = pnl_g.sum(axis=1)
iu = np.triu_indices(24, 1)
print(f"Correlación media entre mercados: real "
      f"{r.corr().to_numpy()[iu].mean():.3f}, "
      f"gemelo {r_g.corr().to_numpy()[iu].mean():.3f}")
print(f"Sharpe de la tendencia en el gemelo: {sharpe(gemelo):.2f}")
```
```output
Correlación media entre mercados: real 0.081, gemelo 0.081
Sharpe de la tendencia en el gemelo: 0.03
```

La misma correlación media entre mercados (0,081) y un Sharpe de 0,03: lo esperable en una serie sin memoria. Más
abajo se usa también el control que trae el propio README, la serie `ruptura` de `regimenes2.parquet`.

## Remuestreo de operaciones: drawdown y riesgo de ruina

Es la forma de Monte Carlo más extendida entre los traders: tomar la lista de operaciones cerradas y generar
secuencias alternativas. Hay dos variantes. **Desordenar** (permutar sin reemplazo) conserva exactamente las mismas
operaciones, y por tanto el mismo resultado final, y solo cambia el orden: sirve para ver qué drawdown podría haber
salido con las mismas operaciones. **Remuestrear** (con reemplazo) cambia también qué operaciones salen y cuántas veces:
sirve para la distribución del resultado y del riesgo de ruina.

En una estrategia de tendencia con señal diaria, una operación es una racha de sesiones con el mismo signo en un
mercado:

```python
def operaciones(signo, pnl):
    """Una operación: racha de sesiones con el mismo signo."""
    filas = []
    for m in pnl.columns:
        s = signo[m].shift(1).loc[pnl.index]
        racha = (s != s.shift()).cumsum()
        g = pnl[m].groupby(racha)
        fechas = pnl.index.to_series().groupby(racha)
        filas.append(pd.DataFrame({
            "mercado": m, "salida": fechas.last(),
            "sesiones": g.size(), "pnl": g.sum()}))
    return (pd.concat(filas).sort_values("salida", kind="stable")
            .reset_index(drop=True))

ops = operaciones(signo, pnl)
print(f"{len(ops)} operaciones; ganadoras: "
      f"{(ops['pnl'] > 0).mean():.0%}")
print("Sesiones por operación:",
      ops["sesiones"].quantile([0.25, 0.5, 0.75, 0.99])
      .round().astype(int).to_dict())
(100 * ops["pnl"]).quantile([0.01, 0.1, 0.5, 0.9, 0.99]).round(3)
```
```output
3229 operaciones; ganadoras: 32%
Sesiones por operación: {0.25: 1, 0.5: 4, 0.75: 16, 0.99: 631}
0.01   -0.827
0.10   -0.254
0.50   -0.050
0.90    0.263
0.99    3.477
Name: pnl, dtype: float64
```

El perfil es el típico de una tendencia: 3.229 operaciones, solo un 32 % ganadoras, la mitad dura 4 sesiones o menos
(la señal cambia de signo varias veces cuando la rentabilidad de 12 meses ronda el cero) y unas pocas duran años. La
operación mediana pierde un 0,050 % del capital; la del percentil 99 gana un 3,48 %. El resultado lo hacen las pocas
operaciones largas, y por eso el orden en que llegan las pérdidas pequeñas importa tanto para el drawdown.

```python
rng = np.random.default_rng(7)
x = ops["pnl"].to_numpy()
N = 2000

def dd_max(m):
    """Drawdown máximo de cada fila de una matriz de resultados."""
    eq = np.cumprod(1 + m, axis=-1)
    pico = np.maximum.accumulate(eq, axis=-1)
    return (eq / pico - 1).min(axis=-1)

dd_hist = dd_max(x)
dd_perm = dd_max(rng.permuted(np.tile(x, (N, 1)), axis=1))
dd_boot = dd_max(rng.choice(x, size=(N, len(x))))

q = [0.05, 0.5, 0.95]
print(f"Drawdown de la secuencia real: {dd_hist:.1%}")
print(f"Desordenados con un drawdown peor: "
      f"{(dd_perm <= dd_hist).mean():.1%}")
pd.DataFrame({
    "desordenar": np.quantile(dd_perm, q),
    "remuestrear": np.quantile(dd_boot, q)},
    index=["5 %", "mediana", "95 %"]).mul(100).round(1)
```
```output
Drawdown de la secuencia real: -21.9%
Desordenados con un drawdown peor: 0.0%
         desordenar  remuestrear
5 %           -15.0        -15.3
mediana       -10.0        -10.1
95 %           -7.4         -7.2
```

`rng.permuted(..., axis=1)` desordena cada fila por separado: 2.000 órdenes distintos de las mismas operaciones en una
sola llamada. El resultado es incómodo. Con las operaciones en orden aleatorio, el drawdown mediano es del −10,0 % y el
del percentil 5, del −15,0 %. La secuencia real tuvo un −21,9 %: **peor que todas** las secuencias desordenadas.

*Fig. — Drawdown máximo de 2.000 órdenes al azar de las 3.229 operaciones de la cartera de tendencia, frente al de la secuencia real.* Histograma de drawdowns máximos en barras de un punto, entre −22 % y −5 %, con el centro hacia −10 %. La línea destacada marca el drawdown de la secuencia real de operaciones, −21,9 %: ninguna de las 2.000 órdenes al azar es peor. Las pérdidas reales llegaron más juntas de lo que supone desordenar.

No es mala suerte. Desordenar supone que las operaciones son intercambiables, y en una cartera de 24 mercados no lo
son: se solapan en el tiempo, y cuando el mercado da la vuelta (un final de tendencia, una crisis) varios mercados
pierden a la vez. La celda siguiente lo mide:

```python
eq = np.cumprod(1 + x)
fondo = int(np.argmin(eq / np.maximum.accumulate(eq)))
pico = int(np.argmax(eq[:fondo + 1]))
print(f"Peor tramo: {ops['salida'][pico]:%Y-%m-%d} a "
      f"{ops['salida'][fondo]:%Y-%m-%d}, {fondo - pico} operaciones")

mes = ops["salida"].dt.to_period("M").to_numpy()
real = pd.Series(x).groupby(mes).sum().std()
barajado = np.mean([pd.Series(rng.permutation(x)).groupby(mes)
                    .sum().std() for _ in range(200)])
print(f"Desviación típica del resultado mensual: real {real:.2%}, "
      f"barajado {barajado:.2%}")
```
```output
Peor tramo: 2001-01-02 a 2003-01-16, 378 operaciones
Desviación típica del resultado mensual: real 3.30%, barajado 2.52%
```

Con las operaciones en su mes real, el resultado mensual tiene una desviación típica del 3,30 %; con las mismas
operaciones repartidas al azar entre los mismos meses, del 2,52 %. Las ganancias y las pérdidas se agrupan en el
tiempo, y el peor tramo de la secuencia real, de enero de 2001 a enero de 2003, encadena 378 operaciones.

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

El remuestreo de operaciones que ofrecen muchas plataformas trata cada operación como independiente. En una cartera
con posiciones simultáneas eso **infravalora** el drawdown, a veces mucho. Úsalo como suelo del riesgo, nunca como
estimación central, y compáralo siempre con el drawdown de la secuencia real: si esta queda en la cola, el supuesto
de independencia no se cumple.

</aside>

Con esa reserva, el remuestreo sirve para una pregunta que el backtest no contesta: **qué apalancamiento soporta** la
estrategia. Si se define la ruina como un drawdown del 20 % o del 40 % (el punto en que un gestor cerraría la
estrategia o un cliente retiraría el dinero), basta con multiplicar las operaciones por el apalancamiento:

```python
filas = {}
for apal in (1, 2, 3, 4):
    dd = dd_max(apal * rng.choice(x, size=(N, len(x))))
    filas[f"x{apal}"] = {
        "dd_mediano": np.median(dd),
        "P(dd < -20 %)": (dd < -0.20).mean(),
        "P(dd < -40 %)": (dd < -0.40).mean()}
pd.DataFrame(filas).T.round(3)
```
```output
    dd_mediano  P(dd < -20 %)  P(dd < -40 %)
x1      -0.101          0.005          0.000
x2      -0.192          0.428          0.000
x3      -0.278          0.952          0.046
x4      -0.359          1.000          0.289
```

Sin apalancar, la probabilidad de pasar por un drawdown del 20 % en 25 años es del 0,5 %; con el doble de tamaño, del
43 %; con el triple, del 95 %. La de llegar al 40 % es nula con el doble (ninguno de 2.000 caminos), del 4,6 % con el
triple y del 29 % con el cuádruple. El drawdown mediano crece algo menos que proporcionalmente (−10,1 %, −19,2 %,
−27,8 % y −35,9 %), porque en un capital compuesto cada pérdida se aplica sobre un capital ya menor; la probabilidad
de cruzar un umbral fijo, en cambio, se dispara. Y todas estas cifras heredan el sesgo optimista de la celda
anterior: la secuencia real ya tuvo un −21,9 % sin apalancar.

## Bootstrap de bloques sobre los rendimientos diarios

El bootstrap de los rendimientos diarios evita el problema del solapamiento: cada día ya contiene todas las
posiciones abiertas, con su correlación. Pero el bootstrap clásico de Efron (sacar días sueltos con reemplazo)
supone que los días son independientes, y los rendimientos de una cartera no lo son: su volatilidad viene en rachas
(lección 1.6.2). La solución es remuestrear **bloques** de días consecutivos. Künsch (1989) propuso bloques de
longitud fija; el **bootstrap circular** une el final de la serie con el principio para que todos los días tengan la
misma probabilidad de aparecer, y el **bootstrap estacionario** de Politis y Romano (1994) usa bloques de longitud
aleatoria, geométrica de media $b$:

$$
P(L = k) = \frac{1}{b}\left(1 - \frac{1}{b}\right)^{k-1}, \qquad E[L] = b
$$

Con bloques de longitud aleatoria, la serie remuestreada es estacionaria, cosa que no garantizan los bloques fijos.
El mecanismo cabe en un bucle:

```python
def indices_estacionario(n, largo, b, rng):
    """Índices de un remuestreo estacionario (Politis y Romano).

    Cada paso sigue al día siguiente con probabilidad 1 - 1/b o
    salta a un día al azar con probabilidad 1/b: bloques de
    longitud geométrica de media b, con vuelta circular."""
    idx = np.empty(largo, dtype=np.int64)
    idx[0] = rng.integers(n)
    salto = rng.random(largo) < 1 / b
    nuevo = rng.integers(n, size=largo)
    for t in range(1, largo):
        idx[t] = nuevo[t] if salto[t] else (idx[t - 1] + 1) % n
    return idx

rng = np.random.default_rng(11)
print(indices_estacionario(len(cartera), 16, 5, rng))
```
```output
[ 841  842 3221 5917 5918 5275 3452 3453 3454 3455 3456 3457 3458 5179
 5180 5181]
```

Se ven los bloques: 841-842, un día suelto, 5917-5918, otro suelto, siete días seguidos desde el 3452 y otros tres al
final, con media 5 porque `b=5`. La librería `arch` tiene la versión eficiente (`StationaryBootstrap`,
`CircularBlockBootstrap`) y el selector automático de **longitud de bloque** de Politis y White (2004), con la
corrección de Patton, Politis y White (2009), que estima la $b$ óptima a partir de la autocorrelación de la serie:

```python
from arch.bootstrap import optimal_block_length

pd.concat({"r": optimal_block_length(cartera),
           "r²": optimal_block_length(cartera ** 2)}).round(1)
```
```output
         stationary  circular
r  None         1.2       1.4
r² None        47.6      54.5
```

Los rendimientos casi no tienen autocorrelación, y el selector pide bloques de 1 sesión. Sus cuadrados, que miden la
volatilidad, piden 48 (estacionario) o 55 (circular). La elección depende de qué estadístico se quiere: la media
depende de los rendimientos; el drawdown, de las rachas de volatilidad. Lo prudente es mirar varias longitudes:

```python
from arch.bootstrap import StationaryBootstrap

def drawdown(x):
    eq = np.cumprod(1 + np.asarray(x))
    return (eq / np.maximum.accumulate(eq) - 1).min()

def stats(x):
    return np.array([sharpe(x), drawdown(x)])

print(f"Histórico: Sharpe {sharpe(cartera):.2f}, "
      f"drawdown {drawdown(cartera):.1%}")
filas = {}
for b in (1, 5, 20, 75, 250):
    bs = StationaryBootstrap(b, cartera.to_numpy(), seed=3)
    v = bs.apply(stats, 1000)
    filas[b] = {"sharpe_2.5": np.quantile(v[:, 0], 0.025),
                "sharpe_97.5": np.quantile(v[:, 0], 0.975),
                "dd_mediano": np.median(v[:, 1]),
                "dd_5 %": np.quantile(v[:, 1], 0.05)}
pd.DataFrame(filas).T.rename_axis("bloque").round(3)
```
```output
Histórico: Sharpe 1.00, drawdown -14.7%
        sharpe_2.5  sharpe_97.5  dd_mediano  dd_5 %
bloque
1            0.591        1.391      -0.179  -0.264
5            0.613        1.374      -0.178  -0.270
20           0.630        1.380      -0.178  -0.265
75           0.600        1.399      -0.177  -0.262
250          0.661        1.326      -0.169  -0.235
```

Tres lecturas. La primera: el intervalo del 95 % del Sharpe va de 0,6 a 1,4 con cualquier bloque. Se parece a la
aproximación de Lo (2002)[^2] para rendimientos i.i.d., $\sigma(\widehat{SR}) \approx \sqrt{(1 + SR^2/2)/T}$ con
$T$ en años: con 1,00 y 25 años, 0,24, y ±1,96 veces eso da de 0,52 a 1,48; el bootstrap sale algo más estrecho.
Veinticinco años de datos no bastan para distinguir un Sharpe de 0,6 de uno de 1,4.

La segunda: en esta cartera el drawdown apenas depende del bloque. El mediano va del −17,9 % con días sueltos al
−17,7 % con bloques de 75 sesiones, y el del percentil 5, del −26,4 % al −26,2 %. La razón está en la construcción:
cada posición se escala por la inversa de su volatilidad, y eso quita a la cartera casi toda la agrupación de
volatilidad que un bloque largo conservaría. En una serie sin escalar es distinto. La celda siguiente repite la
prueba con el futuro de índice `IDA` comprado y mantenido:

```python
ida = r["IDA"].loc["2001-01-02":].to_numpy()
b_ida = optimal_block_length(ida ** 2)["stationary"].iloc[0]
print(f"IDA: bloque de Politis y White para r²: {b_ida:.0f}")
print(f"Drawdown histórico: {drawdown(ida):.1%}")
for b in (1, 75):
    v = StationaryBootstrap(b, ida, seed=3).apply(
        lambda x: np.array([drawdown(x)]), 500)[:, 0]
    print(f"Bloque {b:>2}: drawdown mediano {np.median(v):.1%}, "
          f"percentil 5 {np.quantile(v, 0.05):.1%}")
```
```output
IDA: bloque de Politis y White para r²: 156
Drawdown histórico: -62.2%
Bloque  1: drawdown mediano -53.0%, percentil 5 -74.9%
Bloque 75: drawdown mediano -65.3%, percentil 5 -86.8%
```

En `IDA` el selector pide bloques de 156 sesiones para los cuadrados (48 en la cartera), y el drawdown mediano pasa
del −53,0 % con días sueltos al −65,3 % con bloques de 75 sesiones; el percentil 5, del −74,9 % al −86,8 %. Los bloques
largos conservan las rachas de volatilidad, y con ellas las caídas encadenadas; los días sueltos las reparten y el
drawdown sale optimista: con ellos, el histórico de `IDA` (−62,2 %) parecería peor de lo normal. La regla práctica: el
bloque importa para el drawdown en la medida en que la serie tenga rachas de volatilidad, y lo prudente es mirar
varias longitudes.

La tercera: el drawdown histórico de la cartera (−14,7 %) es más suave que la mediana del bootstrap con cualquier
bloque (entre −16,9 % y −17,9 %). La historia que ocurrió fue algo afortunada en el orden de sus días: el riesgo que
conviene esperar es el del bootstrap, no el del backtest. (El −21,9 % de la secuencia de operaciones no es
comparable: allí cada operación se apunta entera el día de su salida.)

### El abanico de capital

Con el bootstrap estacionario se pueden generar caminos de cualquier longitud. Mil caminos de cinco años con bloques
de 75 sesiones de media dan el abanico de la figura de la cabecera:

```python
rng = np.random.default_rng(5)
n, largo, b = len(cartera), 5 * 252, 75
caminos = np.array([
    cartera.to_numpy()[indices_estacionario(n, largo, b, rng)]
    for _ in range(1000)])
capital = np.cumprod(1 + caminos, axis=1)
bandas = pd.DataFrame(
    np.quantile(capital, [0.05, 0.25, 0.5, 0.75, 0.95], axis=0).T,
    columns=["p5", "p25", "p50", "p75", "p95"])
print(bandas.iloc[[251, 503, 755, 1007, 1259]]
      .set_axis([1, 2, 3, 4, 5]).rename_axis("año").round(2))
print(f"P(perder a 5 años): {(capital[:, -1] < 1).mean():.1%}")
```
```output
       p5   p25   p50   p75   p95
año
1    0.94  1.02  1.09  1.16  1.25
2    0.96  1.10  1.20  1.30  1.45
3    1.01  1.17  1.29  1.44  1.67
4    1.07  1.26  1.41  1.59  1.88
5    1.14  1.36  1.54  1.77  2.10
P(perder a 5 años): 1.2%
```

La mediana llega a 1,54 en cinco años; la banda del 25 al 75 %, de 1,36 a 1,77. El percentil 5 es la línea que importa
para decidir el tamaño: pasa el primer año por 0,94 (un 6 % de pérdida), el segundo por 0,96, no vuelve a 1 hasta el
tercero (1,01) y a los cinco años está en 1,14. Aun con un Sharpe de 1,00, un 1,2 % de los caminos pierde dinero a cinco
años y uno de cada veinte pasa dos años en pérdidas. Es lo que conviene enseñar a quien vaya a invertir antes de que
lo viva.

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

El abanico supone que el futuro se parece al pasado remuestreado: los mismos días, en otro orden. No contiene
ninguna crisis que no esté en la muestra ni un cambio de régimen que reduzca la ventaja. Para eso están las pruebas
de estrés con datos sintéticos de la lección 2.5.7.

</aside>

## Permutar la señal: ¿acierta el momento?

El bootstrap dice cuánto varía el Sharpe; no dice si es distinto de lo que daría una estrategia sin acierto. Un
**test de permutación** construye esa referencia directamente: se rompe la relación entre la señal y los
rendimientos y se recalcula el estadístico muchas veces. El p-valor es la fracción de permutaciones que igualan o
superan lo observado, con la corrección habitual para que nunca sea cero:

$$
p = \frac{1 + \#\{i : S^{(i)} \ge S_{\text{obs}}\}}{1 + N}
$$

Lo delicado es decidir **qué** se permuta. La pregunta es si la dirección elegida en cada momento aporta algo, así que
se permuta solo el signo; la escala por volatilidad y los rendimientos se quedan en su sitio. Y hay dos formas de
permutar el signo: **barajar** las sesiones (rompe también la persistencia de la señal) o **desplazarlo
circularmente** un número aleatorio de sesiones (conserva sus rachas, su rotación y su autocorrelación, y solo cambia
su alineación con el mercado). White (2000) y Hansen (2005)[^3] extienden la idea al caso de muchas reglas a la vez.

```python
def p_valor_perm(signo, escala, r, n_perm=1000, seed=0,
                 modo="circular"):
    """Sharpe observado, p-valor y distribución nula.

    Solo se permuta la DIRECCIÓN (el signo); la escala por
    volatilidad y los rendimientos quedan en su sitio."""
    rng = np.random.default_rng(seed)
    s = signo.shift(1).to_numpy()
    e = escala.shift(1).to_numpy()
    rr = r.to_numpy()
    ok = ~(np.isnan(s).any(axis=1) | np.isnan(e).any(axis=1)
           | np.isnan(rr).any(axis=1))
    s, e, rr = s[ok], e[ok], rr[ok]
    obs = sharpe((s * e * rr).sum(axis=1))
    nula = np.empty(n_perm)
    for i in range(n_perm):
        if modo == "barajar":
            sp = s[rng.permutation(len(s))]
        else:  # desplazamiento circular de al menos un año
            sp = np.roll(s, rng.integers(252, len(s) - 252), axis=0)
        nula[i] = sharpe((sp * e * rr).sum(axis=1))
    p = (1 + (nula >= obs).sum()) / (1 + n_perm)
    return obs, p, nula

res, nulas = {}, {}
for nombre, args in {"tendencia": (signo, escala, r),
                     "gemelo": (signo_g, escala_g, r_g)}.items():
    for modo in ("barajar", "circular"):
        obs, p, nula = p_valor_perm(*args, modo=modo)
        nulas[(nombre, modo)] = nula
        res[(nombre, modo)] = {"sharpe": obs, "p": p,
                               "nula_media": nula.mean(),
                               "nula_95": np.quantile(nula, 0.95)}
pd.DataFrame(res).T.round(3)
```
```output
                    sharpe      p  nula_media  nula_95
tendencia barajar    0.999  0.001       0.202    0.519
          circular   0.999  0.001       0.048    0.552
gemelo    barajar    0.026  0.536       0.039    0.376
          circular   0.026  0.248      -0.066    0.212
```

*Fig. — Test de permutación de la señal (desplazamiento circular, 1.000 veces): distribución nula del Sharpe y Sharpe observado, en los futuros y en su gemelo sin memoria.* Dos histogramas. Izquierda, futuros con efecto plantado: la nula se centra en 0,05 y el Sharpe observado, 1,00 (destacado), queda a la derecha de todas las permutaciones; p = 0,001. Derecha, el gemelo con las sesiones barajadas: la nula se centra en −0,07 y el Sharpe observado, 0,03, cae dentro de ella; p = 0,25.

El método distingue los dos casos. En los futuros con efecto plantado, el Sharpe observado (1,00) supera a todas las
permutaciones, barajando y desplazando: p = 0,001, el mínimo posible con 1.000. En el gemelo sin memoria, el 0,03 cae
dentro de su nula: p = 0,25 con el desplazamiento circular (0,54 barajando).

Fíjate en la media de la nula: 0,20 al barajar y 0,05 con el desplazamiento circular, no 0. Cualquier permutación
conserva la proporción de sesiones largas y cortas de cada mercado, y con ella la parte del resultado que viene de la
exposición media:

```python
largo = (signo.shift(1).loc[pnl.index] > 0).mean()
largo.sort_values().round(2).iloc[[0, 1, -2, -1]].to_dict()
```
```output
{'MEA': 0.42, 'DIC': 0.43, 'IDB': 0.7, 'IDA': 0.71}
```

La cartera está larga el 71 % de las sesiones en `IDA` y el 70 % en `IDB`, y solo el 42 % en el oro (`MEA`). Los
índices llevan la prima de la renta variable del guion, así que una permutación que los deja largos el 70 % del tiempo
gana algo con cualquier orden; lo mismo pasa con la parte del carry que la regla recoge. El test no pregunta si la estrategia gana dinero, sino si el **momento** elegido añade algo
sobre esa exposición. Una estrategia siempre larga en un mercado alcista ganaría dinero y no pasaría el test.

```mermaid
flowchart TB
  Q{¿Qué preguntas?} -- riesgo --> D[Bootstrap bloques]
  Q -- ¿acierta? --> P[Permutación]
  Q -- ¿elegí bien? --> S[DSR y PBO]
  D --> C{¿Pérdidas juntas?}
  C -- sí --> B[Bloques largos]
  C -- no --> T[Trades o i.i.d.]
```

### Potencia: no rechazar no es no tener efecto

Los 24 mercados tienen el mismo efecto plantado. Mercado a mercado:

```python
p_merc = pd.Series({
    m: p_valor_perm(signo[[m]], escala[[m]], r[[m]],
                    n_perm=500, seed=1)[1]
    for m in r.columns})
print(f"Mercados con p < 0,05: {(p_merc < 0.05).sum()} de 24")
print(p_merc.sort_values().round(3).head(6).to_dict())
```
```output
Mercados con p < 0,05: 7 de 24
{'AGB': 0.002, 'BOD': 0.002, 'BOC': 0.01, 'AGC': 0.012, 'BOA': 0.034, 'AGA': 0.042}
```

Solo 7 de los 24 pasan al 5 %, con los agrícolas y los bonos en cabeza. Con un Sharpe verdadero de momentum en torno a
0,15 por mercado y 25 años de datos, el error típico del Sharpe (≈ 0,2) es mayor que el efecto: el test casi nunca lo
detecta en un mercado solo. La cartera lo detecta porque suma 24 efectos débiles poco correlados. Descartar 17 mercados
por «no significativos» sería descartar un efecto real; la conclusión correcta es que un mercado solo no tiene datos
suficientes.

### Y en el ruido, ¿cuántos falsos positivos?

El control del README es la serie `ruptura` de `regimenes2.parquet` después de su ruptura (el 2013-05-22):
rendimientos normales i.i.d. de media cero, donde ninguna regla de tendencia tiene ventaja. Una sola serie de ruido
no basta para saber si el test está bien calibrado, así que se añaden 200 series simuladas como ella:

```python
reg = pd.read_parquet("regimenes2.parquet")
ru = (reg[reg["serie"] == "ruptura"].set_index("fecha")[["precio"]]
      .loc["2013-05-22":])          # tras la ruptura: i.i.d., media 0
s_ru, e_ru, r_ru, _ = tendencia(ru, riesgo=0.10)
obs, p, _ = p_valor_perm(s_ru, e_ru, r_ru)
print(f"Serie «ruptura» desde 2013: Sharpe {obs:.2f}, p = {p:.3f}")

rng = np.random.default_rng(99)
ps = []
for i in range(200):
    ruido = pd.DataFrame(
        np.cumprod(1 + 0.013 * rng.standard_normal(len(ru))),
        index=ru.index)
    ps.append(p_valor_perm(*tendencia(ruido, riesgo=0.10)[:3],
                           n_perm=200, seed=i)[1])
ps = np.array(ps)
print(f"Series de ruido con p < 0,05: {(ps < 0.05).mean():.1%}")
print(np.histogram(ps, bins=5, range=(0, 1))[0])
```
```output
Serie «ruptura» desde 2013: Sharpe 0.55, p = 0.032
Series de ruido con p < 0,05: 4.5%
[36 47 33 27 57]
```

La serie del README da un Sharpe de 0,55 y p = 0,032: un **falso positivo**. No hay error en el test: en 200 series de
ruido como ella, un 4,5 % sale con p < 0,05, y los p-valores se reparten de forma aproximadamente uniforme entre 0 y 1
(36, 47, 33, 27 y 57 por quintil). Eso es lo que significa un nivel del 5 %: uno de cada veinte controles sale
«significativo». Por eso un único p-valor por debajo de 0,05 no demuestra nada, y por eso un control que pasa el test
no invalida el método: invalida la costumbre de probar una sola cosa y creer el resultado.

## Lo que ningún remuestreo corrige

Los tres métodos trabajan sobre una estrategia ya elegida. Si esa estrategia es la mejor de muchas, todos heredan el
sesgo de la elección. El conjunto `ruido2_estrategias.parquet` tiene 1.000 estrategias, 995 de ellas sin ninguna
ventaja (la verdad está en `ruido2_verdad.csv`):

```python
rs = pd.read_parquet("ruido2_estrategias.parquet").pivot(
    index="fecha", columns="estrategia", values="rendimiento")
verdad = pd.read_csv("ruido2_verdad.csv", index_col="estrategia")
sin_ventaja = verdad.index[verdad["sharpe_real"] == 0]
sr = rs[sin_ventaja].apply(sharpe)
mejor = sr.idxmax()
print(f"{len(sin_ventaja)} estrategias sin ventaja; la mejor: "
      f"{mejor}, Sharpe {sr.max():.2f}")
bs = StationaryBootstrap(5, rs[mejor].to_numpy(), seed=2)
v = bs.apply(sharpe, 2000)[:, 0]
print(f"Intervalo del 95 %: {np.quantile(v, 0.025):.2f} a "
      f"{np.quantile(v, 0.975):.2f}")
print(f"Remuestreos con Sharpe <= 0: {(v <= 0).mean():.2%}")
```
```output
995 estrategias sin ventaja; la mejor: e0598, Sharpe 1.50
Intervalo del 95 %: 0.46 a 2.54
Remuestreos con Sharpe <= 0: 0.15%
```

La mejor de las 995 tiene un Sharpe de 1,50 en cuatro años, un intervalo bootstrap de 0,46 a 2,54 y solo un 0,15 % de
remuestreos con Sharpe negativo. Todo indica una estrategia excelente, y su Sharpe verdadero es cero. El bootstrap
responde bien a su pregunta (cuánto varía el Sharpe de **esta** serie) y no puede responder a otra que no se le hizo:
cuántas series se miraron antes de elegirla. Con 995 intentos y un error típico del Sharpe de 0,5, un máximo de 1,5 es
lo esperable. La corrección es el Deflated Sharpe Ratio o el PBO de la lección 2.5.4, que necesitan el número de
pruebas, y el diario de experimentos de la lección 2.2.4, que es donde ese número queda escrito.

En resumen, el orden de trabajo con una estrategia candidata:

1. Bootstrap de bloques de los rendimientos diarios, con varias longitudes de bloque: intervalo del Sharpe, del
   drawdown y abanico para decidir el tamaño.
2. Remuestreo de operaciones solo como suelo del riesgo, comprobando dónde cae la secuencia real.
3. Test de permutación de la señal (circular si la señal es persistente) para separar acierto de exposición, también
   en un control sin efecto.
4. Deflated Sharpe o PBO con el número real de pruebas (lección 2.5.4); la lección 2.5.8 junta todo en una lista.

## Ejercicios

1. Repite el riesgo de ruina con el bootstrap estacionario de los **rendimientos diarios** (bloques de 75) en lugar
   de las operaciones: probabilidad de un drawdown del 20 % en 25 años con apalancamiento 1 y 2. Compáralo con la
   tabla de las operaciones.

**Solución.**

```python
rng = np.random.default_rng(21)
n = len(cartera)
caminos = np.array([cartera.to_numpy()[
    indices_estacionario(n, n, 75, rng)] for _ in range(500)])
for apal in (1, 2):
    dd = dd_max(apal * caminos)
    print(f"x{apal}: drawdown mediano {np.median(dd):.1%}, "
          f"P(dd < -20 %) {(dd < -0.20).mean():.1%}")
```
```output
x1: drawdown mediano -17.6%, P(dd < -20 %) 29.8%
x2: drawdown mediano -33.0%, P(dd < -20 %) 99.6%
```

Con los rendimientos diarios en bloques, el drawdown mediano en 25 años sin apalancar es del −17,6 % y la
probabilidad de pasar por un −20 %, del 29,8 %, frente al 0,5 % que daban las operaciones tratadas como
independientes. Con apalancamiento 2, el drawdown mediano es del −33,0 % y el −20 % llega en el 99,6 % de los caminos. El
bootstrap de bloques conserva el solapamiento de las posiciones y las rachas de volatilidad; el remuestreo de
operaciones, no. Para decidir el tamaño, esta es la cifra de referencia, y la diferencia entre las dos es el tamaño
del error que comete el remuestreo de trades en una cartera.

2. Aplica el test de permutación circular a la misma regla con una ventana de 63 sesiones en lugar de 252, en los
   futuros y en el gemelo. ¿Detecta el test un efecto que el README planta a 252 sesiones?

**Solución.**

```python
for nombre, precios in (("tendencia", px), ("gemelo", px_g)):
    s63, e63, r63, _ = tendencia(precios, L=63)
    obs, p, nula = p_valor_perm(s63, e63, r63, n_perm=500)
    print(f"{nombre}: Sharpe {obs:.2f}, p = {p:.3f}, "
          f"media de la nula {nula.mean():.2f}")
```
```output
tendencia: Sharpe 0.63, p = 0.020, media de la nula 0.07
gemelo: Sharpe 0.00, p = 0.403, media de la nula -0.04
```

Con 63 sesiones la regla sigue capturando parte del efecto en los futuros: Sharpe 0,63 y p = 0,020. El signo
de 3 meses está correlado con el de 12 meses, y el carry, que es persistente, se refleja en la rentabilidad de
cualquier ventana. En el gemelo no hay nada que capturar: Sharpe 0,00 y p = 0,403. Que una ventana distinta de la
plantada también funcione es la idea de la lección 2.5.6: un efecto real suele dar una meseta de parámetros, no un
pico.

3. Una estrategia está larga todos los días en un índice que sube un 8 % anual. ¿Qué p-valor da el test de
   permutación de la señal y por qué? ¿Qué te dice eso sobre lo que mide el test?

**Solución.**

Si la posición es siempre +1, cualquier permutación (barajar o desplazar) devuelve la misma serie de signos: todas las
permutaciones tienen exactamente el Sharpe observado y el p-valor es (1 + N)/(1 + N) = 1. La estrategia gana dinero,
pero no tiene ningún acierto de momento: todo su resultado es exposición. El test de permutación mide solo el valor
del timing respecto a una cartera con la misma exposición media; la rentabilidad de la exposición (la prima del
mercado) hay que juzgarla con otras herramientas, como el alfa frente a un índice (lección 2.6.2).

[^1]: Moskowitz, Ooi y Pedersen (2012), «Time series momentum», Journal of Financial Economics 104(2), 228-250,
    [doi:10.1016/j.jfineco.2011.11.003](https://doi.org/10.1016/j.jfineco.2011.11.003).
[^2]: Lo, A. W. (2002), «The Statistics of Sharpe Ratios», Financial Analysts Journal 58(4), 36-52,
    [doi:10.2469/faj.v58.n4.2453](https://doi.org/10.2469/faj.v58.n4.2453).
[^3]: Hansen, P. R. (2005), «A Test for Superior Predictive Ability», Journal of Business & Economic Statistics 23(4),
    365-380, [doi:10.1198/073500105000000063](https://doi.org/10.1198/073500105000000063).

## Autoevaluación

1. Al desordenar 2.000 veces las 3.229 operaciones de la cartera de tendencia, ninguna secuencia tiene un drawdown peor que el de la secuencia real (−21,9 %). ¿Qué indica?

   - A. Que la estrategia tuvo mala suerte y el drawdown real no se repetirá
   - B. Que las pérdidas reales llegaron agrupadas en el tiempo, y desordenar las operaciones (que supone independencia) infravalora el drawdown
   - C. Que hay un error en el cálculo del drawdown
   - D. Que hacen falta más simulaciones

   *Respuesta B: Que las pérdidas reales llegaron agrupadas en el tiempo, y desordenar las operaciones (que supone independencia) infravalora el drawdown.* Desordenar supone que el orden de las operaciones es intercambiable. En una cartera con 24 mercados las operaciones se solapan y las pérdidas llegan juntas: la desviación típica del resultado mensual es del 3,30 % con el orden real y del 2,52 % con el orden barajado. El remuestreo de trades es un suelo del riesgo, no una estimación.

2. Con el bootstrap estacionario, el drawdown mediano de la cartera de tendencia apenas cambia entre bloques de 1 y de 75 sesiones (−17,9 % y −17,7 %), y el de IDA comprado y mantenido pasa del −53,0 % al −65,3 %. ¿Por qué?

   - A. Porque la cartera tiene más sesiones que IDA
   - B. Porque la cartera escala cada posición por la inversa de su volatilidad y le quedan pocas rachas de volatilidad; en IDA siguen ahí, y los bloques largos las conservan
   - C. Porque el bootstrap estacionario no sirve para índices
   - D. Porque con bloques largos el drawdown siempre sale peor

   *Respuesta B: Porque la cartera escala cada posición por la inversa de su volatilidad y le quedan pocas rachas de volatilidad; en IDA siguen ahí, y los bloques largos las conservan.* El drawdown depende de que las caídas encadenadas se conserven, y eso solo pasa si la serie tiene rachas de volatilidad que un bloque largo pueda conservar. El selector de Politis y White lo anticipa: pide bloques de 48 sesiones para los cuadrados de la cartera y de 156 para los de IDA. El intervalo del Sharpe apenas cambia con el bloque en ningún caso.

3. En el test de permutación, ¿por qué se permuta solo el signo de la posición y no la escala por volatilidad?

   - A. Por velocidad de cálculo
   - B. Porque la pregunta es si acertar la dirección en cada momento aporta algo; desordenar también la escala mezclaría una segunda cosa (el ajuste de riesgo) y la nula dejaría de ser comparable
   - C. Porque la escala es siempre constante
   - D. Porque así el p-valor sale más bajo

   *Respuesta B: Porque la pregunta es si acertar la dirección en cada momento aporta algo; desordenar también la escala mezclaría una segunda cosa (el ajuste de riesgo) y la nula dejaría de ser comparable.* Un test de permutación responde a una pregunta concreta: se rompe la relación que se quiere probar (dirección frente a rendimiento) y se conserva todo lo demás. Si la escala por volatilidad se desordena, la nula toma posiciones grandes en días agitados y su Sharpe baja por otra razón.

4. La nula del test con la señal barajada tiene media 0,20, no 0. ¿Por qué?

   - A. Porque el generador de números aleatorios está sesgado
   - B. Porque barajar conserva la proporción de días largos y cortos de cada mercado: la nula recoge la exposición media (la prima de los índices, el carry). El test mide si el momento elegido añade algo sobre esa exposición
   - C. Porque los futuros siempre suben
   - D. Porque hay pocas permutaciones

   *Respuesta B: Porque barajar conserva la proporción de días largos y cortos de cada mercado: la nula recoge la exposición media (la prima de los índices, el carry). El test mide si el momento elegido añade algo sobre esa exposición.* La cartera está larga el 71 % de las sesiones en IDA y el 70 % en IDB, índices que llevan la prima de la renta variable. Cualquier permutación conserva esos porcentajes y, con ellos, la parte del resultado que viene de la exposición media. Por eso un test de permutación no confunde exposición con acierto.

5. La mejor de 995 estrategias sin ventaja tiene un Sharpe de 1,50 y su intervalo bootstrap del 95 % va de 0,46 a 2,54. ¿Qué falla?

   - A. El bootstrap está mal programado
   - B. Nada: la estrategia tiene ventaja
   - C. El bootstrap remuestrea la serie ya elegida: no sabe que se eligió la mejor de 995. La selección se corrige con el Deflated Sharpe o el PBO (lección 2.5.4)
   - D. Hacen falta bloques más largos

   *Respuesta C: El bootstrap remuestrea la serie ya elegida: no sabe que se eligió la mejor de 995. La selección se corrige con el Deflated Sharpe o el PBO (lección 2.5.4).* Remuestrear una serie dice cuánto varía su estadístico por azar dentro de esa serie, no cuántas series se miraron antes de elegirla. Entre 995 estrategias sin ventaja, la mejor tiene un Sharpe alto por construcción, y su intervalo se centra en ese valor inflado.

## Ideas clave

- Cada método de Monte Carlo conserva una estructura y rompe otra: desordenar trades rompe el agrupamiento de pérdidas; el bootstrap de bloques conserva las rachas de volatilidad dentro del bloque; permutar la señal rompe solo la relación entre dirección y rendimiento.
- En la cartera de tendencia, el drawdown de la secuencia real de operaciones (−21,9 %) es peor que el de las 2.000 secuencias desordenadas: con operaciones solapadas, el remuestreo de trades infravalora el riesgo.
- La longitud de bloque importa para el drawdown cuando la serie tiene rachas de volatilidad (IDA: mediana de −53,0 % con bloques de 1 y −65,3 % con bloques de 75) y casi nada en una cartera escalada por volatilidad o para el intervalo del Sharpe (de 0,6 a 1,4 con cualquier bloque).
- El drawdown histórico de la cartera (−14,7 %) es más suave que la mediana del bootstrap (≈ −17,8 %): el backtest enseña una historia algo afortunada, no el riesgo que cabe esperar.
- El test de permutación separa el acierto del momento de la exposición media: p = 0,001 en la tendencia con efecto plantado, p = 0,25 en su gemelo sin memoria y un 4,5 % de falsos positivos al 5 % en 200 series de ruido.
- Con 25 años de datos y un efecto débil por mercado, solo 7 de los 24 mercados pasan el test por separado: no rechazar no es lo mismo que no tener efecto.
- Ningún remuestreo corrige la selección: la mejor de 995 estrategias sin ventaja tiene un intervalo bootstrap que excluye el cero.

## Recursos

### Research

- [Procesos Estocásticos - Paseos Aleatorios](https://pythonparatrading.com/procesos-estocasticos/) · Research-0005 · 12 min

### Documentación

- [arch: bootstrap (StationaryBootstrap, CircularBlockBootstrap, optimal_block_length)](https://arch.readthedocs.io/en/latest/bootstrap/bootstrap.html) · arch.readthedocs.io

### Libros

- [Politis, D. N. y Romano, J. P. (1994): The Stationary Bootstrap. Journal of the American Statistical Association 89(428), 1303-1313](https://doi.org/10.1080/01621459.1994.10476870) · doi.org
- [Politis, D. N. y White, H. (2004): Automatic Block-Length Selection for the Dependent Bootstrap. Econometric Reviews 23(1), 53-70 (corrección en 28(4), 2009)](https://doi.org/10.1081/ETC-120028836) · doi.org
- [White, H. (2000): A Reality Check for Data Snooping. Econometrica 68(5), 1097-1126](https://doi.org/10.1111/1468-0262.00152) · doi.org
- [Moskowitz, T., Ooi, Y. H. y Pedersen, L. H. (2012): Time series momentum. Journal of Financial Economics 104(2), 228-250](https://doi.org/10.1016/j.jfineco.2011.11.003) · doi.org

### Datos

- [Datos del nivel 2: futuros continuos, 24 mercados, 2000-2025 (Parquet)](https://pythonparatrading.com/curso/datos/nivel-2/futuros2_continuos.parquet) · Descarga · futuros2_continuos.parquet

Lección anterior: [2.5.4 · Deflated Sharpe Ratio y Probability of Backtest Overfitting (PBO)](https://pythonparatrading.com/curso-python-trading/nivel-2/deflated-sharpe-ratio-y-pbo/)

Lección siguiente: [2.5.6 · Sensibilidad de parámetros: mesetas frente a picos, robustez entre activos y periodos](https://pythonparatrading.com/curso-python-trading/nivel-2/sensibilidad-de-parametros-y-robustez/)
