Hyperliquidのリアルタイム板情報をWebSocketで購読する方法

2026年5月6日
  • hyperliquid orderbook websocket

  • real time hyperliquid api

  • hyperliquid リアルタイム板情報

  • hyperliquid websocket 購読

  • hyperliquid l2 book

板情報(オーダーブック)は、取引所における最も重要なデータ構造のひとつです。市場に出ている未約定の買い注文・売り注文について、価格と数量の分布をリアルタイムに示します。クオンツ取引ボット、アービトラージシステム、マーケット分析ダッシュボードでは、最新の板状態をミリ秒単位の低遅延で取得できるかどうかが、戦略の精度に大きく影響します。

HyperliquidはWebSocket APIを提供しており、購読者はL2板情報のスナップショットと差分更新をリアルタイムで受け取れます。本記事では、購読方法、メッセージ形式、ローカル板の管理、切断時の再接続など、本番運用を意識した実装ポイントを解説します。なお、インフラを自前で構築せずに永続先物取引を行いたい場合は、OneKey Perpsでリアルタイムの価格表示と取引機能をすぐに利用できます。

WebSocketとRESTポーリングの違い

技術的な実装に入る前に、なぜREST APIのポーリングではなくWebSocketを使うのかを整理しておきます。

リアルタイム板情報が必要な用途では、RESTポーリングでWebSocketを完全に代替するのは現実的ではありません。低遅延で連続的に更新を受け取る必要があるためです。WebSocket APIの正確な仕様や最新情報は、Hyperliquid公式ドキュメントを確認してください。

WebSocket接続の確立

エンドポイント

HyperliquidのWebSocketエンドポイントは、記事執筆時点では以下です。最新情報は必ず公式ドキュメントを参照してください。

wss://api.hyperliquid.xyz/ws

基本的な接続コード

import asyncio
import json
import websockets

WS_URL = "wss://api.hyperliquid.xyz/ws"

async def connect_and_subscribe():
    async with websockets.connect(WS_URL) as ws:
        # 購読メッセージを送信
        subscribe_msg = {
            "method": "subscribe",
            "subscription": {
                "type": "l2Book",
                "coin": "BTC"
            }
        }
        await ws.send(json.dumps(subscribe_msg))

        # メッセージを継続的に受信
        async for raw_msg in ws:
            msg = json.loads(raw_msg)
            handle_message(msg)

asyncio.run(connect_and_subscribe())

購読メッセージの形式

L2板情報の購読

BTCのL2板情報を購読する例です。

{
  "method": "subscribe",
  "subscription": {
    "type": "l2Book",
    "coin": "BTC"
  }
}

Hyperliquidでは、複数の銘柄を同時に購読できます。各銘柄について購読メッセージを送信すれば、それぞれの更新が独立して配信されます。

購読解除

{
  "method": "unsubscribe",
  "subscription": {
    "type": "l2Book",
    "coin": "BTC"
  }
}

メッセージタイプ:スナップショットと差分更新

初期スナップショット(Snapshot)

購読後、サーバーはまず現在の板全体を表すスナップショットを送信します。これには各価格帯の注文数量が含まれます。

{
  "channel": "l2Book",
  "data": {
    "coin": "BTC",
    "time": 1714500000000,
    "levels": [
      [
        [{"px": "64000.0", "sz": "0.5", "n": 3}],
        [{"px": "64010.0", "sz": "0.3", "n": 2}]
      ]
    ]
  }
}
  • px:価格
  • sz:その価格帯の合計注文数量。0の場合、その価格帯は空になったことを示します
  • n:その価格帯にある注文数

通常、bids(買い板)は高い価格から低い価格へ、asks(売り板)は低い価格から高い価格へ並びます。

差分更新(Delta)

スナップショットの後は、変更があった価格帯のみが差分として送信されます。

{
  "channel": "l2Book",
  "data": {
    "coin": "BTC",
    "time": 1714500000100,
    "levels": [
      [
        [{"px": "63990.0", "sz": "0.0", "n": 0}],
        [{"px": "64010.0", "sz": "0.8", "n": 4}]
      ]
    ]
  }
}

sz"0"または0の場合、その価格帯の注文はすべて約定またはキャンセルされたことを意味します。そのため、ローカルで保持している板情報から該当価格帯を削除する必要があります。

ローカル板情報の管理

WebSocketの板データを利用するうえで最も重要なのは、ローカルの板状態を正しく維持することです。

from sortedcontainers import SortedDict

class LocalOrderBook:
    def __init__(self, coin):
        self.coin      = coin
        self.bids      = SortedDict(lambda x: -float(x))  # 買い板:高い価格から低い価格へ
        self.asks      = SortedDict(lambda x:  float(x))  # 売り板:低い価格から高い価格へ
        self.last_time = None

    def apply_snapshot(self, data):
        self.bids.clear()
        self.asks.clear()

        levels = data["levels"][0]

        for bid in levels[0]:
            if float(bid["sz"]) > 0:
                self.bids[bid["px"]] = float(bid["sz"])

        for ask in levels[1]:
            if float(ask["sz"]) > 0:
                self.asks[ask["px"]] = float(ask["sz"])

        self.last_time = data["time"]

    def apply_delta(self, data):
        levels = data["levels"][0]

        for bid in levels[0]:
            px, sz = bid["px"], float(bid["sz"])
            if sz == 0:
                self.bids.pop(px, None)
            else:
                self.bids[px] = sz

        for ask in levels[1]:
            px, sz = ask["px"], float(ask["sz"])
            if sz == 0:
                self.asks.pop(px, None)
            else:
                self.asks[px] = sz

        self.last_time = data["time"]

    @property
    def best_bid(self):
        return next(iter(self.bids.items()), None)

    @property
    def best_ask(self):
        return next(iter(self.asks.items()), None)

    @property
    def mid_price(self):
        bid = self.best_bid
        ask = self.best_ask
        if bid and ask:
            return (float(bid[0]) + float(ask[0])) / 2
        return None

    @property
    def spread(self):
        bid = self.best_bid
        ask = self.best_ask
        if bid and ask:
            return float(ask[0]) - float(bid[0])
        return None

メッセージ処理ロジック

受信したメッセージが初回スナップショットなのか、以降の差分更新なのかを判定し、ローカル状態に反映します。

order_book = LocalOrderBook("BTC")
is_initialized = False

def handle_message(msg):
    global is_initialized

    if msg.get("channel") != "l2Book":
        return

    data = msg["data"]

    if not is_initialized:
        order_book.apply_snapshot(data)
        is_initialized = True
        print(f"板情報の初期化が完了しました。買い板 {len(order_book.bids)} 段、売り板 {len(order_book.asks)} 段")
    else:
        order_book.apply_delta(data)

    # 現在の盘口情報を表示
    print(f"最良買気配: {order_book.best_bid}, 最良売気配: {order_book.best_ask}, スプレッド: {order_book.spread}")

切断時の再接続処理

WebSocket接続は、ネットワークの不安定化やサーバー側の切断により中断される可能性があります。本番環境では、自動再接続の実装が必須です。

import asyncio
import json
import websockets

async def subscribe_with_reconnect(coin, handle_fn, max_retries=None):
    retries = 0

    while max_retries is None or retries < max_retries:
        try:
            async with websockets.connect(WS_URL, ping_interval=20, ping_timeout=10) as ws:
                retries = 0  # 接続成功後、リトライ回数をリセット
                is_initialized = False

                await ws.send(json.dumps({
                    "method": "subscribe",
                    "subscription": {"type": "l2Book", "coin": coin}
                }))

                async for raw_msg in ws:
                    handle_fn(json.loads(raw_msg))

        except (websockets.ConnectionClosed, ConnectionError, OSError) as e:
            retries += 1
            wait = min(2 ** retries, 60)  # 指数バックオフ。最大60秒待機
            print(f"接続が切断されました({e})。{wait}秒後に再試行します({retries}回目)")
            is_initialized = False  # 状態をリセットし、次回は新しいスナップショットから再構築
            await asyncio.sleep(wait)

再接続時には、必ずis_initializedフラグをリセットします。再接続後はサーバーから再び完全なスナップショットが送られるため、途中の差分から継続する前提で処理してはいけません。

シーケンスとメッセージ順序

HyperliquidのWebSocketメッセージにはタイムスタンプが含まれており、メッセージの順序確認に利用できます。本番システムでは、以下の対応を推奨します。

  • 最後に処理したメッセージのタイムスタンプを記録する
  • 新しいメッセージのタイムスタンプが既に処理済みのものより古い場合、古いメッセージとして破棄する
  • 一定時間メッセージが届かない場合、pingを送るか再接続する
  • 高頻度戦略では、専用回線や地理的に近いノードの利用によりネットワーク遅延を抑えることを検討する

複数銘柄の同時購読

1つのWebSocket接続で複数の銘柄を同時に購読できます。非同期アーキテクチャで並行処理するのが一般的です。

coins = ["BTC", "ETH", "SOL"]
order_books = {coin: LocalOrderBook(coin) for coin in coins}
initialized = {coin: False for coin in coins}

async def multi_subscribe():
    async with websockets.connect(WS_URL) as ws:
        for coin in coins:
            await ws.send(json.dumps({
                "method": "subscribe",
                "subscription": {"type": "l2Book", "coin": coin}
            }))

        async for raw_msg in ws:
            msg = json.loads(raw_msg)

            if msg.get("channel") == "l2Book":
                data = msg["data"]
                coin = data["coin"]

                if coin in order_books:
                    if not initialized[coin]:
                        order_books[coin].apply_snapshot(data)
                        initialized[coin] = True
                    else:
                        order_books[coin].apply_delta(data)

実際の活用例

リアルタイム板情報は、主に以下のような用途で使われます。

  • 取引ボット:スプレッドや流動性を見ながら、指値注文の価格を動的に調整する
  • アービトラージシステム:Hyperliquidと他の取引所、たとえばdYdXやGMXとの価格差を監視する
  • マーケット分析ダッシュボード:板の厚み、スプレッド履歴、流動性分布を可視化する
  • リスク管理システム:スプレッドの急拡大や流動性の急低下を検知してアラートを出す

OneKey Perps:インフラを自前で構築しない選択肢

WebSocketを使った板情報システムを自作するには、データ取得、ローカル板の整合性管理、再接続、監視、障害対応など、相応のエンジニアリング工数が必要です。

目的がクオンツ基盤の開発ではなく、オンチェーンの永続先物取引を実際に行うことであれば、OneKey Perpsを使うことでリアルタイム相場表示と取引機能をすぐに利用できます。OneKeyハードウェアウォレットによる安全な署名フローと組み合わせることで、秘密鍵管理のリスクを抑えながら取引判断に集中しやすくなります。

また、WalletConnect経由で分散型アプリに接続したい場合は、WalletConnectのドキュメントを確認してください。OneKeyは標準的なWalletConnectプロトコルに対応しており、HyperliquidなどのWeb3アプリと安全に連携できます。

まだ試していない場合は、OneKeyアプリをダウンロードし、対応するウォレットを接続したうえでOneKey Perpsを確認してみてください。まずは少額またはデモ的な範囲で操作フローを理解し、リスクを把握したうえで利用することをおすすめします。

よくある質問

Q1:WebSocket接続に同時接続数の上限はありますか?

A:接続数の制限については、Hyperliquid公式ドキュメントを確認してください。一般的には、単一IPから過度に多くの接続を張るのは避けるべきです。複数銘柄を購読する場合は、複数の接続を作るよりも、同一接続を再利用する設計が推奨されます。

Q2:差分更新を1件でも取りこぼすと、ローカル板は不正確になりますか?

A:はい。ネットワークの問題などで差分メッセージを取りこぼすと、ローカルの板状態はサーバー側の実際の状態とずれる可能性があります。スプレッドの異常、最良気配の不自然なジャンプ、長時間の無通信などを検知した場合は、接続を閉じて再購読し、新しいスナップショットから再構築するのが安全です。

Q3:リアルタイム板情報はバックテストに使えますか?

A:リアルタイムストリームをそのままバックテストに使うのは現実的ではありません。バックテストには過去データが必要です。リアルタイム板情報を時系列のtick dataとして保存し、十分な履歴を蓄積してから検証に使うのが一般的です。初期的な検証では、Hyperliquidの過去K線APIと組み合わせる方法もあります。

Q4:WebSocket接続が正常かどうかは、どのように判断できますか?

A:定期的に以下のような指標を監視するのがおすすめです。最後にメッセージを受信してからの経過時間が事前に決めた閾値、たとえば5秒を超えていないか、ping/pongの往復遅延が許容範囲内か、などです。異常を検知した場合は、能動的に再接続処理を実行します。

Q5:OneKeyウォレットは取引レイテンシーに影響しますか?

A:OneKeyハードウェアウォレットでの署名操作は、通常は数百ミリ秒程度で完了するため、多くの取引スタイルでは大きな問題になりにくいです。ただし、1秒間に何十回も注文するような超高頻度戦略では、API Agentのサブアカウントやホットウォレットで高頻度署名を処理し、メイン口座の資金管理をOneKeyで保護するなど、役割を分離する設計が考えられます。

まとめ

リアルタイム板情報の購読は、高品質なクオンツ取引システムにおける重要なデータ基盤です。HyperliquidのWebSocket APIは構成がわかりやすく、L2板のスナップショットと差分更新を利用することで、ローカルに実用的な板状態を構築できます。マーケットメイキングボット、アービトラージシステム、リアルタイム分析ダッシュボードなどは、この仕組みを土台に実装できます。

一方で、永続先物取引に参加することが目的で、低レイヤーのデータ基盤を自作する必要がない場合は、OneKey Perpsを利用するのが実用的です。OneKeyアプリをダウンロードし、OneKey Perpsでリアルタイム相場と取引フローを確認することで、より少ない準備でオンチェーンPerps取引を始められます。

リスク注意:リアルタイム板情報は特定時点の市場状態を示すものであり、取引判断の唯一の根拠にすべきではありません。板情報を利用した自動売買戦略は、市場の急変、技術的障害、戦略設計上の欠陥により大きな損失を生む可能性があります。永続先物取引は高リスクであり、元本をすべて失う可能性があります。本記事は技術的な参考情報であり、投資助言ではありません。十分にリスクを理解したうえで慎重に判断してください。

OneKeyで暗号化の旅を守る

View details for OneKeyのご購入OneKeyのご購入

OneKeyのご購入

世界最先端のハードウェアウォレット。

View details for アプリをダウンロードアプリをダウンロード

アプリをダウンロード

詐欺アラート。すべてのコインをサポート。

View details for OneKey SifuOneKey Sifu

OneKey Sifu

暗号化の疑問を解消するために、一つの電話で。