Quant500 Academy

4. Determinismo: que dos ejecuciones den exactamente lo mismo

Nivel 7: Del cuaderno al proceso

Un resultado que no puedes volver a obtener no es un resultado: es una anécdota. Si hoy no eres capaz de reproducir la señal que enviaste al mercado hace tres semanas, no puedes auditarla, no puedes depurarla y, sobre todo, no puedes afirmar que tu sistema en vivo ejecuta la misma estrategia que probaste.

Las cuatro fugas del determinismo

Un proceso es determinista cuando, con las mismas entradas declaradas, produce siempre la misma salida. Las entradas que se cuelan sin declararse son casi siempre estas cuatro:

Las tres primeras se cierran con disciplina y cuestan una tarde. La cuarta es la cara, porque no depende de ti: obliga a guardar una copia propia de lo que se usó, identificada por una huella del contenido, y a que el registro anote qué copia alimentó cada decisión. Sin eso, dentro de seis meses no podrás distinguir un error de tu código de una revisión silenciosa del proveedor, que es exactamente la discusión que nunca quieres tener a ciegas.

Semillas y versiones: la parte fácil

Fijar la semilla es barato y hay que hacerlo en un solo sitio, el punto de entrada, y nunca dentro de una función que se llama muchas veces:

# ejecutar.py
import random
import numpy as np

SEMILLA = 20240115

def preparar_azar(semilla=SEMILLA):
    random.seed(semilla)
    np.random.seed(semilla)

Anclar versiones es igual de barato: en lugar de pedir la librería a secas, se fija la versión exacta de cada dependencia, incluidas las indirectas, y ese fichero se versiona junto al código. Instalar la última disponible significa que el ordenador de mañana no ejecutará el mismo programa que el de hoy, aunque tu código no haya cambiado ni una coma.

El tiempo es una entrada, no un dato ambiental

Este es el punto que más gente falla. Compara:

# Mal: la funcion lee el reloj por dentro
from datetime import datetime

def es_dia_operable(calendario):
    hoy = datetime.now().date()
    return hoy in calendario

# Bien: el instante entra por la puerta, como cualquier otra entrada
def es_dia_operable(calendario, momento):
    return momento.date() in calendario

La primera versión no se puede probar. No puedes escribir una prueba que compruebe qué hace en Nochebuena, ni reproducir lo que decidió un martes de marzo, porque su respuesta depende de cuándo la ejecutes: la misma llamada da resultados distintos hoy y mañana sin que nadie haya tocado nada. La segunda se prueba con una línea y se reproduce siempre, porque el instante viaja con los argumentos.

La regla general: la hora entra por el punto de entrada, se congela una vez y se pasa hacia abajo. Todo el sistema opera con ese único instante, lo que además elimina una familia entera de fallos sutiles, como que dos partes del programa crucen la medianoche en momentos distintos.

# ejecutar.py
from datetime import datetime, timezone

def main():
    args = parsear_argumentos()
    ahora = (datetime.fromisoformat(args.momento) if args.momento
             else datetime.now(timezone.utc))
    preparar_azar()
    precios = cargar_precios(args.simbolo, hasta=ahora)
    decision = calcular_senal(precios)
    anotar('decision', momento=ahora.isoformat(), decision=decision)

Ese argumento opcional convierte la reproducción de un día pasado en un ejercicio trivial y, de paso, hace que el mismo punto de entrada sirva para recorrer el histórico.

Reproducir es poder recalcular, no poder consultar

Guardar en el registro qué señal salió cada día es imprescindible, pero no es determinismo. El registro te dice qué pasó; el determinismo te permite volver a obtenerlo. Para eso hay que anotar, junto al resultado, todo lo que hizo falta para producirlo: instante congelado, versión del código, versión de las dependencias, semilla, identificador del conjunto de datos usado y configuración efectiva. Con eso, un resultado dudoso se puede rehacer meses después. Sin eso, la única respuesta posible ante una discrepancia es encogerse de hombros.

Caso real: 29 de febrero de 2012

Aquel día el servicio en la nube de Microsoft, Azure, sufrió una caída larga que afectó a clientes de todo el mundo durante horas. La causa raíz, publicada por la propia compañía en su análisis posterior, fue de manual: al crear una máquina virtual, un componente generaba un certificado con validez de un año calculando la fecha de vencimiento como el mismo día del año siguiente. Ejecutado el 29 de febrero de 2012, ese cálculo pedía el 29 de febrero de 2013, una fecha que no existe. El proceso fallaba, el reintento fallaba igual, y el fallo se propagó por el resto del sistema.

Nadie escribió mal esa fórmula por descuido: funcionaba perfectamente en 1.460 de cada 1.461 días. Falló porque la fecha era una entrada oculta que ninguna prueba había podido fijar.

Y una segunda historia, sin bolsa de por medio pero muy ilustrativa: en 2010 un artículo académico muy citado sobre deuda y crecimiento sostenía que los países con deuda por encima del 90% del PIB crecían de media un 0,1% negativo. En 2013, unos investigadores que pidieron la hoja de cálculo original encontraron un rango mal seleccionado que dejaba fuera varios países; con el cálculo corregido, aquel promedio pasaba a ser positivo, en torno al 2,2%. El error tardó tres años en aparecer por una razón muy simple: hasta que alguien no reprodujo el cálculo, no existía forma de verlo. Esto es formación, no asesoramiento de inversión, pero la moraleja operativa vale igual: si nadie puede recalcular tu número, nadie puede corregirlo.

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.