WebSocket в Polymarket: данные в реальном времени без обрывов
Опрос стакана — самый частый способ впустую сжечь бюджет лимита запросов. Вот четыре потока, параметры подписки, которые никто не документирует, и сбой, при котором ваш бот получает тишину и принимает её за затишье.
Если ваш бот опрашивает 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 | Что передаёт | Аутентификация |
|---|---|---|---|
| Market | wss://ws-subscriptions-clob.polymarket.com/ws/market | Публичные обновления стакана, цен и жизненного цикла | Не нужна |
| User | wss://ws-subscriptions-clob.polymarket.com/ws/user | Обновления ваших ордеров и сделок | Да |
| RTDS (устаревший) | wss://ws-live-data.polymarket.com | Комментарии — референсные цены переехали на PolyBolt | Не нужна |
| PolyBolt | wss://ws-live-v2.polymarket.com/ws | Референсные цены криптовалют, акций и TWAP | Да |
| Sports | wss://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-дневного бесплатного пробного периода.
Читать дальше
Polymarket заменил RTDS: миграция на PolyBolt
15 сентября Polymarket тихо перевёл RTDS в разряд устаревших для референсных цен. Если ваш бот получает поток спот-цен, одновременно изменились шесть вещей — включая источник цен и формат символов, — и большинство из них ломается молча, а не громко.
Как создать торгового бота для Polymarket
Большинство руководств по ботам для Polymarket построены на SDK, которых больше не существует. Здесь — актуальный путь от начала до конца: что устанавливать в 2026 году, как на самом деле работает аутентификация, где пробелы в данных и какие сбои убивают ботов в первую неделю.
Лимиты запросов API Polymarket: полный справочник
Polymarket публикует свои лимиты запросов на нескольких страницах, в двух совершенно разных системах и с 10-секундным окном, которого почти никто не ожидает. Здесь всё это собрано в одном наборе таблиц — плюс что на самом деле происходит, когда вы превышаете лимит.
Python SDK для Polymarket в 2026 году (и уход с py-clob-client)
Если ваш код всё ещё импортирует ClobClient из py_clob_client, он построен на устаревшем пакете — как и большая часть кода для Polymarket, который напишет вам ИИ-ассистент. Вот актуальный SDK и как на него перейти.