Quant500 Academy

1. The five places a trading system fails

Level 11: When it fails

A trading system never breaks where you were looking. You spend weeks tuning the signal and the day the alarm goes off it turns out the disk was full or the provider sent a price of zero. This module is the map of the five places a production system really fails, ordered by real frequency: for each one, how it is detected and what the safe default response is.

The inventory, ordered by real frequency

In a system with a year of daily trading, the distribution of scares looks something like this:

Cutting across all five there is a single rule: when in doubt, do not trade. A stopped system misses an opportunity; one that trades on false information loses money and creates positions that then have to be unwound. The asymmetry rules.

Layer 1: the data

They fail in four ways, and they are not ordered by severity the way you would expect:

The first two are detected with a clock; the last two, with a count and a sanity band.

FRESCURA_MAXIMA_S = 90
BANDA_MAXIMA = 0.20

def barra_utilizable(barra, ultimo_cierre_bueno, ahora):
    if barra is None or barra.get('close') is None:
        return False, 'sin dato'
    edad = ahora - barra['ts']
    if edad > FRESCURA_MAXIMA_S:
        return False, 'dato viejo: %d s' % edad
    precio = barra['close']
    if precio <= 0:
        return False, 'precio no positivo'
    if ultimo_cierre_bueno:
        salto = abs(precio / ultimo_cierre_bueno - 1)
        if salto > BANDA_MAXIMA:
            return False, 'salto del %.1f por ciento' % (salto * 100)
    return True, ''

The function corrects nothing: it does not interpolate, does not carry the last good price forward, does not fill the gap. It only says yes or no, and why. The threshold is not universal either: in a large-cap stock 20% intraday is suspicious, in a crypto it is not. The number is a parameter; the check is not.

Layer 2: the network and the broker

Four ways here too, from best to worst. Down: it refuses the connection and you know within a second. Rejecting orders: it says no, with an explicit reason. With an expired session: it returns a 401 and a renewal is enough. And slow: the request does not fail, it takes fourteen seconds, and by the time the reply arrives your decision is no longer valid.

Every call carries an explicit maximum wait: a request with no limit leaves the process hanging, the quietest way of being down. And you watch latency, not just the error rate: a broker going from 200 milliseconds to six seconds is broken even if its replies are correct. When either of the two goes out of range, order sending is cut off until a full cycle comes back healthy.

Layer 3: your code, and layer 4: the machine it lives on

The code failure that matters is not the one that blows up on startup; that one is a gift, because you never get to trade. The one that matters is the uncaught exception halfway through a run: you have sent three of the rotation's eight orders and the process dies. That is not an error, that is half a portfolio.

Infrastructure fails in three flavors: the disk fills up, often because of the logs you write; memory runs out and the operating system kills the process; or the machine reboots on its own. None of them gives warning with a Python exception.

import shutil

MARGEN_DISCO_MB = 500

def preparado_para_operar(ruta_datos):
    libre_mb = shutil.disk_usage(ruta_datos).free / 1e6
    if libre_mb < MARGEN_DISCO_MB:
        return False, 'disco: solo %.0f MB libres' % libre_mb
    return True, ''

def ciclo_protegido(motor, freno):
    try:
        motor.ejecutar_ciclo()
    except Exception as e:
        freno.activar('excepcion no prevista: %r' % e)
        raise

The disk check runs before every cycle, not once at startup. And the wrapper does the only reasonable thing in the face of an unforeseen exception: it activates the brake, leaves a record and re-raises. What it does not do is swallow it and carry on, because a system that continues after an error it does not understand is trading on a state it can no longer justify.

Layer 5: the human who touches something while the system is trading

Somebody closes a position from their phone because they could see it losing. Somebody edits the configuration at half past eleven. Somebody restarts the process so it picks up the change. None of the three is bad in itself; what is bad is that the system does not find out and carries on believing a state that no longer exists. Detection is reconciliation against the broker, and the response is an explicit maintenance flag: before touching anything by hand the system is stopped and it is written down that it is stopped. That way, faced with a divergence, the system can tell apart it does not square because of a failure from it does not square because somebody said so.

Real case: Knight Capital, August 1, 2012

Knight Capital Group, one of the largest market makers in the United States, deployed new code to its routing servers. An old component that had gone unused for years was left active and, at the open, it started firing orders into the market in a loop. In less than an hour, about 45 minutes, the loss came to around 440 million dollars, more than the company's profit for the previous year. Knight was absorbed a few months later.

It is the canonical example because it failed in several layers at once: old code nobody cleaned up, a deployment that did not reach every machine and automated warnings nobody was obliged to escalate. And the system had no limit of its own capable of saying this is now too much. That is what the next module is about.

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.