---
title: "2.5.2 In-sample, out-of-sample y walk-forward"
description: "Optimizar un parámetro sin engañarse: separación dentro y fuera de muestra, walk-forward anclado y móvil, estabilidad del parámetro y caída del Sharpe."
url: "https://pythonparatrading.com/curso-python-trading/nivel-2/in-sample-out-of-sample-y-walk-forward/"
lang: "es"
image: "https://pythonparatrading.com/og/curso-python-trading/nivel-2/in-sample-out-of-sample-y-walk-forward.png"
---

# 2.5.2 · In-sample y out-of-sample: walk-forward anclado y móvil

> Un parámetro elegido con los mismos datos con los que se evalúa siempre sale bien. El walk-forward repite la separación entre optimizar y probar a lo largo de la historia, y mide lo que de verdad interesa: cuánto se pierde al pasar de dentro a fuera de muestra.

- 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: 60 min
- Requisitos: [2.5.1 · Sesgos de backtest: anticipación, supervivencia, selección y narrativa](https://pythonparatrading.com/curso-python-trading/nivel-2/sesgos-de-backtest/), [1.4.6 · Series continuas de futuros: reglas de roll, ajuste por diferencia y ajuste por ratio](https://pythonparatrading.com/curso-python-trading/nivel-1/series-continuas-de-futuros/), [0.5.3 · Selección, filtrado, groupby, merge, pivot, resample y ventanas móviles](https://pythonparatrading.com/curso-python-trading/nivel-0/pandas-groupby-merge-resample-y-rolling/)

## Al terminar esta lección

- Separar datos dentro de muestra (para elegir) y fuera de muestra (para medir) y explicar por qué una sola separación es una sola tirada.
- Programar un walk-forward genérico, anclado y móvil, sobre una matriz de variantes de una estrategia.
- Medir la caída del Sharpe de dentro a fuera de muestra y la estabilidad de los parámetros elegidos en cada ventana.
- Reconocer en el ruido el patrón típico del sobreajuste: Sharpe alto dentro, nulo o negativo fuera, y parámetros que saltan de una ventana a otra.

## Elegir y medir con datos distintos

Casi toda estrategia tiene parámetros: el horizonte de una señal, un umbral, la longitud de una media. Elegirlos es
inevitable, y elegirlos con datos es razonable. El problema aparece cuando se miden con los **mismos** datos con los que
se eligieron: el resultado ya no es una estimación de lo que la estrategia hará, es el máximo de todas las variantes
probadas, que siempre es optimista (lección 2.5.1, sala de la selección).

La solución clásica es separar los datos en dos partes:

- **Dentro de muestra** (*in-sample*, IS): los datos con los que se elige. Aquí se prueban todas las variantes que
  se quiera.
- **Fuera de muestra** (*out-of-sample*, OOS): los datos con los que se mide la variante elegida. Se miran **una
  vez**, cuando la elección ya está hecha.

```mermaid
flowchart LR
  D[Historia] --> IS[Dentro: elegir]
  IS --> P[Parámetro fijo]
  P --> OOS[Fuera: medir]
```

La regla parece fácil de cumplir y se incumple a menudo sin darse cuenta: se mira el resultado fuera de muestra, no
gusta, se cambia algo «razonable» (un filtro, otro umbral) y se vuelve a mirar. En ese momento la muestra de fuera ha
pasado a ser parte de la de dentro. La lección 2.5.1 lo mide: filtrar mil estrategias de ruido por las dos mitades
deja siete falsos positivos.

Hay un segundo problema, más técnico. Una sola separación es **una sola tirada**: el resultado depende de qué régimen
cae a cada lado del corte. El walk-forward resuelve las dos cosas a la vez: repite la separación a lo largo de la
historia, con una regla mecánica que impide mirar antes de tiempo.

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

Los datos de esta lección son sintéticos y el efecto está plantado. En los 24 futuros del nivel 2, el rendimiento
esperado de cada mercado incluye un término de momentum de series temporales con el signo de la rentabilidad de las
**252** sesiones anteriores (Sharpe ≈ 0,15 por mercado), más el carry, la prima de la renta variable en los índices y
la caída de los bonos en 2022. Se sabe, por tanto, qué parámetro es el «verdadero». Ningún resultado de aquí dice nada de los futuros reales.

</aside>

## Un parámetro, ocho variantes

La estrategia de prueba es la de la lección 2.5.1: cada mercado con la misma volatilidad objetivo y el signo de la
rentabilidad de las últimas L sesiones. El parámetro es L, con ocho valores de dos semanas a año y medio. La matriz
`variantes` tiene una columna por valor: el rendimiento diario de la cartera con ese L, ya ejecutado con un día de
retraso.

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

fut = pd.read_parquet("futuros2_continuos.parquet")
precio = fut.pivot(index="fecha", columns="mercado",
                   values="cierre_ajustado")
r = precio.pct_change(fill_method=None)
vol = r.ewm(span=60, min_periods=60).std() * np.sqrt(252)

def sharpe(x):
    return x.mean() / x.std() * np.sqrt(252)

LOOKBACKS = [10, 21, 42, 63, 126, 189, 252, 378]
variantes = {}
for n in LOOKBACKS:
    senal = np.sign(precio.pct_change(n, fill_method=None))
    peso = (senal * 0.40 / vol / precio.shape[1]).shift(1)
    variantes[n] = (peso * r).sum(axis=1, min_count=1)
variantes = pd.DataFrame(variantes).loc["2002":]
print(variantes.shape)
sharpe(variantes).round(2).to_frame("Sharpe 2002-2025").T
```
```output
(6039, 8)
                   10    21    42    63    126   189   252   378
Sharpe 2002-2025  0.03  0.34  0.61  0.62  0.78  0.91  1.06  0.85
```

Mirando toda la historia, el mejor lookback es 252, el que planta el generador: el Sharpe sube desde 0,03 con 10
sesiones hasta 1,06 con 252 y vuelve a bajar a 0,85 con 378. Pero esta tabla es exactamente lo que no se puede hacer
en tiempo real: en 2002 nadie conocía el Sharpe de 2002-2025. La tabla también enseña algo que importa para lo que
sigue: las ocho variantes tienen Sharpes positivos, porque todas son momentum sobre los mismos mercados (las columnas
están muy correlacionadas), y las tres más largas (189, 252 y 378) quedan a menos de 0,25 de distancia. Elegir entre
ellas es elegir entre parecidos, y eso hace la elección inestable.

Una sola separación, para empezar: elegir con los años hasta un corte y medir en los siguientes. Para ver cuánto
depende del corte, cuatro cortes distintos:

```python
filas = {}
for corte in (2006, 2009, 2013, 2017):
    dentro = variantes.loc[:str(corte)]
    fuera = variantes.loc[str(corte + 1):]
    mejor = sharpe(dentro).idxmax()
    filas[f"2002-{corte} / {corte + 1}-2025"] = {
        "L elegido": mejor, "dentro": sharpe(dentro)[mejor],
        "fuera": sharpe(fuera)[mejor], "252 fuera": sharpe(fuera)[252]}
pd.DataFrame(filas).T.astype({"L elegido": int}).round(2)
```
```output
                       L elegido  dentro  fuera  252 fuera
2002-2006 / 2007-2025        189    1.33   0.81       1.09
2002-2009 / 2010-2025        189    0.85   0.95       1.17
2002-2013 / 2014-2025        252    1.01   1.10       1.10
2002-2017 / 2018-2025        252    0.99   1.19       1.19
```

Con un corte en 2013 o en 2017, la separación elige 252 y fuera de muestra da algo más que dentro (1,10 frente a 1,01
y 1,19 frente a 0,99): parece una validación limpia. Con un corte en 2006 o en 2009 elige el vecino, 189, y fuera de
muestra da 0,81 y 0,95, por debajo de lo que habría dado 252 en los mismos años (1,09 y 1,17). Con el corte de 2006 el
Sharpe dentro de muestra, 1,33, promete mucho más de lo que llega fuera. La misma estrategia, con el mismo método, da
conclusiones distintas según dónde caiga el corte, porque cada corte es una sola tirada. Y el número que sale no dice
si el parámetro elegido habría sido estable: solo hay una elección.

## Walk-forward: optimizar, probar, avanzar

El walk-forward convierte la separación en un bucle. En cada paso:

1. Se toma una ventana dentro de muestra que termina el día antes de la ventana de prueba.
2. Se elige la variante con mejor criterio (aquí, el Sharpe) en esa ventana.
3. Se aplica la variante elegida, sin tocarla, a la ventana fuera de muestra siguiente.
4. Se avanza una ventana de prueba y se repite.

Las ventanas fuera de muestra no se solapan y se encadenan: su concatenación es la curva de capital de alguien que
hubiera seguido el procedimiento en tiempo real, reoptimizando cada año. Hay dos formas de definir la ventana dentro
de muestra, y la figura de la cabecera las dibuja con los años de este ejemplo (5 años dentro, 1 fuera):

- **Anclada** (*anchored*, expandida): empieza siempre el primer día de los datos y crece en cada paso.
- **Móvil** (*rolling*): tiene una longitud fija y avanza con la de prueba; lo más antiguo se olvida.

```mermaid
stateDiagram-v2
  [*] --> Optimizar
  Optimizar --> Probar: parámetro fijo
  Probar --> Avanzar: guardar OOS
  Avanzar --> Optimizar: quedan datos
  Avanzar --> [*]: fin
```

La función siguiente vale para cualquier matriz de variantes (filas, días; columnas, variantes), así que sirve igual
para los futuros que para las estrategias de ruido de más abajo:

```python
def walk_forward(var, modo, anos_dentro=5, desde=2007, hasta=2025):
    """Elige cada año la mejor columna de `var` en la ventana
    dentro de muestra y la aplica al año siguiente."""
    filas, fuera = [], []
    for ano in range(desde, hasta + 1):
        if modo == "anclado":
            inicio = var.index[0].year
        else:
            inicio = ano - anos_dentro
        dentro = var.loc[str(inicio):str(ano - 1)]
        s = sharpe(dentro)
        elegido = s.idxmax()
        prueba = var.loc[str(ano), elegido]
        filas.append({"ano": ano, "L": elegido, "sh_dentro": s.max(),
                      "sh_fuera": sharpe(prueba)})
        fuera.append(prueba)
    return pd.DataFrame(filas).set_index("ano"), pd.concat(fuera)

anc, oos_anc = walk_forward(variantes, "anclado")
mov, oos_mov = walk_forward(variantes, "movil")
pd.concat({"anclado": anc, "móvil": mov}, axis=1).round(2)
```
```output
     anclado                    móvil
           L sh_dentro sh_fuera     L sh_dentro sh_fuera
ano
2007     189      1.33    -0.51   189      1.33    -0.51
2008     189      1.04     0.82    42      0.96    -0.33
2009     189      0.99    -0.15   252      1.09    -0.09
2010     189      0.85     0.72   252      0.62     1.65
2011     252      0.92     0.77   252      0.77     0.77
2012     252      0.91     0.57   252      0.88     0.57
2013     252      0.87     2.51   252      0.90     2.51
2014     252      1.01     2.61   252      1.05     2.61
2015     252      1.12     0.76   252      1.59     0.76
2016     252      1.09     0.07   252      1.37     0.07
2017     252      1.03     0.21   378      1.28    -1.03
2018     252      0.99     1.26   252      1.26     1.26
2019     252      1.00     2.18   252      0.99     2.18
2020     252      1.06    -0.06   252      0.91    -0.06
2021     252      1.00     1.89    63      1.06     1.33
2022     252      1.04     0.73    63      1.37     1.85
2023     252      1.03     2.25    63      1.58     0.93
2024     252      1.09     1.71    21      1.65    -0.29
2025     252      1.12    -0.30   189      1.50    -0.78
```

Cada fila es una decisión tomada con los datos de antes. Dos cosas saltan a la vista. La primera, la **estabilidad**:
el anclado elige 189 de 2007 a 2010 y 252 desde 2011, con un solo cambio en 19 años; el móvil cambia siete veces
entre seis valores, de 21 a 378 sesiones, porque con cinco años de datos la diferencia entre variantes parecidas es
ruido. Entre 2021 y 2024 llega a elegir 63 y 21 sesiones, y en 2022 el 63 le sale bien (1,85 frente a 0,73 del
anclado): un año bueno no hace bueno el procedimiento. La segunda, que los Sharpes **fuera de muestra** año a año van
de −1,03 a 2,61: un año es poco para medir un Sharpe (su error típico anual es 1), y por eso lo que se evalúa es la
serie encadenada, no cada ventana.

*Fig. — Lookback elegido en cada año del walk-forward sobre los futuros sintéticos: anclado arriba, móvil de 5 años abajo.* Dos escalones con el lookback L elegido de 2007 a 2025, en escala logarítmica, y una línea discontinua destacada en 252, el valor verdadero. El anclado elige 189 de 2007 a 2010 y 252 desde 2011: un cambio. El móvil salta entre 189, 42, 252, 378, 63 y 21: siete cambios.

## Cuánto se pierde al salir de la muestra

La medida que resume un walk-forward es la caída del Sharpe: el medio de las ventanas dentro de muestra (lo que el
investigador veía al elegir) frente al de la serie fuera de muestra encadenada (lo que habría ganado). Su cociente se
llama a veces **eficiencia walk-forward**, término que Pardo (2008) usa en su libro sobre la evaluación de
estrategias (aquí, con el Sharpe):

$$
\text{eficiencia} = \frac{SR_{\text{fuera}}}{\overline{SR}_{\text{dentro}}}
$$

Una eficiencia cercana a 1 dice que el procedimiento no se está engañando; una cercana a 0 o negativa, que el Sharpe
dentro de muestra era casi todo selección. Como comparación, la variante de 252 fija, sin optimizar nada, en los
mismos años:

```python
resumen = {}
for nombre, tabla, serie in (("anclado", anc, oos_anc),
                             ("móvil", mov, oos_mov)):
    sd, sf = tabla["sh_dentro"].mean(), sharpe(serie)
    resumen[nombre] = {"dentro": sd, "fuera": sf,
                       "eficiencia": sf / sd,
                       "cambios": int((tabla["L"].diff() != 0)
                                      .iloc[1:].sum())}
fijo = sharpe(variantes.loc["2007":, 252])
resumen["252 fijo"] = {"dentro": np.nan, "fuera": fijo,
                       "eficiencia": np.nan, "cambios": 0}
pd.DataFrame(resumen).T.round(2)
```
```output
          dentro  fuera  eficiencia  cambios
anclado     1.02   0.95        0.93      1.0
móvil       1.17   0.70        0.60      7.0
252 fijo     NaN   1.09         NaN      0.0
```

El anclado conserva el 93 % del Sharpe dentro de muestra; el móvil, el 60 %. En estos datos el
anclado gana porque el efecto es constante (252 sesiones durante 26 años): acumular historia solo ayuda a medirlo
mejor. Con un efecto que cambiara de verdad con el tiempo, el móvil podría adaptarse y el anclado se quedaría atrás;
el precio de esa capacidad de adaptación es la inestabilidad de la tabla anterior. Ninguno de los dos supera al
parámetro fijo de 252 (1,09), que es la respuesta conocida: optimizar no añade nada cuando las variantes son parecidas y una
de ellas ya es la buena, y le resta el ruido de la elección.

*Fig. — Sharpe medio dentro de muestra frente al de la serie fuera de muestra encadenada en tres walk-forward sobre datos sintéticos.* Barras horizontales por pares. Futuros con walk-forward anclado: 1,02 dentro y 0,95 fuera. Móvil de 5 años: 1,17 dentro y 0,70 fuera. Ruido, la mejor de 1.000 estrategias cada trimestre: 3,46 dentro y −0,53 fuera (destacado).

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

El 252 fijo no es una alternativa honesta en la práctica: se ha elegido sabiendo la respuesta. Aparece en la tabla
como referencia de lo que vale la verdad en estos años, no como estrategia. En datos reales no hay respuesta conocida,
y el walk-forward es la forma de estimar qué se habría obtenido eligiendo con lo que se sabía en cada momento.

</aside>

## Las ventanas también son parámetros

El walk-forward tiene sus propias decisiones: la longitud de la ventana dentro de muestra, la de la ventana de prueba,
la rejilla de valores y el criterio de elección. Cada una cambia el resultado, y elegirlas mirando el resultado fuera
de muestra es otra forma de selección. En el móvil, con las ventanas de prueba de 2012 a 2025 para que las tres
longitudes usen los mismos años:

```python
filas = {}
for anos in (3, 5, 10):
    t, s = walk_forward(variantes, "movil", anos_dentro=anos,
                        desde=2012)
    filas[f"{anos} años"] = {
        "dentro": t["sh_dentro"].mean(), "fuera": sharpe(s),
        "valores distintos de L": t["L"].nunique()}
pd.DataFrame(filas).T.round(2)
```
```output
         dentro  fuera  valores distintos de L
3 años     1.39   0.88                     6.0
5 años     1.24   0.87                     5.0
10 años    1.09   1.12                     2.0
```

Con diez años la serie fuera de muestra sale mejor (1,12) y la elección casi no se mueve (dos valores de L); con tres
y con cinco, peor y casi igual (0,88 y 0,87), con seis y cinco valores distintos. Encaja con lo que se sabe de estos
datos, un efecto estable que se mide mejor con más historia, pero la diferencia entre 0,87 y 1,12 en 14 años de
prueba está dentro del error típico de un Sharpe en ese plazo ($1/\sqrt{14} \approx 0{,}27$). Y aunque la ventana
de diez años fuera de verdad la mejor, si se publicara porque es la que mejor sale fuera de muestra, el resultado ya no
sería fuera de muestra. La práctica sana es fijar las ventanas antes de
mirar (por el horizonte de la señal, por la cantidad de datos que hace falta para medir un Sharpe con algo de
precisión) y anotar en el diario de investigación (lección 2.2.4) cada combinación probada, como un intento más para
el Deflated Sharpe (lección 2.5.4).

| Decisión del walk-forward | Cómo fijarla sin mirar el resultado |
|---|---|
| Longitud dentro de muestra | Suficiente para medir el criterio: varios ciclos de la señal y años, no meses |
| Longitud fuera de muestra | La frecuencia con la que de verdad se reoptimizaría en producción |
| Rejilla de valores | Pocos valores, espaciados en escala logarítmica, decididos por el horizonte económico |
| Criterio de elección | El mismo que se usará para decidir en real (Sharpe, Sharpe neto de costes) |
| Anclado o móvil | Por la hipótesis: efecto estable, anclado; efecto que cambia, móvil |

## El ruido en un walk-forward

Para ver la firma del sobreajuste sin ninguna ventaja que la tape, el mismo procedimiento sobre las 1.000 estrategias
de `ruido2_estrategias.parquet` (todas sin ventaja salvo cinco). Las estrategias solo cubren 2022-2025, así que las
ventanas son más cortas: un año dentro (252 sesiones), un trimestre fuera (63), avanzando trimestre a trimestre.

```python
est = pd.read_parquet("ruido2_estrategias.parquet").pivot(
    index="fecha", columns="estrategia", values="rendimiento")
verdad = pd.read_csv("ruido2_verdad.csv", index_col="estrategia")

filas, fuera = [], []
for i in range(252, len(est), 63):
    dentro = est.iloc[i - 252:i]
    s = sharpe(dentro)
    elegida = s.idxmax()
    prueba = est.iloc[i:i + 63][elegida]
    fuera.append(prueba)
    filas.append({"desde": est.index[i].date(), "elegida": elegida,
                  "sh_dentro": s.max(), "sh_fuera": sharpe(prueba),
                  "real": verdad.loc[elegida, "sharpe_real"]})
ruido = pd.DataFrame(filas)
print(ruido.round(2).to_string(index=False))
print(f"Dentro, media: {ruido['sh_dentro'].mean():.2f}; "
      f"fuera, encadenado: {sharpe(pd.concat(fuera)):.2f}")
```
```output
     desde elegida  sh_dentro  sh_fuera  real
2023-01-04   e0868       3.29     -1.95   0.0
2023-04-05   e0601       2.63      0.75   0.0
2023-07-07   e0462       3.23      2.27   0.0
2023-10-05   e0542       3.62      3.25   0.0
2024-01-05   e0133       3.96     -3.00   0.8
2024-04-08   e0542       4.03      0.56   0.0
2024-07-09   e0598       3.42     -0.94   0.0
2024-10-07   e0091       3.24      0.97   0.0
2025-01-07   e0032       3.43     -1.79   0.0
2025-04-09   e0897       3.64     -0.78   0.0
2025-07-11   e0023       3.79      0.80   0.0
2025-10-09   e0023       3.21     -3.74   0.0
Dentro, media: 3.46; fuera, encadenado: -0.53
```

Cada trimestre, el mejor de mil tiene un Sharpe dentro de muestra de entre 2,6 y 4,0; fuera, la serie encadenada pierde.
Solo una vez el procedimiento elige una de las cinco estrategias con ventaja (e0133, en enero de 2024) y ese trimestre
pierde. La elección cambia casi cada trimestre: 10 estrategias distintas en 12 ventanas. Es la firma completa del
sobreajuste: **Sharpe muy alto dentro, nulo o negativo fuera y parámetros que no se repiten**. En los futuros, con un
efecto real, la caída existe pero es parcial y la elección tiende a estabilizarse; en el ruido, la caída es total.

La comparación también enseña el límite del walk-forward: con 1.000 candidatos y ventanas de un trimestre, ni las
cinco estrategias con ventaja verdadera (Sharpe de 0,5 a 1,5) se distinguen del ruido. El walk-forward detecta el
sobreajuste, pero no da a la buena estrategia una potencia estadística que los datos no tienen. Para aprovechar mejor
pocos datos están la validación cruzada purgada (lección 2.5.3) y, para corregir por el número de intentos, el
Deflated Sharpe y la probabilidad de sobreajuste (lección 2.5.4).

## Errores típicos

- **Optimizar con datos que se solapan con la prueba.** La ventana dentro de muestra termina el día antes de la de
  prueba. El calentamiento de los indicadores (las 378 sesiones que necesita el lookback más largo) puede tomar datos
  de antes de la ventana dentro de muestra; nunca de después.
- **Reoptimizar tras mirar.** Cambiar la rejilla, el criterio o las ventanas porque el resultado fuera de muestra no
  gusta convierte el walk-forward en una variante más (sección anterior).
- **Evaluar las ventanas por separado.** Un Sharpe de un año tiene un error típico de 1. Lo que se evalúa es la serie
  encadenada y, si acaso, la proporción de ventanas positivas.
- **Confundir la curva fuera de muestra con la estrategia.** La serie encadenada mide el **procedimiento** (elegir
  cada año el mejor L), no un L concreto. Si en producción se va a usar un L fijo, hay que decir cuál y por qué.
- **Olvidar los costes y el giro de cartera.** Un procedimiento que cambia de parámetro a menudo también cambia de
  posiciones; los costes se descuentan antes de elegir y en la prueba (lección 2.4.7).

## Ejercicios

1. Repite el walk-forward anclado con ventanas de prueba de dos años (reoptimizando cada dos años). ¿Cambia la
   elección del parámetro? ¿Y la caída del Sharpe?

**Solución.**

```python
filas, fuera = [], []
for ano in range(2007, 2026, 2):
    dentro = variantes.loc[:str(ano - 1)]
    elegido = sharpe(dentro).idxmax()
    prueba = variantes.loc[str(ano):str(ano + 1), elegido]
    filas.append((ano, elegido, sharpe(dentro).max()))
    fuera.append(prueba)
t = pd.DataFrame(filas, columns=["desde", "L", "sh_dentro"])
print(t.round(2).to_string(index=False))
print(f"Dentro {t['sh_dentro'].mean():.2f}, "
      f"fuera {sharpe(pd.concat(fuera)):.2f}")
```
```output
 desde   L  sh_dentro
  2007 189       1.33
  2009 189       0.99
  2011 252       0.92
  2013 252       0.87
  2015 252       1.12
  2017 252       1.03
  2019 252       1.00
  2021 252       1.00
  2023 252       1.03
  2025 252       1.12
Dentro 1.04, fuera 0.95
```

Con prueba de dos años hay menos decisiones (10 en lugar de 19) y la ventana anclada es la misma en los años que
coinciden, así que la elección no cambia: 189 en 2007 y 2009 y 252 desde 2011. Fuera de muestra da 0,95, lo mismo que
el anclado anual, con 1,04 dentro. Reoptimizar con más frecuencia no mejora nada cuando el efecto es estable; con un
efecto cambiante podría hacerlo, a costa de más ruido en la elección.

2. En el walk-forward sobre el ruido, ¿cuántas veces la estrategia elegida tiene Sharpe positivo fuera de muestra?
   Compáralo con lo que esperarías por azar.

**Solución.**

```python
positivos = int((ruido["sh_fuera"] > 0).sum())
print(f"{positivos} de {len(ruido)} trimestres con Sharpe positivo")
from scipy.stats import binom
print(f"P(≥ {positivos} de {len(ruido)} con p = 0,5): "
      f"{binom.sf(positivos - 1, len(ruido), 0.5):.2f}")
```
```output
6 de 12 trimestres con Sharpe positivo
P(≥ 6 de 12 con p = 0,5): 0.61
```

Son 6 de 12, justo lo que se esperaría lanzando una moneda (una probabilidad de 0,61 de obtener 6 o más). Un
procedimiento con ventaja debería dar más trimestres positivos que una moneda; este no lo hace. Con 12 ventanas, eso
sí, la prueba tiene muy poca potencia: incluso una estrategia con Sharpe 1 tendría muchos trimestres negativos.

3. Un colega te enseña un walk-forward con un Sharpe fuera de muestra de 1,4 y una eficiencia de 0,9, y te dice que lo
   probó «con unas cuantas ventanas hasta que salió estable». ¿Qué le preguntas y qué harías con el resultado?

**Solución.**

Le preguntaría cuántas combinaciones de ventanas, rejillas y criterios probó, y qué resultados dieron las demás. Si
eligió la configuración del walk-forward mirando la serie fuera de muestra, esa serie es dentro de muestra para la
metadecisión: el 1,4 es el máximo de varias pruebas. Lo trataría como el Sharpe del mejor de N intentos y lo
deflactaría con el número total de combinaciones (lección 2.5.4); si es posible, reservaría un periodo final que nadie
haya mirado y lo evaluaría allí una sola vez con la configuración ya congelada.

## Autoevaluación

1. ¿Qué diferencia un walk-forward anclado de uno móvil?

   - A. El anclado usa datos diarios y el móvil, semanales
   - B. En el anclado la ventana de optimización empieza siempre en el mismo día y crece; en el móvil tiene una longitud fija y avanza
   - C. El anclado no tiene ventanas fuera de muestra
   - D. El móvil vuelve a optimizar con los datos fuera de muestra de la misma ventana

   *Respuesta B: En el anclado la ventana de optimización empieza siempre en el mismo día y crece; en el móvil tiene una longitud fija y avanza.* Los dos avanzan la ventana de prueba. El anclado acumula toda la historia anterior (más datos, adaptación lenta); el móvil olvida lo más antiguo (menos datos, adaptación rápida y más ruido en la elección).

2. En el walk-forward móvil de 5 años sobre los futuros, el Sharpe medio dentro de muestra es 1,17 y el de las ventanas fuera de muestra encadenadas, 0,70. ¿Qué mide la diferencia?

   - A. Los costes de transacción
   - B. La parte del Sharpe dentro de muestra que venía de elegir el mejor parámetro con esos mismos datos: optimismo de la selección
   - C. Un error del cálculo del Sharpe
   - D. El efecto de los rolls de los futuros

   *Respuesta B: La parte del Sharpe dentro de muestra que venía de elegir el mejor parámetro con esos mismos datos: optimismo de la selección.* Dentro de muestra se ve el máximo de ocho variantes; fuera, el resultado de una sola, elegida antes de verla. La caída es la medida directa del sobreajuste del procedimiento.

3. Un walk-forward sobre 1.000 estrategias sin ventaja elige cada trimestre la mejor del año anterior. ¿Qué patrón es esperable?

   - A. Sharpe dentro y fuera parecidos, en torno a 0
   - B. Sharpe muy alto dentro (el máximo de mil ruidos) y en torno a 0 fuera, con una estrategia distinta casi cada trimestre
   - C. Sharpe negativo dentro y positivo fuera
   - D. La misma estrategia en todas las ventanas

   *Respuesta B: Sharpe muy alto dentro (el máximo de mil ruidos) y en torno a 0 fuera, con una estrategia distinta casi cada trimestre.* En `ruido2_estrategias` el walk-forward da un Sharpe medio de 3,46 dentro y −0,53 fuera, y elige 10 estrategias distintas en 12 trimestres. Es la firma del ruido.

4. Tras un walk-forward decepcionante, se prueban ventanas de 3, 5 y 10 años y se publica la de 3 porque da el mejor Sharpe fuera de muestra. ¿Qué problema hay?

   - A. Ninguno: el resultado sigue siendo fuera de muestra
   - B. La longitud de la ventana se ha elegido mirando los resultados fuera de muestra: el walk-forward se ha convertido en una variante más de la selección
   - C. Que 3 años es poco para cualquier estrategia
   - D. Que el walk-forward anclado siempre es mejor

   *Respuesta B: La longitud de la ventana se ha elegido mirando los resultados fuera de muestra: el walk-forward se ha convertido en una variante más de la selección.* Las metadecisiones del procedimiento (longitud de las ventanas, rejilla de parámetros, criterio de elección) también son parámetros. Elegirlas mirando el resultado final reintroduce el sesgo de selección de la lección 2.5.1.

5. ¿Por qué no basta con una sola separación (por ejemplo, 2002-2013 dentro y 2014-2025 fuera)?

   - A. Porque es demasiado larga
   - B. Porque es una sola tirada: el resultado depende de qué régimen cae en cada lado, y no dice nada de la estabilidad del parámetro elegido
   - C. Porque el out-of-sample debe ser siempre de un año
   - D. Porque una sola separación usa información del futuro

   *Respuesta B: Porque es una sola tirada: el resultado depende de qué régimen cae en cada lado, y no dice nada de la estabilidad del parámetro elegido.* Con una sola separación se obtiene un número. El walk-forward da una serie de elecciones y resultados que permite ver cómo cambia el parámetro y cuánto se pierde en cada periodo.

## Ideas clave

- Dentro de muestra se elige; fuera de muestra se mide, una sola vez. Cualquier corrección tras mirar fuera de muestra la convierte en dentro.
- El walk-forward repite la separación: optimiza en una ventana, prueba en la siguiente y avanza. El anclado acumula historia; el móvil la olvida.
- En los futuros sintéticos, el anclado pasa de un Sharpe medio de 1,02 dentro a 0,95 fuera, con un solo cambio de lookback; el móvil de 5 años, de 1,17 a 0,70, con siete cambios en 19 años.
- En 1.000 estrategias de ruido, el walk-forward da 3,46 dentro y −0,53 fuera: la caída grande y los parámetros inestables son la firma del sobreajuste.
- La longitud de las ventanas, la rejilla y el criterio también son parámetros: se fijan antes de mirar y se cuentan como intentos.

## Recursos

### Research

- [Estacionalidad Intradiaria IV - Busqueda de campo](https://pythonparatrading.com/estacionalidad-intradia-optimizacion/) · Research-0027 · 7 min

### Documentación

- [scikit-learn: TimeSeriesSplit (ventanas expandidas o de longitud fija)](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.TimeSeriesSplit.html) · scikit-learn.org

### Libros

- [Pardo, R. (2008): The Evaluation and Optimization of Trading Strategies, 2.ª ed. Wiley](https://www.wiley.com/en-us/The+Evaluation+and+Optimization+of+Trading+Strategies%2C+2nd+Edition-p-9780470128015) · wiley.com
- [Bailey, Borwein, López de Prado y Zhu (2014): Pseudo-Mathematics and Financial Charlatanism. Notices of the AMS 61(5), 458-471](https://doi.org/10.1090/noti1105) · 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
- [Datos del nivel 2: 1.000 estrategias de ruido y cinco con ventaja (Parquet)](https://pythonparatrading.com/curso/datos/nivel-2/ruido2_estrategias.parquet) · Descarga · ruido2_estrategias.parquet

Lección anterior: [2.5.1 · Sesgos de backtest: anticipación, supervivencia, selección y narrativa](https://pythonparatrading.com/curso-python-trading/nivel-2/sesgos-de-backtest/)

Lección siguiente: [2.5.3 · Validación cruzada combinatoria purgada para estrategias](https://pythonparatrading.com/curso-python-trading/nivel-2/validacion-cruzada-combinatoria-purgada-cpcv/)
