---
title: "Hyperliquid 訂單、成交與成交明細比較"
description: "在 Hyperliquid 上，訂單、成交與成交明細是三種不同的紀錄。以下說明每種紀錄的 HyperCore payload、它們如何透過 oid 與 tid 關聯，以及如何對帳。"
---

# Hyperliquid 訂單、成交與成交明細比較

<ImageBlock
  src="https://media.alchemy.com/overviews/hyperliquid-orders-trades-fills-hero-16x9-20261020.png"
  alt="Hyperliquid 訂單、成交與成交明細比較"
  width={1920}
  height={1080}
  priority
/>

在 Hyperliquid 上，訂單（order）是買進或賣出的指令，成交（trade）是兩筆訂單之間的一次撮合，而成交明細（fill）則是單一帳戶在這次撮合中的那一方。每筆成交都會產生剛好兩筆成交明細，每個帳戶各一筆。本指南逐一說明 HyperCore 上每種紀錄的 payload、將它們關聯起來的鍵，以及在正式環境中會破壞對帳的失效情境。

這三種紀錄來自不同的串流，也在不同的時間點變化，因此把它們當成同一件事處理的程式碼，會逐漸與交易所的狀態脫節。以下內容皆以 Hyperliquid 主網的即時資料驗證過，本指南中的兩支腳本目前也都能直接對公開的 Hyperliquid API 執行。

## 訂單、成交與成交明細有什麼差別？

HyperCore 是 Hyperliquid 的原生交易引擎，與 HyperEVM 智能合約環境彼此獨立。它運行一個完全鏈上的訂單簿，因此每一次下單、取消與撮合都會記錄在區塊中。

這三種紀錄從不同角度描述這些活動：

<EmbeddedTable
  table={{
    columns: [
      { key: "record", width: 180, title: "紀錄", dataType: "object" },
      {
        key: "what_it_describes",
        width: 180,
        title: "描述內容",
        dataType: "object",
      },
      {
        key: "identifier",
        width: 180,
        title: "識別碼",
        dataType: "object",
      },
      { key: "scope", width: 180, title: "範圍", dataType: "object" },
      { key: "stream", width: 180, title: "串流", dataType: "object" },
    ],
    data: [
      {
        record: { title: "訂單更新", tooltip: "", icon: "" },
        what_it_describes: {
          title: "訂單狀態的變化（open、filled、canceled、rejected）",
          tooltip: "",
          icon: "",
        },
        identifier: { title: "<code>oid</code>", tooltip: "", icon: "" },
        scope: { title: "單一帳戶", tooltip: "", icon: "" },
        stream: {
          title:
            "<code>orderUpdates</code>（原生）、<code>userOrderUpdates</code>（Alchemy）",
          tooltip: "",
          icon: "",
        },
        id: 0,
      },
      {
        record: { title: "成交", tooltip: "", icon: "" },
        what_it_describes: {
          title: "某個市場中買方與賣方之間的一次撮合",
          tooltip: "",
          icon: "",
        },
        identifier: { title: "<code>tid</code>", tooltip: "", icon: "" },
        scope: { title: "單一市場，雙方帳戶", tooltip: "", icon: "" },
        stream: { title: "<code>trades</code>", tooltip: "", icon: "" },
        id: 1,
      },
      {
        record: { title: "成交明細", tooltip: "", icon: "" },
        what_it_describes: {
          title: "單一帳戶在一筆成交中的那一方，含手續費與 PnL",
          tooltip: "",
          icon: "",
        },
        identifier: {
          title: "<code>tid</code> 與 <code>oid</code>",
          tooltip: "",
          icon: "",
        },
        scope: { title: "單一帳戶", tooltip: "", icon: "" },
        stream: { title: "<code>userFills</code>", tooltip: "", icon: "" },
        id: 2,
      },
    ],
  }}
/>

成交是市場視角下的撮合，所以它會列出雙方帳戶，但不含手續費。成交明細是帳戶視角下的同一次撮合，因此帶有該帳戶支付的手續費、成交前的持倉，以及它來自哪一筆訂單。訂單更新描述的是訂單本身，而如下一節所示，它們比大多數開發者預期的還要稀疏。

如果你正在打造交易機器人或投資組合追蹤工具，成交明細是「實際執行了什麼」的唯一可信來源，而訂單更新則是「還有什麼掛在訂單簿上」的唯一可信來源。

## 訂單更新長什麼樣子？

訂單狀態改變時，就會發出一筆訂單更新。payload 會把訂單包在一筆狀態紀錄中：

<CodeSnippet
  language="json"
  code={`{
  "status": "open",
  "statusTimestamp": 1780000000000,
  "hash": "0xabc...def",
  "order": {
    "oid": 12345,
    "coin": "BTC",
    "side": "B",
    "limitPx": "100000.0",
    "sz": "0.10",
    "origSz": "0.10",
    "orderType": "Limit",
    "tif": "Gtc",
    "reduceOnly": false,
    "isTrigger": false,
    "triggerPx": "0.0",
    "cloid": null
  }
}`}
/>

對狀態追蹤最重要的欄位如下：

- **`oid`**：交易所指派的訂單 ID。成交明細會引用它，因此它是訂單與其執行結果之間的關聯鍵。
- **`origSz` 與 `sz`**：下單時的數量，以及這筆狀態紀錄當下仍掛在訂單簿上的數量。
- **`status`**：新的狀態。常見的有 `open`、`filled`、`canceled` 與 `triggered`，另外還有一長串具體的拒絕與取消原因，例如 `badAloPxRejected`、`reduceOnlyCanceled`、`selfTradeCanceled` 與 `liquidatedCanceled`。
- **`tif`**：有效期類型（time in force）。`Gtc` 會一直掛在訂單簿上直到被取消，`Alo`（add liquidity only，僅提供流動性）若會跨越價差就會被拒絕，`Ioc`（immediate or cancel，立即成交否則取消）則會盡可能成交，剩餘部分直接捨棄。
- **`cloid`**：選填的客戶端自訂訂單 ID，可用來將訂單對應到你的系統送出的請求。

[userOrderUpdates 參考文件](https://www.alchemy.com/docs/chains/websockets/hypercore/streams/user-order-updates)列出了在節點輸出中觀察到的所有狀態值，並說明這份清單並不完整。遇到未知的狀態字串時，請把它當成需要記錄並調查的終止狀態，而不是讓程式崩潰。

訂單更新有三種行為，會讓過於簡單的狀態機出錯。

部分成交不會產生訂單更新。我們觀察即時帳戶時發現，一筆部分成交的掛單會先發出 `open`，接著是一筆或多筆成交明細，之後就不再有任何訂單更新，直到它進入終止狀態為止。當這類訂單之後被取消時，`canceled` 紀錄會在 `sz` 中帶出剩餘數量。如果你需要某筆掛單中訂單的即時剩餘數量，請從成交明細計算，或在需要時查詢目前的掛單。

`filled` 狀態不一定代表完全成交。只撮合到部分數量的 `Ioc` 訂單，最後的狀態會是 `filled`，而 `sz` 不為零，這個值就是被捨棄的未成交剩餘數量。在一個活躍帳戶的 45 秒樣本中，127 筆進入 `filled` 的 `Ioc` 訂單裡，有 72 筆仍有剩餘數量。在把訂單視為已完成之前，請先檢查 `sz`。

`hash` 欄位只有在 `open` 時才有值。已取消與已拒絕的紀錄 hash 為 null，因此交易 hash 無法作為查詢訂單歷史的鍵。

## 成交長什麼樣子？

`trades` 串流會發布單一市場中的每一次撮合。每筆紀錄都很精簡：

<CodeSnippet
  language="json"
  code={`{
  "coin": "ETH",
  "side": "A",
  "px": "2658.9",
  "sz": "1.0",
  "time": 1790608928951,
  "hash": "0xd8c5bd7a...25ae9e33",
  "tid": 825882878864874,
  "users": ["0x4cf1...e717", "0xe4f5...c0c4"]
}`}
/>

`side` 是吃單方（taker）的方向，`A`（ask）代表吃單方賣出，`B`（bid）代表吃單方買進。`users` 一律先列買方、後列賣方，因此 `side` 為 `B` 時吃單方是 `users[0]`，`side` 為 `A` 時則是 `users[1]`。上例中吃單方是賣出的一方，所以 `0xe4f5...c0c4` 是吃單方，`0x4cf1...e717` 則是掛單的買方。

一筆吃單訂單如果一次吃掉多筆掛單，就會產生多筆成交。每筆成交都有自己的 `tid`，但它們共用相同的 `hash` 與 `time`，因為它們全都由同一筆交易觸發。也就是說，`hash` 識別的是交易，而不是成交。

有些成交的 hash 全為零。在橫跨八個市場的 80 筆近期成交樣本中，有 7 筆的 hash 為零。我們追查的那一筆是 TWAP 子單，由交易所代替使用者執行，不需要新的簽署交易，而 [Hyperliquid 的 info 端點文件](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint)也證實 TWAP 成交明細的 hash 為零。以 hash 將成交關聯到交易的程式碼，必須處理這種情況。

`trades` 適合用於全市場層級的資料，例如逐筆成交紀錄、交易量與價格圖表。凡是與特定帳戶相關的需求，請改為訂閱成交明細，因為成交紀錄中沒有手續費、沒有訂單 ID，也沒有持倉資訊。

## 成交明細長什麼樣子？

成交明細是單一帳戶在一筆成交中的那一方。以下是上面那筆 ETH 成交對應的兩筆成交明細，取自主網：

<CodeSnippet
  language="json"
  code={`[
  {
    "coin": "ETH", "px": "2658.9", "sz": "1.0", "side": "B",
    "time": 1790608928951, "startPosition": "19.3599", "dir": "Open Long",
    "closedPnl": "0.0", "hash": "0xd8c5bd7a...25ae9e33",
    "oid": 559115773954, "crossed": false, "fee": "-0.079767",
    "tid": 825882878864874, "feeToken": "USDC", "twapId": null,
    "cloid": "0xdd0cd482496f45f79c5ffbe7c27e1511"
  },
  {
    "coin": "ETH", "px": "2658.9", "sz": "1.0", "side": "A",
    "time": 1790608928951, "startPosition": "-776.0", "dir": "Open Short",
    "closedPnl": "0.0", "hash": "0xd8c5bd7a...25ae9e33",
    "oid": 559115788953, "crossed": true, "fee": "1.196505",
    "builderFee": "0.26589",
    "tid": 825882878864874, "feeToken": "USDC", "twapId": null
  }
]`}
/>

兩筆成交明細共用該筆成交的 `tid`、價格、數量與時間。其餘欄位則因帳戶而異：

- **`oid`**：產生這筆成交明細的訂單。買方與賣方各有各的訂單。
- **`crossed`**：吃單方為 `true`，掛單方（原本掛在訂單簿上的那筆訂單）為 `false`。
- **`fee`**：此帳戶支付的手續費。負值代表掛單返佣，如上方買方的成交明細。`builderFee` 若存在，代表支付給 builder（傳遞該訂單的前端或應用程式）的部分，且已包含在 `fee` 中。
- **`startPosition`、`dir` 與 `closedPnl`**：成交前的持倉數量、`Open Long` 或 `Close Short` 之類的顯示標籤，以及這筆成交明細平倉所實現的 PnL。有了這些欄位，就能只靠成交明細重建持倉歷史。
- **`liquidation`**：一般成交明細為 null。若為清算，則會列出被清算的帳戶、標記價格，以及清算是透過市場還是後備機制（backstop）執行。
- **`twapId`**：產生這筆成交明細的 TWAP 訂單（若有）。

手續費、返佣、已實現 PnL 與清算細節只存在於成交明細中，因此任何帳本或稅務報表都必須以成交明細為基礎來建立。

## 訂單、成交與成交明細如何相互關聯？

幾乎所有關聯都靠兩個鍵完成：

<EmbeddedTable
  table={{
    columns: [
      { key: "join", width: 260, title: "關聯", dataType: "object" },
      { key: "key", width: 260, title: "鍵", dataType: "object" },
      {
        key: "cardinality",
        width: 260,
        title: "對應關係",
        dataType: "object",
      },
    ],
    data: [
      {
        join: { title: "成交對應成交明細", tooltip: "", icon: "" },
        key: {
          title:
            "<code>tid</code>，搭配 <code>coin</code> 與 <code>time</code>",
          tooltip: "",
          icon: "",
        },
        cardinality: {
          title: "一筆成交，剛好兩筆成交明細",
          tooltip: "",
          icon: "",
        },
        id: 0,
      },
      {
        join: { title: "成交明細對應訂單", tooltip: "", icon: "" },
        key: { title: "<code>oid</code>", tooltip: "", icon: "" },
        cardinality: {
          title: "一筆訂單，零筆或多筆成交明細",
          tooltip: "",
          icon: "",
        },
        id: 1,
      },
      {
        join: { title: "訂單對應客戶端請求", tooltip: "", icon: "" },
        key: { title: "<code>cloid</code>", tooltip: "", icon: "" },
        cardinality: {
          title: "一對一（有設定時）",
          tooltip: "",
          icon: "",
        },
        id: 2,
      },
      {
        join: { title: "成交明細或成交對應交易", tooltip: "", icon: "" },
        key: { title: "<code>hash</code>", tooltip: "", icon: "" },
        cardinality: {
          title: "多對一，TWAP 子單的 hash 則為零",
          tooltip: "",
          icon: "",
        },
        id: 3,
      },
    ],
  }}
/>

前兩列才是值得依賴的基礎。我們用即時資料驗證了成交與成交明細的關聯：先從 `trades` 取得一筆成交，再查詢 `users` 陣列中兩個地址的成交明細。每個地址都剛好有一筆帶有該成交 `tid` 的成交明細，一筆為 `crossed: true`，另一筆為 `crossed: false`。

訂單的剩餘數量可以從成交明細與訂單的關聯推導出來：

<CodeSnippet
  language="text"
  code={`remaining = origSz - sum(fill.sz for every fill where fill.oid == order.oid)`}
/>

這個公式適用於每一筆訂單，比起你最後看到的那筆訂單更新中的 `sz`，更應該信任這個公式。

## 如何根據成交明細核對訂單狀態？

下面的腳本會訂閱單一帳戶的訂單更新與成交明細，根據成交明細追蹤每筆訂單的已成交數量，並在訂單進入終止狀態時，將計算結果與交易所回報的 `sz` 相互核對。它可在內建 WebSocket 支援的較新 Node 版本上執行，不需要任何相依套件。

<CodeSnippet
  language="javascript"
  code={`// Tracks one account's orders by joining order-status events with fills.
// Usage: node reconcile.mjs 0xYourAddress
const WS_URL = process.env.HL_WS_URL ?? "wss://api.hyperliquid.xyz/ws";
const user = process.argv[2];
if (!/^0x[0-9a-fA-F]{40}$/.test(user ?? "")) throw new Error("pass a 0x address");
// Sizes arrive as decimal strings. Sum them as scaled integers so partial fills add up exactly.
const SCALE = 10;
const units = (s) => {
  const [i, f = ""] = s.split(".");
  return BigInt(i + f.padEnd(SCALE, "0").slice(0, SCALE));
};
const fmt = (u) => (Number(u) / 10 ** SCALE).toString();
const orders = new Map(); // oid -> { coin, origSz, filled, status, reported }
const seenFills = new Set(); // coin|time|tid, so a fill delivered twice is counted once
const fillKey = (f) => \`\${f.coin}|\${f.time}|\${f.tid}\`;
const order = (oid) => {
  if (!orders.has(oid)) orders.set(oid, { filled: 0n, status: "unknown" });
  return orders.get(oid);
};
// Runs whenever a terminal order learns something new, so either stream can arrive first.
function check(oid, o) {
  if (o.reported === undefined) return; // not terminal yet
  const remaining = o.origSz - o.filled;
  if (remaining === o.reported && o.filled === 0n) return; // plain cancel or reject
  const result =
    remaining === o.reported
      ? "ok"
      : remaining > o.reported
        ? \`waiting for fills (\${fmt(remaining - o.reported)} outstanding)\`
        : \`MISMATCH (fills say \${fmt(remaining)})\`;
  console.log(\`\${oid} \${o.coin} \${o.status}: filled \${fmt(o.filled)} of \${fmt(o.origSz)}, sz=\${fmt(o.reported)} \${result}\`);
}
function onOrderUpdate(u) {
  const o = order(u.order.oid);
  Object.assign(o, { coin: u.order.coin, origSz: units(u.order.origSz), status: u.status });
  if (u.status === "open") return;
  o.reported = units(u.order.sz);
  check(u.order.oid, o);
}
function onFill(f, quiet = false) {
  const key = fillKey(f);
  if (seenFills.has(key)) return;
  seenFills.add(key);
  const o = order(f.oid);
  o.filled += units(f.sz);
  if (!quiet) {
    const role = f.crossed ? "taker" : "maker";
    console.log(\`\${f.oid} \${f.coin} fill tid=\${f.tid} \${f.side} \${f.sz}@\${f.px} \${role} fee=\${f.fee}\${f.liquidation ? " LIQUIDATION" : ""}\`);
  }
  check(f.oid, o);
}
const ws = new WebSocket(WS_URL);
ws.onopen = () => {
  for (const type of ["orderUpdates", "userFills"]) {
    ws.send(JSON.stringify({ method: "subscribe", subscription: { type, user } }));
  }
};
ws.onmessage = ({ data }) => {
  const msg = JSON.parse(data);
  if (msg.channel === "orderUpdates") msg.data.forEach(onOrderUpdate);
  if (msg.channel !== "userFills") return;
  // The first message is a snapshot of recent fills. Count it, so orders already partly
  // filled at startup add up, but don't print it. The dedupe set absorbs it on reconnect.
  const { fills, isSnapshot } = msg.data;
  fills.forEach((f) => onFill(f, isSnapshot));
  if (isSnapshot) console.log(\`loaded \${fills.length} fills from the snapshot\`);
};
ws.onclose = () => console.log("closed; reconnect and backfill from your last stored fill time");`}
/>

可以對任何活躍地址執行，例如從近期某筆成交的 `users` 陣列中取得的地址。對一個交易頻繁的帳戶執行 45 秒後，輸出如下：

<CodeSnippet
  language="text"
  code={`loaded 30 fills from the snapshot
580847595557 HYPE fill tid=524471650157202 B 153.74@81.33 taker fee=1.800529
580847595557 HYPE fill tid=1042881790178215 B 60.73@81.33 taker fee=0.71124
580847595557 HYPE filled: filled 214.47 of 221.99, sz=7.52 ok
580847576684 ZEC fill tid=38729269326008 B 0.02@1250.0 maker fee=-0.0005
580847576684 ZEC canceled: filled 0.02 of 2.47, sz=2.45 ok`}
/>

第一行是訂閱開始時送出的快照。接下來三行是同一筆 `Ioc` 訂單：它吃掉兩筆掛單、產生兩筆成交明細，最後以 `filled` 結束，仍有 7.52 HYPE 未成交。最後兩行是一筆 `Alo` 訂單：它掛在訂單簿上，以掛單方身分成交了 0.02 ZEC，之後在其餘數量仍掛在訂單簿上時被取消。在那次執行中，115 筆已進入終止狀態且有成交明細的訂單裡，由成交明細推算出的剩餘數量每一次都與交易所回報的 `sz` 一致。

腳本中有三個細節很重要。數量是字串，若以 JavaScript 浮點數加總，對於存續時間長、部分成交次數多的訂單會累積捨入誤差，因此腳本改以縮放後的整數加總。此外，兩個處理函式都會執行核對。終止狀態會記錄交易所回報的 `sz`，之後送達的任何成交明細都會重新比對一次，因此若某筆訂單的最後一筆成交明細在其 `filled` 狀態之後才送達，輸出會先顯示 `waiting for fills`，再顯示 `ok`，而不會誤報為不一致。最後，成交明細在更新狀態之前會先去重，因此同一筆成交明細即使送達兩次（例如重新連線後出現在快照中，或回填與即時串流重疊時），也只會計算一次。

[Hyperliquid 的 WebSocket 文件](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/websocket/subscriptions)將 `tid` 描述為 50 位元的雜湊值，並指出區塊時間、`coin` 與 `tid` 三者合起來是全域唯一的成交 ID，因此腳本以 `coin`、`time` 與 `tid` 作為成交明細的鍵。一筆成交與它的兩筆成交明細，這三個值都相同。這個鍵在單一帳戶內是唯一的。如果你在同一個程序中追蹤多個帳戶，請在鍵中再加上 `user`，因為一筆成交的雙方共用其餘的值。

## 正式環境中會出什麼問題？

大多數對帳錯誤都來自以下情況。

- **重複傳遞**：重新訂閱時，原生 `userFills` 串流會先送出近期成交明細的快照（`isSnapshot: true`），而回填也會與即時串流已送達的資料重疊。成交明細請依 `user`、`coin`、`time` 與 `tid` 去重，訂單更新則依 `oid` 加上 `status` 加上 `statusTimestamp` 去重。
- **跨串流的順序**：訂單更新與成交明細透過不同的頻道送達。不要假設成交明細一定會比對應訂單的 `filled` 狀態先到，反之亦然。像腳本那樣以 `oid` 作為狀態的鍵、讓任一事件都能建立紀錄，並在延遲送達的成交明細抵達時重新執行核對，就能消除這層相依。
- **斷線**：WebSocket 訂閱只會傳遞連線期間發生的事件，因此任何停機時間都需要回填。請儲存你最後處理的成交明細時間，並從那個時間點開始查詢成交明細，如下所示。
- **TWAP 子單成交明細**：TWAP 子單以全零的 `hash` 執行。在公開的 Hyperliquid API 上，我們追查的那筆子單成交明細出現在 `userTwapSliceFills` 中，而不是 `userFillsByTime`，因此只讀取 `userFillsByTime` 的回填可能會漏掉它們。
- **未知狀態**：新的拒絕與取消原因會持續增加。處理未知值時不要讓程式崩潰，並將 `triggerCondition` 視為顯示用字串。機器可讀的觸發價格是 `triggerPx`。
- **市場識別碼**：coin 字串會編碼市場類型。`BTC` 是永續合約，現貨市場使用 `@index` 格式的字串（例如 `@107`），結果市場（outcome markets）使用 `#N`，由第三方部署的 HIP-3 市場則使用 `dex:name`，詳見[市場、資產與 DEX 文件](https://www.alchemy.com/docs/data/hypercore/concepts/markets-assets-and-dexes)。coin 字串區分大小寫，因此請精確比對，絕對不要轉成小寫。
- **過早推進檢查點**：只有在成交明細寫入儲存空間之後，才持久化你的最後成交明細時間。如果先推進檢查點，兩個步驟之間一旦當機，就會無聲無息地遺漏一筆成交明細。

回填某段時間範圍內的成交明細，只需要一個簡短的迴圈。根據 [Hyperliquid 的 info 端點文件](https://hyperliquid.gitbook.io/hyperliquid-docs/for-developers/api/info-endpoint)，`userFillsByTime` 會從最舊的成交明細開始回傳，每次回應最多 2,000 筆，因此迴圈會從目前看到的最新時間重新開始，並讓同一個去重鍵吸收重疊的部分：

<CodeSnippet
  language="javascript"
  code={`// Pulls every fill for an account between two times, for use after a stream gap.
// Usage: node backfill.mjs 0xYourAddress <startMs> [endMs]
const INFO_URL = process.env.HL_INFO_URL ?? "https://api.hyperliquid.xyz/info";
const [user, start, end = Date.now()] = process.argv.slice(2);
async function fillsBetween(user, startTime, endTime) {
  const byKey = new Map();
  let from = Number(startTime);
  while (true) {
    const res = await fetch(INFO_URL, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ type: "userFillsByTime", user, startTime: from, endTime: Number(endTime) }),
    });
    const page = await res.json();
    const before = byKey.size;
    for (const f of page) byKey.set(\`\${f.coin}|\${f.time}|\${f.tid}\`, f);
    // Pages are capped. Restart at the newest time seen; the dedupe key absorbs the overlap.
    if (page.length === 0 || byKey.size === before) break;
    from = Math.max(...page.map((f) => f.time));
  }
  return [...byKey.values()].sort((a, b) => a.time - b.time);
}
const fills = await fillsBetween(user, start, end);
console.log(\`\${fills.length} fills\`);`}
/>

對同一個交易頻繁的帳戶查詢 30 分鐘的時間區間，共分四頁回傳了 6,049 筆成交明細。將結果送進串流所使用的同一個 `onFill` 處理函式，去重集合就能避免回填與即時串流之間的重疊被重複計算。

同一份文件也指出，`userFillsByTime` 只能取得最近的 10,000 筆成交明細。以上述速率計算，這相當於不到一小時的歷史，因此對這麼活躍的帳戶來說，更長的中斷就會超出公開端點能回傳的範圍。請調整你的告警設定，確保斷線的消費端能在這個時間範圍內及早被發現。

## Alchemy 的 HyperCore 串流帶來哪些改變？

[Alchemy 上的 HyperCore 串流](https://www.alchemy.com/docs/chains/websockets/hypercore)使用原生的訂閱封裝格式，因此上述 payload 與關聯邏輯都維持不變。差異在於每個訊框能告訴你多少關於其來源區塊的資訊，以及單一訂閱可以涵蓋多少範圍。

每個資料訊框除了原生 payload 之外，還帶有頂層的中繼資料，包括 `subscriptionId`、`blockHeight` 與 `blockTime`。訂單更新與成交明細仍然透過不同的頻道送達，但每個訊框都會標明它來自哪個區塊，讓你在對齊兩個串流時有一個共同的參考點。

訂閱也接受陣列。原生訂閱只能指定單一 `user` 或 `coin`，我們的訂閱則接受 `users`、`coins` 與 `builders`，因此一個訂閱就能涵蓋一整組帳戶。每筆成交明細與訂單紀錄都帶有對應的 `user`，因此上述的多帳戶去重鍵也就從邊緣情況變成了預設情況。

另外有幾個串流，讓你不必再從範圍廣泛的資料流中自行推導：

- **`liquidationFills`**：只包含帶有清算資訊的成交明細，可依市場篩選。
- **`builderFills` 與 `builderOrderUpdates`**：歸屬於某個 builder 地址的成交明細與訂單更新，適合負責傳遞訂單、需要追蹤自身訂單流量與手續費的應用程式。
- **`allFills`**：選定市場中所有帳戶的成交明細，採用完整的成交明細結構，而不是較精簡的成交紀錄。

若要對我們的端點執行對帳腳本，請將 `HL_WS_URL` 指向你在取得搶先體驗資格時收到的 WebSocket URL，依照 [HyperCore 連線生命週期文件](https://www.alchemy.com/docs/chains/websockets/hypercore/connection-lifecycle)完成驗證，並改為訂閱 `userOrderUpdates` 而非 `orderUpdates`。我們的訂單更新訊框會把狀態紀錄包在 `orders` 陣列中，因此請從 `msg.data.orders` 讀取，而不是 `msg.data`。在歷史資料方面，我們的 HyperCore REST API 將會新增支援游標分頁的 `userFillsByTime` 與 `historicalOrders`。兩者都已記錄在[查詢錢包交易歷史指南](https://www.alchemy.com/docs/data/hypercore/guides/query-wallet-trading-history)中，但目前尚無法呼叫，因此上面的回填腳本暫時使用公開的 Hyperliquid 端點。

## 如何取得 HyperCore 串流的使用權限？

HyperCore 串流目前處於搶先體驗階段，是[我們在 6 月宣布的 HyperCore 計畫](https://www.alchemy.com/blog/alchemy-going-all-in-on-hyperliquid)的一部分。你直接從我們營運的 Hyperliquid 節點接收串流，不需要自行運行 `hl-node`；如果你已經在 Alchemy 上開發，我們會直接在你目前使用的 API 金鑰上啟用這項功能。請先從 [HyperCore Data API 總覽](https://www.alchemy.com/docs/data/hypercore/overview)了解有哪些串流與 REST 方法，再[申請 HyperCore 搶先體驗](https://www.alchemy.com/hypercore-early-access)，以取得連線資訊並直接與我們的團隊合作。

如果你正在為技術堆疊的其他部分挑選串流傳輸方式，我們的 [webhooks、WebSockets 與 gRPC](https://www.alchemy.com/overviews/webhooks-vs-websockets-vs-grpc) 比較文章說明了各自的取捨。至於 Hyperliquid 的 HyperEVM 部分，請參閱我們的 [Hyperliquid RPC 指南](https://www.alchemy.com/overviews/complete-guide-to-hyperliquid-rpc-providers-top-8-solutions-for-2026)。
