Quant500 Academy

2. El calendario de mercado como fuente de verdad

Nivel 9: Orquestación y calendario

Hay una línea de código que aparece en casi todos los sistemas caseros y que está mal en todos ellos: comprobar si hoy es de lunes a viernes para decidir si se opera. Es una aproximación que acierta la mayoría de los días del año, y eso es precisamente lo que la hace peligrosa: falla pocas veces, siempre por sorpresa, y casi siempre en días en los que el mercado está raro.

Por qué el día de la semana no basta

Un calendario bursátil real tiene, como mínimo, cuatro cosas que el día de la semana no sabe:

El calendario cambia, y por eso no puede vivir en tu código

El argumento definitivo contra la lista de festivos escrita a mano no es que sea fea, es que caduca. En 2022 la bolsa de Nueva York incorporó el 19 de junio como festivo bursátil: cualquier sistema con la lista congelada del año anterior intentó operar ese día contra un mercado cerrado. También cambian los horarios de las subastas, se añaden cierres extraordinarios por duelo nacional y se ajustan las medias sesiones cuando Nochebuena cae en fin de semana.

La regla es: el calendario es un dato que se consulta, no una condición escrita en el código. Puede venir de una librería mantenida, de una tabla en tu base de datos o del propio bróker, pero tiene que ser algo que se pueda actualizar sin desplegar y revisar sin leer código.

import pandas_market_calendars as mcal

nyse = mcal.get_calendar("XNYS")

# Semana de Accion de Gracias de 2026: el jueves 26 no aparece porque es festivo,
# y el viernes 27 aparece con cierre temprano.
sesiones = nyse.schedule(start_date="2026-11-23", end_date="2026-11-27")
print(sesiones.index)              # dias con sesion, en fechas
print(sesiones["market_open"])     # apertura de cada dia, en hora universal
print(sesiones["market_close"])    # cierre de cada dia, en hora universal

Fíjate en un detalle que decide medio módulo: la librería devuelve instantes, no horas de pared. Eso significa que ya te ha resuelto el horario de verano y que puedes compararlos directamente con el reloj del servidor, que estará en hora universal como verás en el módulo cuatro.

Decidir si hoy se opera, y a qué hora exacta

La consulta no es "¿está abierto?", sino dos preguntas separadas: si hoy hay sesión y en qué instante exacto me toca actuar. Ese instante casi nunca es la apertura ni el cierre a secas, sino una hora derivada del horario real del día.

from datetime import timedelta

def sesion_de(cal, dia):
    """Devuelve (apertura, cierre) de ese dia, o None si no hay sesion."""
    s = cal.schedule(start_date=dia, end_date=dia)
    if s.empty:
        return None
    fila = s.iloc[0]
    return fila["market_open"].to_pydatetime(), fila["market_close"].to_pydatetime()

def instante_de_ejecucion(cal, dia, minutos_antes=15):
    sesion = sesion_de(cal, dia)
    if sesion is None:
        return None                      # festivo o fin de semana: no se opera
    apertura, cierre = sesion
    if cierre - apertura < timedelta(hours=4):
        minutos_antes = 5                # media sesion: la ventana util se encoge
    objetivo = cierre - timedelta(minutes=minutos_antes)
    return max(objetivo, apertura + timedelta(minutes=5))

El planificador puede seguir despertando cada pocos minutos: quien decide no es el reloj de cron, es el calendario. La tarea se despierta, pregunta, y en la inmensa mayoría de los despertares no hace nada.

def toca_actuar(cal, ahora_utc, tolerancia_seg=180):
    objetivo = instante_de_ejecucion(cal, ahora_utc.date())
    if objetivo is None:
        return False
    retraso = (ahora_utc - objetivo).total_seconds()
    return 0 <= retraso <= tolerancia_seg   # ni antes de tiempo ni con retraso viejo

La tolerancia no es un adorno. Sin ella, un despertar que llegue tres segundos tarde no coincide nunca con el instante objetivo y el sistema no opera jamás; con una tolerancia infinita, una tarea que arranque a medianoche por un reinicio creería que todavía le toca la sesión de ayer.

Y hay un caso que conviene dejar decidido antes de que ocurra: qué haces si el calendario no se puede consultar porque la librería falla o la tabla no responde. La respuesta segura es la misma de siempre: si no sabes si hay sesión, no operas.

Escenario trabajado

Escenario trabajado, con cifras de ejemplo. Un sistema envía sus órdenes al cierre a las 15:45 hora de Nueva York, quince minutos antes del cierre habitual, con la hora escrita a mano. El viernes siguiente a Acción de Gracias el mercado cierra a la una del mediodía: a las 15:45 llevaba casi tres horas cerrado. Ese día el sistema no operó, se quedó con la posición del día anterior durante todo el fin de semana largo y nadie lo detectó hasta el lunes.

El coste no fue el hueco de un día, sino que la cartera quedó desviada del objetivo justo antes de tres sesiones sin poder ajustar. Con el instante derivado del calendario, la ejecución habría salido a las 12:55 hora local sin tocar una línea. Formación, no asesoramiento financiero.

El resto de Quant500

Lo que estás aprendiendo, aplicado al índice todos los días:

La cartera del modelo, cada semana en tu correo

Cada sábado, su semana en un minuto: cómo fue, lo mejor y lo peor, y qué directivos compraron con su dinero. Y el día que el modelo se rehace, qué entra y qué sale. Es una simulación, no un consejo. Como mucho un correo a la semana, sin publicidad y sin cuenta; te das de baja con un clic. Lo hace una persona: Pol Gracia.

Cartera modelo con resultados simulados, no una recomendación personalizada. Tu correo se usa solo para esto: privacidad.

Detrás de la Academia y del resto de Quant500 hay una sola persona: Pol Gracia Serrahima.