Кратко

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 для каждой задачи, бот будет быстрее, проще и не будет выходить за лимиты запросов.

APIBase URLPurposeAuth Required
CLOB APIclob.polymarket.comРазмещение, отмена и отслеживание ордеров. Чтение стаканов ордеров. Запрос позиций.Да (для торговли)
Gamma APIgamma-api.polymarket.comПросмотр рынков, получение метаданных, изображений, цен исходов, объёма, даты истечения и тегов.Нет (публичный)
Data APIdata-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 ID137 (Polygon mainnet)
  • Signature type1 (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

ParameterWhat 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 года.

EndpointMethodWhat it returns
/marketsGETВсе рынки (с пагинацией через next_cursor). Включает condition_id, tokens[], minimum_tick_size, neg_risk.
/sampling-marketsGETТолько рынки с активным стаканом заявок — самый быстрый способ найти торгуемые token_id.
/book?token_id=GETПолный стакан заявок: bids[] и asks[] с ценой и размером.
/price?token_id=&side=buyGETЛучшая цена для стороны. 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.
/tradesGETПоследние сделки (для ваших — с аутентификацией; по рынку — публично).
/orderPOSTРазместить подписанный ордер (требуется аутентификация).
/orderDELETEОтменить один ордер по id (аутентификация).
/ordersGETВаши открытые лимитные ордера в стакане (аутентификация).
/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 делает это за вас; вот что он формирует внутри:

HeaderWhat it carries
POLY_ADDRESSАдрес вашего кошелька для подписи
POLY_API_KEYAPI-ключ из create_or_derive_api_creds()
POLY_PASSPHRASEPassphrase из того же вызова derive
POLY_TIMESTAMPТекущее время в секундах UNIX (должно совпадать с временем сервера — см. совет по синхронизации часов)
POLY_NONCENonce для каждого запроса
POLY_SIGNATUREHMAC-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)

Кратко о параметрах ордера

FieldMeaningNotes
token_idИсход, которым вы торгуетеНе condition_id — см. часть 5
sideBUY или SELLДля BUY нужен USDC; для SELL нужны доли
price0.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"])

Типы ордеров

TypeCodeBehaviourWhen 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 classLimitBurst
Размещение ордеров (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 есть одни и те же шесть компонентов. Реализуйте каждый как отдельный модуль и сохраняйте слабую связанность между ними.

ComponentResponsibilityAPIs used
СканерЗадача по расписанию: выбирает рынки по вашим критериям (теги, объем, срок до экспирации)Gamma
Модуль ценПоддерживает локальные стаканы заявок в реальном времени через WebSocketCLOB 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. Они поддерживают узкие двусторонние котировки на тысячах рынков.

Формула вознаграждения отдает предпочтение заявкам рядом с серединой спреда, учитывая размер и время нахождения в стакане. Минимальный цикл маркет-мейкинга выглядит так:

  1. Прочитайте стакан заявок целевого рынка
  2. Рассчитайте справедливую середину (например, VWAP по 3 верхним уровням с каждой стороны)
  3. Разместите bid по mid − spread_target/2 и ask по mid + spread_target/2
  4. При каждом обновлении WebSocket пересчитывайте цену, если ваша котировка отклонилась от цели более чем на один тик
  5. Отмените заявки и выйдите, если стакан теряет глубину или появляется важная новость

Часть 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

Шпаргалка «ситуация → действие»

SituationActionWhy
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 — никогда не начинайте с signalSignals без чистого состояния book превращаются в ловушки корреляций; сначала наладьте каналы данных
Production bot упал в 3 ночиНастройте авто-рестарт через systemd, alert в Telegram и сохранение состоянияЛюбой bot без присмотра рано или поздно упадёт; вопрос только в том, перезапустится ли он корректно

Что дальше?

Быстрая проверка