Все статьи
Справочник APIWebSocketsAPI PolymarketТорговые боты

WebSocket в Polymarket: данные в реальном времени без обрывов

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

10 мин чтенияОбновлено 24 сентября 2026 г.

Если ваш бот опрашивает GET /book по таймеру, эта страница сэкономит вам большую часть бюджета лимита запросов и заметную долю задержки. Polymarket присылает обновления стакана через WebSocket, и подписка стоит одного соединения вместо тысяч запросов.

Коротко

Подключитесь к wss://ws-subscriptions-clob.polymarket.com/ws/market, отправьте подписку со своими assets_ids, а затем отправляйте PING каждые 10 секунд, иначе соединение разорвут. Установите custom_feature_enabled: true, если вам нужны события best_bid_ask, new_market или market_resolved, — по умолчанию они выключены, и их отсутствие выглядит точь-в-точь как спокойный рынок.

Четыре потока

ПотокURLЧто передаётАутентификация
Marketwss://ws-subscriptions-clob.polymarket.com/ws/marketПубличные обновления стакана, цен и жизненного циклаНе нужна
Userwss://ws-subscriptions-clob.polymarket.com/ws/userОбновления ваших ордеров и сделокДа
RTDS (устаревший)wss://ws-live-data.polymarket.comКомментарии — референсные цены переехали на PolyBoltНе нужна
PolyBoltwss://ws-live-v2.polymarket.com/wsРеференсные цены криптовалют, акций и TWAPДа
Sportswss://sports-api.polymarket.com/wsТекущий статус и счёт матчейНе нужна

Канал User — тот, о котором забывают

Если ваш бот опрашивает /data/orders, чтобы узнать, исполнился ли ордер, вы тратите бюджет лимита на информацию, которую канал User присылает в момент события — и с меньшей задержкой. Любой бот, выставляющий ордера, должен быть подписан на этот поток.

Референсные цены переехали 15 сентября 2026 года

Polymarket запустил PolyBolt и перевёл RTDS в устаревшие для референсных цен. crypto_prices, crypto_prices_chainlink и equity_prices теперь находятся на wss://ws-live-v2.polymarket.com/ws, источник сменился с Binance/Chainlink на Pyth, символы теперь в нижнем регистре — btcusd, а не btcusdt или btc/usd, и теперь требуется аутентификация. Полное соответствие — в руководстве по миграции на PolyBolt. Всё остальное на этой странице — рыночный и пользовательский каналы CLOB — не затронуто.

Подписка на рыночный канал

Подключитесь и отправьте одно JSON-сообщение подписки. assets_ids принимает ID токенов — те же, что вы получаете из метаданных рынка, — а type должен быть "market".

{
  "assets_ids": ["65818619657568813474341868652308942079804919287380422192892211131408793125422"],
  "type": "market",
  "initial_dump": true,
  "level": 2,
  "custom_feature_enabled": true
}
ПолеОбязательноеПо умолчаниюЧто делает
assets_idsДа—ID токенов для подписки
typeДа—Должно быть "market"
initial_dumpНетtrueОтправить полный снимок стакана при подписке
levelНет2Уровень подписки — 1, 2 или 3
custom_feature_enabledНетfalseВключает события best_bid_ask, new_market и market_resolved

custom_feature_enabled по умолчанию false

Это подвох подписки, который стоит людям полдня. Три типа событий — best_bid_ask, new_market и market_resolved — не доставляются, пока вы их явно не включите. Ни ошибки, ни предупреждения; вы просто никогда их не получаете. Если вы ждёте событие market_resolved, которое так и не приходит, причина в этом.

Оставляйте initial_dump: true, если у вас нет конкретной причины поступить иначе. Без него вы начинаете без состояния стакана и вынуждены ждать инкрементальных обновлений, чтобы его восстановить, — то есть бот начинает работу с неполной картиной.

Типы сообщений

Каждое сообщение содержит event_type. Обрабатывайте все варианты — нераспознанный тип нужно логировать, а не молча отбрасывать, потому что именно так вы узнаете, что протокол изменился.

event_typeЗначениеНужно включать
bookПолный снимок стаканаНет
price_changeИнкрементальное обновление цены/уровняНет
last_trade_priceСовершена сделкаНет
tick_size_changeИзменилась сетка тиков рынкаНет
best_bid_askОбновление лучших ценДа
new_marketСоздан рынокДа
market_resolvedРынок рассчитанДа

Не игнорируйте tick_size_change

Размер тика свой у каждого рынка и может уменьшаться по мере его взросления. Если вы закешировали размер тика при запуске, а рынок его изменил, каждый следующий лимитный ордер будет отклонён из-за нарушения минимального размера тика — это 400, который выглядит как ошибка в вашей логике цен. Обрабатывайте событие и обновляйте кеш.

Как не терять соединение: PING каждые 10 секунд

Это требование ловит всех. Клиент должен отправлять PING каждые 10 секунд; сервер отвечает PONG. Пропустите heartbeat — и соединение разорвут.

Обратите внимание на направление: это клиентский heartbeat. Вы не ждёте, пока сервер вас пропингует. Встроенный keepalive библиотеки WebSocket с другим интервалом его не заменяет — отправляйте прикладной PING сами.

import asyncio, json, time
import websockets

URL = "wss://ws-subscriptions-clob.polymarket.com/ws/market"
STALE_AFTER = 30          # seconds without any message = treat as dead


async def stream(token_ids, on_event):
    backoff = 1
    while True:
        try:
            async with websockets.connect(URL) as ws:
                await ws.send(json.dumps({
                    "assets_ids": token_ids,
                    "type": "market",
                    "initial_dump": True,
                    "custom_feature_enabled": True,   # or lose 3 event types
                }))
                backoff = 1                            # reconnected cleanly
                last_msg = time.monotonic()

                async def heartbeat():
                    while True:
                        await asyncio.sleep(10)        # required cadence
                        await ws.send("PING")

                hb = asyncio.create_task(heartbeat())
                try:
                    while True:
                        # Silence is a failure mode, not a quiet market.
                        raw = await asyncio.wait_for(ws.recv(), timeout=STALE_AFTER)
                        last_msg = time.monotonic()
                        if raw == "PONG":
                            continue
                        for msg in json.loads(raw) if raw.startswith("[") else [json.loads(raw)]:
                            on_event(msg)
                finally:
                    hb.cancel()

        except Exception as exc:
            # Reconnect with capped exponential backoff + jitter.
            wait = min(backoff, 30) * (0.5 + 0.5 * asyncio.get_event_loop().time() % 1)
            print(f"ws down ({exc}); reconnecting in {wait:.1f}s")
            await asyncio.sleep(wait)
            backoff = min(backoff * 2, 30)

Используйте SDK, если нет причин этого не делать

polymarket-client (Python) и @polymarket/client (TypeScript) реализуют heartbeat, переподключение и кадрирование сообщений и объединяют несколько потоков в единый поток типизированных событий. Писать всё вручную стоит, только если нужен контроль, которого SDK не даёт. Учтите, что подписки есть только в асинхронных клиентах — синхронные не реализуют subscribe().

Изменение подписок без переподключения

Пятиминутные рынки постоянно истекают, так что боту, который отслеживает «текущие рынки BTC 5m», нужно непрерывно менять набор подписок. Разрывать соединение для этого не нужно — рыночный канал поддерживает подписку и отписку на живом соединении.

Это важнее, чем кажется. Переподключение при каждой смене рынка означает, что вы снова и снова платите за установку соединения, заново запрашиваете начальные снимки и создаёте окно, в котором не получаете ничего ровно в тот момент, когда открывается новый рынок.

Сбой, который стоит денег

Оборвавшийся WebSocket не выбрасывает исключение. Он перестаёт доставлять сообщения. Для наивного бота «нет обновлений» неотличимо от «рынок не движется» — поэтому бот с открытой позицией будет бесконечно сидеть на устаревшей цене в полной уверенности, что ничего не изменилось.

  • Отслеживайте возраст последнего сообщения. Если на активном рынке ничего не пришло за ~30 секунд, вы отключены, а не в затишье. Таймаут asyncio.wait_for выше делает ровно эту работу.
  • Считайте устаревание ошибкой. Перестаньте котировать, закройте позицию, если этого требуют ваши правила риска, и переподключитесь — не продолжайте торговать по замёрзшему стакану.
  • Делайте отступ при переподключении, со случайным разбросом. Плотный цикл переподключений во время сбоя — верный способ получить ограничение по лимиту запросов вдобавок к отключению, а парк ботов, переподключающихся синхронно, делает всё ещё хуже.
  • Синхронизируйте состояние после переподключения. Пока вас не было, вы пропустили обновления. Принимайте свежий initial_dump за истину и отбрасывайте прежний стакан, а не вливайте инкрементальные обновления в устаревший снимок.
  • Сверяйте позиции после любого перерыва. Если вы были отключены от канала User, вы могли пропустить исполнение. Перечитайте позиции, прежде чем действовать на основе предполагаемого состояния.

WebSocket и лимиты запросов

Частый вопрос, и ответ на него — смысл всего упражнения: подписка не расходует бюджет лимита на запросы так, как опрос. Задокументированные лимиты Polymarket — это HTTP-лимиты на эндпоинты, а потоковая передача почти полностью их обходит.

ПодходСтоимость отслеживания 20 рынков в течение часа
Опрос /book каждую секунду72,000 запросов при бюджете 1,500 запр. / 10 с
Опрос /books (пакетом) каждую секунду3,600 запросов при бюджете 500 запр. / 10 с
Подписка WebSocketОдно соединение плюс 360 heartbeat
К тому же опрос даёт худший ответ: вы видите стакан на момент последнего опроса, а не последнего изменения.

HTTP-вызовы всё равно останутся — поиск рынков, размеры тиков, выставление ордеров, сверка позиций, — и на них по-прежнему действуют задокументированные лимиты запросов. Потоковая передача лишь убирает самый крупный и наименее полезный источник запросов.

Задержка — это замаскированное ограничение скорости

Поскольку слой Cloudflare ограничивает скорость задержкой, а не отказом, бот, превысивший лимит опросом, не видит 429 — он видит, как тихо растёт время ответа. Если ваши данные стакана приходят уже не за 40ms, а за 3 секунды, это не сбой; это ваш собственный темп запросов.

Чего потоки не дадут

Одну границу стоит обозначить прямо, потому что именно это пробуют следующим. WebSocket даёт стакан с момента подписки. Стакан прошлого вторника он дать не может.

Polymarket не хранит исторический стакан. После разрешения рынка его стакан возвращает 404, а prices-history хранит лишь около одной точки в минуту. Если вы хотите проверить стратегию на том, как рынки торговались на самом деле, нужны снимки, которые кто-то записал в то время. PolyTest делает это для крипторынков Up/Down — с 8 уровнями глубины на сторону и сохранёнными разрешившимися рынками. Или запустите собственный сборщик уже сегодня, смиритесь с тем, что начинаете с нулевой историей, и ждите.

Часто задаваемые вопросы

Какой URL у WebSocket Polymarket?
Публичный рыночный канал — wss://ws-subscriptions-clob.polymarket.com/ws/market, аутентифицированный пользовательский канал — wss://ws-subscriptions-clob.polymarket.com/ws/user. Есть и два других потока: RTDS на wss://ws-live-data.polymarket.com для референсных цен, комментариев и торговой активности и спортивный поток на wss://sports-api.polymarket.com/ws.
Есть ли лимит запросов у WebSocket Polymarket?
Подписка WebSocket не расходует бюджет лимита на запросы так, как HTTP-опрос, — задокументированные лимиты Polymarket являются HTTP-лимитами на эндпоинты. Потоковая передача — как раз рекомендуемый способ их избежать. Обязательное требование к соединению — heartbeat: отправляйте PING каждые 10 секунд, иначе соединение разорвут. HTTP-вызовы, которые вы всё равно делаете для поиска рынков, размеров тиков и выставления ордеров, подчиняются обычным лимитам.
Как поддерживать соединение WebSocket с Polymarket?
Отправляйте сообщение PING каждые 10 секунд; сервер отвечает PONG. Это клиентский heartbeat прикладного уровня, поэтому встроенный keepalive библиотеки WebSocket с другим интервалом его не заменяет. Также отслеживайте возраст последнего полученного сообщения — оборвавшееся соединение перестаёт доставлять данные без ошибки, так что считайте примерно 30 секунд тишины на активном рынке разрывом, а не спокойным рынком.
Почему я не получаю события best_bid_ask или market_resolved?
Эти события по умолчанию выключены. Установите custom_feature_enabled в true в сообщении подписки, чтобы включить best_bid_ask, new_market и market_resolved. Если флага нет, не будет ни ошибки, ни предупреждения — события просто никогда не придут, и это легко принять за неактивный рынок.
Боту для Polymarket использовать WebSocket или опрашивать REST API?
Используйте WebSocket для текущих данных стакана и цен. Отслеживание 20 рынков опросом /book раз в секунду стоит 72,000 запросов в час при бюджете 1,500 запросов / 10 с, а подписка — одного соединения плюс heartbeat; к тому же опрос даёт более устаревший ответ, потому что вы видите стакан на момент последнего опроса, а не последнего изменения. REST оставьте для поиска рынков, размеров тиков, выставления ордеров и сверки позиций.
Можно ли поменять набор рынков в подписке без переподключения?
Да. Рыночный канал поддерживает подписку на активы и отписку от них на живом соединении, что важно для короткоживущих рынков вроде 5-минутных контрактов Up/Down, которые постоянно сменяются. Переподключение при каждой смене раз за разом стоит установки соединения и оставляет пробел в покрытии ровно в тот момент, когда открывается новый рынок.

Получите исторические данные, которые Polymarket не хранит

PolyTest записывает крипторынки Polymarket Up/Down в процессе их работы — 240M+ снимков с 8 уровнями глубины стакана, метками времени с точностью до долей секунды и сохранёнными разрешившимися рынками. Начните с 3-дневного бесплатного пробного периода.

Читать дальше