3 de septiembre de 2026 · ingeniería de datos

Los minutos que no existen: huecos, interrupciones y velas de volumen cero en el histórico OHLCV

Los minutos que no existen: huecos, interrupciones y velas de volumen cero en el histórico OHLCV

Un año natural de velas de 1 minuto debería tener 525,600 filas. Nuestra descarga de 2025 de perpetuos BTCUSDT volvió con 525,557: faltaban 43 minutos, con una tasa de integridad del 99.992%. Un perpetuo de una altcoin de capitalización media en la misma plataforma, con la misma descarga y el mismo proceso, volvió con 1,247 minutos menos: 99.76%. Y una serie de minutos de acciones estadounidenses del mismo año tenía aproximadamente 427,000 filas menos que la de criptomonedas, lo cual no es un hueco en absoluto: simplemente, el mercado cierra.

Tres de esas cifras no tienen nada de especial. La que merece mil palabras es 43.

525,600velas de 1 minuto en un año no bisiesto
0.008%ausentes en BTCUSDT, 2025
61%de esos minutos ocurrieron en horas de volatilidad del decil superior

Cuatro cosas distintas que parecen un hueco en tu dataframe

Antes de hablar de los 43, conviene clasificar los casos, porque la mayoría del código para gestionar huecos ya falla en este nivel. Cuando falta una fila en tu parquet local, no sabes cuál de estas causas la produjo, y la respuesta adecuada cambia según el caso.

TipoQué ocurrió realmenteCómo apareceRespuesta adecuada
Sin operacionesEl mercado estaba abierto; nadie cruzó el spread durante ese minutoVela de volumen cero (OHLC idénticos, trades=0) en algunos endpoints; fila ausente en otrosConsérvala. Es información real: nadie quiso operar.
Plataforma caídaMotor de casación fuera de servicio, con o sin avisoFila ausente, a veces en una serie de variasMárcala como obsoleta. No operes durante ese intervalo.
Instrumento suspendidoIncumplimiento de la banda LULD, noticias pendientes, aviso de exclusión de cotizaciónFila ausente y, después, una operación en la subasta de reaperturaMárcala como obsoleta y trata la reapertura como una discontinuidad.
Error tuyoPaginación limitada por tasa, un reintento que omitió una página, un error de límites de zona horaria en el bucleFila ausente, indistinguible de los casos anterioresDetéctala y vuelve a descargarla. Este sí lo puedes corregir.

Las plataformas difieren en cómo representan el primer caso de la tabla, y ahí es donde empiezan los problemas. El endpoint de velas de Coinbase omite por completo los intervalos vacíos, así que el histórico de un par poco líquido está lleno de huecos literales. Los klines de Binance suelen darte una vela sintética con volumen 0 y open=high=low=close fijados al último precio negociado. Es el mismo hecho subyacente, con dos formas distintas; un cargador que reindexa sobre una cuadrícula de minutos completa convierte una forma en la otra sin avisarte. Comprueba qué hace tu plataforma antes de escribir el código para rellenar huecos, no después.

Los 1,247 minutos ausentes de la altcoin eran casi todos del primer tipo: un libro poco profundo a las 04:00 UTC de un domingo, sin nadie operando. Molesto, fácil de entender y en gran medida inofensivo, porque la estrategia tampoco habría operado entonces. Justamente por eso dejé de mirarlo y volví a los 43.

Los 43 minutos no estaban repartidos al azar

Si las ausencias fueran uniformes, 43 minutos repartidos a lo largo de un año serían 43 casos aislados, uno cada ocho días y medio, cada uno un error de redondeo. No fue eso lo que obtuvimos. Se agruparon en seis intervalos: uno de 19 minutos consecutivos, uno de 11, dos de 4 y un par de intervalos de 2. Seis incidentes, no 43 accidentes.

Y estos incidentes dependen de aquello que te importa. Las plataformas se caen cuando están bajo carga, y están bajo carga cuando el precio se mueve. Agrupé todas las horas del año según la volatilidad realizada y comprobé dónde aparecían los minutos ausentes: el 61% cayó en el decil superior. La probabilidad incondicional de que falte un minuto cualquiera es 0.008%. Si el minuto cae dentro de una hora de volatilidad del decil superior, es aproximadamente 0.05%: seis veces más alta y, además, concentrada en grupos.

Así que la métrica de integridad de tu panel de calidad de datos mide lo que no corresponde. 99.992% parece un conjunto de datos en el que ya no hace falta pensar. En realidad, describe una serie completa durante las horas en que tu estrategia no hace nada y agujereada durante aquellas en las que lo hace todo. Un sistema de momentum que se activa cuando aumenta la volatilidad tiene muchas más probabilidades de encontrarse con un hueco de lo que sugiere la cifra general, y lo encuentra en mitad de una operación.

Lo que provoca el relleno hacia delante tres líneas después

Este es el fallo que me llevó a escribir esto. Tomemos el intervalo de 19 minutos. La limpieza de datos habitual: reindexar sobre la cuadrícula completa de minutos, rellenar hacia delante el OHLC con el último cierre y fijar el volumen en cero. Ahora la serie es continua y tus indicadores se ejecutan sin un NaN a la vista.

Esas 19 velas tienen high == low == close. El rango verdadero es cero en todas. Un ATR(14) calculado en ese intervalo, después de una lectura previa a la caída de unos 240 USDT, baja hasta aproximadamente 34 cuando vuelve la plataforma: las cinco velas reales que quedan en el intervalo aportan todo el promedio. Ahora pásalo a un dimensionador de posiciones que escala según la volatilidad, del tipo habitual size = risk_budget / ATR. El tamaño se multiplica por siete.

La siguiente vela real es la operación de reapertura, y no es una vela tranquila. En nuestro caso abrió un 1.8% por encima del último cierre anterior a la caída. El backtest aceptó sin problemas una posición 7x ante un hueco del 1.8%, con un fill que no podría haber existido, a un precio que nadie estaba cotizando. Esa única operación sintética valía más que un mes de P&L legítimo en la curva de capital, pero en la dirección equivocada; nació enteramente de una línea de limpieza de datos escrita para dejar el dataframe ordenado.

Eliminar las filas en vez de rellenarlas tampoco soluciona el problema; es el mismo fallo con otro disfraz. Al eliminarlas, los periodos retrospectivos indexados por número de vela mienten: una «EMA de 20 velas» ahora abarca 39 minutos de reloj a través de la caída, el rendimiento entre velas en el límite es todo el salto del 1.8% tratado como movimiento de un minuto, y cualquier estimación de volatilidad por vela lo interpreta como un evento de 60 sigma. Nada te avisa. El índice sigue siendo monótono.

El remuestreo es donde el problema se vuelve invisible

La mayor parte de la investigación no se hace con velas de 1 minuto, sino con datos agregados, y la agregación encubre el problema. Al remuestrear a 5 minutos, un hueco de 19 minutos se convierte en cuatro velas, de las cuales la primera y la última son parciales. Pandas calcula un OHLC que parece perfectamente razonable a partir de dos minutos supervivientes y les asigna la misma etiqueta que a una vela construida con cinco. Nada en el resultado permite distinguirlas.

La solución más sencilla que conozco: conservar una columna bars_in_window en cada remuestreo y no descartarla nunca. Un entero por fila, y cualquier pregunta posterior sobre la fiabilidad de una vela tiene respuesta. También conservamos seconds_since_last_real_print, que contiene la misma información en un formato que la capa de ejecución puede usar.

La política, por llamarla así

Lo que aplican ahora nuestros agentes, en orden:

  1. Nunca reindexar sin avisar. El cargador genera un manifiesto de huecos: inicio, fin, duración y cuál de las cuatro categorías considera que corresponde. Si un intervalo de minutos ausentes dura menos de 3 velas y el volumen de las velas circundantes es bajo, lo consideramos un minuto sin operaciones. Cualquier intervalo más largo durante horas de actividad se trata como una caída hasta demostrar lo contrario.
  2. Vuelve a descargar antes de interpretar. La mitad de nuestros primeros huecos se debían a errores de paginación. Una segunda descarga desde otro endpoint u otro proveedor resuelve la categoría cuatro y reduce el problema antes de que haya que sacar conclusiones.
  3. Usa un filtro de antigüedad, no rellenes huecos. La estrategia recibe una entrada data_age y una regla estricta: no abrir posiciones nuevas cuando la última operación real tiene más de N velas de antigüedad, y cerrar las posiciones abiertas en la reapertura solo con una orden de mercado cuyo precio aplique un descuento explícito por riesgo de hueco. Los fills imposibles son peores que las operaciones que no se hicieron.
  4. Los indicadores reciben NaN, no ficción. Los precios rellenados hacia delante nunca llegan a la capa de características. Si no se puede calcular ATR, queda sin definir, y sin definir significa posición plana. Un fallo visible es mejor que un 7x silencioso.
  5. Informa del rendimiento condicionado por los huecos. Cada backtest que publicamos muestra el P&L junto a una versión que excluye las operaciones cercanas a caídas. Si esas operaciones sostienen el resultado, el resultado es un artefacto de los datos.

Una auditoría rápida que puedes hacer hoy: agrupa tus minutos ausentes en intervalos consecutivos y comprueba qué proporción de las operaciones del backtest se abre o se cierra en los 30 minutos anteriores o posteriores al límite de un intervalo. Si es menos del 1%, probablemente los huecos no influyen en nada. Si es del 5% o más, la curva de capital cuenta en parte una historia sobre las caídas de la plataforma.

La señal en la que he aprendido a confiar es la forma en que se distribuyen las ausencias, más que su cantidad. Un conjunto de datos con miles de huecos dispersos en horas muertas suele estar bien. Uno con unos pocos grupos compactos te está diciendo que algo falla bajo carga, y lo que falla bajo carga es justo donde opera tu estrategia.

huecos OHLCVingeniería de datosbacktestingremuestreofuturos de criptomonedas
← Todos los artículos