🛡️ sandbox-labs GitHub ↗

CM-02 · Sistema alternativo de transacción#

En una frase, para cualquiera: un mercado es una cola con reglas. Quien ofrece mejor precio va delante; entre dos que ofrecen lo mismo, va delante el que llegó primero. Romper esa regla es la forma más simple de dar ventaja a alguien sin que se note.

Estado real: 🟠 prototype — hay código, faltan los escenarios · Carpeta: domains/capital-markets/cases/02-alternative-trading-system/

Instrumentos, órdenes y participantes simulados. No es una autorización regulatoria ni una recomendación de inversión.

Por qué se realiza este caso#

Un libro de órdenes parece sencillo hasta que se escribe. Las reglas son pocas, y cada una tiene una forma de romperse que no se ve desde fuera:

ReglaCómo se rompe sin que se note
Prioridad precio-tiempoUna orden peor se ejecuta antes «por redondeo»
Prioridad precio-tiempoEl orden de llegada se pierde al reordenar internamente
El libro nunca queda cruzadoQueda una compra a 100 y una venta a 99 sin casar
La orden que descansa fija el precioSe usa el precio de la que llega, y la diferencia se la queda alguien
Cancelar es inmediatoSe ejecuta una orden ya cancelada

Ese cuarto punto es el más sutil y el más caro. Si compras a 105 y hay una venta descansando a 100, la operación es a 100: el precio lo fija quien estaba esperando. Aplicar el precio de la orden entrante mueve dinero de forma sistemática hacia un lado.

La idea que enseña, y que ningún otro caso enseña#

La prioridad como invariante comprobable. No es una preferencia de diseño: es una propiedad que se puede afirmar y verificar después de cada operación. El libro se comprueba a sí mismo tras cada orden, y si alguna vez queda cruzado, el motor está mal, no el mercado.

Casos de uso reales#

Cómo funciona#

flowchart LR
  O["📥 Orden entrante"] --> V{"¿Válida?<br/>precio, tamaño, instrumento"}
  V -- no --> X["🚫 Rechazada con motivo"]
  V -- sí --> M{"¿Cruza con el<br/>otro lado del libro?"}
  M -- sí --> T["🤝 Ejecución al precio<br/>de la orden que DESCANSA"]
  M -- no --> R["📚 Descansa en el libro"]
  T --> I["🔍 Invariante:<br/>el libro no queda cruzado"]
  R --> I
flowchart TB
  subgraph L["📚 Libro de órdenes"]
    direction TB
    C["Compras<br/>102 · 101 · 100"]
    V["Ventas<br/>103 · 104 · 105"]
  end
  N["Llega: comprar a 104"] --> L
  L --> E["Ejecuta contra 103<br/>(el mejor precio disponible)<br/>y el resto descansa a 104"]

Esquemas#

Orden#

{
  "id": "ord-001",
  "instrument": "ACME-SIM",
  "side": "buy",
  "type": "limit",
  "price": { "minorUnits": 10400, "currency": "CLP" },
  "quantity": 100,
  "receivedAt": 1725000000
}

Ejecución#

{
  "trades": [
    { "buy": "ord-001", "sell": "ord-987", "price": { "minorUnits": 10300, "currency": "CLP" }, "quantity": 60 }
  ],
  "resting": { "id": "ord-001", "remaining": 40 },
  "bookCrossed": false
}

Nótese el precio de la ejecución: 10300, el de la orden que descansaba, no

  1. Ese es el invariante en acción.

Software necesario#

ComponentePara qué¿Obligatorio?
Rust 1.75+El motor del libro y sus invariantes
Node.js 20+ / pnpm 9+Visualización de profundidad en el panelNo

No necesita bubblewrap ni Linux: es lógica determinista. Corre en Windows, macOS y Linux por igual.

Instalación#

cargo build --release
cargo test -p sandbox-markets      # ejecuta los invariantes del libro

Procesos que se crean#

cargo test -p sandbox-markets
  │
  └─ un proceso determinista
      ├─ sin red
      ├─ sin reloj del sistema (el tiempo es un número de secuencia)
      └─ mismo resultado en cualquier máquina

Que el tiempo sea un número de secuencia y no el reloj es deliberado: el reloj del sistema haría que la prioridad temporal dependiera de la máquina.

Tiempo de carga#

OperaciónCoste medido
cargo test -p sandbox-markets< 1 s
Una orden procesadamicrosegundos
Comprobación del invariante tras cada ordenmicrosegundos

Estado real y qué falta#

Construido: OrderBook, Order, Trade y OrderError en Rust, con 11 invariantes: prioridad precio-tiempo, precio fijado por la orden que descansa, libro nunca cruzado, ejecución parcial, cancelación y rechazo con motivo.

Falta para llegar a functional:

liquidez, precio anómalo, duplicación, latencia, órdenes fuera de banda e interrupción de mercado.

registro de órdenes y obtener el mismo libro.

Esa última es la que convierte el caso en auditable, y es la que decide el salto a functional.

Si algo falla#

SituaciónCausaCómo se resuelve
bookCrossed: trueQuedó una compra por encima de una venta sin casarEs un fallo del motor, no del mercado. El invariante se comprueba tras cada orden precisamente para que salte aquí y no en producción
Una ejecución sale al precio de la orden entranteSe aplicó el precio equivocadoEl precio lo fija la orden que descansa. Aplicar el de la entrante mueve dinero de forma sistemática hacia un lado. Es uno de los 11 invariantes
Dos órdenes al mismo precio se ejecutan en orden distinto al de llegadaSe perdió la prioridad temporalEl tiempo es un número de secuencia, no el reloj del sistema: el reloj haría que la prioridad dependiera de la máquina
Una orden cancelada se ejecutaCondición de carrera entre cancelación y casaciónCancelar tiene que ser efectivo antes de procesar la siguiente orden. Si ocurre, el motor está mal
No se puede reconstruir la sesiónLa reconstrucción todavía no está construidaEs lo que separa este caso de functional, y es requisito previo de CM-09. Está en ROADMAP
cargo test -p sandbox-markets falla tras tocar el libroUn invariante dejó de cumplirseEl nombre del test dice cuál. Arreglar el motor, no el test: cada invariante está ahí porque romperlo da ventaja a alguien

Los fallos que afectan a cualquier caso —la compilación, el catálogo, la evidencia— están resueltos uno a uno en Cuando algo falla.

Esta familia no necesita aislamiento del sistema: no ejecuta código ajeno, sino reglas de negocio deterministas. Por eso casi ningún fallo suyo viene del entorno, y casi todos vienen de los datos.


Ver también: Catálogo completo · CM-09 · vigilancia · CM-04 · enrutamiento