📝

Draft article

This draft is visible to admins and superusers only. Sign in with an authorized account.

← Volver a los artículos
August 23, 2026
5 min de lectura

Trade Classification When You Have No Side Flag: Tick, Quote, Lee-Ready, BVC

Trade Classification When You Have No Side Flag: Tick, Quote, Lee-Ready, BVC
#microstructure
#trade-classification
#Lee-Ready
#order-flow
#deep-learning

Empecemos descartando la versión de este problema con la que comienzan la mayoría de los manuales de microestructura. En los mercados donde este blog realmente opera, la dirección de la operación no se infiere: viene en un campo. Binance aggTrades envía isBuyerMaker y el flujo de operaciones de Bybit envía side. El artículo sobre construcción de barras toma el lado agresor directamente de esa bandera y nunca ejecuta un clasificador:

def on_trade(self, timestamp: int, price: float, qty: float, is_buyer_maker: bool):
    side = -1 if is_buyer_maker else 1

Esa es la verdad de referencia, gratis y con resolución de nanosegundos. Si construyes order flow firmado a partir de un feed CEX moderno de criptomonedas, no necesitas este artículo.

Lo necesitas en cuatro situaciones, suficientemente comunes como para importar:

  1. Solo tienes barras. La mayoría de los proveedores venden OHLCV histórico y la mayoría de los backtests lo usan; el flujo de operaciones nunca se archivó o cuesta más de lo que vale la investigación. Este es el problema BVC de abajo.
  2. El mercado no indica el lado. Los CEX pequeños, los conjuntos de datos de acciones estilo TAQ y casi todos los archivos anteriores a 2017 solo ofrecen precio y tamaño.
  3. DEX y flujo on-chain. Un evento de swap dice qué token se movió y hacia dónde, pero no hay una bandera de respaldo para atribuir agresión a una operación enrutada de varios saltos o a un agregador.
  4. Reproduces investigación publicada. Las estimaciones de la lambda de Kyle, VPIN y las descomposiciones del spread —casi toda la literatura de microestructura— se basan en datos clasificados con Lee-Ready. Para comparar resultados hay que ejecutar su clasificador, no leer el lado verdadero.

Lo que sigue es el conjunto de reglas, el fallo más importante, el atajo para volumen agregado y un protocolo para medirlo todo, porque las criptomonedas te dan las etiquetas que la literatura sobre acciones nunca tuvo.

La prueba del tick

Secuencia de eventos de prueba de tick

Compara el precio actual de la operación PtP_t con el anterior:

dttick={+1if Pt>Pt11if Pt<Pt1dt1tickif Pt=Pt1d_t^{\text{tick}} = \begin{cases} +1 & \text{if } P_t > P_{t-1} \\ -1 & \text{if } P_t < P_{t-1} \\ d_{t-1}^{\text{tick}} & \text{if } P_t = P_{t-1} \end{cases}

La intuición es sencilla: las operaciones iniciadas por compradores levantan el ask y empujan el precio al alza; las iniciadas por vendedores golpean el bid y lo empujan a la baja. Los ticks cero arrastran el signo anterior, y ahí aparece la mayor parte del error: en un instrumento líquido con spread de un tick, la mayoría de las operaciones consecutivas se ejecutan al mismo precio.

sign = np.sign(np.diff(prices, prepend=prices[0]))
sign[sign == 0] = np.nan
directions = pd.Series(sign).ffill().fillna(1).astype(int).values

Una implementación de producción con la misma semántica de arrastre ya se incluye como _tick_sign() dentro de los generadores de barras de desequilibrio de ticks y volumen en Más allá de las barras temporales. Reutilízala en lugar de mantener una segunda copia.

La regla de cotización

Operaciones posicionadas dentro de la geometría de oferta y demanda

En lugar de comparar operaciones consecutivas, compara el precio de la operación con el punto medio vigente Mt=(At+Bt)/2M_t = (A_t + B_t)/2:

dtquote={+1if Pt>Mt1if Pt<Mtundefinedif Pt=Mtd_t^{\text{quote}} = \begin{cases} +1 & \text{if } P_t > M_t \\ -1 & \text{if } P_t < M_t \\ \text{undefined} & \text{if } P_t = M_t \end{cases}
def quote_rule(trade_prices: np.ndarray,
               bid_prices: np.ndarray,
               ask_prices: np.ndarray) -> np.ndarray:
    """Classify trades using the quote rule.

    Returns +1 (buy), -1 (sell), or 0 (unclassified) for midpoint trades.
    """
    midpoints = (bid_prices + ask_prices) / 2.0
    directions = np.zeros(len(trade_prices), dtype=int)
    directions[trade_prices > midpoints] = 1
    directions[trade_prices < midpoints] = -1
    return directions

La regla de cotización utiliza más información que la prueba del tick, pero falla en otro punto: no dice nada sobre las operaciones ejecutadas exactamente en el mid. En un instrumento con spread estrecho y limitado por ticks, no es un caso extremo, sino una parte importante del tape. La regla necesita un fallback, y ese es Lee-Ready.

Lee-Ready: el híbrido y el supuesto de cotización desactualizada

Flujo comercial y superficie de cotización retrasada

Lee y Ready (1991) combinaron ambos métodos: aplicar la regla de cotización y, para las operaciones en el mid, recurrir a la prueba del tick.

dtLR={dtquoteif PtMtdttickif Pt=Mtd_t^{\text{LR}} = \begin{cases} d_t^{\text{quote}} & \text{if } P_t \neq M_t \\ d_t^{\text{tick}} & \text{if } P_t = M_t \end{cases}
def lee_ready(trade_prices: np.ndarray,
              bid_prices: np.ndarray,
              ask_prices: np.ndarray,
              quote_lag: int = 0) -> np.ndarray:
    """Lee-Ready classification.

    quote_lag : number of quote observations to lag (0 = contemporaneous).
                Set > 0 only if your quote feed is known to trail the trade
                feed; see the discussion of the 5-second rule below.

    Depends on tick_test() with carry-forward semantics — the `_tick_sign()`
    implementation from /blog/beyond-time-bars-candle-construction.
    """
    if quote_lag > 0:
        bid = np.concatenate([bid_prices[:quote_lag], bid_prices[:-quote_lag]])
        ask = np.concatenate([ask_prices[:quote_lag], ask_prices[:-quote_lag]])
    else:
        bid, ask = bid_prices, ask_prices

    directions = quote_rule(trade_prices, bid, ask)

    unclassified = directions == 0
    if unclassified.any():
        directions[unclassified] = tick_test(trade_prices)[unclassified]

    return directions

La mitad más interesante del artículo suele reproducirse sin su justificación. Lee y Ready no propusieron solo un híbrido: propusieron comparar cada operación con la cotización vigente cinco segundos antes. Ese número no es una elección de modelado del mercado, sino una corrección de un artefacto de transmisión: en la cinta consolidada de la NYSE de los años 80, operaciones y cotizaciones se difundían por rutas distintas y con retrasos distintos, y las cotizaciones solían llegar antes que las operaciones a las que pertenecían. Comparar una operación con la cotización contemporánea significaba compararla con una cotización que ya la reflejaba, invirtiendo el signo justo en las operaciones que movieron el mercado.

Siguen dos cosas para cualquiera que transfiera esto a criptografía.

La regla de los 5 segundos está obsoleta y aplicarla es perjudicial. Corrige un retraso de difusión que ya no existe. Los mercados de acciones cerraron esa brecha a mediados de los 90; los flujos de operaciones y de deltas del libro de un mercado cripto comparten espacio de secuencias y llevan la marca del mismo matching engine con resolución de microsegundos. Cinco segundos en BTCUSDT no son una tolerancia de sincronización, sino cientos de actualizaciones del libro. Retrasar las cotizaciones cinco segundos no corrige cotizaciones desactualizadas: las fabrica. Usa quote_lag=0 salvo que hayas medido un desplazamiento real en tu propia captura.

Pero el problema subyacente sobrevive en una forma nueva. Ahora la obsolescencia de las cotizaciones es culpa de tu pipeline, no del exchange. Reconstruye el libro a partir de un stream de deltas WebSocket, une las operaciones por timestamp de reloj y habrás introducido un retraso cuyo signo y tamaño dependen del buffering, la política de resincronización de snapshots y el reloj. Mídelo antes de confiar en un clasificador que lea el libro: reproduce una ventana, une las operaciones al estado del libro y comprueba qué fracción cae fuera de [bid, ask]. Una operación por encima del ask unido demuestra que el libro usado para emparejarla estaba desactualizado.

La solución difiere de la de Lee y Ready, pero el modo de fallo es idéntico.

Clasificación de volumen agregado

Clústeres de volúmenes por clase direccional

Easley, Lopez de Prado y O'Hara (2012) abordan un problema distinto: ¿qué ocurre si nunca ves operaciones individuales? BVC clasifica el volumen agregado dentro de una barra, dividiéndolo con la CDF normal estándar del cambio de precio estandarizado:

Vtbuy=VtΦ(ΔPtσΔP),Vtsell=VtVtbuyV_t^{\text{buy}} = V_t \cdot \Phi\left(\frac{\Delta P_t}{\sigma_{\Delta P}}\right), \qquad V_t^{\text{sell}} = V_t - V_t^{\text{buy}}

donde ΔPt=PtclosePtopen\Delta P_t = P_t^{\text{close}} - P_t^{\text{open}} y σΔP\sigma_{\Delta P} es la desviación estándar de los cambios de precio de la barra.

from scipy.stats import norm

def bulk_volume_classification(open_prices: np.ndarray,
                               close_prices: np.ndarray,
                               volumes: np.ndarray,
                               sigma: float | None = None) -> tuple:
    """Split bar volume into buy/sell using the BVC rule.

    Returns (buy_volume, sell_volume) as continuous, not integer, splits.
    """
    delta_p = close_prices - open_prices

    if sigma is None:
        sigma = max(float(np.std(delta_p)), 1e-8)

    buy_fraction = norm.cdf(delta_p / sigma)
    return volumes * buy_fraction, volumes * (1.0 - buy_fraction)

BVC es una herramienta distinta, no peor, y tres propiedades lo explican. No clasifica operaciones: produce una fracción, por lo que una barra que cierra sin cambios recibe una división 50/50, no un lanzamiento de moneda para cada operación. Si el consumidor necesita un signo por operación, BVC no puede proporcionarlo. Funciona con barras y no con el tape, que es precisamente el objetivo: es el único método aquí que funciona cuando no existen datos de nivel de operación. Además, σ\sigma es un parámetro libre que cambia los resultados: una estimación sobre toda la muestra introduce lookahead, mientras que una estimación rolling corta hace que Φ\Phi se sature y empuja cada barra hacia 0/100. Esta elección merece un análisis de sensibilidad, no un valor por defecto.

El artículo original informa que BVC sigue la clasificación a nivel de operación con suficiente fidelidad para estimar VPIN. Las cifras concretas de accuracy dependen mucho del tamaño de barra, el instrumento y el estimador de σ\sigma, y las fuentes secundarias rara vez especifican los tres. No traslades una cifra de un estudio de acciones: mídela.

El único rasgo específico de la clasificación

Señal de microestructura resaltada

Si entrenas un modelo en lugar de ejecutar una regla, la mayor parte del conjunto de features ya está publicado: spread, depth imbalance, trade-flow imbalance y realized volatility están tabulados e implementados en modelado del spread con machine learning, y los features multinivel del libro aparecen en DeepLOB. Usa esos features.

El feature específico de este problema, ausente en ambos artículos, es la posición de la operación dentro del spread:

relative_pricet=PtBtAtBt\text{relative\_price}_t = \frac{P_t - B_t}{A_t - B_t}

Una operación en el bid se asigna a 0, en el ask a 1 y en el mid a 0,5. Es la generalización continua de la regla de cotización: en lugar de aplicar un umbral en el mid, entregas al modelo la distancia, para que aprenda que una operación a 0,95 es casi con certeza una compra, mientras que una a 0,55 apenas informa. Los valores fuera de [0,1][0, 1] diagnostican cotizaciones desactualizadas de la sección anterior; conviene registrarlos, no recortarlos.

Para un baseline boosted sobre estos features, usa el pipeline ya publicado en modelado del spread con machine learning, cambiando el objective a binario y conservando, sobre todo, su walk-forward CV purgado y con embargo. No evalúes un clasificador de microestructura con un TimeSeriesSplit simple: ese artículo y walk-forward optimization explican por qué el número resultante sería optimista. Para modelos secuenciales sobre snapshots del libro, DeepLOB es la arquitectura de referencia y su sección sobre entrenamiento se aplica sin cambios.

Medirlo bien: las criptomonedas proporcionan las etiquetas

Flujo de datos del mercado criptográfico etiquetado

Cada cifra de accuracy en la literatura de clasificación de operaciones bursátiles se estima frente a un proxy de la verdad —TORQ, submuestras de auditoría o registros internos de una sola plaza— porque el lado agresor real nunca se publicó. Las criptomonedas sí lo publican. isBuyerMaker en Binance aggTrades es la etiqueta gratuita de cada operación. Por tanto, el experimento está al alcance de cualquiera y no lo he visto publicado para cripto. El protocolo:

Datos. Un mes de aggTrades de BTCUSDT más el stream de book ticker, unidos para que cada operación lleve el bid y ask vigentes inmediatamente antes. Repítelo con una altcoin poco líquida, porque la pregunta es qué ocurre cuando el spread es amplio.

Etiqueta. direction = -1 if is_buyer_maker else +1. Después descarta la bandera y ejecuta cada método solo con precios y cotizaciones.

Métodos. Prueba del tick, regla de cotización, Lee-Ready con quote_lag=0, Lee-Ready con el lag histórico de 5 segundos (para cuantificar el daño de la regla obsoleta) y BVC con varios tamaños de barra frente a la fracción real de volumen comprador por barra.

Desgloses. La accuracy global es el número menos interesante. Desglosa por régimen de spread (un tick frente a amplio: el carry-forward de la prueba del tick debería colapsar donde el spread está fijado), por si la operación se ejecutó en el mid (aislando dónde la regla de cotización se abstiene y el fallback de Lee-Ready hace todo el trabajo), por tamaño de operación (aggTrades son sweeps; las operaciones grandes generan impacto y cuestan más si se firman mal) y por régimen de volatilidad (se estima σ\sigma, así que su error depende del régimen por construcción).

Método Global En mid Spread estrecho Spread amplio Operaciones grandes
Prueba del tick
Regla de cotización
Lee-Ready (lag 0)
Lee-Ready (lag 5 s)
BVC (fracción por barra) n/a n/a

La accuracy no es el objetivo

Límite de clasificación e impacto en la ejecución

Siguiendo la disciplina de planteamiento de DeepLOB, la accuracy de clasificación no es un resultado, sino una entrada para obtenerlo. Nadie opera el signo de una operación: opera algo calculado a partir de un flujo completo de signos, y los errores no se propagan de forma neutral. Un clasificador que se equivoca al azar en el 10% de las operaciones y otro que se equivoca sistemáticamente en el 10% de las operaciones grandes que mueven el precio producen order flow firmado muy distinto con la misma accuracy. El fallo de carry-forward de la prueba del tick está específicamente correlacionado con el precio plano, el régimen en el que el flujo firmado debería ser informativo. Por eso los desgloses anteriores, no la cifra principal, son el resultado real.

Con etiquetas de verdad, la pregunta downstream se mide directamente: estima la cantidad que te interesa a partir de los signos reales, vuelve a estimarla con los signos de cada clasificador e informa de la diferencia.

  • Impacto en el precio. Regresa los cambios del mid-price sobre el net taker flow firmado en ventanas fijas. El estimador ya está definido en modelos de costes de slippage, y su implementación funcional sobre datos públicos de Binance aparece en ejecución óptima de Almgren-Chriss. No lo redefinas aquí: ejecútalo dos veces y compara el coeficiente estimado.
  • Order flow imbalance como predictor. Calcula la correlación entre OFI y retornos futuros con signos verdaderos y clasificados. La caída de esa correlación es el coste de clasificar mal, expresado en la única unidad que importa.
  • Métricas de toxicidad. VPIN y los features de desequilibrio del flujo de operaciones consumen volumen clasificado; ambos aparecen en modelado del spread con machine learning. Su sensibilidad al error de clasificación se mide del mismo modo.

Conclusión práctica

Mapa de flujo de órdenes resuelto

Si tu mercado proporciona una bandera de agresor, úsala. Es lo habitual en cripto.

Si no, Lee-Ready con cotizaciones contemporáneas es el valor por defecto correcto, no porque sea preciso, sino porque es el estándar. La comparabilidad con tres décadas de estimaciones publicadas vale más que unos puntos de accuracy que no puedes verificar. La regla de los cinco segundos es un fósil de la latencia de cinta de los años 80; reproducirla con datos modernos inyecta precisamente la obsolescencia que pretendía eliminar. Si solo tienes barras, BVC es la única opción que funciona, pero responde a otra pregunta: una división de volumen, no un signo de operación.

En todos los casos, mide frente a las etiquetas gratuitas de cripto, desglosa el resultado por régimen de spread y tamaño de operación, e informa cuánto cuesta el error a las estimaciones downstream, no solo qué porcentaje fue correcto.

blog.disclaimer

Authors

Eugen Soloviov
Eugen Soloviov

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.

Newsletter

Mantente a la vanguardia

Suscríbete a nuestro boletín para recibir información exclusiva sobre trading con IA, análisis de mercado y actualizaciones de la plataforma.

Respetamos tu privacidad. Puedes darte de baja en cualquier momento.