Кратко
Polymarket предоставляет три публичных API (API — это способ, с помощью которого программы могут автоматически читать данные или размещать сделки): CLOB (торговля), Gamma (поиск рынков) и Data (аналитика). Официальный Python SDK — py-clob-client 0.34.6. Для аутентификации используются API key + ECDSA signature, а ордера подписываются через EIP-712 с помощью прокси-кошелька в Polygon. Лимиты запросов ограничивают вас примерно 60 ордерами в минуту на один ключ. Главная проблема, с которой сталкиваются новые разработчики, — сопоставление condition_id → token_id между Gamma и CLOB. Сначала решите её, и дальше всё станет значительно проще. На Polymarket пользователи получают около 40 млн долларов в месяц в виде вознаграждений за ликвидность и спреда, забираемого ботами; почти всё это приходится на пользователей API.
Часть 1: три API
Polymarket чётко разделяет задачи между тремя отдельными сервисами. Если использовать подходящий API для каждой задачи, бот будет быстрее, проще и не будет выходить за лимиты запросов.
| API | Base URL | Purpose | Auth Required |
|---|---|---|---|
| CLOB API | clob.polymarket.com | Размещение, отмена и отслеживание ордеров. Чтение стаканов ордеров. Запрос позиций. | Да (для торговли) |
| Gamma API | gamma-api.polymarket.com | Просмотр рынков, получение метаданных, изображений, цен исходов, объёма, даты истечения и тегов. | Нет (публичный) |
| Data API | data-api.polymarket.com | Исторические сделки, снимки позиций, пользовательская аналитика, данные таблицы лидеров. | Нет (публичный) |
Типичный цикл работы бота использует Gamma для поиска рынков, CLOB — для получения стаканов ордеров и размещения сделок, а Data — для офлайн-бэктестинга эффективности стратегии. Условно Gamma — это «каталог», CLOB — «биржа», а Data — «хранилище».
Часть 2: аутентификация и модель прокси-кошелька
Polymarket не подписывает сделки приватным ключом вашего основного кошелька. Вместо этого используется прокси-кошелёк в стиле Gnosis Safe. Ваш основной кошелёк авторизует прокси, а прокси выполняет все сделки в Polygon. API-бот взаимодействует именно с этим прокси.
Что понадобится
- API key — сгенерируйте в Polymarket Settings → Developer
- Private key — ключ вашего торгового кошелька (НЕ seed-фраза основного MetaMask)
- Funder address — адрес вашего прокси-кошелька (показан в Settings → Wallet)
- Chain ID —
137(Polygon mainnet) - Signature type —
1(POLY_PROXY, стандарт для розничных пользователей)
Часть 3: установка py-clob-client
Официальный Python SDK — самый быстрый способ пройти путь от нуля до первого ордера. Мы будем использовать версию 0.34.6 — актуальный релиз на PyPI (февраль 2026) и версию, на которой работает почти каждый боевой бот.
# Create a virtual environment first
python3 -m venv venv
source venv/bin/activate # macOS/Linux
venv\Scripts\activate # Windows
# Install the SDK
pip install py-clob-client==0.34.6 requests websocket-client python-dotenvБазовая настройка клиента
import os
from dotenv import load_dotenv
from py_clob_client.client import ClobClient
from py_clob_client.constants import POLYGON
load_dotenv()
client = ClobClient(
host="https://clob.polymarket.com",
key=os.environ["POLY_PRIVATE_KEY"],
chain_id=POLYGON, # 137
signature_type=1, # POLY_PROXY
funder=os.environ["POLY_FUNDER"],
)
# One-time: derive and cache API credentials
client.set_api_creds(client.create_or_derive_api_creds())Вызов create_or_derive_api_creds() подписывает сообщение вашим приватным ключом. В обмен он получает API key, secret и passphrase. После первого запуска сохраните их в .env, чтобы не обращаться к derive endpoint при каждом старте.
Часть 4: Поиск рынков через Gamma
Прежде чем торговать, нужно найти рынки, на которых есть смысл работать. Gamma возвращает JSON со всем, что показывает интерфейс Polymarket: вопросом, исходами, ценами, объёмом за 24 часа, датой истечения, тегами и изображениями.
import requests
resp = requests.get(
"https://gamma-api.polymarket.com/markets",
params={
"active": "true",
"closed": "false",
"tag_slug": "politics",
"limit": 20,
"order": "volume24hr",
"ascending": "false",
},
timeout=10,
)
resp.raise_for_status()
markets = resp.json()
for m in markets:
print(f"{m['slug']:50} Yes ${float(m['outcomePrices'][0]):.3f} Vol24h ${m.get('volume24hr', 0):,.0f}")Полезные параметры запросов Gamma
| Parameter | What it does |
|---|---|
tag_slug | Фильтрация по категории (политика, спорт, криптовалюты, культура и т. д.) |
active=true | Только рынки, которые сейчас принимают сделки |
closed=false | Скрыть завершённые рынки |
order=volume24hr | Сортировка по недавнему объёму (индикатор ликвидности) |
end_date_min | Дата в формате ISO — исключает рынки, которые разрешатся слишком скоро |
limit | До 500 на страницу (для пагинации используйте offset) |
Часть 5: Сопоставление condition_id → token_id
Это главная проблема при разработке ботов для Polymarket. Gamma возвращает condition_id (один на рынок). Для сделок в CLOB используется token_id (один на исход). Вам всегда нужны оба значения.
# Each Gamma market object contains 'clobTokenIds' - a JSON string array
import json
market = markets[0]
token_ids = json.loads(market['clobTokenIds']) # ['7410...', '1120...']
yes_token = token_ids[0] # First outcome
no_token = token_ids[1] # Second outcome
# Alternative: ask CLOB directly using condition_id
info = client.get_market(condition_id=market['conditionId'])
yes_token = info['tokens'][0]['token_id']Подводный камень с порядком исходов
Массив outcomes в Gamma и массив clobTokenIds сопоставлены по индексам. Всегда читайте метку исхода. Не считайте, что индекс 0 — это "Yes." В рынках с несколькими исходами (NegRisk, Oscars, выборы) под индексом 0 может быть "Kamala Harris" или "Taylor Swift." Порядок фиксирован, но он свой для каждого рынка.
Часть 6: Чтение книг ордеров
book = client.get_order_book(token_id=yes_token)
best_bid = float(book.bids[0].price) if book.bids else None
best_ask = float(book.asks[0].price) if book.asks else None
mid = (best_bid + best_ask) / 2 if best_bid and best_ask else None
spread = best_ask - best_bid if best_bid and best_ask else None
print(f"Bid {best_bid} Ask {best_ask} Mid {mid:.4f} Spread {spread:.4f}")Книги ордеров возвращаются в виде отсортированных массивов (bids — по убыванию, asks — по возрастанию). У каждого уровня есть price и size. Чтобы оценить проскальзывание для крупного ордера, пройдите по книге и суммируйте номинальный объём, пока не наберёте нужный размер.
Часть 6b: CLOB v2 REST Endpoints (напрямую, без SDK)
SDK оборачивает эти endpoints, но знание прямых endpoints помогает отлаживать интеграцию, использовать другой язык или собрать тонкий клиент. Базовый URL: https://clob.polymarket.com. Все приведённые ниже операции чтения публичные — аутентификация не нужна. Они были проверены на работающем сервисе в июне 2026 года.
| Endpoint | Method | What it returns |
|---|---|---|
/markets | GET | Все рынки (с пагинацией через next_cursor). Включает condition_id, tokens[], minimum_tick_size, neg_risk. |
/sampling-markets | GET | Только рынки с активным стаканом заявок — самый быстрый способ найти торгуемые token_id. |
/book?token_id= | GET | Полный стакан заявок: bids[] и asks[] с ценой и размером. |
/price?token_id=&side=buy | GET | Лучшая цена для стороны. side не зависит от регистра (buy/BUY). Возвращает {"price":"0.14"}. |
/midpoint?token_id= | GET | {"mid":"0.21"} — середина между лучшей ценой покупки и продажи. |
/spread?token_id= | GET | {"spread":"0.14"} — лучшая цена продажи минус лучшая цена покупки. |
/tick-size?token_id= | GET | {"minimum_tick_size":0.01} — минимально допустимый шаг цены для этого токена. |
/prices-history?market=&interval= | GET | Исторические ценовые точки. interval = 1m,1h,6h,1d,1w,max. |
/trades | GET | Последние сделки (для ваших — с аутентификацией; по рынку — публично). |
/order | POST | Разместить подписанный ордер (требуется аутентификация). |
/order | DELETE | Отменить один ордер по id (аутентификация). |
/orders | GET | Ваши открытые лимитные ордера в стакане (аутентификация). |
/balance-allowance?asset_type= | GET | Ваш баланс USDC и on-chain allowance (аутентификация). Проверяйте перед каждым ордером. |
Проверенные ответы напрямую из live API:
$ curl "https://clob.polymarket.com/price?token_id=7347...&side=buy"
{"price":"0.14"}
$ curl "https://clob.polymarket.com/midpoint?token_id=7347..."
{"mid":"0.21"}
$ curl "https://clob.polymarket.com/spread?token_id=7347..."
{"spread":"0.14"}
$ curl "https://clob.polymarket.com/tick-size?token_id=7347..."
{"minimum_tick_size":0.01}Заголовки аутентификации L2 (для прямого REST без SDK)
Эндпоинты чтения публичны. Чтобы размещать или отменять ордера через прямой REST, нужно подписывать каждый запрос своими API-учетными данными. SDK делает это за вас; вот что он формирует внутри:
| Header | What it carries |
|---|---|
POLY_ADDRESS | Адрес вашего кошелька для подписи |
POLY_API_KEY | API-ключ из create_or_derive_api_creds() |
POLY_PASSPHRASE | Passphrase из того же вызова derive |
POLY_TIMESTAMP | Текущее время в секундах UNIX (должно совпадать с временем сервера — см. совет по синхронизации часов) |
POLY_NONCE | Nonce для каждого запроса |
POLY_SIGNATURE | HMAC-SHA256 от timestamp + method + path + body, подписанный вашим API secret и закодированный в base64-url |
Часть 7. Размещение ордеров — покупка и продажа
Лимитный ордер (GTC — значение по умолчанию)
from py_clob_client.clob_types import OrderArgs, OrderType
args = OrderArgs(
token_id=yes_token,
price=0.45,
size=100, # Shares, not dollars. 100 shares @ $0.45 = $45 max cost.
side="BUY",
)
signed_order = client.create_order(args)
response = client.post_order(signed_order, OrderType.GTC)
print(response)Вызов create_order подписывает структурированное сообщение EIP-712 вашим приватным ключом. Затем post_order отправляет его в CLOB. Вы никогда не передаете приватные ключи по сети в открытом виде — только подписанные ордера.
Сначала выровняйте цену по шагу
Цена каждого ордера должна быть точным кратным minimum_tick_size рынка (0,01 на большинстве рынков, 0,001 на рынках с малым шагом цены). Цена не по шагу будет отклонена. Получите шаг один раз и округляйте цену к нему:
from py_clob_client.clob_types import OrderArgs, OrderType
tick = float(client.get_tick_size(token_id=yes_token)) # e.g. 0.01
def to_tick(p, tick): return round(round(p / tick) * tick, 4)
price = to_tick(0.453, tick) # -> 0.45 on a 0.01 marketПокупка
side="BUY", size указывается в shares (не в долларах). 100 shares по 0,45 доллара США стоят не больше 45 долларов США, а при выигрышном исходе выплачивают 100 долларов США. Минимальная стоимость ордера — около 1 доллара США.
buy = OrderArgs(token_id=yes_token, price=to_tick(0.45, tick), size=100, side="BUY")
resp = client.post_order(client.create_order(buy), OrderType.GTC)
print(resp) # {'success': True, 'orderID': '0x...', 'status': 'live', ...}Продажа
Продажа выполняется тем же вызовом, но с side="SELL". Продавать можно только те доли, которые уже есть у вас на балансе: попытка продать больше текущей позиции будет отклонена с ошибкой "insufficient balance". Чтобы закрыть позицию, продайте тот же token_id, который покупали.
sell = OrderArgs(token_id=yes_token, price=to_tick(0.62, tick), size=100, side="SELL")
resp = client.post_order(client.create_order(sell), OrderType.GTC)Кратко о параметрах ордера
| Field | Meaning | Notes |
|---|---|---|
token_id | Исход, которым вы торгуете | Не condition_id — см. часть 5 |
side | BUY или SELL | Для BUY нужен USDC; для SELL нужны доли |
price | 0.001-0.999 | Должна быть кратна размеру тика |
size | Количество долей | Минимальная стоимость ордера — около 1 USDC; стоимость = price × size |
| тип ордера | GTC / GTD / FOK / FAK | Передается в post_order(...) |
Собираем все вместе: ваша первая сделка через API (один запускаемый скрипт)
Ниже весь процесс от начала до конца: подключиться, найти ликвидный рынок, прочитать стакан, выровнять цену по тику, разместить небольшой реальный ордер, а затем отменить его. Укажите два своих секрета и запустите скрипт. При первом запуске начните с минимального размера — на несколько долларов.
import os, json, requests
from dotenv import load_dotenv
from py_clob_client.client import ClobClient
from py_clob_client.constants import POLYGON
from py_clob_client.clob_types import OrderArgs, OrderType
load_dotenv()
# 1) Connect (signing key in your EOA, funds in your proxy/funder)
client = ClobClient(
"https://clob.polymarket.com",
key=os.environ["POLY_PRIVATE_KEY"],
chain_id=POLYGON, # 137
signature_type=1, # 1 = email/Magic proxy, 2 = browser-wallet proxy
funder=os.environ["POLY_FUNDER"],
)
client.set_api_creds(client.create_or_derive_api_creds()) # cache these after first run
# 2) Find the most-traded open market (Gamma, no auth)
m = requests.get(
"https://gamma-api.polymarket.com/markets",
params={"active": "true", "closed": "false", "order": "volume24hr",
"ascending": "false", "limit": 1}, timeout=10,
).json()[0]
token_id = json.loads(m["clobTokenIds"])[0] # index 0 = first outcome (read the label!)
print("Trading:", m["question"])
# 3) Read the book + the tick size
tick = float(client.get_tick_size(token_id))
book = client.get_order_book(token_id)
best_ask = float(book.asks[0].price)
print("best ask", best_ask, "| tick", tick)
# 4) Place a small BUY at the ask (tiny size to start)
price = round(round(best_ask / tick) * tick, 4) # snap to tick
order = OrderArgs(token_id=token_id, price=price, size=5, side="BUY") # 5 shares
resp = client.post_order(client.create_order(order), OrderType.GTC)
print(resp) # {'success': True, 'orderID': '0x...', ...}
# 5) Cancel it (clean up)
# client.cancel(order_id=resp["orderID"])Типы ордеров
| Type | Code | Behaviour | When to use |
|---|---|---|---|
| Действует до отмены | GTC | Остается в стакане, пока не будет исполнен или пока вы его не отмените | Вариант по умолчанию. Большинство market-making- и лимитных стратегий. |
| Действует до указанной даты | GTD | Автоматически отменяется в заданный timestamp | Для событийных стратегий: "отменить за 5 минут до публикации решения ФРС" |
| Исполнить полностью или отменить | FOK | Должен быть немедленно исполнен на весь объем, иначе полностью отменяется | Арбитражные связки, где частичное исполнение ломает сделку |
| Исполнить доступное и отменить остаток | FAK | Исполняет все, что доступно по лимитной цене, и отменяет остаток | Агрессивное снятие ликвидности — работает как рыночный ордер с ограничением по цене |
Отмена
# Single order
client.cancel(order_id="0xabc...")
# Cancel all orders on a specific market
client.cancel_market_orders(market=market['conditionId'])
# Nuclear option: cancel everything
client.cancel_all()Часть 8: потоковые данные через WebSocket
Опрашивать Gamma каждую секунду неэффективно, к тому же вы быстро упретесь в лимиты запросов. WebSocket-поток передает обновления стакана и сделок в реальном времени с задержкой меньше секунды.
import json, websocket
WS_URL = "wss://ws-subscriptions-clob.polymarket.com/ws/market"
def on_open(ws):
ws.send(json.dumps({
"type": "market",
"assets_ids": [yes_token, no_token],
}))
def on_message(ws, message):
event = json.loads(message)
if event.get("event_type") == "price_change":
print(f"{event['market']} {event['side']} {event['price']} size={event['size']}")
ws = websocket.WebSocketApp(
WS_URL,
on_open=on_open,
on_message=on_message,
)
ws.run_forever(ping_interval=20)Есть два потока. Поток /market передает публичный стакан и сделки. Поток /user передает события по вашим ордерам и исполнениям (требуется аутентификация). Продакшен-боты подключаются к обоим потокам, автоматически переподключаются при разрыве соединения и считают WebSocket источником актуального состояния стакана.
Часть 9: лимиты запросов и backoff
| Endpoint class | Limit | Burst |
|---|---|---|
| Размещение ордеров (CLOB) | ~60 в минуту на API key | ~10 в секунду |
| Отмена ордеров | ~120 в минуту | ~20 в секунду |
| Чтение рыночных данных (стакан CLOB) | ~300 в минуту | выше, зависит от условий |
| Gamma API | Щедрые лимиты; учитывайте 429 | - |
| Сообщения WebSocket | Практического ограничения на входящие сообщения нет | - |
Когда вы достигаете лимита и получаете HTTP 429, сервер возвращает заголовок Retry-After. Используйте exponential backoff with jitter:
import random, time
def post_with_backoff(fn, *args, max_retries=6):
for attempt in range(max_retries):
try:
return fn(*args)
except Exception as e:
if "429" in str(e):
sleep = (2 ** attempt) + random.random()
time.sleep(min(sleep, 30))
continue
raise
raise RuntimeError("Too many retries")Часть 10: эталонная архитектура бота
У любого надежного бота для Polymarket есть одни и те же шесть компонентов. Реализуйте каждый как отдельный модуль и сохраняйте слабую связанность между ними.
| Component | Responsibility | APIs used |
|---|---|---|
| Сканер | Задача по расписанию: выбирает рынки по вашим критериям (теги, объем, срок до экспирации) | Gamma |
| Модуль цен | Поддерживает локальные стаканы заявок в реальном времени через WebSocket | CLOB WS |
| Генератор сигналов | Чистая функция: состояние стакана + метаданные → целевая позиция | - (in-memory) |
| Менеджер заявок | Сравнивает текущие заявки с целевым состоянием и размещает/отменяет только необходимое | CLOB REST |
| Модуль управления рисками | Контролирует лимиты по рынкам, дневные лимиты убытка и аварийные остановки | - (in-memory + DB) |
| Журнал и реестр | Сохраняет каждое решение, исполнение и отмену. Эти данные используются для налоговых отчетов и отладки. | SQLite / Postgres |
Часть 11: типичные сбои
- Отклонение цены не по тику - цена должна быть точным кратным значением для
minimum_tick_sizeрынка. Получите его через/tick-size?token_id=и округлите цену до подписания, иначе заявка будет отклонена. - 404 "No orderbook exists" - вы запросили
/book,/priceили/midpointдля закрытого/разрешенного токена. Используйте/sampling-markets, чтобы найти токены с активным стаканом. - Устаревшие данные WebSocket - отслеживайте время последнего сообщения по каждому активу; если на активном рынке нет обновлений более 30 с, принудительно обновите данные через REST.
- Коллизии nonce - py-clob-client управляет nonce заявок за вас, но если вы пишете собственный подписывающий модуль, увеличивайте nonce для каждой заявки.
- Недостаточный баланс - всегда проверяйте баланс USDC перед размещением; стакан может показать вашу заявку, но матчинг отклонит ее.
- Рынок приостановлен или находится в стадии разрешения - перед торговлей проверяйте
market.active && !market.closed. Во время разрешения Gamma обновляется с задержкой в несколько секунд относительно CLOB. - Несоответствие адаптера NegRisk - рынки с несколькими исходами маршрутизируются через отдельный адаптер NegRisk. SDK обрабатывает это, но убедитесь, что ваша заявка попала на правильную площадку.
Часть 12: вознаграждения за ликвидность через API
Polymarket выплачивает около 5 млн долларов в месяц в рамках общих вознаграждений за ликвидность и еще более 5 млн долларов в месяц в рамках вознаграждений для спортивных рынков (см. Liquidity Rewards). Большая часть этих средств идет маркет-мейкерам, работающим через API. Они поддерживают узкие двусторонние котировки на тысячах рынков.
Формула вознаграждения отдает предпочтение заявкам рядом с серединой спреда, учитывая размер и время нахождения в стакане. Минимальный цикл маркет-мейкинга выглядит так:
- Прочитайте стакан заявок целевого рынка
- Рассчитайте справедливую середину (например, VWAP по 3 верхним уровням с каждой стороны)
- Разместите bid по
mid − spread_target/2и ask поmid + spread_target/2 - При каждом обновлении WebSocket пересчитывайте цену, если ваша котировка отклонилась от цели более чем на один тик
- Отмените заявки и выйдите, если стакан теряет глубину или появляется важная новость
Часть 13: запуск в продакшене
- Хостинг: VPS за 6 долларов в месяц (Hetzner, DigitalOcean) в Европе или US-East достаточно для большинства ботов. Размещайтесь ближе к Polygon RPC, если вам нужна задержка менее 10 мс.
- RPC: используйте Alchemy, Infura или QuickNode для надежного Polygon RPC. Бесплатных тарифов достаточно, пока вы не размещаете сотни заявок в минуту.
- Мониторинг: Prometheus + Grafana для метрик; бот в Telegram для оповещений. Логируйте каждый ID заявки, которую отправляете, и каждое исполнение, которое получаете.
- Резервные копии: сохраняйте состояние каждую минуту. Если VPS упадет во время исполнения, вы должны восстановиться за секунды, а не сверять все вручную.
- Налоги: ваш журнал — это также ваш аудиторский след; см. Tax Guide.
Часть 14 — проверенные советы по работе с Polymarket API
Шпаргалка «ситуация → действие»
| Situation | Action | Why |
|---|---|---|
| 401 "invalid api key" при первом вызове | Проверьте, что signature_type соответствует источнику кошелька, а funder — это адрес прокси | Несоответствие Type 1 и 2 даёт 80% ошибок 401; остальные случаи — когда EOA указан как funder |
| Ордера отклоняются с сообщением "insufficient balance" | Запрашивайте /balance-allowance перед каждым ордером и резервируйте средства локально | CLOB резервирует обеспечение сразу после размещения; два одновременных ордера могут зарезервировать одни и те же средства дважды |
| 429 throttling на endpoint /order | Делайте backoff с jitter: 2^attempt + random(), максимум 30 с | Cloudflare ограничивает частоту, а не отклоняет запросы; наивные повторы только увеличивают очередь |
| WebSocket отключился посреди сделки | Получите snapshot book через REST, сверите локальное состояние, затем подпишитесь заново | Дельты за время разрыва потеряны; snapshot заново синхронизирует ценовые уровни |
| Ордер размещён, но подтверждения fill нет | Запросите /data/order/{id} в течение 5 с; если pending — ждите, если не найден — замените | Редкая, но исправимая ситуация; базовый подход — сначала проверить состояние, затем действовать |
| Маркет был разрешён при активной котировке | Отмените все открытые ордера по этому conditionId при событии resolution | После resolution ордера могут зависнуть как zombie fills, если сработают особенности адаптера |
| Запущен market-making bot | Выставляйте котировки в пределах 2 центов от середины спреда объёмом от 100 долей | Формула вознаграждений учитывает узость спреда, размер и время в book; выигрывают плотные, крупные и устойчивые котировки |
| Запущен arbitrage bot на multi-outcome | Используйте FOK для каждой ноги, а не GTC | Частичный fill по ноге A при полной ноге B = незахеджированная позиция и мгновенный убыток |
| Вы впервые создаёте bot | Сначала сделайте scanner, затем price engine, затем signal — никогда не начинайте с signal | Signals без чистого состояния book превращаются в ловушки корреляций; сначала наладьте каналы данных |
| Production bot упал в 3 ночи | Настройте авто-рестарт через systemd, alert в Telegram и сохранение состояния | Любой bot без присмотра рано или поздно упадёт; вопрос только в том, перезапустится ли он корректно |
Что дальше?
- Инструменты и ресурсы — сторонние dashboards, analytics и data feeds, которые дополняют API
- Продвинутые стратегии — multi-leg arbitrage и конструкции, похожие на options, подходящие для bots
- Liquidity Rewards — точные формулы для получения market-making rebates
- Руководство по order book — более глубокое понимание того, как читать book перед написанием кода для работы с ним
- Глоссарий — простые определения всех терминов из этого руководства на английском языке











