2. Interruptores de emergencia y límites duros
Nivel 11: Cuando falla
Un límite duro es una condición que el sistema comprueba antes de enviar cada orden y que, si no se cumple, impide el envío. No es una advertencia, no es una línea en el registro, no es un parámetro más de la estrategia. Es una puerta cerrada. La estrategia propone y el comprobador dispone, y su respuesta por defecto es que no.
Estos límites te protegen de ti, no del mercado
Conviene romper pronto un malentendido muy extendido. Un límite de pérdida diaria no te protege de una racha mala: una racha mala está dentro de lo que tu estrategia hace, la viste en el backtest y la vas a volver a ver. Lo que un límite duro para de verdad es lo otro: el bucle que manda la misma orden trescientas veces, el tamaño calculado con un divisor que salió cero, el precio de referencia que era el de ayer, el despliegue de las cinco de la tarde con un signo cambiado.
Dicho de otro modo: los límites duros son control de daños contra errores de software y de operación, no una técnica de gestión de riesgo de mercado. Que además recorten alguna cola desagradable del mercado es un efecto secundario agradable. Si los diseñas pensando en el mercado los pondrás demasiado holgados justo para lo que tenían que atrapar. De ahí salen tres propiedades que no se negocian:
- Se comprueban en un único sitio. El paso obligado por el que pasa toda orden, sin atajos ni rutas alternativas para el caso urgente.
- Son absolutos. No dependen de lo convencida que esté la señal ni de lo bien que fuera el mes.
- Rechazan, no corrigen. Recortar el tamaño al máximo permitido y enviar igual es razonable en el dimensionamiento normal, pero es pésimo aquí: si la orden pedía cien veces lo razonable, recortarla convierte un error grave en una operación pequeña y te quedas sin enterarte.
Los cinco límites mínimos
- Número máximo de órdenes por sesión. El más aburrido y el único que habría parado a Knight. Si tu estrategia manda veinte órdenes al día, cuarenta es un techo generoso y aun así imposible de alcanzar por accidente.
- Exposición máxima por posición y total. Un porcentaje del patrimonio por línea y otro para la suma, contando el apalancamiento.
- Pérdida máxima diaria. No rechaza una orden: detiene la sesión entera.
- Tamaño de orden fuera de lo razonable. Contra el volumen medio del valor y contra tu tamaño típico.
- Cordura del precio antes de enviar. Ninguna orden límite sale a un precio muy alejado del último precio válido.
class LimiteDuro(Exception):
pass
class Limites:
def __init__(self, estado, max_ordenes=40, max_peso=0.10,
max_bruto=0.95, max_desvio_precio=0.05, max_pct_volumen=0.02):
self.estado = estado
self.max_ordenes = max_ordenes
self.max_peso = max_peso
self.max_bruto = max_bruto
self.max_desvio_precio = max_desvio_precio
self.max_pct_volumen = max_pct_volumen
def comprobar(self, orden, cartera, referencia):
if self.estado.freno_activo():
raise LimiteDuro('freno activado: %s' % self.estado.motivo())
if self.estado.ordenes_hoy() >= self.max_ordenes:
raise LimiteDuro('tope de ordenes de la sesion')
peso = abs(orden.cantidad * referencia.precio) / cartera.patrimonio
if peso > self.max_peso:
raise LimiteDuro('peso %.3f sobre el maximo' % peso)
if cartera.bruto_tras(orden) > self.max_bruto:
raise LimiteDuro('exposicion bruta fuera de rango')
if orden.cantidad > self.max_pct_volumen * referencia.volumen_medio:
raise LimiteDuro('tamano fuera de lo razonable')
if orden.limite is not None:
desvio = abs(orden.limite / referencia.precio - 1)
if desvio > self.max_desvio_precio:
raise LimiteDuro('precio limite absurdo: %.3f' % desvio)
return TrueCada comprobación devuelve un motivo legible, y eso no es cosmética: el día que salte, la diferencia entre arreglarlo en dos minutos y pasarte la mañana adivinando es ese texto. Y la primera línea consulta el freno, porque la pregunta anterior a todas es si el sistema tiene permiso para operar.
Dónde vive el contador
El error que se repite en todos los sistemas caseros: el contador de órdenes y el freno viven en un atributo del objeto, así que reinicias el proceso y vuelven a cero. Y el escenario en el que más necesitas el límite es precisamente un bucle de fallo y reinicio: arranca, manda órdenes, se cae, el supervisor lo levanta, arranca, manda órdenes. Con el contador en memoria, un tope de cuarenta órdenes por sesión permite cuarenta por reinicio, y un tope no es un tope si se puede reiniciar.
def registrar_envio(conn, hoy):
with conn:
conn.execute(
"INSERT INTO contador(sesion, n) VALUES(?, 1) "
"ON CONFLICT(sesion) DO UPDATE SET n = n + 1", (hoy,))
def revisar_perdida_diaria(conn, patrimonio, apertura, max_perdida=0.03):
caida = patrimonio / apertura - 1
if caida < -max_perdida:
activar_freno(conn, 'perdida diaria %.2f por ciento' % (caida * 100))
return False
return TrueEl contador y el freno se guardan con la fecha de la sesión, en la misma base de datos donde llevas el estado, y el freno solo se desactiva a mano. Nada de que se borre solo al día siguiente: si saltó, alguien tiene que mirar por qué antes de volver a operar. El patrimonio de apertura se registra al abrir la sesión y no se recalcula, porque si lo recalculas después de una caída el límite se mueve con ella y deja de ser un límite.
Los números concretos de este módulo son ejemplos didácticos para ilustrar el mecanismo; esto es formación, no asesoramiento financiero.
Caso real: Mizuho Securities, diciembre de 2005
Un operador de Mizuho Securities quiso vender una acción de la empresa J-Com a 610.000 yenes y tecleó la orden justo al revés: 610.000 acciones a un yen. Esa cantidad superaba con creces el número total de acciones existentes de la compañía. La orden entró en el mercado y la pérdida para Mizuho fue del orden de decenas de miles de millones de yenes, es decir, cientos de millones de dólares.
Lo importante no es el error de tecleo, que es inevitable y volverá a ocurrir mañana en otra mesa. Lo importante es todo lo que no lo paró: nada comparó la cantidad con el volumen habitual del valor, nada comparó el precio con la cotización vigente y el aviso que sí llegó a aparecer se podía descartar con una tecla. Cualquiera de las dos comprobaciones de cordura de este módulo, incluso con umbrales muy generosos, habría bloqueado esa orden antes de salir.