Lección 2.5.2 · 60 min
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.
- Módulo
- 2.5 · Sesgos y validación
- Duración
- 60 min
Lecciones del nivel 2
2.1 · Estadística y econometría para trading
- 2.1.1 Contrastes de hipótesis, p-valores, bootstrap y regresión con errores robustos
- 2.1.2 Estacionariedad: ADF, KPSS, diferenciación y diferenciación fraccional
- 2.1.3 Modelos ARMA y ARIMA: autocorrelación simple (ACF) y parcial (PACF)
- 2.1.4 Modelos de volatilidad: ARCH, GARCH, EGARCH y GJR con la librería arch
- 2.1.5 Cointegración: Engle-Granger, Johansen y vida media de la reversión
- 2.1.6 Filtro de Kalman para hedge ratios dinámicos
- 2.1.7 Detección de regímenes: modelos de Markov ocultos y cambios estructurales
- 2.1.8 El problema de las pruebas múltiples: Bonferroni, FDR y el «factor zoo»
2.2 · Anatomía de un sistema de trading
- 2.2.1 Hipótesis de ventaja: riesgo, comportamiento, estructura e información
- 2.2.2 Universo, señal, filtro, entrada, salida, sizing y gestión del riesgo
- 2.2.3 Frecuencias: intradía, swing y posicional, y la capacidad de una estrategia
- 2.2.4 Diario de investigación y registro de experimentos contra el data snooping
2.3 · Familias de estrategias
- 2.3.1 Seguimiento de tendencia y momentum
- 2.3.2 Reversión a la media: RSI y Bollinger, reversión intradía y huecos
- 2.3.3 Pairs trading y arbitraje estadístico: distancia, cointegración y Kalman
- 2.3.4 Carry en divisas, futuros y bonos; estacionalidad y efectos calendario
- 2.3.5 Factores (value, calidad, baja volatilidad, tamaño) y estrategias de eventos
- 2.3.6 Introducción al market making: inventario, spread y selección adversa (Avellaneda-Stoikov)
- 2.3.7 Estrategias específicas de cripto: momentum, arbitraje de funding y basis
2.4 · Backtesting: motores y paradigmas
- 2.4.1 Backtest vectorizado desde cero: matrices, desfase, costes y límites
- 2.4.2 Backtest por eventos desde cero: bucle, cartera, órdenes y fills
- 2.4.3 vectorbt: barridos masivos de parámetros y simulación de carteras
- 2.4.4 backtesting.py y backtrader: dos motores didácticos y su estado
- 2.4.5 NautilusTrader: arquitectura, datos, estrategias y el mismo código en backtest y en real
- 2.4.6 LEAN/QuantConnect y Zipline Reloaded; bt para asignación de activos
- 2.4.7 Modelado de costes: comisiones, horquilla, impacto, financiación y préstamo de títulos
- 2.4.8 Modelado de fills: órdenes limitadas, cola, fills parciales, huecos y stops
2.5 · Sesgos y validación
- 2.5.1 Sesgos de backtest: anticipación, supervivencia, selección y narrativa
- 2.5.2 In-sample y out-of-sample: walk-forward anclado y móvil
- 2.5.3 Validación cruzada combinatoria purgada para estrategias
- 2.5.4 Deflated Sharpe Ratio y Probability of Backtest Overfitting (PBO)
- 2.5.5 Monte Carlo: remuestreo de trades, bootstrap de bloques y permutaciones de señales
- 2.5.6 Sensibilidad de parámetros: mesetas frente a picos, robustez entre activos y periodos
- 2.5.7 Datos sintéticos para pruebas de estrés: GBM, GARCH y bootstrapping
- 2.5.8 Checklist de «backtest honesto» antes de pasar a paper trading
2.6 · Métricas de rendimiento
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.
Fuente 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.
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.
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(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.85Mirando 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:
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) 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.19Con 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:
- Se toma una ventana dentro de muestra que termina el día antes de la ventana de prueba.
- Se elige la variante con mejor criterio (aquí, el Sharpe) en esa ventana.
- Se aplica la variante elegida, sin tocarla, a la ventana fuera de muestra siguiente.
- 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.
Fuente 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:
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) 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.78Cada 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.
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):
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:
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) 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.0El 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.
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:
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) 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.0Con 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 (). 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.
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}") 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.53Cada 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
- 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
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}") 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.95Con 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.
- 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
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}")6 de 12 trimestres con Sharpe positivo
P(≥ 6 de 12 con p = 0,5): 0.61Son 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.
- 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.
Respuesta plegada en cada pregunta
Autoevaluación
¿Qué diferencia un walk-forward anclado de uno móvil?
- AEl anclado usa datos diarios y el móvil, semanales
- BEn 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
- CEl anclado no tiene ventanas fuera de muestra
- DEl móvil vuelve a optimizar con los datos fuera de muestra de la misma ventana
Ver respuesta
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).
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?
- ALos costes de transacción
- BLa parte del Sharpe dentro de muestra que venía de elegir el mejor parámetro con esos mismos datos: optimismo de la selección
- CUn error del cálculo del Sharpe
- DEl efecto de los rolls de los futuros
Ver respuesta
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.
Un walk-forward sobre 1.000 estrategias sin ventaja elige cada trimestre la mejor del año anterior. ¿Qué patrón es esperable?
- ASharpe dentro y fuera parecidos, en torno a 0
- BSharpe muy alto dentro (el máximo de mil ruidos) y en torno a 0 fuera, con una estrategia distinta casi cada trimestre
- CSharpe negativo dentro y positivo fuera
- DLa misma estrategia en todas las ventanas
Ver respuesta
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_estrategiasel 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.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?
- ANinguno: el resultado sigue siendo fuera de muestra
- BLa 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
- CQue 3 años es poco para cualquier estrategia
- DQue el walk-forward anclado siempre es mejor
Ver respuesta
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.
¿Por qué no basta con una sola separación (por ejemplo, 2002-2013 dentro y 2014-2025 fuera)?
- APorque es demasiado larga
- BPorque 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
- CPorque el out-of-sample debe ser siempre de un año
- DPorque una sola separación usa información del futuro
Ver respuesta
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.