3. Alertas: cuándo hay que despertarte y cuándo no
Nivel 10: Saber qué está pasando
Una alerta es una petición formal de que un ser humano deje lo que está haciendo y actúe. Esa es la definición completa, y de ella se deduce todo lo demás. Si no hay nada que una persona pueda hacer ahora mismo, no es una alerta: es un dato que va a un panel o, como mucho, un ticket para el lunes.
El criterio: acción humana inmediata, y nada más
La tentación es alertar de todo lo que sale mal, porque parece prudente. No lo es. Pregúntate siempre dos cosas antes de crear una alerta: qué haría exactamente la persona que la reciba, y qué pasa si no la recibe hasta mañana. Si la respuesta a la primera es "mirarlo" y a la segunda es "nada grave", no es una alerta.
Conviene tener tres niveles y respetarlos:
- Crítico: llama a un teléfono, a la hora que sea. Hay dinero en riesgo y hay una acción concreta que tomar.
- Aviso: abre un ticket. Se mira en horario laboral, no rompe nada mientras tanto.
- Informativo: va al panel y a las métricas. No interrumpe a nadie jamás.
Un error del registro y una alerta no son lo mismo, y confundirlos es el origen de casi todos los sistemas de avisos insoportables. El registro guarda todo lo que pasó para poder reconstruirlo después; la alerta escoge de ahí el puñado de cosas que no admiten espera. Que un evento se escriba con nivel de error significa que alguien tendrá que mirarlo algún día, no que tenga que mirarlo ahora.
Fatiga de alertas: la que salta cada día ya no existe
Una alerta que suena a diario y siempre se resuelve sola es peor que no tener alerta, y la razón no es psicológica sino operativa: entrena al equipo a ignorar ese canal, y arrastra con ella a todas las demás alertas que comparten canal. El día que el aviso sea real, llegará al mismo sitio donde nadie mira.
La cura no es bajar el volumen ni mandarla a otra carpeta. Es una de estas tres: subir el umbral hasta el punto en que de verdad exige actuar, degradarla a panel si nunca exige actuar, o arreglar la causa. Y además hay que ponerle freno al propio envío, para que un fallo que se repite cien veces en diez minutos genere una notificación y no cien.
import time
ULTIMO_ENVIO = {}
def alertar(urgencia, clave, mensaje, silencio_s=1800):
ahora = time.time()
if ahora - ULTIMO_ENVIO.get(clave, 0) < silencio_s:
M.contar("alertas_silenciadas", clave=clave)
return False
ULTIMO_ENVIO[clave] = ahora
M.contar("alertas_enviadas", urgencia=urgencia, clave=clave)
registrar(logging.WARNING, "alerta", urgencia=urgencia,
clave=clave, mensaje=mensaje)
if urgencia == "critico":
llamar_al_telefono(mensaje)
elif urgencia == "aviso":
abrir_ticket(clave, mensaje)
return TrueMide tus propias alertas. El número de alertas críticas enviadas por semana y el porcentaje de ellas que acabaron en una acción real son la mejor radiografía de si tu sistema de avisos está sano o ya se ha convertido en ruido.
Alertar por ausencia: el fallo que no genera errores
Aquí está la parte que casi nadie implementa y que más disgustos evita. Los sistemas de alertas suelen construirse mirando lo que pasa: excepciones, rechazos, códigos de error. Pero el fallo más peligroso en un sistema de trading no produce ningún error, produce silencio. Si el planificador muere a las ocho de la mañana, la tarea de las 9:30 no se ejecuta y no hay excepción que capturar, porque no hay código corriendo. Un contador de errores por hora nunca se disparará.
Por eso hay que declarar explícitamente lo que se espera que ocurra y comprobar que ha ocurrido.
TAREAS_ESPERADAS = {
"descarga_precios": "09:00",
"generar_senales": "09:30",
"enviar_ordenes": "09:35",
"reconciliacion": "22:15",
}
def comprobar_tareas(ejecutadas_hoy, ahora_hhmm, margen_min=10):
for tarea, hora in sorted(TAREAS_ESPERADAS.items()):
limite = sumar_minutos(hora, margen_min)
if ahora_hhmm > limite and tarea not in ejecutadas_hoy:
alertar("critico", "tarea_no_ejecutada:" + tarea,
"la tarea %s deberia haber corrido a las %s" % (tarea, hora))Un error registrado es una molestia; una tarea que no se ha ejecutado es una estrategia que no está operando o, peor, una posición que debía cerrarse y sigue abierta.
El latido: confirmar que el sistema sigue vivo
El complemento natural es el latido. El proceso escribe cada minuto una marca con la hora y la tarea en curso, y un vigilante comprueba que esa marca no envejece. La condición que hace útil todo el montaje es que el vigilante corra en otra máquina y en otro proceso: si vive dentro del sistema que vigila, muere con él justo cuando hace falta.
import json, os
RUTA_LATIDO = "/var/run/estrategia/latido.json"
def latir(tarea, estado="ok"):
with open(RUTA_LATIDO, "w") as f:
json.dump({"tarea": tarea, "estado": estado, "ts": time.time()}, f)
def vigilar(edad_maxima_s=300):
if not os.path.exists(RUTA_LATIDO):
alertar("critico", "sin_latido", "no existe el fichero de latido")
return
with open(RUTA_LATIDO) as f:
latido = json.load(f)
edad = time.time() - latido["ts"]
if edad > edad_maxima_s:
alertar("critico", "latido_viejo",
"ultimo latido hace %d s en la tarea %s" % (int(edad), latido["tarea"]))Escenario trabajado: la alerta que dejó de leerse
Escenario inventado con fines didácticos. Un sistema envía de media unas doce alertas al día de "retraso de datos alto" durante tres meses; todas se resuelven solas en un par de minutos. El equipo deja de mirarlas. Al cuarto mes el proveedor cambia un formato y el sistema opera media sesión con precios del día anterior: la alerta salta, indistinguible de las mil anteriores, y nadie la abre hasta la tarde. La corrección fue subir el umbral hasta el retraso que impide operar con garantías, mandar el resto al panel, y añadir una comprobación de tareas esperadas. El volumen bajó a menos de una alerta crítica por semana, y esas sí se leen.
La lección, antes de pasar al último módulo: un canal con cero alertas durante un mes puede ser una señal magnífica o una señal terrible, y lo único que las distingue es el latido. Si el vigilante sigue confirmando que todo corre, el silencio es buena noticia; si nadie confirma nada, el silencio no significa absolutamente nada.