Order Flow Imbalance: The Cont-Kukanov-Stoikov Event Decomposition
La mayoría de las señales derivadas de libros en este blog son instantáneas: cuánto volumen hay en cada lado en este momento. El desequilibrio del flujo de pedidos es un objeto diferente. Es una descomposición del flujo de actualizaciones entre dos instantáneas en una única cantidad firmada, una que cuenta una cancelación en la oferta de la misma manera que cuenta una venta en el mercado.
Esa descomposición es el tema de este artículo. Específicamente: los indicadores de eventos de Cont-Kukanov-Stoikov (2014) que lo definen, la extensión multinivel Xu-Gould-Howison (2019) y su reducción de componentes principales, y las reglas Lee-Ready y Bulk Volume Classification que necesita cuando su feed no etiqueta la dirección comercial.
Una nota marco por adelantado, porque rige cómo se debe leer cada número a continuación. El famoso R-cuadrado del 50-65% del documento CKS es una regresión contemporánea: el cambio de precio en un intervalo retrocede en el flujo de órdenes en el mismo intervalo. Es una descomposición de los movimientos de los precios, no un pronóstico de los mismos. Comparándolo con el 1-2% de varianza que un modelo de rendimiento diario de acciones explica es manzanas con naranjas, y este blog ha argumentado extensamente, en DeepLOB y la brecha entre predicción y ganancias y en el resultado negativo honesto, que al combinar los dos es como los tutoriales se convierten en trampas. Una regresión OFI estrictamente rezagada es una cifra legítima y mucho más pequeña. Este artículo no informa ninguno.
Por qué el flujo de pedidos, no las transacciones

Un cambio de precio en una cartera de órdenes de límite continuo ocurre a través de exactamente cuatro mecanismos: una compra de mercado consume solicitudes en reposo; una venta de mercado consume ofertas en reposo; una cancelación de la oferta elimina el apoyo y permite que la oferta caiga; una nueva orden límite sobre la demanda agrega resistencia. Sólo los dos primeros son intercambios. Los dos últimos son invisibles para cualquier medida basada en el volumen (VWAP, volumen en balance, flujo comercial firmado) y en la mayoría de los lugares la relación orden-comercio supera 10:1, por lo que la parte invisible es la parte grande. La única afirmación de utilidad de OFI es que fija el precio de los cuatro mecanismos en la misma escala. (Para el libro como estructura de datos y el vector de características de instantánea estándar , consulte DeepLOB.)
El modelo OFI de Cont-Kukanov-Stoikov

El modelo fundamental proviene del artículo de 2014 de Cont, Kukanov y Stoikov "The Price Impact of Order Book Events" (Journal of Financial Econometrics).
Definición del desequilibrio del flujo de órdenes
Considere el mejor precio de oferta , el mejor precio de oferta y sus tamaños y . Entre observaciones consecutivas en y :
donde las contribuciones de los eventos del lado de la compra y del lado de la venta son:
En lenguaje sencillo: si el mejor precio de oferta aumenta, aparece un nuevo interés de compra; cuente su tamaño total como positivo. Si cae, el interés de compra desaparece: reste el tamaño anterior. Si el precio no cambia, cuente solo el cambio de talla. El lado de la pregunta es la imagen reflejada.
La elegancia es que estas tres ramas de indicadores cubren todas las transiciones posibles de la parte superior del libro, por lo que cada actualización se asigna exactamente a un número firmado. No se requiere clasificación comercial, etiquetas laterales ni alimentación a nivel de mensaje: bastan dos instantáneas consecutivas.
Agregación en intervalos
Para un intervalo que contiene actualizaciones de libros:
El modelo de impacto lineal del precio
donde es el cambio en el precio medio, es el coeficiente de impacto en el precio y es el ruido residual. En el caso de las acciones estadounidenses en intervalos de 10 segundos a 1 minuto, CKS informa un contemporáneo del 50-65% por acción. Nuevamente: el mismo intervalo, sin mirar hacia el futuro.
Escalamiento transversal
CKS también mostró que el coeficiente de impacto escala inversamente con la profundidad:
donde es el volumen promedio en reposo a la mejor oferta y demanda. El mismo flujo empuja aún más el precio en un libro delgado. Esta es la única parte del modelo que se transfiere entre lugares sin reajustar el nivel, porque predice una relación en lugar de un número, y se puede probar directamente en pares de criptomonedas de diferente profundidad.
Desequilibrio del flujo de órdenes multinivel (MLOFI)

El modelo original utiliza sólo la parte superior del libro. Xu, Gould y Howison (2019) lo ampliaron a niveles .
Definición
donde aplica la fórmula idéntica de tres ramas al -ésimo par de oferta/demanda.
Impacto en el precio multinivel
El hallazgo publicado, sobre las acciones del Nasdaq, es que cada nivel adicional agrega fuera de la muestra: aproximadamente entre 10 y 15 puntos porcentuales al pasar de un nivel a cinco, con ganancias marginales aún presentes en diez. Los coeficientes decaen monótonamente, : la parte superior del libro domina, pero los niveles más profundos transmiten una señal incremental no trivial.
Si esto sobrevive en un libro criptográfico es la pregunta abierta más interesante de este artículo. Los libros de criptomonedas son más delgados y mucho más agitados en profundidad, y es completamente plausible que los niveles 2 a 5 no agreguen nada una vez que se abandona un libro de pedidos del Nasdaq.
Reducción del componente principal
Las OIF de niveles adyacentes están altamente correlacionadas (por encima de 0,8 en los resultados de acciones publicados), por lo que una descomposición del componente principal es natural. El primer componente informado captura más del 89% de la varianza total y sirve como una señal agregada única:
con el vector propio principal de la matriz de covarianza OFI. El atractivo práctico es que colapsa una regresión colineal en un escalar bien condicionado; vale la pena hacerlo incluso si la varianza compartida de sus datos es menor.
Clasificación de operaciones: compra versus venta iniciada

La propia OFI no necesita clasificación comercial. Pero si desea compararlo con medidas basadas en el comercio, o si solo tiene barras agregadas, debe inferir la dirección.
Regla de cotización
Compare el precio comercial con el punto medio predominante :
Una operación por encima del punto medio probablemente era un comprador que levantaba la oferta; debajo, un vendedor que acierta la oferta.
El algoritmo Lee-Ready (1991) es la regla de cotización con una regla de tick alternativa para operaciones exactamente en el punto medio, donde la regla de cotización es indeterminada. La regla de tick (signo del último cambio de precio, que lleva el lado anterior hacia adelante en un tick cero) ya se deriva e implementa en Más allá de las barras de tiempo, que envía un _tick_sign() funcional. La precisión de la clasificación Lee-Ready informada es del 72 al 85 % según el mercado y el período.
Clasificación de volumen a granel (BVC)
Cuando no se pueden asignar operaciones individuales (barras agregadas, la mayoría de las API de velas públicas), la clasificación de volumen masivo de Easley, López de Prado y O'Hara estima la fracción de compra del volumen de una barra a partir de su cambio de precio normalizado:
con el CDF normal estándar y estimado a partir de cambios de precios recientes. Menos precisa que la clasificación a nivel de tick, pero funciona en OHLCV.
Lo que no es OFI

Tres cantidades vecinas se confunden con OFI. Cada uno de ellos se trata adecuadamente en otras partes de este blog; las distinciones son lo que importa aquí.
Desequilibrio estático del libro de pedidos (OBI) es la versión instantánea: volumen de oferta en reposo versus volumen de demanda en reposo, sin seguimiento de eventos. Consulte Características LOB tradicionales de DeepLOB para conocer la fórmula y su formato de varios niveles. El contraste es el objetivo de OFI: OBI te dice el estado del libro, OFI te dice cómo llegó allí.
El desequilibrio comercial (TI) y sus variantes ponderadas por volumen son flujos comerciales firmados, definidos e implementados como funciones móviles en modelado de diferenciales con aprendizaje automático. Un resultado publicado sobre la relación tiene peso aquí: cuando OFI y TI se ingresan juntos como regresores en cambios de precio medio, TI se vuelve estadísticamente insignificante. Su contenido está subsumido. Ese es el caso empírico para rastrear eventos de libros en lugar de operaciones: los eventos que casi se convirtieron en operaciones contienen la misma información que los que lo hicieron.
La media ponderada por volumen es un estimador de valor razonable, no una medida de desequilibrio, y se trata en DeepLOB, incluida la advertencia de denominación de que no es el microprecio de Stoikov, que es un estimador ajustado por martingala construido precisamente porque la media ponderada ingenua está sesgada.
Implementación de Python

Una implementación directa de la descomposición de CKS, generalizada a niveles .
Cálculo básico de OIF
import numpy as np
import pandas as pd
from dataclasses import dataclass, field
from typing import Optional
@dataclass
class OrderBookSnapshot:
timestamp: float
bid_prices: np.ndarray # best bid at index 0, descending
ask_prices: np.ndarray # best ask at index 0, ascending
bid_sizes: np.ndarray
ask_sizes: np.ndarray
@dataclass
class OFICalculator:
"""
Computes Order Flow Imbalance from consecutive order book snapshots.
Supports multi-level OFI (MLOFI) up to `n_levels` deep.
"""
n_levels: int = 5
prev_snapshot: Optional[OrderBookSnapshot] = field(default=None, init=False)
def compute_level_ofi(
self,
prev_price: float, curr_price: float,
prev_size: float, curr_size: float,
side: str
) -> float:
"""Compute single-level OFI contribution for bid or ask side."""
if side == "bid":
if curr_price > prev_price:
return curr_size # new level appeared above
elif curr_price < prev_price:
return -prev_size # old level disappeared
else:
return curr_size - prev_size # same level, size changed
else: # ask side
if curr_price < prev_price:
return curr_size # new level appeared below
elif curr_price > prev_price:
return -prev_size # old level disappeared
else:
return curr_size - prev_size # same level, size changed
def update(self, snapshot: OrderBookSnapshot) -> Optional[np.ndarray]:
"""
Process new snapshot, return MLOFI vector of shape (n_levels,).
Returns None on first call (no previous snapshot to compare).
"""
if self.prev_snapshot is None:
self.prev_snapshot = snapshot
return None
prev = self.prev_snapshot
n = min(self.n_levels, len(snapshot.bid_prices), len(prev.bid_prices))
ofi = np.zeros(n)
for level in range(n):
e_buy = self.compute_level_ofi(
prev.bid_prices[level], snapshot.bid_prices[level],
prev.bid_sizes[level], snapshot.bid_sizes[level],
side="bid"
)
e_sell = self.compute_level_ofi(
prev.ask_prices[level], snapshot.ask_prices[level],
prev.ask_sizes[level], snapshot.ask_sizes[level],
side="ask"
)
ofi[level] = e_buy - e_sell
self.prev_snapshot = snapshot
return ofi
Agregación en Windows
@dataclass
class OFIAggregator:
"""
Aggregates raw OFI updates into fixed time windows.
Emits the regression inputs (aggregated MLOFI) and target (delta mid).
"""
window_seconds: float = 10.0
n_levels: int = 5
calculator: OFICalculator = field(init=False)
buffer: list = field(default_factory=list, init=False)
window_start: float = 0.0
def __post_init__(self):
self.calculator = OFICalculator(n_levels=self.n_levels)
def on_snapshot(self, snapshot: OrderBookSnapshot) -> Optional[dict]:
"""
Feed a new order book snapshot.
Returns aggregated window dict when a window completes, else None.
"""
ofi_vec = self.calculator.update(snapshot)
if ofi_vec is None:
self.window_start = snapshot.timestamp
return None
if not self.buffer:
self.window_start = snapshot.timestamp
self.buffer.append({
"timestamp": snapshot.timestamp,
"ofi": ofi_vec.copy(),
"mid": (snapshot.bid_prices[0] + snapshot.ask_prices[0]) / 2,
})
elapsed = snapshot.timestamp - self.window_start
if elapsed >= self.window_seconds:
return self._flush()
return None
def _flush(self) -> dict:
"""Aggregate buffered OFI updates into a single window record."""
ofi_matrix = np.array([b["ofi"] for b in self.buffer])
agg_ofi = ofi_matrix.sum(axis=0) # shape: (n_levels,)
result = {
"window_start": self.window_start,
"window_end": self.buffer[-1]["timestamp"],
"n_updates": len(self.buffer),
"mid_open": self.buffer[0]["mid"],
"mid_close": self.buffer[-1]["mid"],
"delta_mid": self.buffer[-1]["mid"] - self.buffer[0]["mid"],
"ofi_level1": agg_ofi[0],
"ofi_total": agg_ofi.sum(),
"mlofi": agg_ofi,
}
self.buffer.clear()
return result
Observe lo que emite el agregador: mlofi y delta_mid sobre la misma ventana. Ésa es la regresión contemporánea. Para obtener uno predictivo, empareje el mlofi de la ventana con el delta_mid de la ventana y espere un ajuste sustancialmente peor.
Clasificación comercial (Lee-Ready)
def classify_trades_lee_ready(
trades: pd.DataFrame,
quotes: pd.DataFrame
) -> pd.DataFrame:
"""
Classify trades as buy (+1) or sell (-1) using Lee-Ready:
quote rule first, tick rule as fallback at the midpoint.
Parameters
----------
trades : DataFrame with columns ['timestamp', 'price', 'size']
quotes : DataFrame with columns ['timestamp', 'bid', 'ask']
Returns
-------
trades with added 'side' column
"""
trades = trades.sort_values("timestamp").copy()
quotes = quotes.sort_values("timestamp")
trades = pd.merge_asof(
trades, quotes,
on="timestamp",
direction="backward"
)
trades["mid"] = (trades["bid"] + trades["ask"]) / 2
trades["side"] = np.where(
trades["price"] > trades["mid"], 1,
np.where(trades["price"] < trades["mid"], -1, 0)
)
trades["price_diff"] = trades["price"].diff()
tick_sign = np.sign(trades["price_diff"])
tick_sign = tick_sign.replace(0, np.nan).ffill().fillna(1)
midpoint_mask = trades["side"] == 0
trades.loc[midpoint_mask, "side"] = tick_sign[midpoint_mask].astype(int)
return trades
Normalizando la señal
class OFISignal:
"""
Rolling z-score normalization of aggregated OFI.
Deliberately does NOT convert OFI into a predicted return: that
requires a fitted beta, and beta is venue-, pair- and regime-specific.
Fit it on your own data before wiring this into anything.
"""
def __init__(self, window_seconds: float = 10.0, n_levels: int = 5,
lookback: int = 100):
self.aggregator = OFIAggregator(
window_seconds=window_seconds, n_levels=n_levels,
)
self.lookback = lookback
self.ofi_history: list[float] = []
def process(self, snapshot: OrderBookSnapshot) -> Optional[dict]:
agg = self.aggregator.on_snapshot(snapshot)
if agg is None:
return None
ofi = agg["ofi_level1"]
self.ofi_history.append(ofi)
if len(self.ofi_history) > self.lookback:
self.ofi_history.pop(0)
if len(self.ofi_history) >= 20:
arr = np.array(self.ofi_history)
mu, sigma = arr.mean(), arr.std()
zscore = (ofi - mu) / max(sigma, 1e-10)
else:
zscore = 0.0
return {**agg, "ofi_zscore": zscore}
Dónde se conecta OFI

Creación de mercado. OFI ingresa como un término de sesgo predictivo sobre el valor razonable, además del sesgo de inventario ya derivado y codificado en el creador de mercado de Avellaneda-Stoikov: . Los dos términos responden a preguntas diferentes: el término de flujo dice hacia dónde se dirige el precio, el término de inventario dice lo que puede permitirse mantener, y el artículo publicado cubre el segundo, incluido por qué el inventario positivo hace bajar ambas cotizaciones. Un grande también es un desencadenante razonable de selección adversa para las respuestas defensivas (ensanchar, encoger, tirar hacia un lado) catalogadas en huellas digitales de identificación del comerciante y detección de anomalías.
Ejecución. OFI es una entrada para la estimación de probabilidad de cumplimiento de la capa táctica , no un controlador de urgencia separado. La decisión post-contra-cruz es una aritmética explícita de equilibrio ( con ) y vive en [tácticas de ejecución de órdenes secundarias] (/en/blog/post/child-order-execution-tactics), que también argumenta que la capa táctica nunca debe volver a derivar la urgencia de su propia visión del mercado, porque eso crea dos controladores que no están de acuerdo.
Consideraciones prácticas

Vida media de la señal y recalibración continua. Esta es la parte específica de OFI. El contenido predictivo de OFI decae en una escala de tiempo de milisegundos a segundos en instrumentos líquidos, lo que significa que la ventana de agregación no es un parámetro libre: es una apuesta en el horizonte. Y no es una constante: se mueve intradía, con profundidad y alrededor de eventos programados, por lo que cualquier ajuste de producción lo reestima en ventanas móviles en lugar de fijar un valor a partir de una prueba retrospectiva.
Todo lo demás aquí está cubierto en otra parte. Presupuestos de latencia y escalera de coubicación/FPGA/derivación del kernel: sección de producción de DeepLOB. El patrón de liquidez intradía en forma de U y las estadísticas de normalización a la deriva: modelado de diferenciales. Recalibración por lugar y por qué un modelo instalado en Nasdaq no se transferirá a un par criptográfico intacto: DeepLOB nuevamente, con el lado de fragmentación en enrutamiento de pedidos inteligente.
Manipulación. Una consecuencia es específica de este modelo y vale la pena señalarla claramente: la descomposición de CKS pondera cada evento solo por tamaño, sin noción de intención o persistencia. Por lo tanto, un suplantador de tamaño que coloca y cancela inyecta directamente en la señal, con todo su peso, por construcción. Las heurísticas de detección (tasas de cancelación, duración de los pedidos, comportamiento del muro a medida que se acerca el precio) se encuentran en posición de la cola y análisis del muro del libro de pedidos; la liquidez fantasma entre lugares está en [enrutamiento de órdenes inteligente] (/en/blog/post/smart-order-routing-crypto).
Conclusiones clave

-
OFI es una descomposición de eventos, no una instantánea. La fórmula del indicador de tres ramas asigna cada transición en la parte superior del libro (precio hacia arriba, precio hacia abajo, cambio de tamaño) a un número con signo, a partir de dos instantáneas consecutivas y nada más.
-
El R cuadrado del título es contemporáneo. La cifra de CKS 50-65% descompone los movimientos de precios en el mismo intervalo; no es un pronóstico y no debe compararse con modelos de rentabilidad prospectivos.
-
La OFI multinivel agrega niveles y la PCA los colapsa. Los resultados incrementales publicados: y 89% de variación provienen de acciones de Nasdaq. Aquí no se ha probado si la profundidad todavía ayuda en un libro criptográfico.
-
OFI subsume el desequilibrio comercial. Si se ingresan en conjunto, TI se vuelve insignificante: las cancelaciones que OFI ve y TI no son las que marcan la diferencia.
-
El modelo no tiene intención alguna. La ponderación igual por tamaño es exactamente lo que lo hace falsificable.
-
No se mide nada en este artículo. Cada número citado proviene de la literatura sobre acciones. Antes de que esta señal toque el capital, necesita un ajuste de sus propios datos contables, una especificación retrasada informada por separado y una verificación de si la ventaja sobrevive a las tarifas y al diferencial.
Lectura adicional

- Cont, R., Kukanov, A. y Stoikov, S. (2014). "El impacto en el precio de los eventos del libro de pedidos". Revista de Econometría Financiera, 12(1), 47-88.
- Xu, K., Gould, M. y Howison, S. (2019). "Desequilibrio del flujo de órdenes multinivel en un libro de órdenes límite". arXiv:1907.06230.
- Kolm, P., Turiel, J. y Westray, N. (2023). "Desequilibrio profundo del flujo de órdenes: extracción de alfa en múltiples horizontes del libro de órdenes límite". Finanzas Matemáticas, 33(4).
- Lee, C. y Ready, M. (1991). "Inferir la dirección comercial a partir de datos intradiarios". Revista de Finanzas, 46(2), 733-746.
- Easley, D., López de Prado, M., & O'Hara, M. (2012). "Toxicidad del flujo y liquidez en un mundo de alta frecuencia". Revisión de estudios financieros, 25(5), 1457-1493.
Authors
Trading-systems engineer
Trading-systems engineer building bots since 2017: cross-exchange arbitrage (connected up to 30 venues), cointegration-based pairs arbitrage across spot and futures, scalping, news and sentiment-driven strategies, trend algorithms, and portfolio management and balancing algorithms. Also builds sub-millisecond order execution, big-data warehouses, backtesting engines, AI agents, and trading interfaces (incl. open-source profitmaker.cc). Stack: JS/TS, Python, Rust/Zig/Go, DevOps, backend, frontend, architecture.