Quant500 Academy

4. Living on a server: clock, time zone and startup

Level 9: Orchestration and the calendar

Your laptop shuts down, gets taken on trips and changes time zone with you. A server does not. It lives in a room you will never see, reboots when the provider applies a security patch and keeps running while you sleep. This module covers the three things that break a system because it lives on a machine and not on your desk: the clock, daylight saving time and startup.

The server's clock lives in UTC

A rule with no exceptions: the server in UTC, everything you store in UTC, and the conversion to local time only at the last moment, to show it to a human. Every comparison, every subtraction of instants and every database key uses instants with an explicit zone.

The reason is not purism. A local time with daylight saving is not a continuous axis: there is an hour that happens twice a year and another that never happens at all. If you store an order's timestamp as local wall-clock time, on the night of the October change you will have two different orders with the same stamp and no way of knowing which came first.

from datetime import datetime, timezone
from zoneinfo import ZoneInfo

ahora = datetime.now(timezone.utc)                    # always zone-aware, never naive
ny = ahora.astimezone(ZoneInfo("America/New_York"))   # for display only

# ALWAYS compare zone-aware instants; this is correct:
apertura_utc = sesion_de(nyse, ahora.date())[0]
faltan = (apertura_utc - ahora).total_seconds()

Daylight saving time shifts the open for two weeks a year

Here is the detail that catches nearly everybody out. The United States and Europe do not change their clocks on the same day. The United States puts its clocks forward on the second Sunday in March and back on the first Sunday in November; Europe puts them forward on the last Sunday in March and back on the last Sunday in October.

The practical consequence: for about two or three weeks in March, and for a week between late October and early November, the difference between Madrid and New York is five hours instead of the usual six. The New York Stock Exchange open, which in Madrid normally falls at 15:30, moves forward to 14:30 during those days. In UTC it is just as clear: the open goes from 14:30 to 13:30 on the second Sunday in March, and does not come back until November.

If your task is scheduled at a fixed time in UTC, in those weeks it will arrive an hour late to the open. If it is scheduled in Madrid local time, the same, because the shift is produced by somebody else's change, not yours. The only correct way is module two's: derive the instant from the market calendar, which already returns each day's real open and close.

The clock drifts and has to be synchronized

A virtual machine's clock drifts: it runs a few seconds fast or slow each day if nobody corrects it. With seconds of deviation, strange things start happening: signed requests rejected for a timestamp outside the window, orders that appear to have been sent before they were decided, and tolerance windows like module two's that stop matching.

It is worth separating two things that get confused. Drift is the clock slowly wandering off; a jump is the abrupt correction the time service applies once it has wandered too far. The jump is worse for a trading system, because a backwards one can make two consecutive cycles see the same time, or make an execution window open twice. That is why you want a smooth, continuous adjustment, not a big, sporadic correction on the day somebody remembers.

import ntplib

def desviacion_del_reloj(servidor="pool.ntp.org"):
    """Seconds of difference between the local clock and the reference."""
    respuesta = ntplib.NTPClient().request(servidor, version=3, timeout=5)
    return abs(respuesta.offset)

def reloj_fiable(umbral_seg=1.0):
    try:
        return desviacion_del_reloj() < umbral_seg
    except Exception:
        return False        # if you cannot check it, do not assume it is fine

Starting on its own, and starting without assuming anything

If the provider reboots the machine at three in the morning, the process has to come back on its own. That is solved by the system's service manager, not by a script you launch by hand and that dies with your session.

UNIDAD_SYSTEMD = """
[Unit]
Description=Estrategia
After=network-online.target

[Service]
ExecStart=/opt/estrategia/venv/bin/python -m estrategia
Restart=always
RestartSec=10
Environment=BROKER_API_KEY=tu_clave_aqui

[Install]
WantedBy=multi-user.target
"""
# Enabling it is what makes it start after a reboot:
#   systemctl enable --now estrategia

And now the part that separates a serious system from one that surprises you one day: on startup, nothing is assumed. The process does not know how long it was down or what happened meanwhile. Before moving a single euro it checks, in this order:

If something does not add up, start in observation mode and raise an alert. It is never acceptable to reconstruct reality from what the process itself wrote before dying.

Worked scenario

A worked scenario, with example figures. A task scheduled at 14:35 UTC to act five minutes after the open. On the second Sunday in March, the United States puts its clocks forward and the open moves to 13:30 UTC. For almost eight months, until the United States puts its clocks back in November, the task comes in 65 minutes after the open instead of five. The strategy did not fail, did not error and did not raise an alert: it simply traded every day at a different moment from the one it was tested at. 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.