Подписка на стакан Hyperliquid в реальном времени через WebSocket

11 мая 2026 г.

Стакан заявок — одна из ключевых структур данных любой биржи. Он в реальном времени показывает распределение всех неисполненных лимитных ордеров по ценам и объёмам. Для квант-ботов, арбитражных систем и аналитических панелей доступ к актуальному стакану с задержкой на уровне миллисекунд часто критичен: от этого зависит, насколько корректно стратегия видит рынок. Source: Hyperliquid docs.

Hyperliquid предоставляет полноценный WebSocket API, через который можно получать снапшоты и инкрементальные обновления L2-стакана. Ниже разберём формат подписки, структуру сообщений, поддержку локального стакана, переподключение и другие инженерные детали, которые важны в продакшене. Если ты не хочешь поднимать собственную инфраструктуру ради торговли бессрочниками, в OneKey Perps уже есть готовое отображение рынка в реальном времени и удобный торговый workflow.

Ключевая сравнительная таблица

ИзмерениеПодписка WebSocketОпрос REST API
Задержка данныхПередача на уровне миллисекундИнтервал опроса (обычно 100ms–1s)
Нагрузка на серверНизкая (одно рукопожатие, непрерывная передача)Высокая (при каждом опросе создается новый запрос)
Целостность данныхПорядок можно гарантировать с помощью порядкового номераВозможна потеря изменений между двумя опросами
Сложность реализацииСредняя (нужно обрабатывать переподключение при разрыве)Низкая (простой цикл)
Подходящие сценарииВысокочастотная торговля, мониторинг в реальном времениНизкочастотные запросы, начальное состояние

WebSocket или REST polling

Перед техническими деталями важно понять, почему для стакана обычно используют WebSocket, а не постоянный опрос REST API.

REST polling подходит для редких запросов: получить состояние рынка, проверить позицию, загрузить исторические данные. Но для стакана в реальном времени он быстро упирается в задержки, лимиты запросов и риск пропустить быстрые изменения ликвидности.

WebSocket держит постоянное соединение и позволяет серверу пушить обновления сразу после изменения стакана. Для сценариев, где важны bid/ask, спред, глубина и реакция на изменение ликвидности, WebSocket практически незаменим. Полные спецификации лучше сверять с официальной документацией Hyperliquid.

Установка WebSocket-соединения

Endpoint

WebSocket endpoint Hyperliquid, актуальность которого нужно сверять с официальной документацией:

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-стакан

Подписка на L2-стакан BTC:

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

Hyperliquid позволяет подписываться на несколько торговых пар одновременно. Для этого достаточно отправить отдельное сообщение подписки по каждому инструменту: обновления будут приходить независимо.

Отписка

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

Типы сообщений: снапшот и инкрементальные обновления

Начальный снапшот

После подписки сервер сначала отправляет полный снимок стакана: все актуальные ценовые уровни и объёмы на них.

{
  "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))  # bids: цена от высокой к низкой
        self.asks = SortedDict(lambda x: float(x))   # asks: цена от низкой к высокой
        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

Логика обработки сообщений

Нужно отличать начальный снапшот от последующих delta-обновлений и применять их к локальному состоянию.

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"Стакан инициализирован: "
            f"{len(order_book.bids)} bid-уровней, "
            f"{len(order_book.asks)} ask-уровней"
        )
    else:
        order_book.apply_delta(data)

    # Печатаем текущий top of book
    print(
        f"Best bid: {order_book.best_bid}, "
        f"best ask: {order_book.best_ask}, "
        f"spread: {order_book.spread}"
    )

Переподключение при обрыве соединения

WebSocket-соединение может оборваться из-за сетевых проблем или действий сервера. В продакшене обязательно нужен автоматический reconnect.

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)  # exponential backoff, максимум 60 секунд
            print(f"Соединение разорвано ({e}), повтор через {wait} сек. Попытка #{retries}")

            is_initialized = False  # после reconnect ждём новый снапшот
            await asyncio.sleep(wait)

При переподключении нужно сбрасывать флаг is_initialized, потому что после нового соединения сервер снова отправит полный снапшот, а не продолжит поток delta-сообщений с места обрыва.

Timestamp и порядок сообщений

Сообщения WebSocket Hyperliquid содержат timestamp. Его можно использовать для базовой проверки порядка сообщений. В продакшене стоит делать следующее:

  • хранить timestamp последнего обработанного сообщения;
  • если новое сообщение старше уже обработанного, считать его устаревшим и отбрасывать;
  • если долго нет новых сообщений, отправлять ping или принудительно переподключаться;
  • для высокочастотных стратегий рассмотреть инфраструктуру с меньшей сетевой задержкой: более близкие узлы, стабильный канал, специализированный хостинг.

Подписка на несколько монет

Один WebSocket-канал может обслуживать несколько подписок. Обычно лучше переиспользовать одно соединение, чем создавать отдельный socket под каждую пару.

coins = ["BTC", "ETH", "SOL"]
order_books = {coin: LocalOrderBook(coin) 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":
                coin = msg["data"]["coin"]
                if coin in order_books:
                    order_books[coin].apply_delta(msg["data"])

В реальной системе для каждой монеты также нужно отдельно отслеживать состояние инициализации, timestamp последнего сообщения и health checks.

Где используется стакан в реальном времени

Типичные сценарии применения L2-данных:

  • Торговые боты: динамическая перестановка лимитных ордеров с учётом спреда и глубины.
  • Арбитраж: мониторинг разницы цен между Hyperliquid и другими площадками, например dYdX или GMX.
  • Аналитические панели: визуализация глубины стакана, истории спреда и распределения ликвидности.
  • Риск-мониторинг: алерты при резком расширении спреда, исчезновении ликвидности или аномальном движении best bid / best ask.

OneKey Perps: без собственной инфраструктуры

Поднимать WebSocket-инфраструктуру, поддерживать локальные стаканы, следить за reconnection logic и хранить tick data — это отдельная инженерная задача. Если твоя цель не разработка квант-системы, а практичная торговля бессрочниками, проще использовать готовый интерфейс.

OneKey Perps даёт отображение рынка в реальном времени и торговые функции в одном workflow. В связке с аппаратным кошельком OneKey ты получаешь безопасное подписание транзакций и можешь сосредоточиться на торговом решении, а не на поддержке низкоуровневого data pipeline.

Если ты подключаешься к Web3-приложениям через WalletConnect docs, ориентируйся на документацию WalletConnect. OneKey поддерживает стандартный протокол WalletConnect и позволяет безопасно взаимодействовать с dApp, включая приложения экосистемы Hyperliquid.

Ненавязчивый следующий шаг: скачай или открой OneKey, подключи кошелёк и попробуй OneKey Perps на небольшом размере позиции, предварительно разобравшись с рисками плеча и бессрочных контрактов.

Частые вопросы

Q1: Есть ли лимиты на количество WebSocket-соединений?

Да, лимиты могут существовать. Актуальные ограничения нужно проверять в официальной документации Hyperliquid. На практике лучше не открывать слишком много соединений с одного IP: для нескольких пар обычно разумнее использовать одно WebSocket-соединение и несколько подписок внутри него.

Q2: Если потерять одно delta-сообщение, локальный стакан станет неправильным?

Да. Если delta-обновление потерялось из-за сети или сбоя обработки, локальный стакан может разойтись с реальным состоянием на сервере. Если ты видишь признаки рассинхронизации — например, аномальный спред, резкий скачок top of book или долгое отсутствие сообщений — лучше закрыть соединение и подписаться заново, чтобы получить свежий снапшот.

Q3: Можно ли использовать real-time стакан для бэктеста?

Напрямую — не очень удобно. Для бэктеста нужны исторические данные, а WebSocket даёт текущий поток. Рабочий подход — сохранять real-time данные как tick data, накапливать историю и затем использовать её для исследований. Для грубого первичного тестирования можно также комбинировать это с историческими K-line API Hyperliquid.

Q4: Как понять, что WebSocket-соединение здоровое?

Отслеживай как минимум два показателя:

  • сколько времени прошло с последнего полученного сообщения;
  • насколько адекватна ping/pong-задержка.

Если последнее сообщение было слишком давно, например больше заданного порога в 5 секунд, или ping/pong начинает деградировать, запускай принудительный reconnect.

Q5: OneKey-кошелёк увеличивает задержку торговли?

Подписание через аппаратный кошелёк OneKey обычно занимает сотни миллисекунд, и для большинства ручных или полуавтоматических стратегий это не является критичным фактором. Для ультравысокочастотных сценариев, где заявки отправляются десятки раз в секунду, можно разделять роли: горячий кошелёк или API Agent-субаккаунт для частых подписей, а основной капитал — под защитой OneKey.

Итоги

Подписка на стакан в реальном времени — базовый компонент качественной торговой или аналитической системы. WebSocket API Hyperliquid устроен достаточно понятно: ты подписываешься на l2Book, получаешь начальный снапшот, затем применяешь delta-обновления и следишь за корректностью локального состояния.

Если ты строишь маркет-мейкинг-бота, арбитражную систему или аналитический dashboard, этот подход даст хороший фундамент. Если же тебе нужен не backend для стаканов, а удобный вход в торговлю ончейн-бессрочниками, используй OneKey Perps: готовый интерфейс, real-time рынок и безопасное подписание через OneKey.

Риск-предупреждение: данные стакана отражают состояние рынка только в конкретный момент времени и не должны быть единственной основой для торгового решения. Автоматические стратегии на основе стакана могут привести к значительным убыткам из-за волатильности, технических сбоев, потери сообщений или ошибок в логике стратегии. Торговля бессрочными контрактами и использование плеча связаны с высоким риском, включая риск полной потери капитала. Этот материал предназначен только для технического ознакомления и не является инвестиционной, финансовой или юридической рекомендацией.

Защитите свое криптопутешествие с OneKey

View details for Магазин OneKeyМагазин OneKey

Магазин OneKey

Самый продвинутый аппаратный кошелек в мире.

View details for Загрузить приложениеЗагрузить приложение

Загрузить приложение

Предупреждения о мошенничестве. Поддержка всех монет.

View details for OneKey SifuOneKey Sifu

OneKey Sifu

Ясность в криптовалюте — на расстоянии одного звонка.