Order Flow Imbalance: The Cont-Kukanov-Stoikov Event Decomposition
A maioria dos sinais derivados de livros neste blog são instantâneos: quanto volume está em cada lado neste momento. O desequilíbrio do fluxo de pedidos é um objeto diferente. É uma decomposição do fluxo de atualizações entre dois instantâneos em uma única quantidade assinada – uma que conta um cancelamento na oferta da mesma forma que conta uma venda no mercado.
Essa decomposição é o assunto deste artigo. Especificamente: os indicadores de evento Cont-Kukanov-Stoikov (2014) que o definem, a extensão multinível Xu-Gould-Howison (2019) e sua redução de componente principal, e as regras de classificação de volume em massa e Lee-Ready que você precisa quando seu feed não rotula a direção comercial.
Uma nota inicial, porque ela rege como você deve ler cada número abaixo. O famoso R-quadrado de 50-65% do artigo CKS é uma regressão contemporânea: mudança de preço ao longo de um intervalo regredido no fluxo de pedidos durante o mesmo intervalo. É uma decomposição dos movimentos de preços, não uma previsão deles. Compará-lo com 1-2% de variação que um modelo de retorno diário de ações explica é maçãs com laranjas, e este blog argumentou longamente - em DeepLOB e a lacuna de previsão versus lucro e em o resultado negativo honesto - que combinar os dois é como os tutoriais se tornam armadilhas. Uma regressão OFI estritamente defasada é um número legítimo e muito menor. Este artigo não relata nenhum.
Por que fluxo de pedidos, não negociações

Uma mudança de preço em uma carteira de pedidos com limite contínuo ocorre por meio de exatamente quatro mecanismos: uma compra no mercado consome ofertas restantes; uma venda no mercado consome ofertas restantes; um cancelamento da oferta remove o suporte e deixa a oferta cair; uma nova ordem limite na oferta adiciona resistência. Apenas os dois primeiros são negociações. Os dois últimos são invisíveis para qualquer medida baseada em volume – VWAP, volume no balanço, fluxo comercial assinado – e na maioria dos locais a relação ordem/negociação excede 10:1, portanto a parte invisível é a maior parte. Toda a alegação de utilidade da OFI é que ela precifica todos os quatro mecanismos na mesma escala. (Para o livro como uma estrutura de dados e o vetor de recursos de instantâneo padrão , consulte DeepLOB.)
O modelo OFI Cont-Kukanov-Stoikov

O modelo fundamental vem do artigo de 2014 de Cont, Kukanov e Stoikov "The Price Impact of Order Book Events" (Journal of Financial Econometrics).
Definindo desequilíbrio no fluxo de pedidos
Considere o melhor preço de oferta , o melhor preço de venda e seus tamanhos e . Entre observações consecutivas em e :
onde as contribuições do evento buy-side e sell-side são:
Em linguagem simples: se o melhor preço de oferta subir, surge um novo interesse de compra – considere seu tamanho total como positivo. Se cair, o interesse de compra desapareceu – subtraia o valor antigo. Se o preço permanecer inalterado, conte apenas a mudança de tamanho. O lado da pergunta é a imagem espelhada.
A elegância é que esses três ramos do indicador cobrem todas as transições possíveis do topo do livro, de modo que cada atualização é mapeada para exatamente um número assinado. Sem classificação comercial, sem rótulos laterais, sem necessidade de feed em nível de mensagem – dois instantâneos consecutivos são suficientes.
Agregação em intervalos
Para um intervalo contendo atualizações de livros:
O modelo linear de impacto de preços
onde é a mudança no preço médio, é o coeficiente de impacto do preço e é o ruído residual. Nas ações dos EUA em intervalos de 10 segundos a 1 minuto, o CKS reporta contemporâneo de 50-65% por ação. Novamente: mesmo intervalo, não prospectivo.
Dimensionamento transversal
O CKS também mostrou as escalas dos coeficientes de impacto inversamente com a profundidade:
onde é o volume médio restante no melhor lance e venda. O mesmo fluxo empurra o preço ainda mais em um livro fino. Esta é a única parte do modelo que é transferida entre locais sem reajustar o nível, porque prevê um relacionamento em vez de um número – e pode ser testado diretamente em pares criptográficos de diferentes profundidades.
Desequilíbrio de fluxo de pedidos multinível (MLOFI)

O modelo original utiliza apenas a parte superior do livro. Xu, Gould e Howison (2019) estenderam-no para níveis .
Definição
onde aplica a fórmula idêntica de três ramificações ao -ésimo par de compra/venda.
Impacto de preço multinível
A conclusão publicada, sobre as ações da Nasdaq, é que cada nível adicional acrescenta fora da amostra – cerca de 10-15 pontos percentuais, indo de um nível para cinco, com ganhos marginais ainda presentes em dez. Os coeficientes decaem monotonicamente, : o topo do livro domina, mas os níveis mais profundos carregam um sinal incremental não trivial.
Se isso sobrevive em um livro criptográfico é a questão em aberto mais interessante neste artigo. Os livros criptográficos são mais finos e muito mais agitados em profundidade, e é inteiramente plausível que os níveis 2 a 5 não acrescentem nada quando você sai de um livro de pedidos da Nasdaq.
Redução de componentes principais
Os OIF de nível adjacente estão altamente correlacionados (acima de 0,8 nos resultados de capital próprio publicados), pelo que uma decomposição das componentes principais é natural. O primeiro componente relatado captura mais de 89% da variação total e serve como um único sinal agregado:
com o autovetor líder da matriz de covariância OFI. O apelo prático é que ela reduz uma regressão colinear em um escalar bem condicionado – vale a pena fazer isso mesmo que a parcela de variação em seus dados seja menor.
Classificação de negociações: compra vs venda iniciada

O próprio OFI não precisa de classificação comercial. Mas se quiser compará-lo com medidas baseadas no comércio, ou se tiver apenas barras agregadas, será necessário inferir a direção.
Regra de cotação
Compare o preço comercial com o ponto médio predominante :
Uma negociação acima do ponto médio provavelmente representava um comprador levantando a oferta; abaixo dele, um vendedor acertando o lance.
O algoritmo Lee-Ready (1991) é a regra de cotação com um substituto de regra de tick para negociações exatamente no ponto médio, onde a regra de cotação é indeterminada. A regra do tick – sinal da última mudança de preço, transportando o lado anterior para frente em um tick zero – já foi derivada e implementada em Beyond time bars, que envia um _tick_sign() funcional. A precisão da classificação Lee-Ready relatada é de 72-85% dependendo do mercado e do período.
Classificação de volume em massa (BVC)
Quando as negociações individuais não podem ser atribuídas - barras agregadas, a maioria das APIs de velas públicas - Easley, Lopez de Prado e O'Hara's Bulk Volume Classification estimam a fração de compra do volume de uma barra a partir de sua mudança de preço normalizada:
com o CDF normal padrão e estimado a partir de alterações recentes de preços. Menos precisa que a classificação em nível de tick, mas funciona em OHLCV.
O que OFI não é

Três quantidades vizinhas são confundidas com OFI. Cada um é abordado adequadamente em outra parte deste blog; as distinções são o que importa aqui.
Desequilíbrio estático da carteira de pedidos (OBI) é a versão instantânea – volume de lances restante em relação ao volume de pedidos restante, sem rastreamento de eventos. Consulte recursos LOB tradicionais do DeepLOB para obter a fórmula e seu formulário multinível. O contraste é o objetivo do OFI: o OBI informa o estado do livro, o OFI informa como ele chegou lá.
Desequilíbrio comercial (TI) e suas variantes ponderadas por volume são fluxos comerciais assinados, definidos e implementados como recursos contínuos em modelagem de spread com aprendizado de máquina. Um resultado publicado sobre a relação é importante aqui: quando OFI e TI são inseridos juntos como regressores nas mudanças de preços médios, TI torna-se estatisticamente insignificante. Seu conteúdo é subsumido. Esse é o caso empírico para rastrear eventos de livros em vez de negociações – os eventos que quase se tornaram negociações carregam as mesmas informações que aqueles que se tornaram.
O meio ponderado por volume é um estimador de valor justo, não uma medida de desequilíbrio, e é abordado em DeepLOB - incluindo a ressalva de nomenclatura de que não é o micropreço de Stoikov, que é um estimador ajustado por martingale construído precisamente porque o meio ponderado ingênuo é tendencioso.
Implementação Python

Uma implementação direta da decomposição CKS, generalizada para níveis .
Cálculo principal do OFI
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
Agregação no 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 o que o agregador emite: mlofi e delta_mid na mesma janela. Essa é a regressão contemporânea. Para obter um preditivo, emparelhe mlofi da janela com delta_mid da janela e espere um ajuste substancialmente pior.
Classificação 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 o sinal
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}
Onde o OFI se conecta

Criação de mercado. OFI entra como um termo de distorção preditiva no valor justo, além da distorção de estoque já derivada e codificada no criador de mercado Avellaneda-Stoikov: . Os dois termos respondem a perguntas diferentes – o termo de fluxo diz para onde está indo o preço, o termo de estoque diz o que você pode manter – e o artigo publicado cobre a segunda, incluindo por que o estoque positivo empurra ambas as cotações para baixo. Um grande também é um gatilho de seleção adversa razoável para as respostas defensivas (ampliar, encolher, puxar para um lado) catalogadas em impressões digitais de identificação do comerciante e detecção de anomalias.
Execução. OFI é uma entrada para a estimativa de probabilidade de preenchimento da camada tática , não um controlador de urgência separado. A decisão pós-versus-cruzada é aritmética explícita do ponto de equilíbrio — com — e vive em táticas de execução de ordens secundárias, que também argumenta que a camada tática nunca deve derivar novamente a urgência de sua própria visão do mercado, porque isso cria dois controladores que discordam.
Considerações Práticas

Meia-vida do sinal e recalibração contínua. Esta é a parte específica do OFI. O conteúdo preditivo do OFI decai numa escala de tempo de milissegundos a segundos em instrumentos líquidos, o que significa que a janela de agregação não é um parâmetro livre – é uma aposta no horizonte. E não é uma constante: ele se move intradiário, com profundidade e em torno de eventos programados, portanto, qualquer ajuste de produção o reestima em janelas contínuas, em vez de fixar um valor de um backtest.
Todo o resto aqui é abordado em outro lugar. Orçamentos de latência e a escada de co-localização / FPGA / kernel-bypass: seção de produção do DeepLOB. O padrão de liquidez intradiária em forma de U e estatísticas de normalização flutuante: modelagem de spread. Recalibração por local e por que um modelo instalado na Nasdaq não será transferido para um par criptográfico intocado: DeepLOB novamente, com o lado da fragmentação em roteamento de ordem inteligente.
Manipulação. Uma consequência é específica deste modelo e vale a pena ser declarada claramente: a decomposição do CKS pondera cada evento apenas pelo tamanho, sem noção de intenção ou persistência. Um tamanho de colocação e cancelamento de spoofer, portanto, é injetado diretamente no sinal, com peso total, por construção. Heurísticas de detecção – taxas de cancelamento, vida útil dos pedidos, comportamento da parede à medida que o preço se aproxima – estão em análise da posição da fila e da parede da carteira de pedidos; a liquidez fantasma entre locais está em roteamento inteligente de pedidos.
Principais conclusões

-
OFI é uma decomposição de evento, não um instantâneo. A fórmula do indicador de três ramos mapeia cada transição no topo do livro – aumento de preço, redução de preço, mudança de tamanho – para um número assinado, a partir de dois instantâneos consecutivos e nada mais.
-
O título R-quadrado é contemporâneo. O valor CKS 50-65% decompõe os movimentos de preços no mesmo intervalo; não é uma previsão e não deve ser comparada com modelos de retorno prospectivos.
-
OFI multinível adiciona níveis e o PCA os reduz. Os resultados incrementais publicados - e variância de 89% são de ações da Nasdaq. Se a profundidade ainda ajuda em um livro criptográfico não foi testado aqui.
-
OFI inclui desequilíbrio comercial. Inscrito em conjunto, o TI torna-se insignificante – os cancelamentos que o OFI vê e o TI não pode são o que fazem a diferença.
-
O modelo é cego quanto à intenção. O peso igual por tamanho é exatamente o que o torna falsificável.
-
Nada neste artigo é medido. Todos os números citados são provenientes da literatura sobre ações. Antes que esse sinal atinja o capital, ele precisa de um ajuste em seus próprios dados contábeis, uma especificação defasada relatada separadamente e uma verificação para saber se a vantagem sobrevive às taxas e ao spread.
Leitura Adicional

- Cont, R., Kukanov, A., & Stoikov, S. (2014). "O impacto nos preços dos eventos do livro de pedidos." Jornal de Econometria Financeira, 12(1), 47-88.
- Xu, K., Gould, M. e Howison, S. (2019). "Desequilíbrio de fluxo de pedidos multinível em um livro de pedidos com limite." arXiv:1907.06230.
- Kolm, P., Turiel, J. e Westray, N. (2023). "Profundo desequilíbrio no fluxo de pedidos: extraindo alfa em múltiplos horizontes do livro de pedidos com limite." Finanças Matemáticas, 33(4).
- Lee, C. e Ready, M. (1991). "Inferindo a direção comercial a partir de dados intradiários." Jornal de Finanças, 46(2), 733-746.
- Easley, D., Lopez de Prado, M., & O'Hara, M. (2012). "Fluxo de toxicidade e liquidez em um mundo de alta frequência." Revisão de Estudos Financeiros, 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.