Quant500 Academy

4. Vivir en un servidor: reloj, zona horaria y arranque

Nivel 9: Orquestación y calendario

Tu portátil se apaga, se lleva de viaje y cambia de zona horaria contigo. Un servidor no. Vive en una sala que no verás nunca, se reinicia cuando el proveedor aplica un parche de seguridad y sigue funcionando mientras duermes. Este módulo trata las tres cosas que rompen un sistema por vivir en una máquina y no en tu escritorio: el reloj, el horario de verano y el arranque.

El reloj del servidor vive en hora universal

Regla sin excepciones: el servidor en hora universal, todo lo que guardas en hora universal, y la conversión a hora local solo en el último momento, para enseñárselo a un humano. Toda comparación, toda resta de instantes y toda clave de base de datos usan instantes con zona explícita.

El motivo no es purismo. Una hora local con horario de verano no es un eje continuo: hay una hora que ocurre dos veces al año y otra que no ocurre nunca. Si guardas la marca de tiempo de una orden como hora de pared local, la madrugada del cambio de octubre tendrás dos órdenes distintas con la misma marca y ninguna forma de saber cuál fue primero.

from datetime import datetime, timezone
from zoneinfo import ZoneInfo

ahora = datetime.now(timezone.utc)                    # siempre con zona, nunca ingenuo
ny = ahora.astimezone(ZoneInfo("America/New_York"))   # solo para mostrar

# Comparar SIEMPRE instantes con zona; esto es correcto:
apertura_utc = sesion_de(nyse, ahora.date())[0]
faltan = (apertura_utc - ahora).total_seconds()

El horario de verano descoloca la apertura dos semanas al año

Aquí está el detalle que pilla a casi todo el mundo. Estados Unidos y Europa no cambian la hora el mismo día. Estados Unidos adelanta el reloj el segundo domingo de marzo y lo atrasa el primer domingo de noviembre; Europa lo adelanta el último domingo de marzo y lo atrasa el último domingo de octubre.

La consecuencia práctica: durante unas dos o tres semanas en marzo, y durante una semana entre finales de octubre y principios de noviembre, la diferencia entre Madrid y Nueva York es de cinco horas en lugar de las seis habituales. La apertura de la bolsa de Nueva York, que en Madrid cae normalmente a las 15:30, se adelanta a las 14:30 durante esos días. En hora universal se ve igual de claro: la apertura pasa de las 14:30 a las 13:30 el segundo domingo de marzo, y no vuelve hasta noviembre.

Si tu tarea está programada a una hora fija en hora universal, esas semanas llegará una hora tarde a la apertura. Si está programada en hora local de Madrid, también, porque el desfase lo produce el cambio ajeno, no el tuyo. La única forma correcta es la del módulo dos: derivar el instante del calendario de mercado, que ya devuelve la apertura y el cierre reales de cada día.

El reloj se desincroniza y hay que sincronizarlo

El reloj de una máquina virtual deriva: se adelanta o se atrasa unos segundos al día si nadie lo corrige. Con segundos de desviación empiezan cosas raras: peticiones firmadas rechazadas por marca de tiempo fuera de ventana, órdenes que parecen enviadas antes de haberse decidido, y ventanas de tolerancia como la del módulo dos que dejan de casar.

Merece la pena separar dos cosas que se confunden. La deriva es el reloj alejándose poco a poco; el salto es la corrección brusca que aplica el servicio de hora cuando ya se ha alejado mucho. El salto es peor para un sistema de trading, porque uno hacia atrás puede hacer que dos ciclos consecutivos vean la misma hora, o que una ventana de ejecución se abra dos veces. Por eso interesa un ajuste suave y continuo, no una corrección grande y esporádica el día que alguien se acuerda.

import ntplib

def desviacion_del_reloj(servidor="pool.ntp.org"):
    """Segundos de diferencia entre el reloj local y la referencia."""
    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        # si no puedes comprobarlo, no lo des por bueno

Arrancar solo, y arrancar sin suponer nada

Si el proveedor reinicia la máquina a las tres de la madrugada, el proceso tiene que volver solo. Eso lo resuelve el gestor de servicios del sistema, no un script que tú lanzas a mano y que muere con tu sesión.

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
"""
# Habilitarla es lo que la hace arrancar tras un reinicio:
#   systemctl enable --now estrategia

Y ahora la parte que separa un sistema serio de uno que un día te sorprende: al arrancar no se supone nada. El proceso no sabe cuánto tiempo estuvo caído ni qué pasó mientras tanto. Antes de mover un euro comprueba, en este orden:

Si algo no cuadra, arranca en modo observación y avisa. Nunca vale reconstruir la realidad a partir de lo que el propio proceso escribió antes de morir.

Escenario trabajado

Escenario trabajado, con cifras de ejemplo. Una tarea programada a las 14:35 en hora universal para actuar cinco minutos después de la apertura. El segundo domingo de marzo, Estados Unidos adelanta su reloj y la apertura pasa a las 13:30 en hora universal. Durante casi ocho meses, hasta que Estados Unidos vuelve a atrasar su reloj en noviembre, la tarea entra 65 minutos después de la apertura en lugar de cinco. La estrategia no falló, no dio error y no avisó: simplemente operó cada día en un momento distinto del que se probó. 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.