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:
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¶
- Datos BBO L1 observados, con secuencia estricta y timestamps de disponibilidad.
- Un operador OFI en el registro de
finaz_flowcon paridad batch ≡ replay ≡ stream, como el resto de operadores. - Un carril de ejecución por cotización con latencia declarada, distinto del ledger de barras (fill next-open).
- 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¶
- Recalcula el OFI del ejemplo si en
n = 4el ask se hubiera quedado en 100,03 con tamaño 50. - ¿Por qué el z se calcula con las 60 ventanas anteriores y no incluyendo la actual?
- 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.
- ¿Qué propiedad del runtime stream (watermarks, fencing, replay) es imprescindible para que un backtest de Q10 sea reproducible?