Туторіал ботів Полімаркет · Розділ 24 з 32
Полімаркет Perps має повноцінний публічний API з офіційними SDK для TypeScript і Python. У цьому розділі ми будуємо бота на реальному інтерфейсі. Він охоплює автентифікацію через проксі-ключ для торгівлі, ринкові дані через REST і WebSocket та ордери з тейк-профітом і стоп-лосом. Також ми додаємо контроль ризиків, щоб бот із кредитним плечем не спустошив рахунок.
Що охоплює цей розділ
- Ключові цифри для розробників ботів
- Як влаштований API Perps
- Крок 1: встановіть SDK і відкрийте сесію
- Крок 2: отримуйте ринкові дані через REST і WebSocket
- Крок 3: розміщуйте ордери з тейк-профітом і стоп-лосом
- Крок 4: каркас бота з контролем ризиків у коді
- Помилки, автоматичне скасування на випадок збою, правила для ботів і хостинг
Кожен метод SDK і кожен ендпоінт тут узято з документації Perps від Полімаркет (docs.polymarket.com/perps, переглянуто 6 листопада 2026 р.). Нашу власну логіку позначено в коментарях, а функція стратегії є позначеною заглушкою.
Ключові цифри для розробників ботів (станом на листопад 2026 р.)
| Параметр | Значення |
|---|---|
| Базовий URL REST | https://api.perpetuals.polymarket.com |
| WebSocket | wss://ws.perpetuals.polymarket.com/v1/ws |
| TypeScript SDK | @polymarket/client (з viem для підпису) |
| Python SDK | polymarket-client (асинхронний) |
| Публічні дані | /v1/info/*, без автентифікації |
| Приватні запити на читання | /v1/account/* із заголовками polymarket-proxy і polymarket-secret |
| Підписані запити на запис | /v1/trade/*, підпис EIP-712 проксі-ключем, дійсний 5 хвилин |
| Облікові дані сесії | За замовчуванням спливають через тиждень |
| Мінімальний ордер | $10 номіналу; мінімальний депозит 10 pUSD |
| Найбільший ринковий ордер | $1 млн на ринках 50x і 20x, $500 тис. на більшості інших, $50 тис. на монетах 3x і 5x |
| Формат ціни та розміру | Десяткові рядки, не більше знаків після крапки, ніж дозволяє ринок, і не більше 5 значущих цифр |
| Максимальне кредитне плече | 50x (BTC, ETH, USA500, US100), в інших випадках 20x, 10x, 5x або 3x залежно від ринку, менше для великих позицій |
| Комісії (базовий рівень) | Тейкер 0.040%, мейкер 0.0125%; для ANT удвічі дешевше |
| Фінансування | Щогодини, не більше +/-4% на годину |
| Тригери TP/SL | Маркувальна ціна, а не ціна останньої угоди |
| Підтримка зʼєднання WebSocket | Ping приблизно кожні 25 секунд; вважайте зʼєднання неактивним після приблизно 65 секунд тиші |
| Актуальність тікера | Дані до 10 секунд давності |
Як влаштований API Perps
Perps є гібридною біржею: зіставлення ордерів відбувається offchain, а зберігання коштів і розрахунки виконуються на Polygon. Onchain проходять лише депозити й виведення, тому за ордери не потрібно платити газ. API має три рівні:
- Публічні дані (
/v1/info/): інструменти, тікери, стакан, свічки, угоди, фінансування, комісії. Ключ не потрібен, географічного блокування немає. - Приватні запити на читання (
/v1/account/): портфель, баланси, ордери, виконання, налаштування. - Підписані запити на запис (
/v1/trade/): ордери, скасування, кредитне плече, ізольована маржа, автоматичне скасування. Кожен запис кодується в MessagePack, хешується через keccak256 і підписується проксі-ключем як типізовані дані EIP-712.
SDK підписують запити за вас. Також є специфікація OpenAPI (/api-spec/perps-openapi.json на сайті документації) і специфікація AsyncAPI для WebSocket.
BTC-PERP. Робочий API повертає BTC-USD, SP500-USD (USA500), NAS100-USD (US100), ANT-USD тощо. Завантажте список інструментів, знайдіть актуальний symbol і збережіть числовий id. Не копіюйте ID з прикладів: у робочому API BTC має 6, а SP500 має 1.Крок 1: встановіть SDK і відкрийте сесію
Встановіть пакет командою pip install polymarket-client або npm install @polymarket/client@latest viem. Боту потрібні адреса вашого гаманця на Полімаркет і приватний ключ підписувача, якому належить рахунок Perps (рахунок привʼязаний до EOA підписувача, а не до Safe). Під час відкриття сесії ви ставите один підпис, щоб авторизувати новий проксі-ключ для торгівлі. Далі він підписує кожен ордер, тож ваш ключ із коштами ніколи не торгує. Зберігайте session.credentials у зашифрованому вигляді, щоб відновити сесію пізніше.
import asyncio
import os
from datetime import timedelta
from polymarket import AsyncSecureClient
async def main():
client = await AsyncSecureClient.create(
private_key=os.environ["PRIVATE_KEY"],
wallet=os.environ["POLYMARKET_WALLET_ADDRESS"],
)
# One signature creates a proxy trading key. Orders are signed by the proxy,
# not by your wallet key. Credentials expire after one week by default.
session = await client.open_perps_session(
expires_in=timedelta(days=1),
label="perps-bot",
)
stored = session.credentials.model_dump(mode="json") # keep it encrypted
instruments = await client.fetch_perps_instruments()
btc = next(i for i in instruments if i.symbol == "BTC-USD")
print(btc.id, btc.max_leverage, btc.min_notional, btc.price_decimals)
asyncio.run(main())
import { createSecureClient } from "@polymarket/client";
import { privateKey } from "@polymarket/client/viem";
const client = await createSecureClient({
wallet: process.env.POLYMARKET_WALLET_ADDRESS!,
signer: privateKey(process.env.PRIVATE_KEY!),
});
// One signature creates a proxy trading key. Orders are signed by the proxy,
// not by your wallet key. Credentials expire after one week by default.
const session = await client.openPerpsSession({
expiresIn: 24 * 60 * 60 * 1000,
label: "perps-bot",
});
const stored = session.credentials; // keep it encrypted
const instruments = await client.fetchPerpsInstruments();
const btc = instruments.find((i) => i.symbol === "BTC-USD");
if (!btc) throw new Error("BTC-USD not listed");
console.log(btc.id, btc.maxLeverage, btc.minNotional, btc.priceDecimals);
Для депозиту з коду (deposit_to_perps / depositToPerps) також потрібен ключ Relayer API з розділу Settings, API Keys на polymarket.com. Простіше й безпечніше поповнювати рахунок у вебзастосунку і не давати боту доступу до переказів коштів. З 5 листопада 2026 р. рахунки, що мають адресну книгу для виведення, можуть виводити кошти лише на збережені адреси. Для дашбордів використовуйте ключ лише для читання (проксі з міткою read-only:, який відхиляє підписані запити на запис), і ніколи не використовуйте для цього торговий ключ. Зберігання ключів описано в нашому розділі про гаманці та автентифікацію.
Крок 2: отримуйте ринкові дані через REST і WebSocket
Тікер містить три ціни: індексну (справедлива вартість із фідів, таких як Pyth, Chainlink Data Streams і Hyperliquid), маркувальну (для PnL, маржі, ліквідації та TP/SL) і останню (лише для виконання), а також ставку фінансування і час наступного нарахування фінансування. Будуйте сигнали на маркувальній або індексній ціні.
# Inside your async main(), after Step 1 (btc is the BTC-USD instrument)
from polymarket import AsyncPublicClient
from polymarket.streams import PerpsBboSpec
client = AsyncPublicClient()
ticker = await client.fetch_perps_ticker(instrument_id=btc.id)
print(ticker.mark_price, ticker.index_price, ticker.funding_rate)
stream = await client.subscribe(PerpsBboSpec(instrument_id=btc.id))
async for event in stream:
print(event.payload.bid_price, event.payload.ask_price)
// After Step 1 (btc is the BTC-USD instrument)
import { createPublicClient } from "@polymarket/client";
const client = createPublicClient();
const ticker = await client.fetchPerpsTicker({ instrumentId: btc.id });
console.log(ticker.markPrice, ticker.indexPrice, ticker.fundingRate);
const stream = await client.subscribe([{ topic: "perps.bbo", instrumentId: btc.id }]);
for await (const event of stream) {
console.log(event.payload.bidPrice, event.payload.askPrice);
}
Без SDK підпишіться через «сирий» WebSocket повідомленням {"id": 1, "req": "sub", "chs": ["bbo::6"]}. Серед каналів є bbo::<id>, book::<id>, trades::<id>, tickers::<id> і klines::<id>::1m. Кожне оновлення має порядковий номер sq: після пропуску або перепідключення заново зчитайте стан через REST. Для бектестів list_perps_candles / listPerpsCandles посторінково завантажує історію (ендпоінт REST повертає до 1 000 свічок за один запит).
Крок 3: розміщуйте ордери з тейк-профітом і стоп-лосом
Ордер має side (BUY або SELL), quantity в одиницях базового активу, необовʼязкову price у pUSD і строк дії (gtc, ioc або fok). Post-only потребує GTC; reduce-only може лише зменшити позицію. Окремого прапорця long чи short немає: коли позиції немає, BUY відкриває long, а SELL відкриває short.
Брекет-ордер прикріплює до входу один тейк-профіт і один стоп-лос. Вони неактивні, доки вхідний ордер не виконається повністю, і скасовуються, якщо вхід скасовано або виконано лише частково. Ціни нижче умовні.
# Inside your async main(), after Step 1 (btc is the BTC-USD instrument)
from polymarket import PerpsTpSlTrigger
result = await session.place_order(
instrument_id=btc.id,
side="BUY",
price="100000",
quantity="0.01",
time_in_force="gtc",
take_profit=PerpsTpSlTrigger(trigger_price="104000"),
stop_loss=PerpsTpSlTrigger(trigger_price="98000"),
)
print(result.order.id, result.order.status)
// After Step 1 (btc is the BTC-USD instrument)
import { OrderSide, PerpsTimeInForce } from "@polymarket/client";
const result = await session.placeOrder({
instrumentId: btc.id,
side: OrderSide.BUY,
price: "100000",
quantity: "0.01",
timeInForce: PerpsTimeInForce.GTC,
takeProfit: { triggerPrice: "104000" },
stopLoss: { triggerPrice: "98000" },
});
console.log(result.order.id, result.tpSl.stopLoss?.orderId);
Для відкритої позиції place_position_tp_sl / placePositionTpSl додає тригери, які закривають усю позицію. Правила:
- Тригери відстежують маркувальну ціну і надсилають ордер reduce-only. TP/SL позиції виходить за ринком, тож у тонкому стакані очікуйте прослизання.
- На кожному ринку можна мати один тейк-профіт і один стоп-лос позиції. Щоб пересунути стоп, його треба скасувати й створити заново, і ця операція не атомарна.
- Тригер, який маркувальна ціна вже перетнула, відхиляється, як і лімітні ціни, що надто далекі від маркувальної.
Щоб спробувати процес розміщення ордерів вручну перед автоматизацією, відкрийте ринок BTC на Полімаркет Perps і розмістіть найменший дозволений ордер. Коли ви побачите виконання, комісії та панель позиції, відповіді API буде легше зрозуміти.
Крок 4: каркас бота з контролем ризиків у коді
Каркас на Python нижче використовує лише задокументовані виклики SDK; логіка ризиків наша. Три рівні захисту:
- Обмеження кредитного плеча. Кредитне плече встановлюється на 3x або на максимум ринку, залежно від того, що менше, з ізольованою маржею. Розміри, що потрапляють у рівень ризику з нижчим лімітом, відхиляються.
- Аварійні вимикачі. Файл HALT, денний ліміт збитку 5% і вихід у разі ліквідації. Вимикач закриває позиції ордерами reduce-only IOC, скасовує решту ордерів, а потім перевіряє, що не лишилося ні позицій, ні ордерів.
- Стоп на кожній позиції. Входи fill-or-kill гарантують, що не буде незахищених часткових виконань. Після кожного виконання встановлюються стоп і тейк-профіт позиції; якщо їх відхилено, позиція одразу закривається.
import asyncio
import logging
import os
from datetime import timedelta
from decimal import Decimal, ROUND_DOWN
from polymarket import AsyncSecureClient, PerpsPositionTpSlTrigger
SYMBOL = "BTC-USD" # live API symbol; docs examples say "BTC-PERP"
LEVERAGE_CAP = 3 # our cap, far below the 50x BTC allows
RISK_PER_TRADE = Decimal("20") # pUSD we accept losing if the stop fills
STOP_PCT = Decimal("0.02") # stop 2% from entry
TAKE_PCT = Decimal("0.04") # take profit 4% from entry
MAX_NOTIONAL = Decimal("2000") # hard cap per position, in pUSD
MAX_SLIPPAGE = Decimal("0.002") # worst entry price: ask + 0.2%
MAX_FUNDING = Decimal("0.0005") # skip longs if funding > 0.05% per hour
DAILY_LOSS_LIMIT = Decimal("0.05") # kill switch at -5% of start equity
HALT_FILE = "/opt/perps-bot/HALT" # touch this file to stop the bot
def fit(value: Decimal, decimals: int, sig: int = 5) -> str:
"""Round down to the market's decimals and to 5 significant figures."""
v = value.quantize(Decimal(1).scaleb(-decimals), rounding=ROUND_DOWN)
if v != 0:
step = Decimal(1).scaleb(v.adjusted() - sig + 1)
if step > Decimal(1).scaleb(-decimals):
v = v.quantize(step, rounding=ROUND_DOWN)
return format(v, "f")
def tier_cap(inst, notional: Decimal) -> int:
"""Max leverage allowed for this notional by the market's risk tiers."""
cap = inst.max_leverage
for tier in inst.risk_tiers:
if notional >= Decimal(tier.lower_bound):
cap = tier.max_leverage
return cap
async def close_all(session):
"""Close every position with reduce-only IOC orders, then cancel the rest."""
portfolio = await session.fetch_portfolio()
for pos in portfolio.positions:
if pos.size != 0:
await session.place_order(
instrument_id=pos.instrument_id,
side="SELL" if pos.size > 0 else "BUY",
quantity=abs(pos.size),
time_in_force="ioc",
reduce_only=True,
)
await session.cancel_all_orders()
async def kill_switch(session, reason: str):
logging.error("KILL SWITCH: %s", reason)
for attempt in range(3):
await close_all(session)
portfolio = await session.fetch_portfolio()
open_orders = await session.fetch_open_orders()
if not open_orders and all(p.size == 0 for p in portfolio.positions):
return
await asyncio.sleep(2)
logging.critical("Positions still open after kill switch: act manually")
async def open_long(client, session, inst):
ticker = await client.fetch_perps_ticker(instrument_id=inst.id)
if Decimal(ticker.funding_rate) > MAX_FUNDING:
return # longs would pay too much
book = await client.fetch_perps_book(instrument_id=inst.id, depth=10)
if not book.asks:
return
ask = Decimal(book.asks[0].price)
stop = ask * (1 - STOP_PCT)
qty = RISK_PER_TRADE / (ask - stop) # size from the stop, not leverage
qty = min(qty, MAX_NOTIONAL / ask)
notional = qty * ask
if notional < Decimal(inst.min_notional):
return
if tier_cap(inst, notional) < LEVERAGE_CAP:
return # size would break our cap
entry = await session.place_order(
instrument_id=inst.id,
side="BUY",
price=fit(ask * (1 + MAX_SLIPPAGE), inst.price_decimals),
quantity=fit(qty, inst.quantity_decimals),
time_in_force="fok", # all or nothing
)
if entry.order.status != "filled":
return
try: # stop on the whole position
await session.place_position_tp_sl(
instrument_id=inst.id,
take_profit=PerpsPositionTpSlTrigger(
trigger_price=fit(ask * (1 + TAKE_PCT), inst.price_decimals)),
stop_loss=PerpsPositionTpSlTrigger(
trigger_price=fit(stop, inst.price_decimals)),
)
except Exception:
logging.exception("TP/SL rejected: closing the unprotected position")
await close_all(session)
async def my_signal(client, inst) -> bool:
"""PLACEHOLDER: your strategy goes here. Not part of the SDK."""
return False
async def main():
logging.basicConfig(level=logging.INFO)
client = await AsyncSecureClient.create(
private_key=os.environ["PRIVATE_KEY"],
wallet=os.environ["POLYMARKET_WALLET_ADDRESS"],
)
session = await client.open_perps_session(
expires_in=timedelta(days=1), label="perps-bot")
inst = next(i for i in await client.fetch_perps_instruments()
if i.symbol == SYMBOL)
# Control 1: leverage cap and isolated margin, set before any order
await session.update_leverage(
instrument_id=inst.id,
leverage=min(LEVERAGE_CAP, inst.max_leverage),
cross_margin=False,
)
# Field names below follow the documented portfolio JSON
# (margin.total_account_value, in_liquidation). Check them in your SDK.
start_equity = Decimal((await session.fetch_portfolio()).margin.total_account_value)
while True:
portfolio = await session.fetch_portfolio()
equity = Decimal(portfolio.margin.total_account_value)
# Control 2: kill switches
if os.path.exists(HALT_FILE):
await kill_switch(session, "HALT file found")
break
if equity < start_equity * (1 - DAILY_LOSS_LIMIT):
await kill_switch(session, f"daily loss limit hit: {equity}")
break
if portfolio.in_liquidation:
await kill_switch(session, "account in liquidation")
break
in_position = any(p.instrument_id == inst.id and p.size != 0
for p in portfolio.positions)
if not in_position and await my_signal(client, inst):
await open_long(client, session, inst) # Control 3 lives here
await asyncio.sleep(5)
asyncio.run(main())
Розрахунок розміру. Розмір визначається відстанню до стопу, а не кредитним плечем. Візьмімо умовну ціну продажу BTC 100 000 pUSD і стоп 2%: стоп стоїть на 98 000. Ризик на одиницю становить 2 000, тож кількість дорівнює 20 / 2 000 = 0.01 BTC, тобто номінал 1 000 pUSD. З плечем 3x для цього потрібно близько 333 pUSD маржі. Ставка підтримувальної маржі для BTC становить 1% (0.5 / 50x), тож ціна ліквідації лонга дорівнює 100 000 x (1 - 1/3) / (1 - 0.01) = приблизно 67 340, тобто падіння на 32.7%. Стоп на -2% спрацює задовго до цього. Якщо стоп виконається за ціною тригера, збиток становитиме 20 pUSD плюс комісії: 0.40 за відкриття і приблизно 0.39 за закриття за тарифом тейкера 0.040%. Разом це приблизно 20.79 pUSD без урахування прослизання. Кредитне плече визначає лише те, скільки маржі заблоковано. Воно не змінює збиток на стопі.
Перед запуском на реальних коштах додайте денний ліміт угод, сповіщення про виконання та спрацювання аварійних вимикачів, а також звірку стану під час старту (fetch_portfolio, fetch_open_orders, fetch_orders, list_fills). Дивіться наші розділи про код для керування ризиками і паперову торгівлю, посібник з кредитного плеча та ліквідації в Perps і посібник зі ставок фінансування в Perps.
Помилки, повторні спроби та автоматичне скасування на випадок збою
503 service_unavailableне означає, що запит точно не пройшов. Повторюйте запити на читання і зміни кредитного плеча зі збільшенням інтервалу та враховуйтеRetry-After. Для підписаних змін балансу (маржа, виведення) повторно надсилайте лише точно той самий підписаний запит; новий підпис означатиме другу зміну.signature expired: підпис старший за 5 хвилин. Синхронізуйте годинник.insufficient_margin/invalid_leverage: перевірки враховують вашу позицію плюс усі активні ордери.message_rate_limited: ліміт повідомлень WebSocket вичерпано. Ключі лише для читання мають 1 000 повідомлень на хвилину з однієї IP-адреси (пікове навантаження 50) і чотири зʼєднання на рахунок на шлюз; інші ліміти дивіться на сторінці Rate Limits у документації.- Помилки формату: забагато знаків після крапки або понад 5 значущих цифр. Саме для цього потрібна
fit().
Автоматичне скасування скасовує всі відкриті ордери, якщо ваш бот перестає його поновлювати. Дедлайн має бути щонайменше на 5 секунд у майбутньому, а кількість спрацювань за добу UTC обмежена, тому вимикайте його під час коректного завершення роботи.
// For quoting bots: cancel all resting orders if this process dies
await session.armAutoCancel({ cancelAt: Date.now() + 60_000 });
const rearm = setInterval(() => {
// A missed re-arm is fail-safe: the armed switch still fires.
session.armAutoCancel({ cancelAt: Date.now() + 60_000 }).catch(console.error);
}, 20_000);
// On a clean shutdown
clearInterval(rearm);
await session.disarmAutoCancel();
Змагання, коди білдерів і правила для ботів
У документації серед сценаріїв використання API названо «торгових ботів, що реагують на ринкові сигнали» та маркетмейкерів. Діють два набори правил.
- Змагання. Заборонено фіктивну торгівлю (wash trading), торгівлю із самим собою, кілька рахунків, торгівлю переважно заради обсягу та «ботів, скрипти або автоматизовані засоби, призначені для маніпулювання Програмою». Бот, що справді торгує, дозволений; бот, що накручує обсяг, можуть дискваліфікувати.
- Коди білдерів. Застосунок, що торгує для інших користувачів, може додавати комісію до 10 базисних пунктів за згодою кожного трейдера. Він має перевіряти місцезнаходження кожного користувача й блокувати ордери з юрисдикцій з обмеженнями, а не лише попереджати.
Винагороди мейкерам за ліквідність і змагання описано в посібнику з Полімаркет Perps.
Хостинг бота
Бот із кредитним плечем не варто запускати на ноутбуці. Він має підтримувати WebSocket-зʼєднання, реагувати на виконання за секунди й переживати перебої домашнього інтернету. Запускайте його на VPS під systemd з автоматичним перезапуском, тримайте ключ у файлі змінних середовища, який може читати лише користувач бота, і синхронізуйте годинник. Розміщувати ордери з регіонів з обмеженнями заборонено, тому не хостьте бота у США чи Канаді й ніколи не використовуйте VPS, щоб обійти блокування, яке стосується вас. Наші власні боти працюють на TradingVPS (партнерське посилання: ми отримуємо невелику комісію, а ціна для вас не змінюється). У нашому розділі про VPS-хостинг його порівняно з іншими провайдерами за затримкою, ціною та регіонами.
Після паперової торгівлі поповніть рахунок невеликою сумою на сторінці Полімаркет Perps і тиждень працюйте з мінімальним розміром, перш ніж масштабуватися.
Головне
- Використовуйте офіційні SDK,
@polymarket/clientабоpolymarket-client. Ваш гаманець підписує один раз; ордери підписує проксі-ключ. - Шукайте ринки за актуальним символом (
BTC-USD, а неBTC-PERP) і зберігайте числовий ID. - Визначайте розмір від відстані до стопу, обмежуйте кредитне плече в коді, використовуйте ізольовану маржу і ставте стоп на кожну позицію одразу після виконання.
- TP/SL спрацьовує за маркувальною ціною і виходить за ринком. Автоматичне скасування може прибрати стопи.
- Боти дозволені, маніпулювання змаганнями заборонене. Ніколи не торгуйте з країни з обмеженнями.





