# Как читать лидерборд Hyperliquid и позиции кошельков

> Hyperliquid публикует позиции всех кошельков, но лидерборд, который эти кошельки называет, находится на отдельном хосте и в официальной справке отсутствует. Разбираем, где он расположен, как прочитать кошелёк через clearinghouseState и где эта связка упирается в потолок.

- Источник: https://papaproxy.net/ru/blog/hyperliquid-leaderboard-api.php
- Опубликовано: 2026-08-07
- Автор: Alex Young
- Рубрика: API и данные · Блог PapaProxy.net

---

## Ключевые выводы

- Лидерборд отдаётся обычным GET с `stats-data.hyperliquid.xyz/Mainnet/leaderboard` и в официальной справке отсутствует: считайте его форму нестабильной, кешируйте раз в час и проверяйте доступность этого хоста отдельно от `api.hyperliquid.xyz`.
- `clearinghouseState` возвращает открытые позиции кошелька без ключа и подписи, вес 2 — дешёвый уровень; пустой `assetPositions` означает «в нуле», а не «ошибка».
- Пользовательские вебсокет-подписки ограничены 10 уникальными пользователями на IP: дальше их либо разносят по адресам, либо фильтруют общерыночный поток сделок (в каждой сделке есть массив `users`) и сверяют затронутые кошельки через `clearinghouseState`.
- `userFills` отдаёт не более 2 000 последних исполнений (через `userFillsByTime` доступно 10 000); полный ответ на 2 000 записей стоит около 120 весов — примерно как 60 вызовов `clearinghouseState`, — поэтому листайте перекрывающимся курсором и дедуплицируйте по `tid`.
- Эндпоинта копитрейдинга нет, отдельного закрытия позиции тоже: вы выставляете собственный ордер с `reduce_only`, а «рынок» выражается агрессивным лимитом с ограничением по проскальзыванию. Нативная альтернатива — Vaults: экспозиция на стратегию лидера, а не зеркалирование произвольного кошелька.
- В нашем тесте августа 2026 года первый 429 появился на 1 212–1 381 вызове в минуту — примерно вдвое выше номинальной арифметики, — но устойчивый темп, 574 в минуту с датацентрового адреса, близок к документированным 600: планируйте по документации, а запас считайте допуском на всплески. Лимит считается по адресу, а не по /24, и остаток квоты в ответах не отдаётся.

## Где на самом деле находится лидерборд

Основной API отвечает на `POST https://api.hyperliquid.xyz/info`, и документация info API подробно его описывает — все запросы уровня кошелька из этой статьи есть там. Лидерборд — исключение: он отдаётся обычным GET по адресу `https://stats-data.hyperliquid.xyz/Mainnet/leaderboard`, и этого пути в справке нет. Публичные SDK работают с ним как со вторым базовым адресом рядом с основным API, а не как с info-запросом, — и это самый ясный сигнал о его устройстве: другой хост, другая форма ответа, нет поля `type`, нет POST-тела.

Отсюда два следствия, и оба операционные, а не теоретические. Первое: доступность у хостов независимая. Это разные сервисы, поэтому проверка `api.hyperliquid.xyz` ничего не говорит о том, отвечает ли статистический хост. Мониторьте их раздельно, иначе задача по лидерборду будет молча завершаться с ошибкой, пока все остальные вызовы работают. Второе: ответ тяжёлый и медленный по сравнению с info-вызовом. По замеру напрямую с нашего сервера медианное время ответа лидерборда — около 0,7 секунды против примерно 0,3 секунды у `clearinghouseState`, причём отдаёт он весь рейтинг целиком, а не страницу.

Ответ представляет собой рейтинг — по объекту на кошелёк, — и его форму стоит знать до того, как на неё опираться:

JSONКопировать код

```json
{
  "ethAddress": "0x...",
  "accountValue": "1234567.89",
  "displayName": "имя трейдера или null",
  "prize": 0,
  "windowPerformances": [
    ["day",     {"pnl": "12345.6", "roi": "0.0123", "vlm": "9876543.2"}],
    ["week",    {"pnl": "...", "roi": "...", "vlm": "..."}],
    ["month",   {"pnl": "...", "roi": "...", "vlm": "..."}],
    ["allTime", {"pnl": "...", "roi": "...", "vlm": "..."}]
  ]
}
```

`ethAddress` — тот самый ключ, который вы понесёте во все вызовы уровня кошелька ниже. Показатели приходят парами «окно — метрики», а не плоскими полями, поэтому «топ по недельному PnL» означает сначала выбрать нужное окно; а все числа приходят строками — это единообразно для всего API и регулярно приводит к ошибкам сравнения. Поле `displayName` необязательное и часто пустое.

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

PythonКопировать код

```python
import json, time, requests

STATS = "https://stats-data.hyperliquid.xyz/Mainnet/leaderboard"
CACHE, TTL = "leaderboard.json", 3600          # совпадает с частотой обновления снимка

def leaderboard():
    try:                                        # отдаём из кеша, пока он свежий
        with open(CACHE) as f:
            blob = json.load(f)
        if time.time() - blob["fetched"] < TTL:
            return blob["rows"]
    except (FileNotFoundError, json.JSONDecodeError, KeyError):
        pass
    r = requests.get(STATS, timeout=30)          # с запасом: этот хост медленнее /info
    r.raise_for_status()
    rows = r.json().get("leaderboardRows", [])
    with open(CACHE, "w") as f:
        json.dump({"fetched": time.time(), "rows": rows}, f)
    return rows

rows = leaderboard()
print(len(rows), "кошельков")
print(rows[0]["ethAddress"], rows[0]["accountValue"])
```

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

## Как прочитать состояние одного кошелька

Когда адреса есть, дальше всё — документированный API. Запрос clearinghouseState возвращает состояние кошелька по бессрочным контрактам: сводку по марже, доступный к выводу баланс и открытые позиции пользователя в `assetPositions`. Весит он 2 — дешёвый уровень, вместе с `l2Book` и `allMids`, — и не требует ни ключа, ни подписи, ни аккаунта. Одна деталь области действия, которую упускают старые гайды: запрос принимает необязательный параметр `dex`, и без него вы получаете первый perp-DEX. Позиции на развёрнутых разработчиками DEX по HIP-3 нужно запрашивать отдельно, если они вам важны.

PythonКопировать код

```python
import requests

INFO = "https://api.hyperliquid.xyz/info"

def wallet_state(address):
    r = requests.post(INFO, json={"type": "clearinghouseState", "user": address}, timeout=10)
    r.raise_for_status()
    return r.json()

state = wallet_state("0x0000000000000000000000000000000000000000")   # адрес пользователя или субаккаунта
for item in state["assetPositions"]:
    p = item["position"]
    print(p["coin"], p["szi"], "вход", p["entryPx"], "uPnL", p["unrealizedPnl"])
```

Чтение позиций по адресу устроено ровно так: `szi` — знаковый размер, поэтому отрицательное значение означает шорт; `entryPx` — средняя цена входа; рядом находятся `unrealizedPnl` и `marginUsed`. Для корректно определённого адреса пользователя или субаккаунта пустой список `assetPositions` означает отсутствие открытых позиций, а не ошибку, — это важно, когда вы идёте по сотням адресов и должны отличать «в нуле» от «не ответил». Оговорка, о которой предупреждает документация: не направляйте этот запрос на адрес агента или API-кошелька — там можно получить пустой результат по кошельку, который очевидно держит позиции.

Два ограничения определяют, что на этом вызове можно построить. Это снимок, а не поток: об изменении позиции вы узнаёте, только спросив снова, поэтому ваше разрешение по времени равно интервалу опроса. И пользовательские вебсокет-подписки перестают масштабироваться в пределах одного адреса: документированные лимиты Hyperliquid разрешают максимум **10 уникальных пользователей в пользовательских вебсокет-подписках** на один IP.

Этот потолок формирует архитектуру, а не закрывает её. Работают три схемы, и они комбинируются:

- **До десяти кошельков на адрес** — пользовательские подписки, вариант с наименьшей задержкой и правильный выбор для личного кабинета.
- **Разнести подписки** — по десять уникальных пользователей на исходящий адрес, тогда список наблюдения растёт вместе с числом адресов.
- **Следить за рынком, а не за кошельками** — общерыночная подписка на сделки несёт массив `users` с покупателем и продавцом каждой сделки, поэтому можно фильтровать поток по своему списку наблюдения для обнаружения изменений, а затем сверять только затронутые кошельки через `clearinghouseState`. Именно так построены некоторые современные трекеры кошельков: задача подписки на каждый кошелёк превращается в задачу фильтрации.

Третья схема не убирает опрос — она его перенаправляет. Вместо того чтобы спрашивать каждый кошелёк по таймеру, вы спрашиваете те, что только что торговали, плюс медленный полный обход, чтобы подобрать пропущенное потоком. Тогда вопрос сводится к тому, как быстро может идти эта сверка, — и здесь в дело вступают бюджеты.

## Как достать сделки, стоящие за позицией

Позиция говорит, где кошелёк находится; сделки — как он туда пришёл. Эндпоинт userFills возвращает до 2 000 последних исполнений по адресу, а флаг `aggregateByTime` объединяет частичные исполнения одного пересекающего ордера. Для всего, что ограничено временем, есть userFillsByTime: он принимает обязательный `startTime` в миллисекундах и необязательный `endTime` со значением «сейчас» по умолчанию, а документация отмечает не более 2 000 исполнений в ответе при доступных всего 10 000 последних.

Поведение startTime — то место, где первая реализация обычно ошибается, и ловушка не в размере окна, а в работе с курсором при обрезанных ответах. `startTime` включающий и задаётся в миллисекундах, а несколько исполнений могут попасть в одну и ту же миллисекунду, поэтому сдвиг на `last_time + 1` способен молча потерять записи, стоявшие за последней увиденной. Безопасная схема — перекрывающийся курсор: начинайте заново с последней отметки времени, а не за ней; дедуплицируйте по идентификатору исполнения — `tid` или хешу транзакции; и сдвигайте курсор только после обработки всего, что вернул ответ. Учитывайте и горизонт: не более 2 000 исполнений в ответе и всего 10 000 последних, так что глубокой истории здесь нет вовсе, а попытка сделать вид, что она есть, даёт молча обрезанную выгрузку.

Их стоимость легко оценить неверно в обе стороны. Оба эндпоинта по сделкам весят 20 — базовый уровень для info-запросов — плюс **дополнительный вес за каждые 20 элементов в ответе**. Полный ответ на 2 000 исполнений стоит, таким образом, около 120 весов по документированной формуле — примерно как 60 вызовов `clearinghouseState`: достаточно дорого, чтобы история сделок жила в медленной фоновой задаче, и достаточно дёшево, чтобы один такой вызов сам по себе не сломал минутный бюджет.

## Как следить за кошельком на практике

Две оговорки до всякой архитектуры, потому что словосочетание «копитрейдинг» обещает больше, чем открывает платформа. API копитрейдинга у Hyperliquid нет — в смысле эндпоинта, который зеркалил бы действия другого трейдера на ваш счёт. Есть публичный доступ на чтение состояния любого кошелька и обычный торговый API для ваших собственных ордеров. Любая система «следования» — это ваш собственный конвейер: прочитать кошелёк, принять решение, выставить свой ордер. А значит, и задержка, и проскальзывание, и само решение торговать — тоже ваши.

Ближайший нативный аналог — Vaults, и устроены они иначе: вкладчик вносит средства в хранилище и пропорционально участвует в прибылях и убытках стратегии его лидера. Это даёт нативную экспозицию на стратегию — но не позволяет выбрать произвольный кошелёк из лидерборда и зеркалить его отдельные сделки.

Закрытие позиции рыночным ордером — та же история с другой стороны. У торгового API Hyperliquid нет отдельного эндпоинта закрытия: позиция закрывается встречным ордером с флагом `reduce_only`, а рыночное исполнение выражается агрессивным лимитным ордером с ограничением по проскальзыванию, а не типом `MARKET`. Хелпер `market_close` в официальном Python SDK делает под капотом именно это — прочитайте его, прежде чем писать своё.

Так что архитектура сводится к бюджету опроса, и бюджет документирован: REST-запросы делят общий лимит в **1 200 весов в минуту на один IP-адрес**. При весе 2 у `clearinghouseState` арифметика даёт 600 проверок кошельков в минуту с адреса. Но прежде чем планировать мощность вокруг этого числа, важнее три программных рычага:

- **Кешируйте то, что не меняется.** Лидерборд — раз в час, `meta` — раз в сутки. Это вызовы весом 20, которым нечего делать в цикле.
- **Опрашивайте по уровням, а не равномерно.** Кошелькам с открытыми позициями нужны секунды, пустым — минуты. Равномерный опрос тратит большую часть бюджета на подтверждение того, что ничего не произошло.
- **Отступайте по наблюдаемому сигналу, а не по предполагаемому.** Заголовка с остатком квоты в ответах нет, поэтому считайте расход сами, а 429 воспринимайте как данные: наши замеры ниже показывают, что восстановление намного короче, чем подсказывает формулировка «в минуту».

## Наш тест: сколько кошельков обслуживает один адрес

**Гипотеза.** Документированный бюджет описывает реальное поведение, и одного исходящего адреса не хватит для наблюдения за большим набором кошельков.

**Окружение и профиль нагрузки.** Адреса PapaProxy.net по SOCKS5 с IP whitelisting, цель — публичный `POST /info` на `api.hyperliquid.xyz`; без ключей и подписи, адреса кошельков взяты из ответа самого лидерборда. Датацентровый и ISP-пулы прогонялись отдельными сериями и нигде не сводились в общую цифру. Число одновременных запросов с адреса наращивали ступенями по 150 секунд — каждая покрывает полную минуту по часам, — а лестницу останавливали на первом HTTP 429. Прогон 6–7 августа 2026 года.

**Точка первого наблюдённого 429.** Формулировка важна: лестница, остановленная на первом отказе, измеряет, где началось применение лимита, а не подтверждённый устойчивый потолок. Это разные измерения, и в этом прогоне мы сделали первое.

| Пул | Вызовов `clearinghouseState` в минуту на первом 429 | Конкурентность | Задержка p50 |
| --- | --- | --- | --- |
| Датацентр | 1 212 | 8 | 284 мс |
| ISP | 1 381 | 12 | 332 мс |

**Применение лимита началось заметно выше номинальной арифметики.** Бюджет 1 200 весов в минуту при весе 2 подразумевает 600 вызовов; первый отказ пришёл примерно вдвое позже, и тот же рисунок повторился на каждом эндпоинте, где удалось достичь лимита:

| Эндпоинт | Пул | Вызовов до 429 | Вес по документации | Израсходовано весов по документации |
| --- | --- | --- | --- | --- |
| `clearinghouseState` | Датацентр | 1 212 | 2 | 2 424 |
| `clearinghouseState` | ISP | 1 381 | 2 | 2 762 |
| `allMids` | ISP | 1 089 | 2 | 2 178 |
| `meta` | ISP | 131 | 20 | 2 620 |

Во всех четырёх случаях израсходованный бюджет оказался в 1,8–2,3 раза выше документированных 1 200. Измеренное поведение в целом сохранило документированные весовые уровни, но точка первого 429 заметно менялась между прогонами и эндпоинтами — около 21% между двумя эндпоинтами веса 2 на одном пуле.

**И вот вывод, который важнее самого запаса.** Темп, на котором мы остановились для устойчивой многоадресной работы, — 574 вызова в минуту с датацентрового адреса — оказался близок к документированной плановой цифре в 600. То есть документация остаётся разумной базой для планирования мощности, а дополнительный запас до первого отказа правильнее считать допуском на всплески, а не той мощностью, на которой можно сэкономить при покупке.

**Три находки, которые меняют то, как вы пишете клиент.** Остаток квоты не отдаётся: ни один ответ не нёс заголовка с лимитом — ни при успехе, ни при 429, — поэтому ведите собственный счётчик по документированным весам, а 429 сообщает лишь о том, что применение лимита уже наступило. Восстановление в этих прогонах было быстрым: после 429 адрес снова отвечал через 5,3–5,4 секунды. Считайте это наблюдением, а не гарантированным периодом остывания, и сохраняйте и отступление, и собственный учёт весов. И лимит считается по адресу, а не по подсети: четыре адреса внутри одной /24 держали 2 278 вызовов в минуту на датацентре против 2 274 у четырёх адресов из разных /24 (2 052 против 2 044 на ISP), причём отказов не было ни в одной группе, — покупать адреса вразброс по блокам смысла нет.

**Одна аномалия, которую мы не объяснили.** В пересчёте на документированные веса три потолка из четырёх собираются вокруг 2 500 (±12%). Но `allMids` с датацентра прошёл 1 622 вызова в минуту — 3 244 веса — вообще без отказа, тогда как тот же вызов с ISP упёрся в 429 уже на 1 089. Разрыв смотрит в сторону, обратную результату по `clearinghouseState`, так что систематического преимущества пула здесь не видно. Правдоподобные объяснения по убыванию: потолок не константа и меняется от прогона к прогону; более крупный ответ `allMids` не дал набрать нужный темп до конца лестницы; лимит на этом эндпоинте считается иначе. Наши данные их не различают. Практический урок важнее самой загадки: **потолок — не константа, поэтому не планируйте вплотную к измеренному максимуму, оставляйте запас.**

**Устойчивая рабочая точка.** Это и есть число для планирования — то, которое мы держали, а не то, на котором сломалось:

| Пул | Устойчиво вызовов/мин с адреса | К документированным 600 |
| --- | --- | --- |
| Датацентр | 574 | ~96% |
| ISP | 514 | ~86% |

Совокупная пропускная способность росла вместе с числом адресов: строго линейно до пяти адресов и близко к линейному дальше. Один вызов `clearinghouseState` равен одному кошельку, поэтому нужное число адресов равно «кошельки, делённые на интервал обновления»:

| Кошельков под наблюдением | Обновление раз в 10 с | раз в 30 с | раз в 60 с |
| --- | --- | --- | --- |
| 100 | 2 | 1 | 1 |
| 250 | 3 | 1 | 1 |
| 500 | 6 | 2 | 1 |
| 1 000 | 11 | 4 | 2 |
| 5 000 | 53 | 18 | 9 |

Проще говоря: один адрес обслуживает примерно 574 кошелька при обновлении раз в минуту, 287 — раз в 30 секунд и 95 — раз в 10 секунд. То есть паре сотен кошельков при обновлении раз в полминуты хватит ровно одного адреса, а пяти тысячам понадобится девять даже при неспешном минутном цикле — и больше полусотни при десятисекундном. А если вы используете общерыночный поток сделок для обнаружения изменений, эти числа описывают вашу сверку, а не таймер по каждому кошельку, — и счёт обычно получается заметно меньше.

К чему это приводит связку, с которой статья началась? Всё, что до нескольких сотен кошельков, — задача программная: кешировать лидерборд, фильтровать поток сделок вместо опроса каждого кошелька, разнести оставшийся опрос по уровням и отступать по собственному счётчику. Дальше ограничением становится документированный поадресный бюджет, и мощность добавляется так же, как добавляются воркеры: новыми исходящими адресами, у каждого свой бюджет, а их число считается по таблице выше. Ровно эту часть закрывают наши [выделенные прокси](/ru/individual.php) — выделенные IPv4-адреса от одного IP, статичные на весь срок плана: адрес закреплён за вами, а непрерывные блоки штрафа не несут, раз лимит считается по адресу. Перед масштабированием парка опроса сверьтесь с актуальными условиями использования площадки, а вызов лидерборда держите вне быстрого цикла независимо от того, сколько у вас адресов.
