Saltar al contenido
ALX Quant
← M0.5 · Puesta a punto: Python y tu primer script

Lección 418 min60 XPGratis

Agregar barras en pandas: de 15 minutos a 1 hora sin partir la sesión

Al terminar sabrás

  • Remuestrear barras OHLCV a un intervalo mayor con la regla correcta por columna
  • Alinear los bins de agregación al inicio de la sesión y no a la hora en punto
  • Detectar el error de agregación más común y explicar por qué no da error de ejecución

Antes de esta conviene haber hecho Descargar datos reales: tu primer CSV de mercado.


Convertir barras de 15 minutos en barras de una hora parece trivial: cuatro en una. Pero pandas, por defecto, alinea las horas a las en punto, y la sesión regular de ES empieza a las 8:30. Resultado: tu primera vela horaria tiene dos barras en vez de cuatro, la última tiene tres, y ninguna coincide con las que ves en TradingView. No da error. Solo da números distintos.

El problema

El remuestreo es la operación más frecuente en research de intradía y la que más bugs silenciosos produce: regla de agregación equivocada para alguna columna (media del volumen en vez de suma), bins desalineados con la sesión, etiqueta de la barra por cierre cuando el resto de tu código asume apertura. Esta lección cierra el Nivel 0 con la herramienta y las tres reglas para hacerlo bien.

Tres capas

Intuición

Tienes cuatro fotos de 15 minutos y quieres una de una hora. La apertura de la hora es la apertura de la primera foto; el cierre, el cierre de la última; el máximo, el más alto de las cuatro; el mínimo, el más bajo; el volumen, la suma. Eso es la regla por columna, y es siempre la misma.

Lo que cambia es dónde empiezas a contar la hora. Si la sesión empieza a las 8:30 y tú cuentas horas desde las 8:00, tu primera "hora" va de 8:00 a 9:00 y solo pilla las fotos de 8:30 y 8:45. Tienes que decirle a pandas: empieza a contar donde empiezan los datos. Con eso, la primera hora va de 8:30 a 9:30 y pilla las cuatro.

Ejemplo numérico

Las cuatro barras de M0.4.1 (8:30, 8:45, 9:00, 9:15 CT). Dos formas de agregarlas a 1 hora:

Bins a la hora en punto (por defecto):

BinBarras incluidasOpenHighLowCloseVolumen
8:008:30, 8:455.400,005.409,005.398,255.403,7570.100
9:009:00, 9:155.403,755.405,255.397,755.401,2543.900

Bins desde el inicio de la sesión (origin="start"):

BinBarras incluidasOpenHighLowCloseVolumen
8:30las cuatro5.400,005.409,005.397,755.401,25114.000

La segunda es la vela horaria que ves en TradingView con sesión RTH. La primera es una vela que no existe en ninguna plataforma, y es la que produce resample("1h") a secas. Ambas ejecutan sin error.

Formal

DataFrame.resample(regla, origin=..., label=..., closed=...) agrupa por intervalos de tiempo y aplica una función de agregación por columna. Parámetros que importan:

  • regla: "15min", "1h", "1D". Para barras diarias de futuros hay que definir el día de sesión (17:00 CT → 16:00 CT del día siguiente), lo que requiere desplazar el índice antes de agregar o usar origin con un offset; con "1D" a secas se agrupa por medianoche y se parte la sesión de Globex (M0.2.5).
  • origin: "start_day" (por defecto: medianoche del primer día), "start" (la primera marca temporal de los datos), "epoch", o un Timestamp explícito. Para sesiones que no empiezan a la hora en punto, origin="start" o un timestamp de apertura de sesión.
  • label y closed: qué extremo etiqueta el bin ("left" por defecto: la barra 8:30-9:30 se etiqueta 8:30, coherente con TradingView) y qué extremo está incluido. Cambiarlos sin motivo desplaza toda la serie un intervalo, que es una forma de look-ahead si luego usas la etiqueta como "momento en que el dato estaba disponible".
  • Agregación: agg({"open": "first", "high": "max", "low": "min", "close": "last", "volume": "sum"}). Cualquier otra combinación (media de precios, último volumen) es un error conceptual.

Bins vacíos. Si en un intervalo no hay barras (noche, festivo), resample produce una fila con NaN. Hay que decidir: eliminarla (dropna) o rellenarla; para OHLCV lo correcto es eliminarla, porque una barra sin operaciones no existe.

Zona horaria. El índice debe llevar zona (America/Chicago para definir la sesión de CME; UTC para almacenar). resample respeta la zona del índice; si los datos están en UTC y quieres bins de sesión de Chicago, conviertes antes (tz_convert("America/Chicago")), agregas, y vuelves a UTC para guardar.

Comprobación. Tras agregar, verifica: número de barras por sesión (7 horarias en RTH de ES), que la primera barra de cada día abre a la hora de apertura de sesión, y que la suma de volumen se conserva respecto a la serie original. Las tres son una línea de pandas cada una.

Ejemplo trabajado con código

# pandas 3.0 — Agregar barras de 15 min a 1 h respetando el inicio de sesión (M0.4.1 y M0.2.5)
import pandas as pd
idx = pd.date_range("2026-09-22 08:30", periods=4, freq="15min", tz="America/Chicago")
m15 = pd.DataFrame({"open": [5400, 5404.75, 5403.75, 5400.5], "high": [5406.5, 5409, 5405.25, 5402],
                    "low": [5398.25, 5403.5, 5399, 5397.75], "close": [5404.75, 5403.75, 5400.5, 5401.25],
                    "volume": [41200, 28900, 24100, 19800]}, index=idx)
regla = {"open": "first", "high": "max", "low": "min", "close": "last", "volume": "sum"}
mal  = m15.resample("1h").agg(regla)                    # bins alineados a la hora en punto: parte la sesión
bien = m15.resample("1h", origin="start").agg(regla)    # bins desde la primera barra: 08:30-09:30
print("Sin origin='start' (mal):"); print(mal.to_string()); print()
print("Con origin='start' (bien):"); print(bien.to_string())
Sin origin='start' (mal):
                              open     high      low    close  volume
2026-09-22 08:00:00-05:00  5400.00  5409.00  5398.25  5403.75   70100
2026-09-22 09:00:00-05:00  5403.75  5405.25  5397.75  5401.25   43900
 
Con origin='start' (bien):
                             open    high      low    close  volume
2026-09-22 08:30:00-05:00  5400.0  5409.0  5397.75  5401.25  114000

Figura

Línea de tiempo de 8:00 a 9:30 CT con cuatro barras de 15 min dibujadas como velas pequeñas en 8:30, 8:45, 9:00 y 9:15. Encima, dos filas de corchetes: la superior, en coral, con bins 8:00-9:00 y 9:00-10:00 que parten las cuatro barras en dos grupos desiguales, etiquetada 'resample(1h) por defecto'; la inferior, en teal, con un solo bin 8:30-9:30 que abarca las cuatro, etiquetada origin=start.
Mismos datos, misma función, un parámetro distinto. La fila coral produce velas que ninguna plataforma muestra.

(Figura estática en SVG.)

El error típico

Agregar con mean() para todas las columnas porque "es lo más fácil": la apertura media, el cierre medio y el volumen medio de cuatro barras no significan nada, y el high medio es menor que el máximo real, así que tus stops calculados con ATR salen mal. El segundo error es más sutil y más caro: remuestrear con label="right" (etiqueta por cierre) en un script y con label="left" en otro, juntar las dos series, y que la señal de una use la barra siguiente de la otra. Es look-ahead por convención de etiquetas, y no lo detecta ninguna métrica. Regla: un solo convenio en toda la academia, etiqueta por apertura (label="left"), y se escribe en la cabecera de cada script que remuestrea.

Quiz

5 preguntas · se aprueba con 60 %

  1. 1.Regla de agregación correcta para OHLCV al remuestrear:

  2. 2.`resample("1h")` sobre barras que empiezan a las 8:30, sin más parámetros:

  3. 3.Convención de etiqueta de barra en la academia:

  4. 4.Un bin horario sin barras (noche) aparece como fila con NaN. Lo correcto es:

  5. 5.Tras remuestrear a 1 h una sesión RTH de ES, ¿cuántas barras esperas por día?

0 de 5 respondidas

Ejercicio (cierre del Nivel 0)

  1. Con descargar_binance.py modificado para intervalo 1h (cambia 1d por 1h en la URL y en el nombre de archivo), descarga un mes de barras horarias de BTCUSDT.
  2. Remuestrea a 4h con la regla correcta y origin="start". Comprueba que el volumen total se conserva y que hay 6 barras de 4 h por día.
  3. Pasa el resultado a primer_script.py (adaptando cargar para leer close en vez de cierre, o renombrando la columna) y anota volatilidad y drawdown del mes. Anualiza con √(365 × 6) porque son barras de 4 h y cripto cotiza todos los días.

Resultado verificable: df.volume.sum() antes y después de remuestrear debe ser idéntico al céntimo; df.resample("1D").size() debe dar 6 para todos los días completos. Si el primer o último día dan menos de 6, es porque el mes no empieza o acaba alineado con tu origin: es normal y hay que decidir si descartar esos días parciales (sí, para research).

Qué te llevas

  1. OHLCV se agrega con first/max/min/last/sum. Nunca con medias.
  2. Los bins se alinean al inicio de la sesión (origin), no a la hora en punto. Y se comprueba el conteo de barras por día.
  3. Una sola convención de etiqueta (por apertura) en toda la academia. Mezclarlas es look-ahead que ninguna métrica detecta.

Fin del módulo M0.5 y del Nivel 0. Tienes: un entorno reproducible, un script de métricas, un descargador de datos, un validador y un remuestreador. Y tu CSV de fills de M0.3. Toca el examen final del Nivel 0: 30 preguntas de los cinco módulos, 80 % para aprobar, y el rango de Lector de precios. Después: Nivel 1, Matemáticas, beta y factores, donde cada fórmula que has visto de refilón se explica desde el problema que resuelve.