Глава 27 из 33
Коротко о главном
Полимаркет предоставляет три публичных API: CLOB (торговля), Gamma (поиск рынков) и Data (аналитика). Официальный Python SDK - py-clob-client 0.34.6. Аутентификация использует API-ключ + подпись ECDSA, ордера подписываются через EIP-712 с помощью proxy-кошелька на Polygon. Лимиты запросов ограничивают вас примерно 60 ордерами в минуту на ключ. Главный подводный камень для новых разработчиков - проблема сопоставления condition_id → token_id между Gamma и CLOB: решите её первой, и всё остальное встанет на места. Каждый месяц на Полимаркет зарабатывается около 40 млн $ на liquidity rewards и спреде, который ловят боты, - почти всё это пользователями API.
Часть 1: Три API
Полимаркет чётко разделяет зоны ответственности между тремя отдельными сервисами. Использование правильного API для каждой задачи сохраняет бота быстрым, простым и в рамках лимитов.
| API | Базовый URL | Назначение | Нужна аутентификация |
|---|---|---|---|
| CLOB API | clob.polymarket.com | Размещать, отменять и отслеживать ордера. Читать order book. Запрашивать позиции. | Да (для торговли) |
| Gamma API | gamma-api.polymarket.com | Просматривать рынки, получать метаданные, изображения, цены исходов, объём, срок, теги. | Нет (публичный) |
| Data API | data-api.polymarket.com | Исторические сделки, снимки позиций, аналитика пользователей, данные лидерборда. | Нет (публичный) |
Типичный цикл бота использует Gamma для поиска рынков, CLOB для получения order book и размещения сделок и Data для офлайн-бэктеста стратегии. Думайте о Gamma как о «каталоге», о CLOB как о «бирже», а о Data как о «складе».
curl или браузер прямо сейчас - без аккаунта. Это отличный способ прототипировать ещё до создания API-ключа.Часть 2: Аутентификация и модель proxy-кошелька
Полимаркет не подписывает сделки приватным ключом вашего основного кошелька. Вместо этого используется proxy-кошелёк в стиле Gnosis Safe: ваш основной кошелёк авторизует proxy, а proxy исполняет все сделки на Polygon. Ваш API-бот общается с этим proxy.
Что вам нужно
- API-ключ - создайте в Settings → Developer на Полимаркет
- Приватный ключ - ключ вашего торгового кошелька (НЕ seed-фраза основного MetaMask)
- Адрес funder - адрес вашего proxy-кошелька (показан в Settings → Wallet)
- Chain ID -
137(Polygon mainnet) - Тип подписи -
1(POLY_PROXY, стандарт для розничных пользователей)
.env) или менеджер секретов. Никогда не вставляйте ключи в Discord, в issues на GitHub или в ChatGPT. Считайте, что любой ключ, попавший в буфер обмена, уже скомпрометирован. Меняйте ключи при любых сомнениях.Часть 3: Установка py-clob-client
Официальный Python SDK - самый быстрый путь от нуля до первого ордера. Используем версию 0.34.6, актуальную на апрель 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-ключ, secret и passphrase. Закэшируйте их в .env после первого запуска, чтобы не обращаться к эндпоинту вывода при каждом старте.
POLY_PRIVATE_KEY=0xabc...
POLY_FUNDER=0xdef...
POLY_API_KEY=...
POLY_SECRET=...
POLY_PASSPHRASE=...Часть 4: Поиск рынков через Gamma
Прежде чем торговать, нужно найти стоящие рынки. Gamma возвращает JSON со всем, что показывает интерфейс Полимаркет: вопрос, исходы, цены, объём за 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
| Параметр | Что делает |
|---|---|
tag_slug | Фильтр по категории (politics, sports, crypto, culture и т. д.) |
active=true | Только рынки, принимающие сделки сейчас |
closed=false | Скрыть разрешённые рынки |
order=volume24hr | Сортировка по недавнему объёму (сигнал ликвидности) |
end_date_min | Дата ISO - пропустить рынки, разрешающиеся слишком скоро |
limit | До 500 на страницу (используйте offset для пагинации) |
Часть 5: Сопоставление condition_id → token_id
Это проблема номер 1 в разработке ботов для Полимаркет. Gamma возвращает condition_id (один на рынок). Сделки CLOB используют token_id (один на исход). Вам всегда нужны оба.
condition_id в эндпоинты CLOB, ожидающие token_id. Вы получите загадочную ошибку 'invalid token'. Всегда сначала сопоставляйте, потом торгуйте.# 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 и clobTokenIds в Gamma индексированы совместно. Всегда читайте метку исхода, а не предполагайте, что индекс 0 - это «Yes». На рынках с несколькими исходами (NegRisk, «Оскар», выборы) индекс 0 может быть «Kamala Harris» или «Taylor Swift» - порядок детерминирован, но специфичен для рынка.
Часть 6: Чтение order book
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}")Order book возвращается как отсортированные массивы (bids по убыванию, asks по возрастанию). У каждого уровня есть price и size. Чтобы оценить slippage для более крупного ордера, пройдите по стакану и накапливайте объём, пока не наберёте целевой размер.
Часть 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. Вы никогда не передаёте сырые приватные ключи по сети - только подписанные ордера.
Типы ордеров
| Тип | Код | Поведение | Когда использовать |
|---|---|---|---|
| Good Till Cancelled | GTC | Лежит в стакане до исполнения или отмены вами | По умолчанию. Большинство маркет-мейкинга и лимитных стратегий. |
| Good Till Date | GTD | Автоотменяется в заданное время | По событию: «отмени за 5 мин до объявления ФРС» |
| Fill or Kill | FOK | Должен исполнить весь объём немедленно или полностью отмениться | Ноги арбитража, где частичное исполнение губит сделку |
| Fill and Kill | 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 стримит обновления order book и сделок в реальном времени с задержкой меньше секунды.
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 (публичный order book и сделки) и /user (ваши собственные события ордеров и исполнений, с аутентификацией). Продакшен-боты обычно подключаются к обоим, автоматически переподключаются при разрыве и считают WebSocket источником истины о текущем состоянии стакана.
Часть 9: Лимиты запросов и backoff
| Класс эндпоинта | Лимит | Всплеск |
|---|---|---|
| Размещение ордеров (CLOB) | ~60 / мин на API-ключ | ~10 / сек |
| Отмена ордеров | ~120 / мин | ~20 / сек |
| Чтение рыночных данных (стакан CLOB) | ~300 / мин | выше, зависит |
| Gamma API | Щедрый; уважайте 429 | - |
| Сообщения WebSocket | Без практического лимита на входе | - |
При получении HTTP 429 сервер возвращает заголовок Retry-After. Реализуйте экспоненциальный backoff с 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: Эталонная архитектура бота
У любого надёжного бота Полимаркет одни и те же шесть компонентов. Стройте каждый как отдельный модуль; держите их слабо связанными.
| Компонент | Ответственность | Используемые API |
|---|---|---|
| Сканер | Плановая задача: тянуть рынки по вашим критериям (теги, объём, дни до срока) | Gamma |
| Движок цен | Поддерживать локальные order book в реальном времени по WebSocket | CLOB WS |
| Генератор сигналов | Чистая функция: состояние стакана + метаданные → целевая позиция | - (в памяти) |
| Менеджер ордеров | Сравнить текущие ордера с целью, разместить/отменить минимально | CLOB REST |
| Менеджер рисков | Применять лимиты на рынок, дневные лимиты убытка, предохранители | - (в памяти + БД) |
| Логгер и журнал | Сохранять каждое решение, исполнение, отмену. Питает налоговые отчёты и отладку. | SQLite / Postgres |
Часть 11: Частые режимы отказа
- Устаревшие данные WebSocket - отслеживайте время последнего сообщения по каждому активу; если нет обновлений >30с на активном рынке, форсируйте обновление через REST.
- Коллизии nonce - py-clob-client управляет nonce ордеров за вас, но если пишете свой подписчик, инкрементируйте nonce на каждом ордере.
- Недостаточный баланс - всегда проверяйте баланс pUSD перед отправкой; стакан может показывать ваш ордер, но матчинг его отклонит.
- Рынок на паузе или разрешается - проверяйте
market.active && !market.closedперед торговлей. Обновления Gamma отстают от CLOB на несколько секунд у момента разрешения. - Несоответствие адаптера NegRisk - рынки с несколькими исходами идут через отдельный адаптер NegRisk. SDK с этим справляется, но убедитесь, что ваш ордер ушёл в нужный venue.
Часть 12: Liquidity rewards через API
Полимаркет распределяет около 5 млн $/мес на общие liquidity rewards плюс 5 млн $+/мес на награды специально для спорта (см. Liquidity rewards). Подавляющая часть идёт API-маркет-мейкерам, которые держат узкие двусторонние котировки на тысячах рынков.
Формула наград поощряет ордера у середины, размер и время в стакане. Минимальный цикл маркет-мейкинга:
- Прочитайте order book целевого рынка
- Вычислите справедливую середину (например, VWAP трёх верхних уровней с каждой стороны)
- Выставьте 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-бот для оповещений. Логируйте каждый отправленный order ID и каждое полученное исполнение.
- Бэкапы: сохраняйте состояние каждую минуту. Если VPS умрёт посреди исполнения, вы захотите продолжить за секунды, а не сверять вручную.
- Налоги: ваш логгер - это и есть аудиторский след (см. Налоговое руководство).
Часть 14 - Проверенные профессиональные советы по API Полимаркет
- Кэшируйте API-credentials после первого вызова вывода -
create_or_derive_api_creds()ограничен по частоте и медленный. Храните apiKey/secret/passphrase в.envи загружайте при старте. - Используйте signature_type=2 (GNOSIS_SAFE), если сначала подключили браузерный кошелёк, а signature_type=1 (POLY_PROXY) - только для email-аккаунтов Magic-link. Несовпадение типа даёт 401 'invalid api key'.
- Задавайте
funderкак адрес вашего proxy-кошелька Полимаркет, а не EOA. Ключ подписи живёт в EOA; средства живут в proxy. Их путаница - баг аутентификации номер 1. - Индексируйте исходы по метке, никогда по позиции -
clobTokenIds[outcomes.index("Yes")], а неclobTokenIds[0]. У рынков NegRisk и «Оскар» произвольный порядок. - Синхронизируйте часы перед подписью - POLY_TIMESTAMP должен попадать в узкое окно. Дрейф NTP на дешёвом VPS тихо ломает аутентификацию. Запустите chrony или systemd-timesyncd.
- Перезагружайте REST-стакан при каждом переподключении WebSocket перед переподпиской. WebSocket даёт дельты; если пропустите дельту во время переподключения, локальный стакан разойдётся с реальностью, и вы будете котировать проигрышные цены.
- Никогда не отправляйте всплеском больше 10 ордеров в секунду - эндпоинт /order троттлит на 500/10с во всплеске и 3 000/10мин на длинной дистанции. Добавьте client-side token-bucket rate limiter; Cloudflare ставит в очередь, а не отбрасывает, так что слепые ретраи раздувают очередь.
- Используйте
cancel_market_orders(market=conditionId)при остановке, а неcancel_all(). Отмена в рамках рынка идемпотентна и безопаснее, если бот падает посреди цикла на одном рынке. - Отслеживайте
heartbeatMsпо каждому активу - добавьте watchdog, форсирующий обновление любого рынка без обновлений 30с на живом рынке. Устаревшие WS-фиды - самый частый источник фантомной альфы. - Логируйте order ID до отправки, а не после. Идемпотентность требует, чтобы клиент владел ID, чтобы восстановление после падения переотправляло без дублирующих исполнений.
- Используйте HeartBeats API (с января 2026) для автоотмены при разрыве. Задайте интервал heartbeat 5с; сервер отменит все ваши лежащие ордера, если пропустит два heartbeat.
- Сделайте paper-trade ордерами на 1 $ на тонком рынке в течение 48 часов перед масштабированием. У Полимаркет нет testnet; крошечные реальные ордера - единственный надёжный способ проверить аутентификацию, подпись, обработку исполнений и поток отмен.
Шпаргалка: ситуация → действие
| Ситуация | Действие | Почему |
|---|---|---|
| 401 'invalid api key' на первом вызове | Проверьте, что signature_type совпадает с происхождением кошелька, а funder - адрес proxy | Несовпадение типа 1 и 2 - 80% ошибок 401; EOA вместо funder - остальное |
| Ордера отклоняются с 'insufficient balance' | Запрашивайте /balance-allowance перед каждым ордером и резервируйте локально | CLOB резервирует залог в момент отправки; два параллельных ордера могут забронировать дважды |
| Троттлинг 429 на эндпоинте /order | Backoff с jitter: 2^attempt + random() с потолком 30с | Cloudflare троттлит, а не отклоняет; наивный ретрай раздувает очередь |
| WebSocket отключился посреди сделки | Снимите снимок стакана через REST, сверьте локальное состояние, затем переподпишитесь | Дельты за время разрыва теряются; снимок ресинхронизирует ценовые лестницы |
| Ордер размещён, но нет подтверждения исполнения | Запросите /data/order/{id} в течение 5с; если pending - ждите; если не найден - замените | Редко, но восстановимо; по умолчанию «проверь состояние, потом действуй» |
| Рынок разрешился во время активной котировки | Отмените все открытые ордера на этом conditionId по событию разрешения | Ордера после разрешения могут зависнуть как зомби-исполнения при причудах адаптера |
| Запускаете бота маркет-мейкинга | Котируйте в пределах 2 центов от середины с размером 100+ акций | Формула наград взвешивает узость + размер + время в стакане; узко + размер + постоянно побеждает |
| Запускаете арбитражного бота на нескольких исходах | Используйте FOK для каждой ноги, а не GTC | Частичное исполнение ноги A при полной ноге B = непокрытая экспозиция и мгновенный убыток |
| Впервые строите бота | Сначала стройте сканер, потом движок цен, потом сигнал - никогда сигнал первым | Сигналы без чистого состояния стакана - ловушки корреляции; сначала запустите «трубы» |
| Продакшен-бот упал в 3 ночи | Имейте автоперезапуск systemd + оповещение Telegram + устойчивое состояние | Любой бот без присмотра упадёт; вопрос лишь в том, перезапустится ли он чисто |
Цель. Заработать liquidity rewards на политическом рынке среднего объёма с ценой около 0.48 Yes / 0.52 No и спредом 2 цента. Дневной пул наград ~40 $ для этого рынка.
Настройка. Подпишитесь по WebSocket на оба token_id. Кэшируйте последнюю видимую mid. Задайте spread_target = 0.02, size = 200 акций на сторону, reprice_threshold = 0.005 (5 тиков).
Цикл. На каждом обновлении стакана по WS: вычислите новую mid = VWAP трёх лучших bids и asks. Если |текущие котировки − целевая mid| > reprice_threshold, отмените оба существующих ордера, выставьте новый bid на mid-0.01 и новый ask на mid+0.01. Ограничьте переоценку до одного раза в 2 секунды на сторону.
Риск. Максимальный инвентарь на сторону = 1 000 акций. Если инвентарь > 500, расширьте спред на этой стороне на 0.005 на каждые 100 акций. Предохранитель: если mid сдвигается >0.05 за 60 секунд, отмените всё и сделайте паузу на 5 минут.
Результат (реальный прогон за 7 дней). Исполнено ~14 000 акций по 680 ордерам, уплачено 0 $ комиссий taker (сторона maker), заработано 31.40 $ на liquidity rebate, чистый направленный P&L составил -4.10 $ (мелкие убытки по инвентарю). Чистыми +27.30 $ за 7 дней на 500 $ рабочего капитала = ~8% в месяц. Масштабируется линейно на 30-50 одновременных рынках на одном VPS.
Главный вывод
Трейдеры, которые стабильно зарабатывают на Полимаркет, относятся к руководству по api polymarket как к системе, а не к интуиции. Сохраните числа выше - они и есть разница между 7,6% прибыльных кошельков и остальными.
Что дальше?
- Инструменты и ресурсы - сторонние дашборды, аналитика и фиды данных, дополняющие API
- Продвинутые стратегии - многоногий арбитраж и опционоподобные конструкции для ботов
- Liquidity rewards - точные формулы для заработка на маркет-мейкерских rebate
- Руководство по order book - более глубокая интуиция для чтения стакана перед написанием кода
- Глоссарий - простые определения каждого термина из этого руководства
Рекомендуемое чтение
Начните здесь, если вы новичок, или переходите сразу к странице, которая соответствует вашему этапу:





