Strasmore Research
Deep Dives · Matt ConnorBy Matt Connor ·

Bandas de confianza bootstrap para un backtest

Una curva de capital es una sola muestra. Aprende a construir y leer un intervalo bootstrap para el Sharpe de un backtest, que a menudo incluye cero.

Un intervalo de confianza de un backtest responde una pregunta: ¿qué parte de la curva de capital corresponde a la estrategia y qué parte a la muestra concreta de historia sobre la que se ejecutó? El bootstrap de un backtest construye ese intervalo mediante el remuestreo repetido de la serie de rendimientos. Después, recalcula el estadístico en cada remuestra y obtiene los percentiles de los resultados. Un Sharpe de 1.4 medido durante un solo año de observaciones diarias tiene un intervalo del 95 por ciento lo bastante amplio como para incluir cero. Los paneles siguientes muestran por qué.

Por qué una sola curva de capital es una sola muestra

Un backtest entrega un número por estadística: un ratio de Sharpe, un rendimiento anualizado, la peor caída y una tasa de aciertos. Ninguno representa el valor verdadero de la estrategia. Cada uno es una estimación construida a partir de un periodo finito de sesiones. Otro periodo de igual duración habría mostrado un resultado diferente.

El panel siguiente elimina por completo la estrategia del análisis. Mide la posición más sencilla posible: mantener SPY durante un año calendario, utilizando los rendimientos de precio de cierre a cierre.

ConsultaUna posición, un año a la vez: Sharpe anualizado de SPY por año calendario
El SQL exacto detrás de cada cifra
WITH daily AS
(
    SELECT
        date,
        toFloat64(close) / nullIf(lagInFrame(toFloat64(close), 1)
            OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW), 0) - 1 AS ret
    FROM global_markets.stocks_daily_aggs
    WHERE ticker = 'SPY'
      AND date >= '2011-12-01'
      AND date <  '2026-01-01'
)
SELECT
    toString(toYear(date))                            AS year,
    count()                                           AS obs_count,
    round(avg(ret) * 252 * 100, 2)                    AS ann_return_pct,
    round(stddevSamp(ret) * sqrt(252) * 100, 2)       AS ann_vol_pct,
    round(avg(ret) / stddevSamp(ret) * sqrt(252), 2)  AS sharpe_ratio
FROM daily
WHERE date >= '2012-01-01'
  AND ret IS NOT NULL
GROUP BY year
ORDER BY year
Run this yourself

Cada fila cubre aproximadamente 250 sesiones, cerca del año bursátil estándar. La posición no cambió entre los distintos periodos. En 2012, el Sharpe anualizado fue de 1.06; en 2025, fue de 0.88, con 14 años representados en el gráfico. Mantener un índice amplio durante un año puede mover la cifra principal mucho más de lo que la mayoría de los lectores considerarían ruido. Un backtest de una estrategia con la misma duración hereda, como mínimo, esa variabilidad. La convención de anualización utilizada en la columna se explica en nuestra guía sobre el ratio de Sharpe, y la mecánica de los rendimientos del periodo, en cómo se miden los rendimientos mensuales.

¿Qué tan amplio es el rango de un Sharpe obtenido en un backtest?

El error estándar de una estimación de Sharpe disminuye aproximadamente con la raíz cuadrada del número de observaciones. En lugar de basarnos en esa fórmula, midamos directamente la dispersión. Dividimos veinte años de sesiones en ventanas no superpuestas de una longitud fija, calculamos el Sharpe anualizado dentro de cada ventana y observamos qué tan separados quedan esos valores.

ConsultaDispersión del Sharpe medida en ventanas no superpuestas de SPY, 2006 a 2025
El SQL exacto detrás de cada cifra
WITH daily AS
(
    SELECT
        date,
        toFloat64(close) / nullIf(lagInFrame(toFloat64(close), 1)
            OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW), 0) - 1 AS ret
    FROM global_markets.stocks_daily_aggs
    WHERE ticker = 'SPY'
      AND date >= '2005-12-01'
      AND date <  '2026-01-01'
),
indexed AS
(
    SELECT
        ret,
        row_number() OVER (ORDER BY date) AS i
    FROM daily
    WHERE date >= '2006-01-01'
      AND ret IS NOT NULL
),
blocks AS
(
    SELECT
        w,
        intDiv(i, w)                                            AS blk,
        count()                                                 AS n,
        avg(ret) / nullIf(stddevSamp(ret), 0) * sqrt(252)        AS sharpe
    FROM indexed
    CROSS JOIN (SELECT arrayJoin([21, 63, 126, 252, 504]) AS w) AS ws
    GROUP BY w, blk
    HAVING n = w
)
SELECT
    concat(toString(w), ' sessions')                          AS horizon,
    count()                                                   AS block_count,
    round(quantileDeterministic(0.05)(sharpe, blk), 2)        AS sharpe_p05,
    round(quantileDeterministic(0.50)(sharpe, blk), 2)        AS sharpe_p50,
    round(quantileDeterministic(0.95)(sharpe, blk), 2)        AS sharpe_p95,
    round(quantileDeterministic(0.95)(sharpe, blk)
        - quantileDeterministic(0.05)(sharpe, blk), 2)        AS sharpe_band_width
FROM blocks
WHERE sharpe IS NOT NULL
GROUP BY w
ORDER BY w
Run this yourself

Con 21 sessions por ventana, el percentil 5 al 95 de los Sharpe medidos va de -3.37 a 6.97: una dispersión de 10.34 puntos de Sharpe en 238 ventanas. Si ampliamos cada ventana a 504 sessions, la dispersión baja a 1.67 puntos, medida ahora sobre solo 8 ventanas. Hay dos factores que cambian al mismo tiempo. La estimación gana precisión con un mayor tamaño de muestra, pero la cantidad de muestras independientes que puede aportar el historial se reduce. Esa tensión es el tema central.

Cómo construir un intervalo de confianza bootstrap para un backtest

El procedimiento es lo bastante breve como para escribirlo completo.

  1. Comienza con la serie de retornos realizados de la estrategia, un dato por periodo, N en total.
  2. Extrae N retornos de esa lista al azar, con reemplazo. Algunos aparecen dos veces y otros no aparecen.
  3. Vuelve a calcular el estadístico sobre la remuestra.
  4. Repite el proceso varios miles de veces y conserva todos los valores.
  5. Ordena los valores almacenados y toma los percentiles 2.5 y 97.5 para obtener un intervalo del 95 por ciento.

Antes del paso 2, fija la semilla del generador de números aleatorios y registra esa semilla junto al intervalo publicado. Un bootstrap es una estimación de Monte Carlo: dos ejecuciones sin semilla pueden diferir en los últimos dígitos. Además, un revisor que no pueda volver a ejecutar tu intervalo no tiene forma de comprobarlo. Es la misma disciplina descrita en una configuración reproducible del backtest.

El retorno medio y el Sharpe pasan sin problemas por el paso 2, porque ambos leen la lista de retornos como un conjunto. El drawdown máximo no. El drawdown depende del orden de la trayectoria. Una remuestra que reordena los retornos puede producir un drawdown máximo que no generó ninguna ordenación de la secuencia real de operaciones. Aun así, vale la pena aplicar bootstrap, siempre que el resultado se etiquete correctamente: representa la distribución del drawdown en historiales reordenados, no un pronóstico del próximo drawdown. La definición aparece en drawdown máximo.

Por qué el bootstrap IID es incorrecto para los rendimientos del mercado

El paso 2 supone que cada rendimiento es independiente y tiene la misma distribución: el supuesto IID. Los rendimientos diarios lo incumplen de una forma que modifica el ancho del intervalo. Los rendimientos con signo solo conservan una memoria de un día muy débil. Sus magnitudes se agrupan: los movimientos grandes tienden a suceder junto a otros movimientos grandes, y los días tranquilos se presentan en rachas.

ConsultaAutocorrelación de primer rezago: rendimientos con signo frente a rendimientos absolutos, 2016 a 2025
El SQL exacto detrás de cada cifra
WITH daily AS
(
    SELECT
        ticker,
        date,
        toFloat64(close) / nullIf(lagInFrame(toFloat64(close), 1)
            OVER (PARTITION BY ticker ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW), 0) - 1 AS ret
    FROM global_markets.stocks_daily_aggs
    WHERE ticker IN ('SPY', 'MSFT', 'KO', 'XOM', 'JNJ', 'PG')
      AND date >= '2015-11-01'
      AND date <  '2026-01-01'
),
lagged AS
(
    SELECT
        ticker,
        date,
        ret,
        lagInFrame(ret, 1) OVER (PARTITION BY ticker ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW) AS ret_prev
    FROM daily
    WHERE ret IS NOT NULL
      AND abs(ret) < 0.35
)
SELECT
    ticker,
    count()                                AS obs_count,
    round(corr(ret, ret_prev), 3)          AS return_autocorr,
    round(corr(abs(ret), abs(ret_prev)), 3) AS abs_return_autocorr
FROM lagged
WHERE date >= '2016-01-01'
  AND ret_prev IS NOT NULL
GROUP BY ticker
ORDER BY abs_return_autocorr DESC
Run this yourself

Para SPY, la autocorrelación de primer rezago de los rendimientos diarios absolutos mide 0.366 en 2514 sesiones, frente a -0.133 en los rendimientos con signo de los mismos días. Compare las dos barras para cualquier nombre del gráfico. Barajar una serie de rendimientos destruye esa agrupación, y un bootstrap IID aplicado a estos datos reporta un intervalo más estrecho de lo que respalda la muestra. El error apunta en la dirección menos útil: favorece artificialmente a la estrategia.

El bootstrap móvil por bloques y la elección de la longitud del bloque

La solución es volver a muestrear bloques contiguos en lugar de rendimientos individuales. Elija una longitud de bloque L, extraiga al azar bloques de L rendimientos consecutivos con reemplazo y únalos hasta que la serie sintética tenga N observaciones. La dependencia dentro de cada bloque se mantiene intacta: esos rendimientos conservan su orden original. Solo las uniones entre bloques son artificiales.

La longitud del bloque contrapone dos errores, y ningún ajuste evita ambos. Los bloques cortos se comportan como el bootstrap IID y subestiman el intervalo; eso es sesgo. Los bloques largos conservan más dependencia, pero dejan menos bloques distintos para extraer. Por ello, cada remuestra repite grandes segmentos de la misma historia y el propio intervalo se vuelve más ruidoso; eso es varianza. Las reglas prácticas publicadas escalan la longitud con N elevado a la potencia de un tercio. Tómalas como puntos de partida.

Un diagnóstico más económico consiste en comprobar cómo escala la varianza con el horizonte. Bajo independencia, la varianza de la suma de rendimientos de k días equivale a k veces la varianza de un día, y el cociente entre ambas se mantiene en 1.

ConsultaRazón de varianza por longitud de bloque: ¿la varianza de SPY escala como observaciones independientes?
El SQL exacto detrás de cada cifra
WITH daily AS
(
    SELECT
        date,
        toFloat64(close) / nullIf(lagInFrame(toFloat64(close), 1)
            OVER (ORDER BY date ROWS BETWEEN 1 PRECEDING AND CURRENT ROW), 0) - 1 AS ret
    FROM global_markets.stocks_daily_aggs
    WHERE ticker = 'SPY'
      AND date >= '2005-12-01'
      AND date <  '2026-01-01'
),
indexed AS
(
    SELECT
        ret,
        row_number() OVER (ORDER BY date) AS i
    FROM daily
    WHERE date >= '2006-01-01'
      AND ret IS NOT NULL
),
base AS
(
    SELECT varSamp(ret) AS var_1d FROM indexed
),
blocks AS
(
    SELECT
        k,
        intDiv(i, k)  AS blk,
        count()       AS n,
        sum(ret)      AS block_ret
    FROM indexed
    CROSS JOIN (SELECT arrayJoin([2, 3, 5, 10, 21, 42, 63]) AS k) AS ks
    GROUP BY k, blk
    HAVING n = k
)
SELECT
    concat(toString(k), ' sessions')                          AS block_length,
    count()                                                   AS block_count,
    round(varSamp(block_ret) / (k * any(var_1d)), 3)          AS variance_ratio
FROM blocks
CROSS JOIN base
GROUP BY k
ORDER BY k
Run this yourself

En 2 sessions, el cociente fue de 0.844; en 63 sessions, fue de 0.584, calculado sobre 78 bloques no superpuestos. Los valores cercanos a 1 indican que la suma escala como lo harían observaciones independientes en ese horizonte. Los valores alejados de 1 señalan los horizontes en los que la dependencia todavía influye, y ese es el rango que debe cubrir la longitud del bloque. Elija el bloque más corto que cubra ese rango y después observe en cuánto se ha reducido el número de bloques.

Remuestreo por grupos y el número junto a cada intervalo

El remuestreo dentro de grupos, por régimen de volatilidad o por mes calendario, conserva la condición que hizo interesante la pregunta. Si la afirmación es que una estrategia obtiene su Sharpe en regímenes de alta volatilidad, el intervalo construido usando únicamente días de alta volatilidad es el que pone a prueba esa afirmación. El costo es aritmético. Dividir 250 observaciones en cuatro grupos deja aproximadamente 60 por grupo, y un bootstrap de 60 observaciones produce un intervalo cerca del doble de amplio que el de la muestra completa.

Por eso, reporte siempre el número de observaciones del grupo junto a cada intervalo. La columna block_count de los paneles anteriores hace visible ese criterio: un percentil 5 calculado sobre nueve ventanas es un objeto distinto de uno calculado sobre doscientas, aunque ambos se muestren con dos decimales.

Qué no puede corregir un bootstrap

Un bootstrap cuantifica una sola cosa: el ruido muestral de un estadístico calculado a partir de los datos disponibles. No indica si esos datos alguna vez pudieron obtenerse en tiempo real.

Un backtest contaminado por sesgo de anticipación produce una serie de rendimientos que nunca se pudo negociar. Un bootstrap de esa serie devuelve una banda estrecha y aparentemente confiable alrededor de una ficción. Un universo formado con los integrantes actuales de un índice incorpora sesgo de supervivencia, y cada remuestreo de ese universo lo hereda. Existe una tercera brecha: el efecto de selección. Si se ejecutan 200 variantes y se conserva la mejor, su intervalo bootstrap describe el ruido muestral de esa variante e ignora las 199 pruebas que se usaron para seleccionarla. Las bandas calculadas de forma rigurosa son útiles. No sustituyen a los datos fuera de muestra.

Notas sobre los datos y el método
  • Los rendimientos son variaciones de precio de cierre a cierre calculadas con barras diarias. Se excluyen los dividendos, por lo que el nivel de cada rendimiento y de cada cifra de Sharpe queda subestimado aproximadamente en el rendimiento por dividendos. La dispersión analizada en este artículo prácticamente no se ve afectada.
  • Las ventanas y los bloques no se superponen, de modo que cada estadístico medido utiliza un tramo independiente del historial. Las ventanas superpuestas inflarían el número aparente de observaciones.
  • Los cuantiles utilizan un estimador determinista. Por ello, al volver a ejecutar un panel se obtiene el mismo percentil y no una nueva aproximación.
  • El panel de autocorrelación utiliza seis acciones de gran capitalización que no registraron un split dentro de la ventana. Además, excluye cualquier día con un movimiento superior a 35%, lo que evita que los efectos de ajuste de precios distorsionen la correlación.

Preguntas frecuentes

¿Qué indica un intervalo de confianza bootstrap sobre un backtest?

Muestra el rango de valores que probablemente tomaría el estadístico si se repitiera el muestreo del mismo proceso durante el mismo número de periodos. Un intervalo del 95 por ciento que contiene cero indica que la muestra es demasiado corta para distinguir la estrategia de la ausencia total de ventaja.

¿Cuántas remuestras bootstrap son suficientes?

Para un intervalo del 95 por ciento, unos pocos miles de remuestras suelen ofrecer resultados estables, y 10,000 es un valor predeterminado habitual que requiere pocos recursos. Los cuantiles extremos necesitan más observaciones: el percentil 1 de 1,000 extracciones se obtiene aproximadamente a partir de diez valores.

¿Qué longitud de bloque debe usar un moving block bootstrap?

No existe una longitud universalmente correcta. Las reglas generales la relacionan con la raíz cúbica del tamaño de la muestra. Una comprobación práctica consiste en identificar el horizonte a partir del cual la varianza deja de escalar linealmente; el panel de ratio de varianza anterior mide ese punto. Publique la longitud utilizada junto con el intervalo.

¿Se puede aplicar bootstrap a un drawdown máximo?

Sí, pero hay que tener en cuenta el orden de las observaciones. El drawdown depende de la secuencia de rendimientos, por lo que una remuestra construida mediante aleatorización muestra el drawdown de una historia reordenada. Un bootstrap por bloques mantiene intactas las rachas cortas y es una herramienta más adecuada para las estadísticas que dependen de la trayectoria.

¿El bootstrap corrige el sobreajuste?

No. Mide el ruido de muestreo dentro de una única serie de rendimientos. El sesgo de anticipación y el efecto de selección derivado de probar muchas variantes quedan fuera de su alcance.


Cada panel incluye el SQL que lo generó. Cambie el ticker o la duración de la ventana y ejecútelo usted mismo en la terminal de Strasmore.

#backtesting#bootstrap#statistics#confidence intervals#sharpe ratio