Polymarket Bot Tutorial · Глава 8 из 32
Polymarket CLOB API для bots: REST endpoints для order book snapshots, WebSocket subscriptions для real-time updates, parsing bids/asks, computing mid-price and depth, code samples.
Что охватывает эта глава
CLOB API - это место, где orders подписываются, отправляются, сопоставляются и где живет order book. У Polymarket есть два поколения SDK - устаревший v1 и текущий v2. Эта глава охватывает только v2; v1 не должен появляться ни в одном bot, который вы выпускаете в 2026 году. Мы разберем REST snapshot path, WebSocket update channel, детали parsing, на которых спотыкаются новички, и reconnect logic, без которой long-running bot за несколько часов рассинхронизируется.
- CLOB v1 vs v2 (use v2)
- Order book REST snapshot
- WebSocket subscriptions: market and user channels
- Parsing bids/asks/depth
- Computing mid-price and best-bid/ask
- Maker fees, taker fees, rebates
- Code: connect WS and process price-change events
- Reconnect and gap-handling
CLOB v1 vs v2 (use v2)
У Polymarket поддерживаются два поколения SDK. v1 (@polymarket/clob-client в npm, py-clob-client <0.30) устарел и не поддерживает несколько order types, добавленных в 2024 году. v2 (@polymarket/clob-client-v2 v1.0.6 в Node, py-clob-client 0.34.6+ в Python) - текущий standard.
Три конкретных отличия. v2 поддерживает флаг negRisk для multi-outcome markets - он требуется с момента запуска NegRisk exchange в конце 2024 года. v2 поставляется с TypeScript types для WebSocket message shapes; v1 возвращает any. v2 нативно обрабатывает flow подписи Gnosis Safe, появившийся в августе 2025 года; v1 требует custom signing glue.
Остальная часть этой главы написана с расчетом на v2. Если вы видите v1 code в старом tutorial, считайте его сломанным, пока не доказано обратное - особенно placement orders на NegRisk markets: под v1 они могут silently route to the wrong exchange contract.
Order book REST snapshot
REST snapshot endpoint возвращает полный book для одного token на определенный момент времени.
GET https://clob.polymarket.com/book?token_id=<ERC1155_TOKEN_ID>
Форма ответа:
{
"market": "0x...",
"asset_id": "5413...",
"timestamp": "1715600000000",
"hash": "0x...",
"bids": [{"price":"0.45","size":"120"}, {"price":"0.44","size":"380"}, ...],
"asks": [{"price":"0.47","size":"85"}, {"price":"0.48","size":"210"}, ...]
}
Prices - это strings с 2-3 знаками после запятой; sizes - strings, представляющие количество shares (не dollars). Bids отсортированы по убыванию, asks - по возрастанию. hash - это marker дедупликации: повторные запросы к неизменившемуся book возвращают тот же hash, и ваш bot может пропустить обработку.
REST snapshot - правильный выбор для разовых lookup (price check при принятии решения о входе). Для непрерывного monitoring используйте WebSocket channel ниже.
WebSocket subscriptions: market and user channels
Имеют значение два WebSocket channel.
Market channel: wss://ws-subscriptions-clob.polymarket.com/ws/market. Подписывайтесь на один или несколько tokens; получайте order-book updates по мере их появления.
{"type":"Market","markets":["0xCondId1","0xCondId2"]}
Messages приходят при каждом изменении. Типы включают book (full snapshot), price_change (delta), tick_size_change (rare) и last_trade_price (most recent fill).
User channel: wss://ws-subscriptions-clob.polymarket.com/ws/user. Authenticated; получайте события по своим orders - fills, partial fills, cancellations.
{"type":"User","auth":{"apiKey":"...","secret":"...","passphrase":"..."}}
User channel - самый чистый способ определить fill. Polling orders REST endpoint стоит дороже и может пропустить state changes между опросами; WebSocket отправляет event в момент, когда matcher его подтверждает.
Parsing bids/asks/depth
Order book - это список price levels с агрегированным size. Два соглашения parsing, которые важно не перепутать.
Order direction: bids - это buy orders (кто-то хочет BUY по этой цене). Когда YOUR bot продает, он hits a bid. Когда ваш bot покупает, он lifts an ask. Polymarket UI показывает то же направление; на некоторых других exchanges оно inverted.
Sorting: bids приходят отсортированными по убыванию (best bid first). asks приходят отсортированными по возрастанию (best ask first). Best bid - это bids[0]; best ask - asks[0]. Осторожно: public WebSocket иногда отправляет partial book updates, которые не pre-sorted - после любого merge всегда re-sort defensively.
Depth на уровне - это dollar value, который можно наторговать: price * size. Depth по top-5 levels - распространенный liquidity metric: sum(b.price * b.size for b in bids[:5]). Если top-5 depth меньше $100, book illiquid, и большинство strategy assumptions ломаются.
Computing mid-price and best-bid/ask
Три derived price points, которые нужны вашему bot.
- Best bid / best ask:
bids[0].priceиasks[0].price. Prices, по которым вы реально можете торговать, на одну share. - Mid-price:
(best_bid + best_ask) / 2. Математический центр spread. Полезен для valuation; торговать по mid вы не будете. - VWAP price for size N: проходите по book, пока cumulative size не достигнет N, и возвращайте size-weighted average price. Реальная стоимость BUY N shares прямо сейчас с учетом sweep в deeper levels.
Edge case: пустая сторона bid или ask (никто не продает или никто не покупает) означает, что book one-sided. В market structure Polymarket это бывает в resolved или near-resolved markets, где одна сторона стоит на 0.999, а на стороне проигравшего никто не предлагает liquidity. Считайте best-bid = 0 или best-ask = 1 сигналами "do not trade".
Maker fees, taker fees, rebates
Большую часть своей истории Polymarket вообще не брал trading fees. В 2026 году это изменилось: fees ввели в начале года на 15-минутных crypto markets, 30 марта 2026 распространили на Sports, а затем выкатили на большинство категорий. Любой туториал, который всё ещё утверждает, что Polymarket без fees, устарел - и тот, кто упустит это в высокочастотной strategy, будет тихо съеден. Вот как модель работает на самом деле, по состоянию на середину 2026 года.
Сначала о двух сторонах каждой сделки. Maker - это тот, кто кладёт в book покоящийся limit order, который остаётся ждать; taker - это тот, кто отправляет order, исполняющийся сразу против уже имеющейся ликвидности. Makers по-прежнему платят ноль fee и вдобавок получают rebate; fee платят только takers.
Taker fee - это не фиксированный процент. Она следует кривой, которая зависит и от размера order, и от цены:
fee = shares × feeRate × price × (1 - price)
Член price × (1 - price) максимален при цене 0.50 (настоящий рынок «орёл или решка») и уменьшается к 0 или 1. Иными словами, на самых неопределённых markets вы платите самую высокую fee, а на почти решённых - почти ничего. feeRate задаётся по категориям:
- Crypto: feeRate 0.07 (самая высокая, пик около 1.8% эффективных), maker rebate 20%.
- Sports: feeRate 0.03 (пик около 0.75%), maker rebate 25%.
- Finance, Politics, Tech, Mentions: feeRate 0.04, maker rebate 25%.
- Economics, Culture, Weather, общее: feeRate 0.05, maker rebate 25%.
- Geopolitics и крупные мировые события: 0, по-прежнему без fee.
Пример расчёта. Допустим, ваш bot берёт 100 shares crypto market по цене 0.50. Fee составит 100 × 0.07 × 0.50 × (1 - 0.50) = 100 × 0.07 × 0.25 = $1.75. Если вы возьмёте те же 100 shares по 0.90, fee упадёт до 100 × 0.07 × 0.90 × 0.10 = $0.63, потому что цена далеко от неопределённой середины. Для bot вывод однозначен: брать ликвидность на волатильных, почти сбалансированных crypto и sports markets стоит максимума fee - значит именно там выгоднее всего котироваться как maker и забирать rebate, а не платить fee.
Вдобавок к явной fee вы каждый раз платите bid-ask spread, когда пересекаете его. Spread - это разрыв между лучшей ценой покупки и лучшей ценой продажи, и для strategy, которая входит и выходит как taker, этот разрыв - реальная стоимость поверх fee. Закладывайте 1-3 cents round-trip на типичных books, больше на illiquid. NegRisk markets (multi-outcome exchange) используют ту же модель fee, но сеттлятся на отдельном contract, поэтому их rewards accrue отдельно. Chapter 19 рассматривает liquidity-rewards farming, где забор maker rebates - это сама strategy, а не просто побочный эффект.
Code: connect WS and process price-change events
Минимальный Node example: connect, subscribe, логировать каждое price-change event для одного token.
import WebSocket from "ws";
const ws = new WebSocket("wss://ws-subscriptions-clob.polymarket.com/ws/market");
ws.on("open", () => {
ws.send(JSON.stringify({ type: "Market", markets: ["<CONDITION_ID>"] }));
});
ws.on("message", (data) => {
const msg = JSON.parse(data.toString());
if (msg.event_type === "price_change") {
console.log("price_change", msg.asset_id, msg.changes);
} else if (msg.event_type === "book") {
console.log("book snapshot", msg.bids?.[0], msg.asks?.[0]);
}
});
ws.on("close", () => console.log("closed"));
ws.on("error", (e) => console.error("err", e.message));
Комфортно подписывайтесь до ~30 tokens на одно WebSocket connection. Больше - распределяйте по нескольким connections: сервер иногда silently drops large subscriptions without erroring, из-за чего появляются stale book reads.
Reconnect and gap-handling
Long-running WebSocket connection будет обрываться. Cloudflare циклически меняет connections каждые несколько часов; сети моргают; Polymarket иногда выкатывает deployments. Планируйте это заранее.
Reconnect strategy: при close или error подождите min(2^attempt, 30) секунд с jitter, затем выполните re-subscribe. Сбрасывайте счетчик попыток при первом успешном message после reconnect.
Gap handling важнее, чем speed reconnect. Пока WebSocket был disconnected, book мог измениться. При каждом reconnect заново запрашивайте REST snapshot каждого subscribed token и reconcile: любые open positions, у которых book существенно сдвинулся, требуют повторной проверки state, exits, возможно, должны сработать, а alarms могут быть stale. Случай "я пропустил 30 секунд book updates" - тихий убийца long-running bots: они продолжают работать на stale state и выставляют orders по ценам, которых уже не существует.
Defensive pattern: snapshot каждого subscribed book раз в минуту независимо от состояния WebSocket и рассматривайте WS как fast-path optimization поверх snapshot poll.










