Posicionamiento del mercado (OBI × CVD)
Cómo se obtiene, dimensiona y lee el cuadrante OBI × CVD: desequilibrio 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
Dos streams de Binance USDT-M futuros alimentan un único daemon de microestructura por activo. El cuadrante es single-source en v1.0.0; la expansión cross-exchange (OBI y CVD por venue más un indicador de divergencia) está prevista para una próxima versión.
- Profundidad.
wss://fstream.binance.com/stream?streams=<symbol>@depth@100ms— diff stream aplicado sobre una snapshot REST del libro (1000 niveles). El libro mantenido respalda tanto el cálculo de OBI como otras superficies de profundidad. - Prints agresores.
wss://fstream.binance.com/market/ws/<symbol>@aggTrade— cada print agregado del agresor, con el flagis_buyer_makerque da el signo del flujo.
El daemon scripts/microstructure_daemon.py escribe un heartbeat cada 30 s en data/<asset>/microstructure_snapshot.json y una barra por minuto en data/<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. 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 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 50 niveles superiores por lado que caen dentro de la banda. Se traza como eje X del cuadrante, sin suavizado. El widget muestra OBI como tag con tres decimales para que un movimiento de 0,01 sea visible.
Delta de volumen acumulado (CVD)
CVD suma el notional agresor con signo sobre una ventana rotatoria. La convención de signo sigue la lectura clásica de cinta: las compras agresoras suben, las ventas agresoras presionan.
para cada evento aggTrade: notional = qty * price // USD si is_buyer_maker == false: 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 por evento queda en ~10–50 prints/s según la actividad del venue.
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 en la cadencia de 5 m del daemon 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 daemon 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
- Single-source. v1.0.0 lee solo Binance. Un veredicto del cuadrante refleja por tanto microestructura de Binance, no presión cross-exchange agregada. OBI y CVD cross-exchange con indicador de divergencia están planificados para una próxima versión.
- Fronteras duras del cuadrante. Una posición a 0,01 a cada lado de cero en OBI invierte el veredicto verbal. v1.0.0 no tiene banda muerta ni guardia de flujo bajo — interpreta posiciones cerca del origen como indecisas, no como lectura estricta de zona.
- La banda ±0,2 % es un knob fijo. El ancho de la banda OBI es el mismo en todos los activos. 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.
- La convención de signo agresor depende del venue. El flag
is_buyer_makeren Binance aggTrade es la señal autoritativa aquí. Las extensiones cross-venue tendrán que normalizar primero la semántica del lado (Bybit y OKX codifican el lado de forma distinta). - 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.
Versionado
Metodología versión v2.0.0 · actualizado 2026-05-12. 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.