22 de octubre de 2026 · datos

Reconstruimos un backtest después de que un proveedor de datos modificara su historial

Reconstruimos un backtest después de que un proveedor de datos modificara su historial

Seis semanas después de ejecutar un backtest, volvimos a ejecutarlo con el mismo código, la misma configuración y el mismo rango de fechas. El capital final era distinto. No era una diferencia enorme, pero bastaba para que el Sharpe pasara de 1.31 a 1.08 y cambiara cuál conjunto de parámetros parecía el mejor.

La estrategia no había cambiado. Los datos históricos, sí. Un proveedor había revisado varios días de operaciones y velas, y en la primera ejecución no habíamos conservado la instantánea exacta de los datos de entrada. Pasamos los días siguientes tratando de averiguar si el nuevo resultado se debía a una corrección de datos, a un error en el pipeline o a un proceso de investigación que no podíamos reproducir.

Lunes: la nueva ejecución no coincidía

La primera pista fue una pequeña diferencia en un par líquido del mercado spot. La vela original marcaba un máximo de 62,418. La nueva ejecución mostraba 62,431. La diferencia parecía inocua hasta que rastreamos la entrada de la estrategia: su umbral estaba entre esos precios. Unas cuantas velas distintas bastaron para generar señales diferentes y, varios días después, posiciones distintas.

Comparamos los conteos diarios de operaciones y vimos que la discrepancia comenzaba en una sola fecha. Después comparamos las filas sin procesar que alimentaban esa fecha. La respuesta actual del proveedor incluía algunas operaciones corregidas y una vela agregada revisada. La respuesta que habíamos usado no incluía ningún registro de cambios.

Este comportamiento es normal en los datos. Los exchanges reparan registros; los proveedores completan lagunas, corrigen operaciones erróneas y ajustan la lógica de agregación. Una consulta de «datos de enero» puede devolver un resultado distinto en marzo.

Martes: encontramos el comprobante que faltaba

Habíamos guardado en el registro de la ejecución el nombre del proveedor, el símbolo y el rango de fechas. No habíamos guardado la respuesta ni un hash del contenido de cada partición de entrada. Eso significaba que podíamos demostrar qué habíamos solicitado, pero no qué bytes había devuelto el proveedor.

Recuperamos los archivos en caché más antiguos que encontramos y los comparamos con la respuesta actual. La diferencia no se debía a un ajuste por split ni a una conversión de zona horaria. Era un pequeño conjunto de operaciones corregidas que pasaban por nuestro generador de velas. Eso descartó una hipótesis, pero la caché estaba incompleta, así que seguíamos sin poder reconstruir con exactitud todas las velas afectadas.

Qué registramosQué podíamos averiguarQué faltaba
Proveedor, símbolo, rango de fechasQué solicitud hizo la ejecuciónEl contenido de la respuesta
Versión del pipelineQué código generó las velasLas filas exactas de entrada
Marca de tiempo de la ejecuciónCuándo ejecutamos el trabajoQué revisión del proveedor estaba disponible entonces

Habíamos dado por hecho que «misma consulta» significaba «mismos datos». Esa suposición hacía que el experimento pareciera más reproducible de lo que era.

Miércoles: la corrección cambió la historia

Reprodujimos las fechas afectadas con ambas versiones de los datos disponibles. Las velas revisadas eliminaron 2 entradas y adelantaron 1 salida una vela. Esas 3 operaciones explicaban la mayor parte del cambio en el Sharpe. La estrategia era sensible a unas pocas observaciones cercanas a su activador, aunque la serie de precios general pareciera casi idéntica en un gráfico.

Eso no significaba que el resultado original fuera fraudulento ni que el resultado revisado fuera automáticamente mejor. Significaba que había que delimitar la afirmación: la ejecución anterior describía la instantánea de datos que teníamos en ese momento, mientras que la nueva describía el historial actual del proveedor. No podíamos decir honestamente que una reprodujera la otra.

Jueves: cambiamos el registro de investigación

Añadimos instantáneas inmutables de los datos de entrada a las nuevas ejecuciones de investigación. Cada partición recibe un hash de contenido, y el manifiesto de la ejecución registra la fuente, la hora de recuperación, la versión del esquema, la política de ajuste y la versión del código de transformación. Para los conjuntos de datos grandes, guardamos los archivos en un almacenamiento direccionado por contenido, de modo que las particiones idénticas puedan compartirse sin sobrescribir versiones anteriores.

Cuando las reglas de almacenamiento o las licencias nos impiden conservar los datos sin procesar, guardamos la evidencia más completa que permiten las condiciones: hashes, parámetros de solicitud, identificadores de revisión de la fuente y una pequeña muestra de filas analizadas para diagnosticar problemas. Un hash no puede recuperar los datos, pero sí puede indicarnos cuándo 2 ejecuciones no usaron bytes idénticos.

Un backtest reproducible necesita identificar los datos de entrada, no solo tener una URL de datos. Registra qué llegó, cuándo llegó y cómo se convirtió en las filas que consumió tu estrategia.

Viernes: qué nos saltaríamos la próxima vez

Nos saltaríamos comparar únicamente las métricas principales. Un cambio en el Sharpe indica que algo varió, pero no localiza la causa. Empezaríamos por los conteos de filas y los hashes de las particiones; después buscaríamos la primera fecha de entrada diferente. A partir de ahí, compararíamos, en ese orden, las filas transformadas, las señales, las órdenes y las ejecuciones.

También nos saltaríamos sustituir en silencio los datos antiguos por un historial corregido. Las correcciones son útiles, pero las ejecuciones anteriores deben seguir vinculadas a sus datos de entrada originales. Un conjunto de datos nuevo debe dar lugar a una ejecución nueva, con una relación visible con la anterior.

Nuestra regla práctica ahora es sencilla: si queremos que una nueva ejecución reproduzca un experimento, fijamos la instantánea de datos. Si queremos aprovechar un historial corregido, la etiquetamos como un experimento nuevo. Tardamos unos días en aprender la diferencia; ahora queda registrada en una línea del manifiesto de la ejecución.

datos de mercadobacktestingingeniería de datosreproducibilidad
← Todos los artículos