Metodología del order flow — cuadrante OBI × CVD
Cómo se obtiene, dimensiona y lee el cuadrante de order flow: desequilibrio del libro derivado de la profundidad, CVD por flujo agresor, normalización P95 rotatoria, clasificación de zonas, régimen HTF y reglas de frescura.
Consulta el widget en vivo en /perpetuals/positioning.
Fuentes de datos
El cuadrante agrega profundidad en reposo y flujo agresor a través de cuatro venues de perpetuos — Binance, Bybit, OKX y Hyperliquid — en ambos ejes. El runtime es todo-Rust; no hay daemons de Python en el hot path.
- Ingesta de profundidad. El binario
mt-ingestse suscribe al feed de profundidad de futuros de cada venue para los seis activos, mantiene un libro por venue aTOP_LEVELS_PER_SIDE = 200niveles por lado y publica cada libro enprod:<venue>:<asset>:depth. Estos libros por venue respaldan el cálculo de OBI. - Flujo agresor / CVD. El binario
mt-cvdconsume el stream de trades de cada venue en una cinta cross-venue (prod:trades:*) y un CVD firmado corriente (prod:*:cvd). La semántica del lado agresor por venue se normaliza aquí — Bybit, OKX y Hyperliquid codifican el lado del agresor de forma distinta a Binance, y todos se mapean a una única convención de signo. - Agregación cross-exchange. El binario
mt-aggdune los libros y flujos por venue a 10 Hz, alineando venues sobreevent_tssellado por el exchange, y publica la snapshot combinada enprod:agg:<asset>:*. - Overlay + barras. El binario
mt-microstructurecalcula el overlay OBI/CVD (prod:agg:*:micro) y escribe una barra por minuto endata/<asset>/microstructure_bars.jsonl— la superficie del trail.
El WebSocket de backend /ws/market empuja una snapshot cada ~10 s y una trama trail/normalizador al suscribirse y al cambiar de ventana.
Desequilibrio del libro (OBI)
OBI mide el skew de liquidez en reposo dentro de una banda ajustada alrededor del precio medio. Se calcula primero por venue, sobre el libro propio de cada venue; la mezcla cross-venue se describe en la siguiente sección. Los niveles fuera de la banda quedan excluidos porque no afectan a un trade que se come el spread.
mid = (best_bid + best_ask) / 2 // por venue banda = [mid * 0.998, mid * 1.002] // ±0.2 % bid_qty = Σ qty de bids dentro de la banda ask_qty = Σ qty de asks dentro de la banda OBI = (bid_qty - ask_qty) / (bid_qty + ask_qty) Rango: [-1, +1] +1 → asks vacíos, todo el peso en bids -1 → bids vacíos, todo el peso en asks 0 → equilibrado dentro de la banda
Calculado sobre los 200 niveles superiores por lado que caen dentro de la banda (TOP_LEVELS_PER_SIDE = 200). El eje X ploteado es la mezcla cross-venue de abajo, sin suavizar en esta etapa; el widget muestra OBI como tag con tres decimales para que un movimiento de 0,01 sea visible.
Agregación cross-venue de OBI (flow-weighted)
El OBI por venue (arriba) de cada venue se combina en un único OBI cross-venue ponderado por el flujo agresor reciente de ese venue — flow-weighted (FW) — no por varianza inversa. El peso de un venue sigue dónde se están imprimiendo realmente los trades, así los libros tranquilos aportan poco y los libros activos dirigen el consenso.
El 2026-05-26 la agregación pasó de ponderación por varianza inversa (IVW) a FW. Bajo IVW, Hyperliquid dominaba la mezcla: su libro cuantizado por nSigFigs (grueso) produce un OBI de varianza artificialmente baja, que IVW premia con peso alto sin importar cuán poco tamaño haya detrás. El flow-weighting ata el peso al volumen agresor realizado, así un venue de cuantización gruesa ya no puede dominar la lectura combinada.
Delta de volumen acumulado (CVD)
CVD suma el notional agresor con signo sobre una ventana rotatoria, a través de los cuatro venues. La convención de signo sigue la lectura clásica de cinta: las compras agresoras suben, las ventas agresoras presionan. El lado agresor de cada venue se normaliza en la ingesta (Bybit, OKX y Hyperliquid lo codifican de forma distinta a Binance), de modo que cada print aporta el mismo signo.
para cada print agresor (cualquier venue): notional = qty * price // USD si agresor == compra: delta = +notional // agresor compró si no: delta = -notional // agresor vendió añadir (ts, delta) a un deque de 2 horas cvd_30m_usd = Σ delta para ts en [ahora - 30m, ahora] cvd_2h_usd = Σ delta para ts en [ahora - 2h, ahora]
El deque de 2 h es el horizonte más largo; la ventana de 30 m se toma del mismo buffer en cada escritura. El archivo de barras persiste una fila por minuto con ambas ventanas, de modo que el trail pueda replayear historia tras un reinicio. El throughput agregado de los cuatro venues queda en ~40–200 prints/s según la actividad.
Normalizador del eje Y
Las magnitudes crudas de CVD a 30 m varían en dos órdenes de magnitud entre BTC (movimientos de orden $1 M), ETH, SOL y los perpetuos más pequeños. Plotear USD crudo aplastaría los activos menores. El eje Y por eso divide por un P95 rotatorio de 7 días de |cvd_30m_usd| por activo y recorta a [-1, +1].
y_norm = clamp(cvd_30m_usd / p95_30m_usd, -1, +1)
p95_30m_usd: percentil rotatorio 7 días, refrescado por el endpoint de snapshot
fallback: 2 000 000 USD cuando el percentil aún no está disponible
(arranque en frío o muestra escasa)El endpoint /api/market/cvd-normalizer?asset=… sirve el P95 por activo, replicado por WebSocket como trama normalizer para que el FE reescale el trail con coherencia cuando el valor llega después de los datos del trail.
Clasificación de zonas (v2)
La regla de cuadrante crudo (signo de OBI, signo de CVD) se envuelve en un pipeline de tres capas para que el veredicto no parpadee con ruido sub-bp. Cada capa actúa en secuencia; sólo las señales que sobreviven a las tres se convierten en la zona mostrada.
Capa 1 — EMA sobre OBI (CVD ya es un agregado de ventana deslizante)
obi_ema(t) = α · obi_raw(t) + (1 - α) · obi_ema(t-1)
α = 1 - exp(-dt / span_segundos)
span_segundos: 30 s en el trail 30m (por defecto; ajustable por activo)
Capa 2 — Banda muerta de magnitud (Schmitt-trigger, brazo inferior)
Dentro de la banda muerta, candidato = zona actual:
candidato = zona_actual si |obi_ema| < OBI_DB
o |cvd| < CVD_DB_PCT × p95_30m_usd
De lo contrario, candidato = cuadrante de (obi_ema, cvd).
OBI_DB por activo: BTC/ETH 0.05, SOL 0.07, XRP 0.08,
BNB 0.10, DOGE 0.12
CVD_DB_PCT 10 % del p95 |cvd_30m| rolling 7d del activo
Capa 3 — Tenencia mínima (Schmitt-trigger, brazo superior)
Un nuevo candidato debe mantenerse ≥ MIN_TENURE_S antes de
convertirse en actual.
MIN_TENURE_S 60 s en trail 30m; 300 s en 4h; 1800 s en 24h.El nombre de los cuadrantes no cambia respecto a v1.0:
x >= 0 Y y >= 0 → "Compradores al mando" x < 0 Y y < 0 → "Vendedores dominando" x < 0 Y y >= 0 → "Demanda absorbiendo" x >= 0 Y y < 0 → "Libro sostiene"
Mientras un candidato espera su tenencia mínima, el FE renderiza una sub-línea explícita bajo el veredicto (“Vendedores · sostenido 4m 22s · Compradores pendientes 18s de 60s”). El clasificador es una máquina de estados visible, no una caja negra.
Los parámetros se ajustan por activo desde un sweep de grid de 7 días (scripts/replay_classifier.py); los valores seleccionados viven en backend/classifier/config.pyy pueden ser sobrescritos por env (p. ej.MICRO_DEADBAND_DOGE=0.20) para hot-patch sin redeploy. El estado (OBI EMA, timers del candidato, entered-at) se persiste en Redis entre ciclos del agregador, de modo que un reinicio del daemon resume en la misma zona en lugar de arrancar en frío.
Historial del trail
El trail dibuja una polilínea con fade por edad de las últimas N posiciones del cuadrante. Ventanas seleccionables: 30m, 4h y 24h. Cada ventana extrae una serie downsampleada del archivo de barras para que un replay de 24 h no transmita 1440 minutos crudos:
- 30 m. Buckets de 1 minuto.
- 4 h. Buckets de 5 minutos.
- 24 h. Buckets de 30 minutos.
El FE limita el trail renderizado a 60 puntos para que la ventana de 24 h siga encajando sin sobreploteo. Las snapshots en vivo se anexan al mismo buffer, dejando una estela detrás de la serie bucketeada. Al cambiar de activo el buffer local se reinicia para que un cuadrante fresco no quede contaminado por el trail del activo anterior.
Etiqueta de régimen HTF
La etiqueta Trend BULL/BEAR/NEUTRAL en la cabecera del widget la calcula htf_regime() en scripts/btc_monitor.py (un cron writer que se mantiene) en la cadencia de 5 m del cron de snapshot:
toma 300 velas de 1 minuto (requiere >= 289 barras válidas) pct_4h = (close - close_4h_atras) / close_4h_atras * 100 pct_24h = (close - close_24h_atras) / close_24h_atras * 100 BULL si pct_4h > +0.5 O pct_24h > +1.5 BEAR si pct_4h < -0.5 O pct_24h < -1.5 NEUTRAL en caso contrario (también cuando la muestra es insuficiente)
La etiqueta es solo contexto HTF, no una lectura de flujo. Existe para que los veredictos de ventana corta del cuadrante (CVD 30 m) se contrasten con la dirección multi-hora.
Long/short ratio
El stat L/S lee el globalLongShortAccountRatio de Binance en período de 15 m, encuestado cada ~5 m por el cron de snapshot. Valores por encima de 1.0 significan cuentas retail netas largas; por debajo de 1.0 netas cortas. Es posicionamiento de cuentas, no flujo — aparece junto a OBI y CVD en el panel lateral, no en el plano del cuadrante.
Frescura
Tres señales independientes pueden marcar el widget como no-vivo:
micro_book_state≠"ok"— el daemon está mid-resync tras una reconexión WS; OBI queda obsoleto hasta que llegue de nuevo el snapshot REST. La pastilla de estado pasa aDaemon <estado>.micro_stale— el heartbeat es más antiguo que su ventana. El FE deja de anexar nuevos puntos al trail.- Trail marcado
partial: true— el jsonl de respaldo de la ventana es más corto que el lookback solicitado (habitual justo después de un reinicio del daemon). Aparece una pastilla ámbar sobre el canvas.
Limitaciones
- Skew temporal cross-exchange. El agregador une venues sobre
event_tssellado por el exchange, pero a 10 Hz la ventana de alineación es de solo ~100 ms. Durante un movimiento rápido el mismo evento de precio puede caer en snapshots adyacentes en distintos venues, así que el OBI cross-venue de un solo frame puede discrepar brevemente consigo mismo. - Venues de cuantización gruesa. Hyperliquid publica un libro cuantizado por
nSigFigs, así que su OBI por venue es grueso. El flow-weighting (que reemplazó la ponderación por varianza inversa el 2026-05-26) limita cuánto puede mover la mezcla, pero aún entra en el consenso — lee la contribución de HL como direccional, no precisa. - La banda ±0,2 % es un knob fijo. El ancho de la banda OBI es el mismo en todos los activos y venues. Los majors con spread tight (BTC, ETH) ven libro más profundo dentro de la banda que las alts con libro fino — parte de la razón por la que CVD es el veredicto primario y OBI es el eje de apoyo.
- L/S es posicionamiento retail de Binance. El ratio cuenta cuentas en Binance, no agregado cross-venue, y pesa todas las cuentas por igual sin importar tamaño. Úsalo como contexto blando, no como input de flujo.
- El régimen HTF usa thresholds tipo-BTC para todo activo. Los umbrales 4 h ±0,5 % / 24 h ±1,5 % fueron tuneados contra BTC. Para activos de mayor vol (DOGE, SOL) el BULL/BEAR salta antes de lo que se siente natural — trata la etiqueta como contexto grueso, no como filtro de tendencia calibrado.
- El cuadrante es descriptivo, no predictivo. Lee la presión actual del libro y del flujo agresor; no es un pronóstico del próximo movimiento. Trata una zona como una foto de dónde está el flujo ahora.
Versionado
Metodología versión v2.0.0 · actualizado 2026-07-04. Los cambios materiales (nuevos venues, ajustes de fórmula, cambios de umbral) suben la versión y actualizan dateModified en los datos estructurados de arriba.