Logo
新用户订阅 RPC,首月享 6.5 折优惠查看优惠
OnFinality Learn
可靠性与一致性阅读约 14 分钟

Hyperliquid l2Book WebSocket:维护一致的本地订单簿

一份实用指南,介绍如何订阅 Hyperliquid 的 l2Book 频道、解析快照档位、检测缺口,并重新播种可靠的本地订单簿。

TL;DR

Hyperliquid 的 l2Book WebSocket 频道以 levels 数组形式传递订单簿更新,该数组包含两个子数组:bids 和 asks,每个条目格式为 [price, size, order count]。该频道文档说明每条消息都是完整快照,因此正确的本地订单簿算法是在每条消息上替换整个订单簿,而不是应用增量差异。为避免出现短暂的错误订单簿,先从 REST info 端点的 l2Book POST 播种,然后切换到 WebSocket 流,并将第一个快照与种子进行对账。使用消息的 time 字段和单调序列检测缺口,在任何心跳超时或重连时,丢弃本地订单簿并从 REST 重新播种,加上第一个新的 WebSocket 快照。生产环境消费者应定期与新的 REST 快照对账,绝不要无限期信任长期存在的本地订单簿。

Hyperliquid l2Book 频道订阅机制

l2Book 频道是一个公开的 WebSocket 订阅,用于流式传输单个币种的聚合订单簿档位。要订阅,发送一条 JSON 消息,其中 method 为 'subscribe',并包含一个 subscription 对象,其中 type 为 'l2Book',coin 为币种符号,例如 { method: 'subscribe', subscription: { type: 'l2Book', coin: 'BTC' } }。币种命名遵循 Hyperliquid 的市场符号,通常是大写代码,如 'BTC'、'ETH' 或 'SOL'。订阅模型和完整频道列表在 Hyperliquid WebSocket 订阅指南 中介绍。

每条 l2Book 消息包含一个 levels 数组,其中恰好有两个子数组:第一个是 bids,第二个是 asks。每个条目是一个三元素数组 [px, sz, n],其中 px 是价格字符串,sz 是数量字符串,n 是该档位的订单数量。负载结构在 Hyperliquid WebSocket 订阅文档 中有说明。由于该频道文档说明发送完整快照,更新算法是整体替换本地订单簿,而不是应用差异。

消息还包含一个 time 字段。将其视为排序和新鲜度检查的权威时间戳。单调递增的序列,无论是从时间派生还是单独计数器,对于检测乱序或丢失的更新至关重要。如果您不熟悉 WebSocket 可靠性模式,RPC WebSocket 重连与缺口恢复 文章涵盖了此处适用的一般机制。

  • 使用 { method: 'subscribe', subscription: { type: 'l2Book', coin: 'BTC' } } 订阅。
  • 币种符号是大写市场代码;请对照交易所的市场列表进行验证。
  • levels 数组始终是 [bids, asks],条目为 [px, sz, n]。
  • 除非提供商文档另有说明,否则将每条消息视为完整快照。

将 l2Book 档位解析并排序为规范订单簿

收到消息后,将 levels 数组解析为两个独立的集合:bids 和 asks。对于 bids,按价格降序排序,使最优买价排在首位。对于 asks,按价格升序排序,使最优卖价排在首位。将价格和数量存储为字符串或高精度小数,以避免浮点舍入错误。订单数量 n 对流动性分析有用,但不影响聚合订单簿中的价格-时间优先级。

规范订单簿表示应暴露最优买价、最优卖价和价差。价差是最优卖价减去最优买价。如果任一侧为空,则订单簿是单边的,价差未定义。始终验证价格为正且数量非负;格式错误的条目应记录并跳过,而不是破坏本地状态。

由于 l2Book 消息是完整快照,您无需合并档位。在每条消息上替换整个 bids 和 asks 集合。这简化了一致性:本地订单簿恰好是最后一条消息的档位,按规范排序。权衡是您无法通过缺失差异来检测丢失的消息;您必须依赖时间和序列检查。

  • Bids:按价格降序排序;asks:按价格升序排序。
  • 对 px 和 sz 使用字符串或十进制算术。
  • 在每个快照上替换整个订单簿;不要合并。
  • 验证条目并记录异常,而不是应用它们。

快照与增量语义及序列跟踪

Hyperliquid 的 l2Book 频道文档说明发送完整快照,但确切的差异/序列语义以及任何校验和字段是按频道记录的,并且可能变化。在假设仅快照之前,始终对照 Hyperliquid WebSocket 订阅文档 确认当前行为。如果频道切换为增量差异,更新算法将变为应用价格档位更新和删除,并且您必须跟踪序列号以检测缺口。

消息的 time 字段提供了粗略的排序信号。如果您收到的消息时间早于最后应用的消息,则将其视为乱序并丢弃。为了更强的保证,如果提供商暴露了单调序列计数器,请维护它。没有序列,您只能通过定期与新的 REST 快照比较来检测缺口。

多个币种之间的消息排序并非全局保证。如果您订阅 BTC 和 ETH 的 l2Book,它们的消息相对顺序不是市场范围排序的可靠指标。为每个币种维护单独的序列跟踪,绝不要假设跨币种因果关系。

  • 文档行为:l2Book 发送完整快照;请按频道验证。
  • 使用时间和任何可用的序列来检测乱序或丢失的更新。
  • 跨币种消息排序并非全局保证。
  • 如果引入差异,请切换到带序列检查的应用和删除逻辑。

从 REST Info 端点播种本地订单簿

在打开 WebSocket 之前,从 REST info 端点播种本地订单簿。向 /info 发送 POST 请求,请求体为 { type: 'l2Book', coin: 'BTC' }。响应包含与 WebSocket 频道相同的 levels 结构。这个种子为您提供了一个即时、一致的起点。该端点在 Hyperliquid info 端点文档 中有说明。

播种后,打开 WebSocket 并订阅 l2Book。由于两次调用之间经过的时间,第一个 WebSocket 快照可能与 REST 种子不同。通过用第一个 WebSocket 快照替换本地订单簿来进行对账,但记录种子与第一个快照之间的差异。如果差异超过您定义的阈值,请发出警报并考虑重新播种。这避免了本地状态混合了过时的 REST 数据和新 WebSocket 数据的短暂错误订单簿。

一个常见的错误是将第一个 WebSocket 快照作为对 REST 种子的差异应用。由于 l2Book 是完整快照,您必须替换,而不是合并。对账步骤纯粹是观察性的:比较、记录,然后替换。

  • 使用 POST /info { type: 'l2Book', coin: 'BTC' } 播种。
  • 打开 WebSocket 并为同一币种订阅 l2Book。
  • 用第一个 WebSocket 快照替换本地订单簿。
  • 记录种子与快照的差异以进行可观测性。
const seedBook = async (coin) => {
  const res = await fetch('https://api.hyperliquid.xyz/info', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ type: 'l2Book', coin })
  });
  const data = await res.json();
  return data.levels; // [bids, asks]
};

// Usage
seedBook('BTC').then(levels => {
  console.log('Seed bids:', levels[0].length, 'asks:', levels[1].length);
});

检测和恢复缺口与重连

当本地订单簿错过一个或多个更新时,就会发生缺口。对于完整快照,丢失的消息意味着本地订单簿在下一次快照到达之前是过时的。如果下一个快照很快到达,缺口会自愈。如果套接字静默,本地订单簿可能保持过时但看似合理,这对交易逻辑是危险的。使用心跳超时来检测静默。Hyperliquid WebSocket 心跳与保活检测 文章详细介绍了保活机制。

在任何心跳超时或重连时,完全丢弃本地订单簿。不要尝试应用部分差异或从最后已知状态恢复。从 REST 重新播种,加上第一个新的 WebSocket 快照。这保证了起点一致。一般模式在 RPC WebSocket 重连与缺口恢复 中描述。

如果您检测到时间倒退或序列跳跃,请将其视为缺口。丢弃并重新播种。重新播种的成本是短暂没有订单簿的时期,这比在过时订单簿上交易更安全。对于生产系统,考虑使用断路器暂停交易逻辑,直到订单簿重新播种并验证。

  • 心跳超时或重连:丢弃本地订单簿。
  • 从 REST 重新播种,加上第一个新的 WebSocket 快照。
  • 时间倒退或序列跳跃:视为缺口,重新播种。
  • 在订单簿验证之前暂停依赖逻辑。

可运行的 Node.js 示例:l2Book 订阅与订单簿维护

以下 Node.js 示例订阅 BTC 的 l2Book,维护排序的 bid 和 ask 映射,计算最优买价/卖价和价差,并在重连时重新播种。它使用 ws 包进行 WebSocket,使用 fetch 进行 REST 播种。将 WebSocket URL 替换为您的提供商的端点。有关端点选项,请参阅 Hyperliquid RPC 端点(RPC Assistant)。

该示例将每条消息视为完整快照并替换本地订单簿。它跟踪最后消息时间并丢弃乱序消息。在重连时,它在重新订阅之前从 REST 重新播种。这是一个最小但正确的起点;为生产环境添加日志、指标和警报。

const WebSocket = require('ws');

const COIN = 'BTC';
const WS_URL = 'wss://api.hyperliquid.xyz/ws';
const INFO_URL = 'https://api.hyperliquid.xyz/info';

let bids = new Map(); // price -> size
let asks = new Map();
let lastTime = 0;

const seed = async () => {
  const res = await fetch(INFO_URL, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ type: 'l2Book', coin: COIN })
  });
  const data = await res.json();
  applySnapshot(data.levels);
};

const applySnapshot = (levels) => {
  bids = new Map(levels[0].map(([px, sz]) => [px, sz]));
  asks = new Map(levels[1].map(([px, sz]) => [px, sz]));
};

const bestBid = () => {
  let best = null;
  for (const [px] of bids) {
    if (best === null || parseFloat(px) > parseFloat(best)) best = px;
  }
  return best;
};

const bestAsk = () => {
  let best = null;
  for (const [px] of asks) {
    if (best === null || parseFloat(px) < parseFloat(best)) best = px;
  }
  return best;
};

const connect = async () => {
  await seed();
  const ws = new WebSocket(WS_URL);

  ws.on('open', () => {
    ws.send(JSON.stringify({
      method: 'subscribe',
      subscription: { type: 'l2Book', coin: COIN }
    }));
  });

  ws.on('message', (raw) => {
    const msg = JSON.parse(raw);
    if (msg.channel !== 'l2Book') return;
    if (msg.data.time < lastTime) return; // out-of-order
    lastTime = msg.data.time;
    applySnapshot(msg.data.levels);
    const bb = bestBid();
    const ba = bestAsk();
    if (bb && ba) {
      console.log('Best bid:', bb, 'Best ask:', ba, 'Spread:', parseFloat(ba) - parseFloat(bb));
    }
  });

  ws.on('close', () => {
    console.log('Reconnecting...');
    setTimeout(connect, 1000);
  });
};

connect();

针对 REST 快照的可复现一致性检查

要验证您的本地订单簿,定期获取新的 REST 快照,并将最优买价、最优卖价和盘口顶部数量与您的本地状态进行比较。以固定间隔运行此检查,例如每 30 秒一次,并记录差异。如果差异超过您定义的阈值,请发出警报并重新播种。此方法可复现,且不依赖提供商特定的保证。

使用下表记录您的测量结果。用来自您自己的端点环境的值填充它。不要依赖其他来源的基准数字;测量您自己的。

  • 通过 POST /info { type: 'l2Book', coin: 'BTC' } 获取 REST 快照。
  • 将本地最优买价/卖价和顶部数量与快照进行比较。
  • 记录差异,如果超过阈值则发出警报。
  • 在持续差异时重新播种。
const checkConsistency = async (localBids, localAsks) => {
  const res = await fetch('https://api.hyperliquid.xyz/info', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ type: 'l2Book', coin: 'BTC' })
  });
  const data = await res.json();
  const restBids = data.levels[0];
  const restAsks = data.levels[1];
  const restBestBid = restBids[0]?.[0];
  const restBestAsk = restAsks[0]?.[0];
  const localBestBid = [...localBids.keys()].sort((a,b) => parseFloat(b) - parseFloat(a))[0];
  const localBestAsk = [...localAsks.keys()].sort((a,b) => parseFloat(a) - parseFloat(b))[0];
  console.log('REST best bid:', restBestBid, 'Local best bid:', localBestBid);
  console.log('REST best ask:', restBestAsk, 'Local best ask:', localBestAsk);
  if (restBestBid !== localBestBid || restBestAsk !== localBestAsk) {
    console.warn('Divergence detected');
  }
};

端点与环境测量结果表

使用此表记录您自己的测量结果。这些列旨在捕获本地订单簿相对于 REST 快照的一致性和新鲜度。以固定间隔运行一致性检查并记录结果。这是一种由读者验证的方法;不提供基准数字,因为它们取决于您的网络、提供商和负载。

用您自己的数据填充表格。如果您看到频繁的差异,请调查您的 WebSocket 连接、心跳设置和重新播种逻辑。Hyperliquid WebSocket 交易与成交频道机制 文章可能有助于您将订单簿更新与交易活动关联起来。

  • 时间戳:检查运行的时间。
  • 本地最优买价/卖价:来自您的本地订单簿。
  • REST 最优买价/卖价:来自新快照。
  • 差异:价格或数量的绝对差。
  • 操作:无、警报或重新播种。

排查常见的 l2Book 一致性故障

如果您的本地订单簿频繁出现差异,请检查您是否将 l2Book 消息视为差异而不是快照。将快照作为差异应用会破坏订单簿。验证您在每条消息上替换整个 bids 和 asks 集合。如果您正在合并,请切换到替换。

如果您看到乱序消息,请确保您比较 time 字段并丢弃较旧的消息。如果提供商不保证排序,您可能需要缓冲并按时间排序。如果您看到缺口,请验证您的心跳超时是否过长;静默的套接字可能会留下过时的订单簿。Hyperliquid WebSocket 心跳与保活检测 文章介绍了检测方法。

如果重新播种失败,请检查您的 REST 端点和速率限制。RPC 定价 页面描述了计划限制。有关端点选项,请参阅 Hyperliquid RPC 端点(RPC Assistant)。如果您需要托管 API 服务,请参阅 API 服务。

  • 症状:第一条消息后订单簿出现差异。原因:将快照作为差异应用。修复:替换整个订单簿。
  • 症状:订单簿过时且无更新。原因:套接字静默。修复:心跳超时并重新播种。
  • 症状:乱序更新。原因:没有时间检查。修复:丢弃较旧的消息。
  • 症状:重新播种失败。原因:REST 错误或速率限制。修复:检查端点和计划。

本地订单簿维护的局限性与权衡

确切的差异/序列语义以及任何校验和字段是按频道记录的,并且可能变化。始终对照 Hyperliquid WebSocket 订阅文档 进行验证。多个币种之间的消息排序并非全局保证,因此不要假设跨币种因果关系。本地订单簿的新鲜度仅取决于最后应用的消息;静默的套接字可能会留下过时但看似合理的订单簿,看起来正确但实际上不正确。

生产环境消费者应定期对账,而不是无限期信任长期存在的本地订单簿。对账的成本是额外的 REST 调用,这可能受到速率限制。根据您的交易策略平衡新鲜度与速率限制。对于低延迟交易,考虑使用专用提供商;请参阅 Hyperliquid RPC 端点(RPC Assistant)。

没有提供商提供的序列号和校验和,任何本地订单簿都无法保证完美一致性。如果提供商不暴露这些,您最好的防御是频繁对账和保守的重新播种。记录您的假设并监控差异。

  • 差异/序列语义按频道记录,并且可能变化。
  • 跨币种排序并非全局保证。
  • 静默的套接字可能会留下过时但看似合理的订单簿。
  • 定期对账;不要无限期信任。

生产级订单簿消费者的后续步骤

首先实现带心跳超时和重连时重新播种的播种-替换模式。添加针对 REST 快照的一致性检查并记录差异。一旦稳定,添加指标和警报。有关 OnFinality 上 Hyperliquid 的更广泛概述,请参阅 Hyperliquid 和 OnFinality Learn 中心。

如果您需要具有可靠性功能的托管 WebSocket 端点,请探索 API 服务 和 RPC 定价。有关端点选择,请参阅 Hyperliquid RPC 端点(RPC Assistant)。继续阅读 Hyperliquid WebSocket 订阅指南 和 Hyperliquid WebSocket 交易与成交频道机制 以加深理解。

最后,查看 RPC WebSocket 重连与缺口恢复 文章,了解适用于 Hyperliquid 之外的一般模式。在不利条件下测试您的实现:网络中断、提供商重启和高波动性。只有到那时,才能信任您的本地订单簿进行自动决策。

  • 实现带心跳超时的播种-替换。
  • 添加一致性检查和差异日志。
  • 使用托管端点以提高可靠性。
  • 在信任订单簿之前,在不利条件下进行测试。

永远不用担心基础设施

OnFinality 消除了 DevOps 的繁重工作,让您能够更聪明、更快地构建。

开始