Quant500 Academy

2. The market calendar as the source of truth

Level 9: Orchestration and the calendar

There is one line of code that shows up in almost every home-made system and is wrong in all of them: checking whether today is Monday to Friday to decide whether to trade. It is an approximation that gets it right on most days of the year, and that is precisely what makes it dangerous: it fails rarely, always by surprise, and almost always on days when the market is behaving oddly.

Why the day of the week is not enough

A real exchange calendar has, as a minimum, four things the day of the week does not know:

The calendar changes, and that is why it cannot live in your code

The decisive argument against the hand-written holiday list is not that it is ugly, it is that it expires. In 2022 the New York Stock Exchange added June 19 as an exchange holiday: any system with the previous year's list frozen in tried to trade that day against a closed market. Auction times change too, extraordinary closures are added for national mourning, and half sessions are adjusted when Christmas Eve falls at a weekend.

The rule is: the calendar is data you query, not a condition written into the code. It may come from a maintained library, from a table in your database or from the broker itself, but it has to be something you can update without deploying and review without reading code.

import pandas_market_calendars as mcal

nyse = mcal.get_calendar("XNYS")

# Thanksgiving week 2026: Thursday the 26th does not appear because it is a holiday,
# and Friday the 27th appears with an early close.
sesiones = nyse.schedule(start_date="2026-11-23", end_date="2026-11-27")
print(sesiones.index)              # days with a session, as dates
print(sesiones["market_open"])     # each day's open, in UTC
print(sesiones["market_close"])    # each day's close, in UTC

Notice one detail that settles half this module: the library returns instants, not wall-clock times. That means it has already resolved daylight saving time for you and that you can compare them directly with the server's clock, which will be on UTC as you will see in module four.

Deciding whether to trade today, and at exactly what time

The query is not "is it open?", but two separate questions: whether there is a session today and at exactly what instant I am supposed to act. That instant is almost never the open or the close plainly, but a time derived from the day's real schedule.

from datetime import timedelta

def sesion_de(cal, dia):
    """Returns (open, close) for that day, or None if there is no session."""
    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                      # holiday or weekend: no trading
    apertura, cierre = sesion
    if cierre - apertura < timedelta(hours=4):
        minutos_antes = 5                # half session: the usable window shrinks
    objetivo = cierre - timedelta(minutes=minutos_antes)
    return max(objetivo, apertura + timedelta(minutes=5))

The scheduler can keep waking up every few minutes: the one who decides is not cron's clock, it is the calendar. The task wakes up, asks, and on the vast majority of wake-ups does nothing.

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   # neither early nor stale

The tolerance is not decoration. Without it, a wake-up arriving three seconds late never coincides with the target instant and the system never trades at all; with an infinite tolerance, a task starting at midnight after a restart would believe it still owed yesterday's session.

And there is one case worth deciding before it happens: what you do if the calendar cannot be queried because the library fails or the table does not respond. The safe answer is the usual one: if you do not know whether there is a session, you do not trade.

Worked scenario

A worked scenario, with example figures. A system sends its market-on-close orders at 15:45 New York time, fifteen minutes before the usual close, with the time written in by hand. On the Friday after Thanksgiving the market closes at one o'clock: at 15:45 it had been shut for almost three hours. That day the system did not trade, kept the previous day's position through the whole long weekend and nobody spotted it until Monday.

The cost was not one day's gap, but that the portfolio was left off target right before three sessions with no chance to adjust. With the instant derived from the calendar, the execution would have gone out at 12:55 local time without touching a line. Education, not financial advice.

The rest of Quant500

What you are learning, applied to the index every day:

The model portfolio, weekly in your inbox

Every Saturday, its week in one minute: how it went, the best and the worst, and which insiders bought with their own money. Plus, the day the model is rebuilt, which stocks come in and which go out. A simulation, not advice. At most one email a week, no ads, no account; one-click unsubscribe. Made by one person: Pol Gracia.

A model portfolio with simulated results, not a personalized recommendation. Your email is used for this only: privacy.

Behind the Academy and the rest of Quant500 there is one person: Pol Gracia Serrahima.