Saltar a contenido

Q10 · Order Flow Imbalance L1

Campo Valor
Familia Microestructura: señal de muy corto plazo a partir de los cambios en el mejor bid y el mejor ask
Universo / panel Un instrumento con cotizaciones BBO (nivel 1) evento a evento
Datos BBO con precios y tamaños bid/ask, secuencia temporal estricta, tick y lote, latencia declarada
Features event_ofi, window_ofi, depth_normalization, ofi_zscore
Parámetros y defaults window_ms = 1000, normalization_windows = 60, entry = 2.0, exit = 0.5, holding_ms = 10000, latency_ms = 1, max_spread_ticks = 2
Rejilla window_ms ∈ {250, 1000, 5000}, entry ∈ {1, 2, 3}, holding_ms ∈ {1000, 5000, 10000} → 27 candidatos
Coste esperado O(eventos), estado rolling pequeño
Estado Especificada, no implementada (specified_not_implemented, hito H4); la matriz de capacidades da MISSING_DATA: no observed L1 BBO
Motores implicados Ninguno ejecuta Q10. Base prevista: el runtime stream con watermarks y el reloj de eventos (finaz_clock, finaz_runtime/stream, finaz_transport sobre Redpanda, finaz_replay), que ya procesan flujos de eventos de forma causal

Estado

Q10 no está implementada y no hay datos: la plataforma tiene barras OHLCV, no cotizaciones BBO L1 observadas. La matriz de capacidades de la API de backtesting la declara UNSUPPORTED en todos los carriles con MISSING_DATA: no observed L1 BBO. La receta prohíbe expresamente fabricar OFI a partir de velas. El ejemplo numérico es didáctico, calculado a mano con cotizaciones inventadas.

Fuentes: catalog/strategy_catalog.json, docs/API.md (matriz de capacidades, ejemplo Q10), finazbench/api/routers/capabilities.py, docs/v2/MOTORES.md (AG-010, AG-015, AG-016).

Qué vas a aprender

  • Qué es el order flow imbalance (OFI) y por qué mide presión compradora o vendedora en el libro.
  • Cómo se calcula la contribución de cada actualización del mejor bid y ask.
  • Cómo se agrega en ventanas, se normaliza por profundidad y se convierte en z-score.
  • Por qué la latencia y el orden estricto de eventos son parte de la receta, no un detalle.
  • Qué partes del runtime de la plataforma servirían de base y qué falta.

La idea en una frase

Sumar, segundo a segundo, cuánta liquidez aparece o desaparece en el mejor bid frente al mejor ask; cuando el desequilibrio normalizado es extremo, seguirlo durante unos segundos.

Intuición y evidencia académica

Intuición. Si el mejor bid sube o crece su tamaño, hay compradores más agresivos; si el mejor ask baja o crece, hay vendedores más agresivos. La literatura de microestructura sobre el impacto de los desequilibrios del libro documenta una relación contemporánea fuerte entre el OFI y el cambio de precio en intervalos cortos. La predictibilidad futura, después de costes y latencia, es mucho más débil.

El catálogo lo dice sin rodeos: la investigación de impacto no es una promesa de predictibilidad rentable después de costes.

Cuándo falla.

  • Latencia: la señal se degrada en milisegundos; con la latencia real de un participante, la ventaja puede desaparecer.
  • Spread: cruzar el spread en cada operación cuesta más que el movimiento medio capturado; por eso el filtro de max_spread_ticks.
  • Libro manipulado o fantasma: órdenes que aparecen y se cancelan inflan el OFI sin intención real.

El modelo matemático

Para cada actualización n del BBO, con b bid, a ask, qb y qa sus tamaños:

e_n = I(b_n >= b_{n−1}) · qb_n
    − I(b_n <= b_{n−1}) · qb_{n−1}
    − I(a_n <= a_{n−1}) · qa_n
    + I(a_n >= a_{n−1}) · qa_{n−1}

Lectura: si el bid sube, cuenta todo el tamaño nuevo como presión compradora; si baja, resta todo el tamaño anterior; si se queda igual, cuenta la variación de tamaño. Simétrico en el ask, con signo contrario.

Agregación y normalización:

OFI_w      = sum de e_n en la ventana de window_ms
depth_w    = profundidad media (qb + qa)/2 en la ventana
OFI_norm_w = OFI_w / depth_w
z_w        = (OFI_norm_w − media de las 60 ventanas anteriores) / desv. de esas 60

Reglas:

entrar +1 si z > entry ; −1 si z < −entry        (no entrar si spread > max_spread_ticks)
salir si |z| < exit o tras holding_ms
ejecutar en la PRIMERA cotización posterior a disponibilidad + latency_ms,
con secuencia estrictamente posterior aunque comparta timestamp

Pseudocódigo causal

para cada evento BBO en orden (timestamp, secuencia):
    e = contribución OFI; acumular en la ventana abierta
    al cerrar la ventana (watermark de tiempo): OFI_norm, z con las 60 ventanas PREVIAS
    decisión según z, spread y holding
    orden de mercado de tamaño pequeño; fill en la primera cotización con
        ts >= ts_decisión + latency_ms  y  seq > seq_decisión

Cómo lo haría la plataforma

Pieza Quién la cubriría Estado
Datos BBO L1 Nueva fuente con precios y tamaños bid/ask por evento No existe (MISSING_DATA)
Log de eventos durable y ordenado finaz_transport sobre Redpanda Existe (AG-016)
Watermarks, backpressure y ventanas finaz_runtime/stream Existe (paridad 3 modos, kill -9 + resume, fencing por lease)
Relojes por evento, volumen o valor finaz_clock (ClockSpec) Existe (AG-010)
Reproducción determinista con fallos inyectados finaz_replay Existe (AG-015)
Cálculo OFI y z Operador nuevo en el registro de finaz_flow No existe
Ejecución con latencia y cotización posterior Carril de ejecución por cotización No existe en el ledger SIM-S de barras
flowchart LR
    D["BBO L1 por evento<br/>(sin datos: MISSING_DATA)"] --> T["finaz_transport<br/>Redpanda · orden estricto"]
    T --> S["finaz_runtime/stream<br/>watermark · ventanas 1 s"]
    S --> O["event_ofi → window_ofi<br/>/ profundidad media<br/>(operador no implementado)"]
    O --> Z["ofi_zscore<br/>60 ventanas previas"]
    Z --> R["Reglas<br/>entry 2 · exit 0.5 · 10 s<br/>spread <= 2 ticks"]
    R --> E["Ejecución en la primera cotización<br/>tras latency_ms<br/>(no implementada)"]

Ejemplo numérico

Ejemplo didáctico

Cinco cotizaciones inventadas dentro de una misma ventana de 1 segundo; tick 0,01.

n bid qb ask qa
0 100,00 300 100,02 200
1 100,00 500 100,02 200
2 100,01 100 100,02 150
3 100,01 100 100,03 400
4 100,00 250 100,02 50

Contribuciones evento a evento:

n=1: bid igual, crece 300→500:   +500 − 300 = +200 ; ask igual 200→200: −200 + 200 = 0   → e = +200
n=2: bid sube a 100,01:          +100 − 0    = +100 ; ask igual 200→150: −150 + 200 = +50 → e = +150
n=3: bid igual 100→100:          +100 − 100  = 0    ; ask sube a 100,03: −0 + 150  = +150 → e = +150
n=4: bid baja a 100,00:          +0   − 100  = −100 ; ask baja a 100,02: −50 + 0   = −50  → e = −150

OFI_w = 200 + 150 + 150 − 150 = +350

Normalización por la profundidad media de la ventana:

(qb + qa)/2 por cotización = 250, 350, 125, 250, 150   → media 225
OFI_norm = 350 / 225 = 1.556

Si las 60 ventanas anteriores tuvieran media 0,10 y desviación 0,60 (valores supuestos):

z = (1.556 − 0.10) / 0.60 = 2.43 > entry = 2.0
spread en n=4: 100,02 − 100,00 = 2 ticks  (no supera max_spread_ticks = 2)  → ENTRADA +1

La orden se ejecutaría en la primera cotización con timestamp >= cierre de la ventana + 1 ms y secuencia estrictamente posterior; la posición se cierra si |z| cae por debajo de 0,5 o a los 10 segundos.

Lo que no se puede hacer. Con una vela de 1 minuto (OHLCV) no hay bid, ask ni tamaños: cualquier "OFI" derivado del volumen de la vela es otra cosa y la receta lo prohíbe.

Cómo lanzarla

Hoy no se puede lanzar. La propia docs/API.md usa Q10 como ejemplo de combinación no soportada:

GET /v1/capabilities
  { "template_id": "Q10", "VTA_CPU_LEDGER": "UNSUPPORTED", "NT_FEATURES": "UNSUPPORTED",
    "NT_INTENT_REPLAY": "UNSUPPORTED", "NT_ONLINE": "UNSUPPORTED",
    "unsupported_reason": "MISSING_DATA: no observed L1 BBO." }

POST /v1/backtest con Q10
  → 501 STRATEGY_UNSUPPORTED_BY_ADAPTER
    {"template_id":"Q10","adapter":"NT_ONLINE","reason":"MISSING_DATA: no hay BBO L1"}

Lo que sí se puede ejercitar hoy es la infraestructura de eventos: el runtime stream y el replay determinista de la plataforma (ver Runtime batch, replay y stream).

Qué haría falta para implementarla

  1. Datos BBO L1 observados, con secuencia estricta y timestamps de disponibilidad.
  2. Un operador OFI en el registro de finaz_flow con paridad batch ≡ replay ≡ stream, como el resto de operadores.
  3. Un carril de ejecución por cotización con latencia declarada, distinto del ledger de barras (fill next-open).
  4. Tamaño pequeño y liquidez comprobada: órdenes de mercado en el carril simple; colas y órdenes pasivas son otra prueba.

Riesgos, límites y qué no hace

  • Sin datos: es, con Q09, la receta más lejos de ejecutarse.
  • No se fabrica OFI desde OHLCV ni se usa el volumen de la vela como profundidad.
  • Latencia y costes dominan: un efecto contemporáneo fuerte puede no ser explotable.
  • Colas y órdenes pasivas quedan fuera: la receta usa órdenes de mercado en el carril simple.
  • Empates de timestamp: la secuencia debe ser estrictamente posterior; ejecutar en la misma cotización que generó la señal es una fuga.

Resumen

  • OFI por evento a partir de cambios de precio y tamaño en el mejor bid y ask; agregado en ventanas de 1 s y normalizado por profundidad.
  • z con las 60 ventanas previas; entrada a ±2, salida a 0,5 o 10 s; filtro de spread; latencia declarada.
  • Especificada, no implementada y sin datos (MISSING_DATA).
  • La plataforma tiene la infraestructura de eventos (stream, watermarks, replay, reloj); faltan los datos, el operador y el carril de ejecución por cotización.

Para practicar

  1. Recalcula el OFI del ejemplo si en n = 4 el ask se hubiera quedado en 100,03 con tamaño 50.
  2. ¿Por qué el z se calcula con las 60 ventanas anteriores y no incluyendo la actual?
  3. Explica por qué una latencia de 1 ms obliga a buscar la "primera cotización posterior" y no la cotización de la decisión.
  4. ¿Qué propiedad del runtime stream (watermarks, fencing, replay) es imprescindible para que un backtest de Q10 sea reproducible?