# Как построить торгового бота на API Polymarket

> Отправить один ордер на Polymarket — работа на вечер. Удерживать бота живым месяцами — другая задача: учётные данные, зависящие от типа аккаунта, сеттлмент, происходящий уже после матчинга, heartbeat, который отменяет вашу книгу, если он прекратился, и рестарты, отвечающие HTTP 425 вместо привычной ошибки.

- Источник: https://papaproxy.net/ru/blog/polymarket-trading-bot.php
- Опубликовано: 2026-08-09
- Автор: Alex Young
- Рубрика: Автоматизация · Блог PapaProxy.net

---

## Ключевые выводы

- Gamma, Data API и эндпоинты рыночных данных CLOB не требуют аутентификации; торговле в CLOB нужны выведенные через L1 учётные данные, заголовки L2 **и** подпись самого тела ордера.
- Ставьте актуальный единый SDK для TypeScript или Python: и `py-clob-client`, и `py-clob-client-v2` отсылают к нему, а официальный клиент на Rust пока прежнего поколения.
- Новые аккаунты используют депозитный кошелёк и обеспечение pUSD; типы «прокси» и Safe устарели, а современный клиент определяет тип аккаунта по адресу кошелька.
- Heartbeat ордеров — это dead-man switch: пропустите валидный heartbeat на 10 секунд, и все открытые ордера этих учётных данных будут отменены, причём проверка идёт каждые пять секунд, — отправляйте heartbeat раз в пять секунд, возвращая предыдущий `heartbeat_id`.
- Рестарты матчинг-движка отвечают `HTTP 425`, после чего две минуты действует режим post-only; `503` сообщает о режиме cancel-only или post-only. Отступайте и меняйте то, что отправляете, а не ретрайте настойчивее.
- Защищайте каждый рыночный ордер: сначала оцените уровень исполнения, затем отправляйте `max_price` (для продажи — `min_price`) вместе с `max_spend`, который ограничивает итог с комиссиями; без него комиссии начисляются сверх `amount`.
- Матчинг — не расчёт: сеттлмент происходит в блокчейне позже, поэтому «сматчено» не финальный статус. А поскольку клиентского идентификатора ордера в этом API нет, сохраняйте подписанное намерение до отправки и сверяйтесь с открытыми ордерами и недавними сделками, когда исход неоднозначен.
- При переподключении считайте локальную книгу недействительной, переподпишитесь, дождитесь свежего снимка и только потом применяйте инкременты: порядкового номера для сшивки REST-снимка с пропуском в потоке не существует.
- Ордерные лимиты следуют за подписантом, поэтому больше адресов не даёт больше ордеров; лимиты чтения следуют за IP — и только эта сторона масштабируется горизонтально.

## Что нужно боту помимо вызова ордера

[API Polymarket для автоматической торговли](/ru/blog/polymarket-three-apis.php) охватывает три хоста, и бот обращается ко всем. Gamma отвечает, что существует, — рынки, condition ID, ID токенов, которыми вы торгуете. CLOB отвечает, сколько это стоит, и принимает ордера. Data API отвечает, что держит кошелёк, — так вы сверяетесь после перезапуска.

Чтение обходится без церемоний: Gamma, Data API и эндпоинты рыночных данных CLOB — стакан, цены, спреды — не требуют аутентификации вовсе. Торговле в CLOB нужны оба уровня его аутентификации *плюс* подпись самого ордера. У других механизмов площадки — релеера, моста — свои отдельные схемы, так что «аутентифицированный» здесь не означает что-то одно единообразное. Практический вывод: рыночную половину бота можно построить, протестировать и запустить до того, как вы вообще коснётесь приватного ключа.

Торговая автоматизация здесь раскладывается на шесть слоёв, отказывающих независимо, и только первый — про отправку ордеров:

1. **Аутентификация** — учётные данные, привязанные к типу аккаунта и кошельку с деньгами.
2. **Сборка ордера** — шаг цены, минимальный размер, флаг отрицательного риска и подпись тела.
3. **Живость** — heartbeat, отменяющий ваши стоящие ордера, если процесс встал.
4. **Сеттлмент** — матчинг ещё не является ончейн-сделкой.
5. **Обработка отказов** — отклонения, троттлинг, режимы рестарта и исходы, которые нельзя определить.
6. **Восстановление** — пересборка состояния книги и аккаунта после разрыва.

Большинство ботов, умирающих в проде, умирают на слоях с третьего по шестой — много позже того, как заработал первый.

## Как подписать и отправить ордер

Аутентификация двухуровневая. **L1** — подпись приватным ключом кошелька поверх сообщения EIP-712; она доказывает владение и используется один раз, чтобы создать или вывести учётные данные API: `apiKey`, `secret`, `passphrase`. **L2** — эти данные, применяемые как подпись HMAC-SHA256 к каждому аутентифицированному запросу и передаваемые пятью заголовками: `POLY_ADDRESS`, `POLY_SIGNATURE`, `POLY_TIMESTAMP`, `POLY_API_KEY` и `POLY_PASSPHRASE`. Приватный ключ остаётся у вас, торговля — некастодиальной.

Далее деталь, экономящая больше всего времени на отладке, прямо сформулированная в документации: *даже при наличии заголовков L2 методы, создающие пользовательские ордера, всё равно требуют, чтобы пользователь подписал тело ордера.* L2 аутентифицирует запрос; ордер — отдельно подписываемый объект. Бот с верными заголовками, который всё равно получает отказ, обычно не имеет этой второй подписи, а вовсе не сломан в учётных данных.

Модель аккаунта изменилась. Новые аккаунты Polymarket используют депозитный кошелёк в качестве смарт-кошелька, а прежние типы — прокси и Gnosis Safe — стали устаревшими; обеспечением служит pUSD. В актуальном едином SDK вы передаёте приватный ключ и адрес кошелька, а клиент сам определяет тип аккаунта: явная возня с `signature_type`, которую показывают старые туториалы, относится к предыдущему поколению клиентов. Торговый бот на Python начинается так — по официальному quickstart:

PythonКопировать код

```python
import asyncio, os
from polymarket import AsyncSecureClient

async def main():
    client = await AsyncSecureClient.create(
        private_key=os.environ["POLYMARKET_PRIVATE_KEY"],
        wallet=os.environ["POLYMARKET_WALLET_ADDRESS"],   # ваш депозитный кошелёк
    )

    market = await client.get_market(slug="some-market-slug")
    token_id = market.outcomes.yes.token_id
    assert token_id is not None

    # 1. Оценить уровень, до которого ордер дойдёт при текущей глубине книги.
    estimated = await client.estimate_market_price(
        token_id=token_id, side="BUY", amount="10", order_type="FAK",
    )

    # 2. Отправить с обеими защитами: худшая цена и жёсткий потолок траты.
    response = await client.place_market_order(
        token_id=token_id,
        side="BUY",
        amount="10",          # номинал до комиссий
        max_spend="10",       # всего с комиссиями; без него комиссии начислятся сверху
        max_price=estimated,  # худшая приемлемая цена (для SELL — min_price)
        order_type="FAK",
    )
    if not response.ok:
        raise RuntimeError(response.message)              # отказ несёт код и сообщение

    print(response.order_id)

asyncio.run(main())
```

На что смотреть. Смысл примера — в двух защитах. `max_price` — это ограничение худшей цены, а не целевая цена: оно страхует от движения книги между вашей оценкой и отправкой, а для продажи используется `min_price`. `max_spend` ограничивает итог с учётом комиссий: SDK уменьшает подписываемую сумму покупки так, чтобы ордер вместе с комиссиями уложился в потолок, — а если параметр опустить, `amount` считается суммой до комиссий, и комиссии начислятся сверх неё. Для бота это важнее любого хелпера ожидания расчёта, потому что бот отправляет такие ордера без присмотра.

Идентификатор ордера приходит от сервера в `response.order_id`; клиентского идентификатора, который можно было бы задать самому и потом по нему искать, в этом API нет — это важно для восстановления, о чём ниже. Рыночный ордер никогда не остаётся в книге: всё, что не исполнилось, отменяется. И обратите внимание, о чём ответ умалчивает: матчинг и ончейн-расчёт — разные шаги, об этом в разделе про отказы.

Две рыночные детали, которые нужно читать, а не предполагать. Шаг цены различается у рынков — документированная сетка идёт от `0.1` до `0.0001`, — поэтому запрашивайте его для каждого рынка, а не зашивайте: цена вне сетки отклоняется с `INVALID_ORDER_MIN_TICK_SIZE`. И часть рынков задерживает матчинг: спортивные применяют односекундную задержку размещения к исполняемым по рынку ордерам и вовсе снимают стоящие лимитные ордера в момент начала игры, а на отдельных крипто- и финансовых рынках действует тейкерская задержка в 250 мс. В течение любой такой задержки ордер находится в ожидании и **не может быть отменён**.

## Когда лучше взять SDK вместо прямых запросов

Реализовать L1, L2 и подпись ордера самостоятельно можно, и для необычного языка или аудированного пути подписи это разумно. Во всех остальных случаях SDK для торговых ботов — прагматичный вариант по умолчанию: переписывать пришлось бы ровно то, в чём легче всего ошибиться незаметно, — разделители домена EIP-712, канонизацию HMAC, порядок полей в структуре ордера, единицы времени.

Проверьте, какое поколение вы ставите: их три, и туториалы ссылаются на все. И оригинальный `py-clob-client`, и его преемник `py-clob-client-v2` теперь несут уведомления с рекомендацией перейти на единый SDK, который объединяет REST API и вебсокеты в одном пакете и используется в актуальной документации. Единый SDK сейчас покрывает TypeScript и Python; официальный клиент на Rust остаётся прежнего поколения — `polymarket_client_sdk_v2`. Если руководство показывает `ClobClient` с явными `signature_type` и `funder` — оно описывает старшее поколение: код, возможно, ещё работает, но вместе с ним вы наследуете его представления о типах аккаунтов.

Одна известная ловушка, которую стоит проверить до выбора типа аккаунта: в мае 2026 года на python- и rust-клиенты v2 завели баг — при типе подписи депозитного кошелька выведенный API-ключ привязывался к подписывающему EOA, а не к депозитному кошельку, из-за чего каждый ордер падал с `HTTP 400 — the order signer address has to be the address of the API KEY`. Убедитесь, исправлено ли это в той версии, которую ставите.

## Где боты ломаются чаще всего

Разработка торговых ботов на Polymarket отказывает узнаваемыми кластерами. Знание их превращает часы диагностики в минуты.

**Heartbeat — это dead-man switch, и работает он в обе стороны.** У CLOB есть heartbeat ордеров: как только первый принят, площадка ожидает, что вы продолжите их слать, и если валидный heartbeat не приходит в течение 10 секунд, **все открытые ордера этих учётных данных отменяются**. Проверка выполняется каждые пять секунд, поэтому фактическая отмена может наступить на пять секунд позже таймаута. Документированный паттерн — отправлять heartbeat раз в пять секунд, возвращая `heartbeat_id` из предыдущего ответа, а на первом вызове передавая пустую строку; неверный или истёкший идентификатор даёт `400` с правильным значением в ответе. Включайте механизм осознанно: это лучшая защита от зависшего процесса, оставившего живые котировки в книге, и одновременно способ потерять всю книгу из-за паузы сборщика мусора, если выставить интервал впритык к лимиту.

**У рестартов матчинг-движка свой протокол.** В окне рестарта CLOB возвращает `HTTP 425 (Too Early)` на ордерных эндпоинтах. Это временное состояние, а не ошибка: отступайте экспоненциально, начиная с одной-двух секунд, и продолжайте. После каждого рестарта движок входит в **режим post-only на две минуты**: отмены принимаются, а новые ордера должны быть post-only — остальные отклоняются. Возможны и ответы `503`, сообщающие о режиме cancel-only или post-only. Ничего из этого нельзя ретраить вслепую: каждый случай требует изменить то, что вы отправляете, а не отправлять то же самое чаще. Агрессивные ретраи вдобавок рискуют упереться в лимиты ровно в тот момент, когда движок вернётся.

**Матчинг — ещё не расчёт.** Ордера матчатся офчейн, затем оператор отправляет сделку в блокчейн, где контракт биржи переводит pUSD и сделка достигает финальности в Polygon. Принятый ответ по ордеру может прийти раньше, чем появится хоть один хеш транзакции. Машина состояний, считающая «сматчено» финальным статусом, будет неверно показывать позиции в промежутке — и ей нужен явный путь на случай, если ончейн-часть не завершится как ожидалось.

**Отказы по ордеру** кучкуются в предотвратимых причинах: цена вне сетки шага, размер ниже минимального для рынка, недостаточный баланс или разрешение, post-only-ордер, который пересёк бы рынок, fill-or-kill без исполнения. Запрашивайте шаг цены и минимальный размер у рынка до отправки, а после любого ончейн-депозита или апрува обновляйте кешированное биржей представление о вашем балансе и разрешениях.

**Лимиты — это две системы сразу.** Cloudflare применяет по IP burst- и устойчивые окна и сначала троттлит, а лишь потом отклоняет; поверх лежат токен-бакеты на подписанта с раздельными балансами ордеров и отмен, масштабируемые тиром по объёму, сообщающие состояние в `Poly-RateLimit-Remaining`, `-Reset` и `-Tier`. Читайте эти заголовки и не пытайтесь решить ордерный лимит инфраструктурой: он следует за подписантом, а не за адресом подключения. Цифры на стороне чтения и поведение лимитов на практике — в нашем [тесте лимитов Polymarket](/ru/blog/polymarket-api-rate-limits.php).

**Неоднозначному исходу нужны записи намерений, а не клиентские ID ордеров.** Неоднозначен далеко не всякий отказ: документированное отклонение означает, что ордер до книги не дошёл, а `503` с указанием режима cancel-only или post-only имеет известную семантику. Но транспортный таймаут или неклассифицированный серверный сбой действительно оставляют исход неизвестным, и слепой ретрай в этом случае — путь к двум позициям вместо одной. Форма восстановления задана самим API: идентификатор ордера выдаёт сервер, поэтому клиентского идентификатора для последующего поиска не существует. Сохраняйте подписанное намерение до отправки — токен, сторону, цену, размер, отметку времени — и при неоднозначном результате сверяйтесь с открытыми ордерами и недавними сделками, чтобы понять, прошёл ли он. (Не переносите сюда привычки площадок, включая перпетуальный API самой Polymarket, где клиентский идентификатор ордера действительно есть.)

**Состояние после переподключения.** Обрывы [вебсокета](/ru/blog/polymarket-websocket-api.php) — штатная работа. Безопасное восстановление начинается с признания локальной книги недействительной в момент разрыва: переподписаться, дождаться свежего полного снимка книги, который канал рынка присылает при подписке, и только затем применять инкрементальные изменения цен. Универсального порядкового номера, которым можно было бы сшить REST-снимок с неизвестным пропуском в потоке, нет, поэтому склеивать по времени не пытайтесь. Для приватного состояния перед возобновлением стратегии запросите открытые ордера и недавние сделки. Пользовательский канал вдобавок ожидает heartbeat уровня приложения — текстовый фрейм `PING` примерно раз в десять секунд, на который сервер отвечает `PONG`, — а соединение, которое так и не подписалось, может быть закрыто.

**География проверяется в момент ордера.** Polymarket ограничивает торговлю в ряде юрисдикций, и ордера из ограниченных локаций отклоняются. Проверьте свою правомочность до того, как строить ордерный путь, и относитесь к этому как к юридическому ограничению, а не техническому.

## Что автоматизируют чаще всего

Два сценария доминируют в вопросах, и оба ограниченнее, чем кажутся.

Арбитражный бот обычно целится либо во внутреннюю комплементарность одного рынка, где Yes и No должны в сумме стоить примерно 1, либо в спред между Polymarket и другой площадкой по одному вопросу о реальном мире. Первое — в основном задача про комиссии и задержку, и комиссии здесь уже не погрешность: площадка применяет тейкерские комиссии к сматченным сделкам в зависимости от типа рынка и ведёт программы мейкерских и тейкерских ребейтов, поэтому читайте конфигурацию комиссий из объекта рынка, а ребейты считайте частью арифметики, а не бонусом. Второе добавляет риск расчёта: две площадки могут разрешить один вопрос по-разному — другая формулировка, другие источники, другое время. Ни то ни другое не бесплатный обед, и эта статья не советует, стоит ли этим торговать.

Копитрейдинговый бот упирается в структурный факт: эндпоинта копитрейдинга у Polymarket нет. Есть публичный доступ на чтение — Data API возвращает [позиции и активность](/ru/blog/polymarket-data-api-positions.php) любого кошелька по адресу — и ваш собственный аутентифицированный путь ордеров. Поэтому «копирование» — это конвейер, целиком принадлежащий вам: заметить изменение в отслеживаемом кошельке, принять решение, выставить свой ордер по своей цене. Всё, что между, — ваша задержка и ваше проскальзывание, а кошелёк, за которым вы следуете, не обязан оставаться прибыльным.

Именно на стороне чтения обоих сценариев кусаются бюджеты. Наблюдение за многими кошельками или рынками означает опрос эндпоинтов с лимитами по IP-адресу: `/positions` — 150 запросов за 10 секунд, `/markets` — 300, что заметно жёстче торгового пути. Эту сторону мы измеряли сами в [отдельном тесте лимитов Polymarket](/ru/blog/polymarket-api-rate-limits.php), включая поведение площадки за пределом лимита и рост пропускной способности с числом адресов.

Отсюда единственный инфраструктурный вывод, который здесь уместен. Пропускная способность по ордерам привязана к подписанту и добавлением адресов не масштабируется. Пропускная способность чтения считается по IP, поэтому широкий список наблюдения либо замедляется, либо распределяется по исходящим адресам — для этого и нужны наши [прокси для криптопроектов](/ru/individual.php): выделенные IPv4-адреса, статичные на весь срок плана, к каждому из которых применяется своя документированная квота. И чтобы не оставалось двусмысленности: речь о стабильном исходящем адресе и масштабировании чтения для правомочной интеграции, а не о способе обойти географические ограничения торговли Polymarket.
