WebSocket API
Потоковая передача данных в реальном времени для торговли опционами на Hypercall.
Ознакомьтесь с интерактивным справочником по WebSocket API для более удобного просмотра с живыми примерами и деталями схемы.
Скачайте спецификацию AsyncAPI для программного использования.
Подключение
Подключайтесь к wss://HOST/ws:
Эндпоинты:
- Продакшн:
wss://api.hypercall.xyz/ws - Локально:
ws://localhost:3000/ws
Тестнет временно отключён, пока Hypercall не приобретёт больше тестового HYPE.
Идентификация кошелька
Чтобы получать данные по аутентифицированным каналам (заявки, исполнения, портфель), идентифицируйте свой кошелёк после подключения, отправив сообщение Authenticate:
{"type": "Authenticate", "wallet": "0x1234..."}
Сервер отвечает подтверждением:
{"type": "Authenticated", "wallet": "0x1234..."}
После получения Authenticated вы можете подписываться на аутентифицированные каналы. Если адрес кошелька недействителен, сервер отвечает сообщением Error, и соединение остаётся открытым.
Параметр запроса ?wallet= по-прежнему поддерживается для обратной совместимости, но считается устаревшим и будет удалён в одном из будущих выпусков. Предпочтительнее использовать описанный выше подход на основе сообщений.
Живучесть соединения
Сервер обеспечивает WebSocket-heartbeat:
- Отправляет управляющий кадр
Pingкаждые 20 секунд - Ожидает соответствующий
Pongв течение 60 секунд - Закрывает соединение с кодом закрытия
1008и причинойpong timeout, если клиент перестаёт отвечать
Браузерные реализации WebSocket обрабатывают ping/pong автоматически. Многие Rust-библиотеки для websocket, включая tungstenite и tokio-tungstenite, также обрабатывают ping/pong управляющих кадров за вас. Проверьте документацию библиотеки вашего клиента, прежде чем добавлять ручную обработку Pong. Пользовательские или низкоуровневые реализации сокетов должны отвечать на кадры Ping кадрами Pong.
Восстановление после медленного потребителя
Сервер закрывает соединение /ws, которое не может освободить исходящие данные в пределах настроенного предела безопасности по количеству сообщений, закодированным байтам, возрасту очереди или записи в сокет. Когда соединение всё ещё может принять кадр закрытия, сервер использует код 1008 и компактную причину в формате JSON:
{"error":"slow_consumer","class":"ordered_public","cause":"message_age","recovery":"snapshot_resubscribe"}
Поля причины следующие:
| Поле | Значение |
|---|---|
class | Класс доставки, кадр которого пересёк границу безопасности. |
cause | message_limit, byte_limit, message_age или write_timeout. |
recovery | Требуемое следующее действие, например resubscribe, snapshot_resubscribe, portfolio_refetch или rest_reconcile. |
После любого отключения переподключитесь, при необходимости снова идентифицируйте кошелёк, переподпишитесь и сверьте текущее состояние, прежде чем обрабатывать новые события. Упорядоченные публичные каналы требуют нового снимка. Приватные каналы событий требуют сверки через авторитетный REST-интерфейс, поскольку воспроизведение по курсору пока недоступно. Полностью зависшее соединение может завершиться до того, как сможет прочитать причину закрытия, поэтому клиенты должны использовать этот процесс восстановления и при нечистом закрытии.
Используйте отдельные соединения для высокочастотных публичных рыночных данных и для аутентифицированных команд или приватных потоков. Классы доставки определяют метрики и поведение при восстановлении, но кадры на одном соединении всё равно используют один упорядоченный путь записи в сокет. Поэтому зависшая публичная запись может задержать последующие приватные кадры на том же соединении, пока крайний срок записи не закроет его.
Подписка на каналы
Отправьте JSON-сообщение для подписки:
{"type": "Subscribe", "channel": "orderbook"}
Чтобы отписаться:
{"type": "Unsubscribe", "channel": "orderbook"}
Вы получите подтверждение:
{"type": "Subscribed", "channel": "orderbook"}
Фильтрация по символам
Каналы order_updates и fills поддерживают необязательный фильтр symbols. Когда он указан, сервер отправляет только те сообщения, базовый актив которых совпадает с одним из указанных символов.
{"type": "Subscribe", "channel": "order_updates", "symbols": ["BTC"]}
Принимаются как чистые базовые активы ("BTC"), так и полные названия инструментов ("BTC-20260131-100000-C"). Чтобы добавить больше символов, отправьте ещё один Subscribe. Чтобы удалить конкретные символы:
{"type": "Unsubscribe", "channel": "order_updates", "symbols": ["BTC"]}
Если symbols не указаны, пересылаются все обновления для вашего кошелька.
Фильтрация цепочки опционов
Канал options_chain поддерживает фильтрацию по базовым символам, дате экспирации и типу опциона:
{
"type": "Subscribe",
"channel": "options_chain",
"symbols": ["BTC-20260131-100000-C"],
"expiry": "2026-01-31",
"option_type": "call"
}
| Фильтр | Значения | По умолчанию |
|---|---|---|
symbols | Массив полных символов инструментов (например, ["BTC-20260131-100000-C"]) | Все инструменты |
expiry | Строка даты "YYYY-MM-DD" | Все экспирации |
option_type | "call", "put" или опустить для обоих | Оба |
Доступные каналы
| Канал | Требуется аутентификация | Описание |
|---|---|---|
orderbook | Нет | Обновления книги заявок L2 для всех символов |
trades | Нет | Публичная лента сделок |
market_updates | Нет | Изменения листинга рынков (создан/удалён/истёк) |
options_chain | Нет | Инкрементальные обновления цепочки опционов (фильтруются по symbols, expiry, option_type) |
index_prices | Нет | Спотовые/индексные цены в реальном времени для всех базовых активов |
indicative_market_data | Нет | Поток провайдеров котировок из allowlist. Пока недоступен в общем доступе |
order_updates | Да | Изменения статуса ваших заявок (фильтруются по символу) |
fills | Да | Исполнения ваших сделок (фильтруются по символу) |
portfolio | Да | Обновления ваших позиций и баланса |
liquidation | Да | Изменения состояния вашей ликвидации |
competition | Да | Сводка вашего P&L по соревнованию, ранг и итоговая статистика |
competition_engagement | Да | Изменения ранга, разрыв до следующего ранга и итоговое положение |
rfq | Да | Котировки RFQ, обновления статуса и уведомления об исполнении |
Типы сообщений
Размещение заявки (аутентифицировано)
Разместите заявку через командный путь WebSocket.
{
"type": "PlaceOrder",
"wallet": "0x1234...",
"symbol": "BTC-20260131-100000-C",
"side": "Buy",
"size": "1",
"price": "100",
"tif": "gtc",
"route": "book_only",
"client_id": "my-order-1",
"nonce": 1000,
"signature": "0x..."
}
| Поле | Тип | Описание |
|---|---|---|
wallet | string | Адрес кошелька, которому принадлежит заявка |
symbol | string | Символ опциона |
side | string | "Buy" или "Sell" |
size | string | Размер контракта, точно совпадающий с подписанным значением |
price | string | Лимитная цена, точно совпадающая с подписанным значением |
tif | string | Необязательное время действия, по умолчанию "gtc" |
route | string | Необязательный маршрут. Используйте "book_only" для WebSocket-заявок с учётом маршрута. Пропущенный маршрут остаётся допустимым как минимум до 4 июля 2026 года. |
client_id | string | Необязательный клиентский идентификатор заявки |
nonce | integer | Уникальный nonce подписи |
signature | string | Подпись EIP-712 PlaceOrder |
WebSocket PlaceOrder в настоящее время направляет заявку напрямую в книгу заявок. route="best_execution" и route="rfq_only" отклоняются в WebSocket, поскольку этот путь пока не выполняет маршрутизацию RPI/RFQ. Используйте POST /order для best_execution.
Обновление книги заявок
Снимок/обновление книги заявок L2 для символа.
{
"type": "OrderbookUpdate",
"symbol": "BTC-20260131-100000-C",
"bids": [["95000.5", "10.5"], ["94999.0", "25.0"]],
"asks": [["95001.0", "8.0"], ["95002.5", "15.0"]],
"timestamp": 1737331200000
}
| Поле | Тип | Описание |
|---|---|---|
symbol | string | Символ опциона |
bids | array | Уровни бида в виде кортежей [price, size], размер указан в человекочитаемых контрактах |
asks | array | Уровни аска в виде кортежей [price, size], размер указан в человекочитаемых контрактах |
timestamp | integer | Unix-метка времени (миллисекунды) |
Сделка
Публичное событие сделки.
{
"type": "Trade",
"symbol": "BTC-20260131-100000-C",
"price": "0.0523",
"size": "5.0",
"side": "buy",
"timestamp": 1737331200000
}
| Поле | Тип | Описание |
|---|---|---|
symbol | string | Символ опциона |
price | string | Цена сделки в USD |
size | string | Размер сделки в контрактах |
side | string | Сторона агрессора (buy или sell) |
timestamp | integer | Unix-метка времени (миллисекунды) |
Исполнение (аутентифицировано)
Уведомление об исполнении вашей сделки.
{
"type": "Fill",
"order_id": 12345,
"fill_id": 67890,
"symbol": "BTC-20260131-100000-C",
"side": "buy",
"price": "0.0523",
"size": "5.0",
"timestamp": 1737331200000,
"wallet_address": "0x1234...abcd",
"fee": "0",
"trade_id": 99999,
"is_taker": true
}
| Поле | Тип | Описание |
|---|---|---|
order_id | integer | ID вашей заявки |
fill_id | integer | ID исполнения |
symbol | string | Символ опциона |
side | string | Сторона сделки (buy или sell) |
price | string | Цена исполнения в USD |
size | string | Объём исполнения в контрактах |
timestamp | integer | Unix-таймстамп (миллисекунды) |
wallet_address | string | Адрес вашего кошелька |
fee | string | Взимаемая торговая комиссия. Возвращает 0, пока комиссии стартовой площадки отключены |
trade_id | integer | Уникальный ID сделки |
is_taker | boolean | Были ли вы тейкером |
builder_code_address | string? | Кошелёк builder code (если есть) |
builder_code_fee | string? | Комиссия builder code. Возвращает null, пока комиссии стартовой площадки отключены |
Обновление портфеля (с аутентификацией)
Обновление потока портфеля по позициям, балансам, марже и грекам.
Пример обновления греков:
{
"type": "PortfolioUpdate",
"timestamp": 1737331200000,
"per_leg": [
{
"symbol": "BTC-20260131-100000-C",
"quantity": "2.0",
"delta": 0.91,
"gamma": 0.003,
"theta": -0.12,
"vega": 0.44,
"iv": 0.63
}
],
"aggregate": {
"delta": 0.91,
"gamma": 0.003,
"theta": -0.12,
"vega": 0.44,
"iv": 0.63
}
}
Для пустых портфелей обновления греков используют:
per_leg: []aggregate: null
Сводка PnL по соревнованию (с аутентификацией)
Обновление потока соревнования для отображения PnL в шапке/подвале.
{
"type": "CompetitionPnlSummary",
"wallet_address": "0x1234...abcd",
"lifetime_realized_pnl": "1250.50",
"active_competition": {
"competition_id": 7,
"competition_name": "Spring Sprint",
"competition_state": "active",
"rank": 12,
"pnl": "420.25",
"volume": "25000",
"efficiency": "0.01681",
"medal": null
},
"timestamp": 1737331200000
}
Когда активного соревнования нет, active_competition равно null.
Обновление заявки (с аутентификацией)
Уведомление об изменении статуса заявки.
{
"type": "OrderUpdate",
"order_id": 12345,
"client_order_id": "my-order-1",
"status": "filled",
"filled_size": "10.0",
"remaining_size": "0",
"avg_fill_price": "0.0523"
}
Обновление рынка
Изменения в листинге рынков.
Рынок создан:
{
"type": "MarketUpdate",
"action": "Created",
"symbol": "BTC-20260131-100000-C",
"strike": "100000",
"is_call": true,
"underlying": "BTC",
"expiry": 1738281600,
"timestamp": 1737331200000
}
Срок рынка истёк:
{
"type": "MarketUpdate",
"action": "Expired",
"symbol": "BTC-20260131-100000-C",
"strike": "100000",
"is_call": true,
"underlying": "BTC",
"expiry": 1738281600,
"timestamp": 1738281600000
}
Позиция истекла (с аутентификацией)
Уведомление, когда ваша позиция рассчитывается на экспирации.
{
"type": "PositionExpired",
"wallet_address": "0x1234...abcd",
"symbol": "BTC-20260131-100000-C",
"position_size": "10.0",
"settlement_price": "105000",
"settlement_value": "500.0",
"timestamp": 1738281600000
}
Изменение состояния ликвидации (с аутентификацией)
Изменение состояния ликвидации вашего счёта.
{
"type": "LiquidationStateChange",
"wallet_address": "0x1234...abcd",
"previous_state": "Normal",
"new_state": "Warning",
"equity": "10000.0",
"mm_required": "9500.0",
"shortfall": "0",
"auction_id": null,
"timestamp": 1737331200000
}
| Состояние | Описание |
|---|---|
Normal | Счёт в нормальном состоянии |
Warning | Приближение к маржин-коллу |
Liquidating | Активен аукцион ликвидации |
Обновление индексной цены
Пакетные спотовые/индексные цены для всех базовых активов.
{
"type": "IndexPriceUpdate",
"prices": [
{"underlying": "BTC", "price": "97250.50"},
{"underlying": "ETH", "price": "3200.00"},
{"underlying": "HYPE", "price": "28.50"}
],
"timestamp": 1737331200000
}
| Поле | Тип | Описание |
|---|---|---|
prices | array | Массив записей {underlying, price} для каждого отслеживаемого базового актива |
prices[].underlying | string | Символ базового актива (например, "BTC", "ETH") |
prices[].price | string | Текущая спотовая/индексная цена в USD |
timestamp | integer | Unix-таймстамп (миллисекунды) |
Индикативные рыночные данные
Поток от провайдеров котировок из белого списка с агрегированными лучшими бид/аск от зарегистрированных провайдеров котировок. Этот канал пока недоступен в общем доступе. Используйте REST рыночные данные и аутентифицированные каналы заявок/исполнений/портфеля, если Hypercall не включил для вашей интеграции стриминг от провайдеров котировок.
{
"type": "IndicativeMarketData",
"instrument": "BTC-20260131-100000-C",
"best_bid": "0.0520",
"best_ask": "0.0530",
"indicative_bid_size": "50.0",
"indicative_ask_size": "25.0",
"num_providers": 3,
"timestamp": 1737331200000
}
| Поле | Тип | Описание |
|---|---|---|
instrument | string | Символ опциона |
best_bid | string | Опциональная лучшая агрегированная цена бид |
best_ask | string | Опциональная лучшая агрегированная цена аск |
bid_iv | number | Опциональная подразумеваемая волатильность лучшего бида |
ask_iv | number | Опциональная подразумеваемая волатильность лучшего аска |
indicative_bid_size | string | Опциональный суммарный объём бид по всем провайдерам |
indicative_ask_size | string | Опциональный суммарный объём аск по всем провайдерам |
num_providers | integer | Число активных провайдеров котировок |
timestamp | integer | Unix-таймстамп (миллисекунды) |
Изменение ранга в соревновании (с аутентификацией)
Уведомление, когда ваш ранг меняется в активном соревновании.
{
"type": "CompetitionRankChange",
"wallet_address": "0x1234...abcd",
"competition_id": 7,
"from_rank": 15,
"to_rank": 12,
"delta_places": 3,
"pnl": "420.25",
"timestamp": 1737331200000
}
Обновление отрыва в соревновании (с аутентификацией)
Расстояние до следующего ранга над вами.
{
"type": "CompetitionGapUpdate",
"wallet_address": "0x1234...abcd",
"competition_id": 7,
"rank": 12,
"next_rank": 11,
"gap_metric_value": "50.00",
"timestamp": 1737331200000
}
Итоговое место в соревновании (с аутентификацией)
Отправляется, когда соревнование завершается, с вашими итоговыми результатами.
{
"type": "CompetitionFinalStanding",
"wallet_address": "0x1234...abcd",
"competition_id": 7,
"rank": 12,
"pnl": "420.25",
"volume": "25000",
"efficiency": "0.01681",
"medal": null,
"timestamp": 1737331200000
}
Котировки RFQ (с аутентификацией)
Котировки, полученные в ответ на вашу отправку RFQ.
{
"type": "RfqQuotes",
"rfq_id": "550e8400-e29b-41d4-a716-446655440000",
"quotes": [
{
"quote_id": "660e8400-e29b-41d4-a716-446655440001",
"net_premium": "52.30",
"expires_at": 1737331225000
}
],
"status": "quoted",
"taker_wallet": "0x1234...abcd"
}
Обновление статуса RFQ (с аутентификацией)
Изменение статуса отправленного вами RFQ.
{
"type": "RfqStatusUpdate",
"rfq_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "executed",
"taker_wallet": "0x1234...abcd"
}
Ошибка
Сообщение об ошибке сервера.
{
"type": "Error",
"message": "Invalid channel: foobar"
}
Аутентификация
Аутентифицированные каналы требуют сообщения с идентификацией кошелька после подключения:
{"type": "Authenticate", "wallet": "0x1234567890abcdef..."}
Сообщения на аутентифицированных каналах фильтруются так, чтобы показывать только данные вашего кошелька. Для WebSocket-подключений подпись не требуется.
Пример: Python-клиент
import asyncio
import websockets
import json
async def main():
uri = "wss://api.hypercall.xyz/ws"
async with websockets.connect(uri) as ws:
# Identify the wallet before subscribing to authenticated channels.
await ws.send(json.dumps({
"type": "Authenticate",
"wallet": "0xYourWallet"
}))
# Subscribe to orderbook
await ws.send(json.dumps({
"type": "Subscribe",
"channel": "orderbook"
}))
# Subscribe to fills for BTC only
await ws.send(json.dumps({
"type": "Subscribe",
"channel": "fills",
"symbols": ["BTC"]
}))
# Listen for messages
async for message in ws:
data = json.loads(message)
print(f"Received: {data['type']}")
asyncio.run(main())
Пример: TypeScript-клиент
const ws = new WebSocket("wss://api.hypercall.xyz/ws");
ws.onopen = () => {
ws.send(JSON.stringify({ type: "Authenticate", wallet: "0xYourWallet" }));
// Subscribe to channels
ws.send(JSON.stringify({ type: "Subscribe", channel: "orderbook" }));
// Subscribe to order updates filtered to BTC
ws.send(JSON.stringify({
type: "Subscribe",
channel: "order_updates",
symbols: ["BTC"],
}));
};
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
console.log(`Received: ${msg.type}`);
if (msg.type === "OrderbookUpdate") {
console.log(`${msg.symbol}: ${msg.bids.length} bids, ${msg.asks.length} asks`);
}
};