El endpoint /ws de Hyperliquid utiliza un heartbeat JSON a nivel de aplicación: el servidor envía {"method":"ping"} y espera {"method":"pong"}, y las conexiones inactivas sin tráfico pueden cerrarse desde el servidor. Esto es distinto del keepalive TCP y de los frames ping/pong a nivel de protocolo WebSocket, y es importante porque un socket puede permanecer OPEN a nivel de sistema operativo mientras pierde silenciosamente actualizaciones de órdenes. Un cliente robusto envía el ping documentado en un intervalo, registra una marca de tiempo lastMessageAt, declara la conexión muerta cuando no llega ningún pong o dato dentro de una ventana acotada, y fuerza una reconexión con repetición de suscripciones. Tras una ventana muerta detectada, reconcilia los orderUpdates perdidos contra el estado REST para cerrar la brecha.
Capas del Heartbeat: Keepalive TCP, Frames WebSocket y Ping/Pong JSON de Hyperliquid
A menudo se confunden tres mecanismos de keepalive diferentes. El keepalive TCP es una sonda a nivel de sistema operativo que envía paquetes ACK vacíos para confirmar que el host remoto sigue siendo accesible; opera por debajo de la aplicación y no demuestra que tu sesión WebSocket o sus suscripciones estén sanas. Los frames ping/pong a nivel de protocolo WebSocket están definidos por la RFC 6455 y son gestionados por la implementación de WebSocket, normalmente sin exponerse al código de la aplicación. Hyperliquid añade una tercera capa: un heartbeat JSON a nivel de aplicación documentado en Hyperliquid Docs - Timeouts and heartbeats, donde el endpoint /ws envía {"method":"ping"} y espera una respuesta {"method":"pong"}, y las conexiones inactivas sin tráfico pueden cerrarse desde el servidor. Consulta RFC 6455 Sección 5.5.2 (Frames Ping y Pong) para la definición a nivel de protocolo.
Como estas capas son independientes, un keepalive TCP exitoso o un pong a nivel de protocolo correcto no garantizan que tu aplicación esté recibiendo actualizaciones de órdenes. El heartbeat de Hyperliquid es el contrato que mantiene viva la sesión de la aplicación y te da una señal para detectar un par muerto. Trátalo como la comprobación de vitalidad autoritativa para tu envoltorio de suscripción, y trata las otras dos capas como higiene de transporte en lugar de salud de la aplicación. El formato del heartbeat en sí está documentado en la referencia de Timeouts and heartbeats de Hyperliquid.
- Keepalive TCP: a nivel de sistema operativo, demuestra accesibilidad del host, no la salud de la sesión o suscripción.
- Ping/pong a nivel de protocolo WebSocket: frames RFC 6455, gestionados por la librería, a menudo invisibles para el código de la aplicación.
- Ping/pong JSON de Hyperliquid: a nivel de aplicación, documentado, y la señal que tu cliente debe rastrear.
Por Qué un Socket OPEN Puede Ser un Zombi Que Pierde Actualizaciones de Órdenes
Una conexión semiabierta ocurre cuando un lado cree que la sesión está viva pero el otro lado ha dejado de entregar datos. El socket local puede seguir reportando OPEN porque no se recibió ningún FIN o RST, pero no llegan orderUpdates, trades ni fills. Esta es la clásica conexión zombi descrita en websocket.org - WebSocket Heartbeat: Ping/Pong and zombie connections. En trading, un zombi es peor que una desconexión limpia porque tu código cree que está suscrito mientras los fills se quedan sin procesar silenciosamente.
El comportamiento documentado de Hyperliquid de que las conexiones inactivas sin tráfico pueden cerrarse desde el servidor significa que el servidor puede desconectarte durante períodos tranquilos, pero lo inverso también es peligroso: una ruta de red puede fallar silenciosamente mientras el servidor aún te considera conectado. La única forma fiable de distinguir un mercado tranquilo y sano de un socket muerto es exigir una prueba de vida periódica. Esa prueba es el pong JSON, reforzado opcionalmente por cualquier mensaje de datos entrante.
- Estado zombi: socket OPEN localmente, sin mensajes entrantes, sin error lanzado.
- Riesgo: orderUpdates y fills perdidos durante la ventana silenciosa.
- Mitigación: ventana de vitalidad acotada basada en pong o cualquier frame de datos.
Diseño del Envoltorio de Suscripción: Intervalo de Ping, lastMessageAt y Declaración de Muerte
El envoltorio debe asumir cuatro responsabilidades: enviar el ping documentado en un intervalo, actualizar una marca de tiempo lastMessageAt en cada frame entrante, declarar la conexión muerta cuando no llega ningún pong o dato dentro de una ventana acotada, y forzar una reconexión con repetición de suscripciones. Mantén el intervalo de ping más corto que la ventana muerta para que al menos un ping pueda ser respondido antes de declarar el fallo. Por ejemplo, haz ping cada 15 segundos y declara muerta tras 45 segundos de silencio, pero ajusta estos valores a tu tolerancia al riesgo en lugar de copiarlos ciegamente.
Cada mensaje entrante, incluido el pong y los datos de suscripción, debe refrescar lastMessageAt. Esto evita falsos positivos durante mercados activos donde los datos llegan continuamente y los pongs pueden ser menos frecuentes. Cuando transcurra la ventana muerta, cierra el socket explícitamente, limpia los temporizadores y activa la ruta de reconexión. La ruta de reconexión debe repetir las suscripciones; de lo contrario, te reconectas a un socket silencioso sin canales. Consulta Suscripciones WebSocket de Hyperliquid: ciclo de vida de la conexión y reconexión para los detalles del ciclo de vida de los que depende este envoltorio.
- El intervalo de ping debe ser más corto que la ventana muerta.
- lastMessageAt se refresca con el pong y con cualquier frame de datos.
- La declaración de muerte cierra el socket e inicia reconexión más repetición.
Envoltorio de Heartbeat en Node.js Ejecutable con Ventana de Vitalidad Acotada
El siguiente ejemplo en Node.js usa el paquete ws e implementa el ping JSON documentado, un rastreador lastMessageAt, una ventana muerta y repetición de suscripciones. Es intencionalmente mínimo para que puedas adaptarlo a tu lógica de gestión de órdenes. Reemplaza el payload de suscripción con tus canales reales y conecta la reconexión a tu flujo de reconciliación de órdenes.
Ten en cuenta que el payload de ping es el mensaje documentado a nivel de aplicación, no un frame de protocolo WebSocket. Enviarlo mantiene viva la sesión y provoca un pong que demuestra que el par responde.
const WebSocket = require('ws');
const URL = 'wss://api.hyperliquid.xyz/ws';
const PING_INTERVAL_MS = 15000;
const DEAD_WINDOW_MS = 45000;
let ws;
let pingTimer;
let lastMessageAt = 0;
let subscriptions = [];
function connect() {
ws = new WebSocket(URL);
ws.on('open', () => {
lastMessageAt = Date.now();
for (const sub of subscriptions) ws.send(JSON.stringify(sub));
pingTimer = setInterval(() => {
if (Date.now() - lastMessageAt > DEAD_WINDOW_MS) {
console.error('dead connection detected, reconnecting');
return reconnect();
}
ws.send(JSON.stringify({ method: 'ping' }));
}, PING_INTERVAL_MS);
});
ws.on('message', (raw) => {
lastMessageAt = Date.now();
const msg = JSON.parse(raw.toString());
if (msg.method === 'pong') return;
handleMessage(msg);
});
ws.on('close', reconnect);
ws.on('error', (err) => console.error('ws error', err.message));
}
function reconnect() {
clearInterval(pingTimer);
if (ws) ws.terminate();
setTimeout(connect, 1000);
}
function handleMessage(msg) {
// route orderUpdates, trades, fills here
}
function subscribe(sub) {
subscriptions.push(sub);
if (ws && ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(sub));
}
connect();Detector de Conexiones Semiabiertas y Reconexión Forzada con Repetición de Suscripciones
Un detector de conexiones semiabiertas es la lógica que convierte el silencio en acción. Debe ejecutarse en un temporizador independiente del temporizador de ping para que un bucle de eventos estancado o un envío bloqueado no impidan la detección. El detector compara Date.now() con lastMessageAt y, cuando la diferencia supera la ventana muerta, marca la conexión como muerta, termina el socket y programa una reconexión. Terminar en lugar de cerrar de forma ordenada evita esperar a un par que puede no responder nunca.
La repetición de suscripciones debe ser idempotente. Almacena las suscripciones en una lista y reenvíalas en cada evento open. Si tu cliente usa IDs de suscripción, regenéralos o reutilízalos de forma consistente para poder correlacionar respuestas. Tras la repetición, tu paso de reconciliación de órdenes debe obtener las órdenes abiertas actuales y los fills recientes para cerrar cualquier brecha creada durante la ventana muerta. La guía hermana Reconexión de WebSocket RPC sin pérdida de datos cubre el patrón de recuperación de brechas en detalle.
- El detector se ejecuta en su propio temporizador, no dentro del callback de ping.
- Termina el socket para evitar esperar a un par que no responde.
- Repite las suscripciones en cada open y luego reconcilia el estado de las órdenes.
Reconciliar orderUpdates Perdidos Tras una Ventana Muerta Detectada
Cuando el detector se dispara, tienes una brecha conocida: desde el último mensaje procesado hasta el momento en que la nueva conexión está suscrita y transmitiendo. Durante esa ventana, pueden haberse emitido y perdido orderUpdates. El patrón de reconciliación consiste en obtener el estado autoritativo vía REST tras la reconexión, compararlo con tu libro de órdenes local y aplicar cualquier diferencia. Esto es más seguro que asumir que la repetición del WebSocket entregará actualizaciones históricas, porque el modelo documentado de heartbeat y suscripción no garantiza el relleno de eventos perdidos.
Para los fills específicamente, la mecánica del canal de trades y fills determina qué puedes reconstruir. Revisa Mecánica del canal de trades y fills del WebSocket de Hyperliquid para entender qué campos puedes usar para la reconciliación. Si tu estrategia depende de un orden preciso de fills, trata la ventana muerta como una brecha de datos y reconcilia desde REST en lugar de confiar en el estado local.
- Marca el inicio de la brecha en la marca de tiempo del último mensaje procesado.
- Obtén órdenes abiertas y fills recientes vía REST tras la reconexión.
- Aplica las diferencias al estado local antes de reanudar el procesamiento en vivo.
Medir Tu Propio Comportamiento de Heartbeat: Guía de Tabla de Resultados
No confíes en números genéricos para el intervalo de ping o la ventana muerta. Mide contra tu propio endpoint y ruta de red. Ejecuta una prueba controlada en la que te conectas, te suscribes a un canal de bajo tráfico y registras el tiempo entre tu ping y el pong, así como el tiempo entre mensajes de datos consecutivos. Luego simula una ruta muerta bloqueando el tráfico y registra cuánto tarda tu detector en dispararse.
Usa una tabla de resultados como la siguiente para registrar tus observaciones. Rellénala con tus propias mediciones; los valores mostrados son marcadores de posición, no benchmarks. Este método es reproducible y te permite ajustar la ventana muerta a tu tolerancia al riesgo sin adivinar.
- Ida y vuelta ping-a-pong: mide al menos 100 muestras.
- Tiempo entre llegadas de datos: mide durante mercados tranquilos y activos.
- Tiempo de disparo del detector: mide tras simular una ruta bloqueada.
- Tasa de falsos positivos: cuenta los disparos del detector durante sesiones sanas.
| Metric | Sample 1 | Sample 2 | Notes |
| --- | --- | --- | --- |
| Ping-to-pong RTT (ms) | | | |
| Max data gap (s) | | | |
| Detector fire time (s) | | | |
| False positives | | | |Solución de Problemas de Desconexiones Persistentes y Falsas Declaraciones de Muerte
Si tu cliente se desconecta con frecuencia, primero comprueba si estás enviando el ping documentado. Un cliente que nunca envía ping puede ser cerrado desde el servidor durante períodos de inactividad. Segundo, verifica que tu lastMessageAt se actualice en cada frame entrante, no solo en el pong. Un cliente que ignora los frames de datos declarará falsamente la muerte durante mercados activos. Tercero, confirma que tu intervalo de ping sea más corto que la ventana muerta; de lo contrario, puedes declarar la muerte antes de que un pong tenga la oportunidad de llegar.
Si ves falsas declaraciones de muerte, aumenta la ventana muerta o reduce el intervalo de ping, y registra los mensajes sin procesar alrededor del evento. Si las desconexiones se correlacionan con respuestas de error específicas, revisa Manejo de errores de la API de Hyperliquid y rechazos de órdenes para la semántica de rechazo que puede indicar una suscripción malformada en lugar de un fallo de transporte. Para síntomas relacionados con la latencia, consulta Latencia RPC de Hyperliquid para separar el retraso de red de la muerte de la conexión.
- Ping ausente: el servidor puede cerrar conexiones inactivas.
- lastMessageAt obsoleto: actualízalo en todos los frames entrantes.
- Intervalo más largo que la ventana muerta: falsos positivos.
- Suscripciones malformadas: revisa las respuestas de error antes de culpar al transporte.
Limitaciones y Compensaciones de los Heartbeats a Nivel de Aplicación
Los heartbeats a nivel de aplicación añaden sobrecarga y complejidad. Cada ping es un mensaje que consume ancho de banda y procesamiento, y un intervalo corto aumenta ese coste. Un intervalo largo reduce la sobrecarga pero aumenta el tiempo para detectar una conexión muerta, lo que incrementa directamente el riesgo de fills perdidos. No hay una configuración universalmente correcta; la compensación depende de la sensibilidad de tu estrategia a las actualizaciones perdidas y tu tolerancia a los falsos positivos.
Otra limitación es que el heartbeat demuestra que el par responde, no que tus suscripciones estén entregando datos. Un par puede responder pong mientras un canal específico está silencioso debido a condiciones de mercado o un problema de suscripción. Combina el heartbeat con comprobaciones de obsolescencia por canal si tu estrategia requiere datos continuos. Por último, el comportamiento documentado de que las conexiones inactivas pueden cerrarse desde el servidor significa que no puedes confiar en que un socket tranquilo permanezca abierto indefinidamente; el heartbeat es obligatorio, no opcional.
- Intervalo corto: detección más rápida, más sobrecarga.
- Intervalo largo: menos sobrecarga, mayor riesgo de fills perdidos.
- El pong demuestra capacidad de respuesta, no flujo de datos por canal.
- Los sockets inactivos pueden cerrarse desde el servidor; el heartbeat es obligatorio.
Próximos Pasos: Fortalecer Tu Cliente WebSocket de Hyperliquid
Empieza implementando el envoltorio anterior y midiendo tus propios tiempos de ida y vuelta ping-a-pong y de inter-llegada de datos. Luego añade comprobaciones de obsolescencia por canal y un paso de reconciliación que se ejecute tras cada reconexión. Si necesitas endpoints gestionados con comportamiento predecible, revisa Endpoints RPC de Hyperliquid (RPC Assistant) y el servicio API para opciones de conexión. Para contexto a nivel de red, consulta Hyperliquid.
Por último, documenta tus intervalos elegidos y la ventana muerta en tu runbook, y prueba el detector regularmente simulando una ruta bloqueada. El centro de aprendizaje de OnFinality contiene guías relacionadas sobre suscripciones, fills y recuperación de reconexión. Si estás evaluando planes de proveedores, Precios RPC describe las opciones. El objetivo no es cero desconexiones sino cero fills perdidos: un detector que se dispara temprano y reconcilia correctamente es más valioso que un socket que nunca se reconecta.
- Implementa y mide antes de ajustar.
- Añade obsolescencia por canal y reconciliación tras la reconexión.
- Prueba el detector simulando una ruta bloqueada.
- Documenta los intervalos y la ventana muerta en tu runbook.