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):
| Bin | Barras incluidas | Open | High | Low | Close | Volumen |
|---|---|---|---|---|---|---|
| 8:00 | 8:30, 8:45 | 5.400,00 | 5.409,00 | 5.398,25 | 5.403,75 | 70.100 |
| 9:00 | 9:00, 9:15 | 5.403,75 | 5.405,25 | 5.397,75 | 5.401,25 | 43.900 |
Bins desde el inicio de la sesión (origin="start"):
| Bin | Barras incluidas | Open | High | Low | Close | Volumen |
|---|---|---|---|---|---|---|
| 8:30 | las cuatro | 5.400,00 | 5.409,00 | 5.397,75 | 5.401,25 | 114.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 usarorigincon unoffset; 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 unTimestampexplícito. Para sesiones que no empiezan a la hora en punto,origin="start"o un timestamp de apertura de sesión.labelyclosed: 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 114000Figura
(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.Regla de agregación correcta para OHLCV al remuestrear:
2.`resample("1h")` sobre barras que empiezan a las 8:30, sin más parámetros:
3.Convención de etiqueta de barra en la academia:
4.Un bin horario sin barras (noche) aparece como fila con NaN. Lo correcto es:
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)
- Con
descargar_binance.pymodificado para intervalo1h(cambia1dpor1hen la URL y en el nombre de archivo), descarga un mes de barras horarias de BTCUSDT. - Remuestrea a
4hcon la regla correcta yorigin="start". Comprueba que el volumen total se conserva y que hay 6 barras de 4 h por día. - Pasa el resultado a
primer_script.py(adaptandocargarpara leercloseen vez decierre, 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
- OHLCV se agrega con first/max/min/last/sum. Nunca con medias.
- 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. - 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.
