Tutorial bot Polymarket · Bab 24 dari 32
Polymarket Perps memiliki API publik lengkap dengan SDK resmi untuk TypeScript dan Python. Bab ini membangun bot di atas antarmuka yang sebenarnya: autentikasi dengan kunci trading proxy, data pasar lewat REST dan WebSocket, order dengan take-profit dan stop-loss, serta kontrol risiko yang mencegah bot ber-leverage menguras akun.
Isi bab ini
- Angka-angka penting untuk pembuat bot
- Cara API Perps dibangun
- Langkah 1: instal SDK dan buka sesi
- Langkah 2: baca data pasar lewat REST dan WebSocket
- Langkah 3: pasang order dengan take-profit dan stop-loss
- Langkah 4: kerangka bot dengan kontrol risiko di dalam kode
- Error, dead man's switch, aturan untuk bot, dan hosting
Setiap metode SDK dan endpoint di sini berasal dari dokumentasi Perps Polymarket (docs.polymarket.com/perps, dibaca pada 6 November 2026). Logika buatan kami sendiri ditandai di komentar, dan fungsi strategi adalah placeholder yang diberi label.
Angka-angka penting untuk pembuat bot (per November 2026)
| Item | Nilai |
|---|---|
| URL dasar REST | https://api.perpetuals.polymarket.com |
| WebSocket | wss://ws.perpetuals.polymarket.com/v1/ws |
| SDK TypeScript | @polymarket/client (dengan viem untuk penandatanganan) |
| SDK Python | polymarket-client (async) |
| Data publik | /v1/info/*, tanpa autentikasi |
| Pembacaan privat | /v1/account/* dengan header polymarket-proxy dan polymarket-secret |
| Penulisan bertanda tangan | /v1/trade/*, tanda tangan EIP-712 oleh kunci proxy, berlaku selama 5 menit |
| Kredensial sesi | Kedaluwarsa setelah satu minggu secara default |
| Order minimum | Nosional $10; deposit minimum 10 pUSD |
| Order market terbesar | $1 juta di pasar 50x dan 20x, $500 ribu di sebagian besar pasar lain, $50 ribu di koin 3x dan 5x |
| Format harga dan ukuran | String desimal, tidak melebihi jumlah desimal pasar dan maksimal 5 angka penting |
| Leverage maksimum | 50x (BTC, ETH, USA500, US100), selain itu 20x, 10x, 5x, atau 3x per pasar, lebih rendah untuk posisi besar |
| Biaya (tier dasar) | Taker 0,040%, maker 0,0125%; ANT setengah harga |
| Funding | Per jam, dibatasi +/-4% per jam |
| Trigger TP/SL | Harga mark, bukan harga trade terakhir |
| Keepalive WebSocket | Ping sekitar setiap 25 detik; anggap koneksi basi setelah sekitar 65 detik tanpa data |
| Kesegaran ticker | Data hingga 10 detik |
Cara API Perps dibangun
Perps adalah bursa hibrida: pencocokan order dilakukan offchain, sedangkan kustodi dan penyelesaian berada di Polygon. Hanya deposit dan penarikan yang terjadi onchain, jadi tidak ada biaya gas per order. API ini memiliki tiga lapisan:
- Data publik (
/v1/info/): instrumen, ticker, buku pesanan, candle, trade, funding, biaya. Tidak perlu kunci dan tidak ada pemblokiran geografis. - Pembacaan privat (
/v1/account/): portofolio, saldo, order, eksekusi, konfigurasi. - Penulisan bertanda tangan (
/v1/trade/): order, pembatalan, leverage, margin terisolasi, auto-cancel. Setiap penulisan dienkode dengan MessagePack, di-hash dengan keccak256, lalu ditandatangani sebagai data bertipe EIP-712 oleh kunci proxy.
SDK menangani penandatanganan untuk Anda. Tersedia juga spesifikasi OpenAPI (/api-spec/perps-openapi.json di situs dokumentasi) dan spesifikasi AsyncAPI untuk WebSocket.
BTC-PERP. API live mengembalikan BTC-USD, SP500-USD (USA500), NAS100-USD (US100), ANT-USD, dan seterusnya. Ambil daftar instrumen, cocokkan dengan symbol live, lalu simpan id numeriknya. Jangan menyalin ID dari contoh: di API live, BTC adalah 6 dan SP500 adalah 1.Langkah 1: instal SDK dan buka sesi
Instal dengan pip install polymarket-client atau npm install @polymarket/client@latest viem. Bot memerlukan alamat wallet Polymarket Anda dan private key milik penanda tangan yang memiliki akun Perps (akun terikat ke EOA penanda tangan, bukan ke Safe). Saat membuka sesi, Anda menandatangani sekali untuk mengotorisasi kunci trading proxy baru, yang kemudian menandatangani setiap order, sehingga kunci pendanaan Anda tidak pernah dipakai untuk trading. Simpan session.credentials dalam keadaan terenkripsi agar sesi bisa dilanjutkan nanti.
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 dari kode (deposit_to_perps / depositToPerps) juga memerlukan Relayer API key dari menu Settings polymarket.com, API Keys. Cara yang lebih sederhana dan lebih aman: danai akun lewat aplikasi web dan jauhkan bot dari perpindahan dana. Sejak 5 November 2026, akun yang memakai buku alamat penarikan hanya dapat menarik dana ke alamat yang tersimpan. Untuk dashboard, gunakan kunci read-only (proxy berlabel read-only:, yang menolak penulisan bertanda tangan), jangan pernah memakai kunci trading. Penyimpanan kunci dibahas di bab wallet dan autentikasi kami.
Langkah 2: baca data pasar lewat REST dan WebSocket
Ticker memiliki tiga harga: index (nilai wajar dari feed seperti Pyth, Chainlink Data Streams, dan Hyperliquid), mark (dipakai untuk PnL, margin, likuidasi, dan TP/SL), dan last (hanya untuk eksekusi), ditambah funding rate dan waktu funding berikutnya. Dasarkan sinyal pada harga mark atau index.
# 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);
}
Tanpa SDK, lakukan subscribe di WebSocket mentah dengan {"id": 1, "req": "sub", "chs": ["bbo::6"]}. Channel yang tersedia antara lain bbo::<id>, book::<id>, trades::<id>, tickers::<id>, dan klines::<id>::1m. Setiap pembaruan memiliki nomor urut sq: setelah ada celah atau koneksi ulang, baca ulang state lewat REST. Untuk backtest, list_perps_candles / listPerpsCandles melakukan paginasi data historis (endpoint REST mengembalikan hingga 1.000 candle per panggilan).
Langkah 3: pasang order dengan take-profit dan stop-loss
Sebuah order memiliki side (BUY atau SELL), quantity dalam satuan aset dasar, price opsional dalam pUSD, dan time in force (gtc, ioc, atau fok). Post-only memerlukan GTC; reduce-only hanya dapat memperkecil posisi. Tidak ada flag long atau short: jika belum ada posisi, BUY membuka long dan SELL membuka short.
Bracket order memasang satu take-profit dan satu stop-loss pada order entry. Keduanya tetap nonaktif sampai entry tereksekusi penuh, dan dibatalkan jika entry dibatalkan atau hanya tereksekusi sebagian. Harga di bawah ini bersifat hipotetis.
# 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);
Untuk posisi yang sudah terbuka, place_position_tp_sl / placePositionTpSl menambahkan trigger yang menutup seluruh posisi. Aturannya:
- Trigger memantau harga mark dan mengirim order reduce-only. TP/SL posisi keluar dengan harga market, jadi perhitungkan slippage di buku pesanan yang tipis.
- Satu take-profit dan satu stop-loss posisi per pasar. Memindahkan stop berarti membatalkan lalu membuat yang baru, dan proses ini tidak atomik.
- Trigger yang sudah dilewati harga mark akan ditolak, dan harga limit yang terlalu jauh dari harga mark juga ditolak.
Untuk mencoba alur order secara manual sebelum mengotomatiskannya, buka pasar BTC di Polymarket Perps dan pasang order terkecil yang diizinkan. Dengan melihat langsung eksekusi, biaya, dan panel posisi, respons API akan lebih mudah dipahami.
Langkah 4: kerangka bot dengan kontrol risiko di dalam kode
Kerangka Python di bawah ini hanya memakai panggilan SDK yang terdokumentasi; logika risikonya buatan kami. Ada tiga lapisan perlindungan:
- Batas leverage. Leverage diatur ke 3x atau batas maksimum pasar, mana yang lebih rendah, dengan margin terisolasi, dan ukuran posisi yang masuk ke tier risiko dengan batas lebih rendah akan ditolak.
- Kill switch. File HALT, batas kerugian harian 5%, dan keluar saat terjadi likuidasi. Switch ini menutup posisi dengan order IOC reduce-only, membatalkan order yang tersisa, lalu memeriksa bahwa keduanya sudah tidak ada.
- Stop di setiap posisi. Entry fill-or-kill berarti tidak ada eksekusi sebagian yang tidak terlindungi. Stop dan take-profit posisi dipasang setelah setiap eksekusi; jika ditolak, posisi langsung ditutup.
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())
Perhitungan ukuran posisi. Ukuran ditentukan dari jarak stop, bukan dari leverage. Dengan harga offer BTC hipotetis 100.000 pUSD dan stop 2%, stop berada di 98.000. Risiko per unit adalah 2.000, jadi kuantitasnya 20 / 2.000 = 0,01 BTC, dengan nilai nosional 1.000 pUSD. Pada 3x, posisi ini membutuhkan margin sekitar 333 pUSD. Tingkat maintenance margin BTC adalah 1% (0,5 / 50x), jadi harga likuidasi posisi long adalah 100.000 x (1 - 1/3) / (1 - 0,01) = sekitar 67.340, atau penurunan 32,7%. Stop di -2% terpicu jauh sebelum itu. Jika stop tereksekusi tepat di harga trigger-nya, kerugiannya 20 pUSD ditambah biaya: 0,40 untuk membuka dan sekitar 0,39 untuk menutup dengan tarif taker 0,040%, jadi sekitar 20,79 pUSD sebelum slippage. Leverage hanya menentukan berapa banyak margin yang terkunci. Leverage tidak mengubah besar kerugian saat stop tersentuh.
Sebelum go-live, tambahkan batas trade harian, notifikasi untuk eksekusi dan kill switch, serta rekonsiliasi saat startup (fetch_portfolio, fetch_open_orders, fetch_orders, list_fills). Lihat bab kode manajemen risiko dan paper trading kami, panduan leverage dan likuidasi Perps, serta panduan funding rate Perps.
Error, retry, dan dead man's switch
503 service_unavailabletidak membuktikan bahwa request gagal. Ulangi pembacaan dan perubahan leverage dengan backoff, dan patuhiRetry-After. Untuk perubahan saldo bertanda tangan (margin, penarikan), kirim ulang hanya request bertanda tangan asli yang sama persis; tanda tangan baru akan dihitung sebagai penyesuaian kedua.signature expired: tanda tangan lebih lama dari 5 menit. Pastikan jam sistem tetap tersinkronisasi.insufficient_margin/invalid_leverage: pemeriksaan memperhitungkan posisi Anda ditambah semua order yang masih aktif di buku.message_rate_limited: kuota WebSocket habis. Kunci read-only mendapat 1.000 pesan per menit per IP (burst 50) dan empat koneksi per akun per gateway; lihat halaman Rate Limits di dokumentasi untuk batas lainnya.- Error format: terlalu banyak desimal atau lebih dari 5 angka penting. Karena itulah ada
fit().
Auto-cancel membatalkan semua order terbuka jika bot Anda berhenti mengaktifkannya ulang. Tenggatnya harus minimal 5 detik ke depan, dan jumlah pemicuan per hari UTC dibatasi, jadi nonaktifkan fitur ini saat shutdown normal.
// 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();
Kompetisi, builder code, dan aturan untuk bot
Dokumentasi mencantumkan "bot trading yang bereaksi terhadap sinyal pasar" dan market maker sebagai kegunaan API. Ada dua set aturan yang berlaku.
- Kompetisi. Wash trading, self-trading, penggunaan banyak akun, trading terutama demi volume, dan "bot, skrip, atau sarana otomatis yang dimaksudkan untuk memanipulasi Program" dilarang. Bot yang benar-benar melakukan trading tidak masalah; bot yang hanya memutar volume dapat didiskualifikasi.
- Builder code. Aplikasi yang melakukan trading untuk pengguna lain dapat menambahkan biaya hingga 10 basis poin dengan persetujuan setiap trader. Aplikasi tersebut wajib memverifikasi lokasi setiap pengguna dan memblokir order dari yurisdiksi terlarang, bukan sekadar memberi peringatan.
Reward likuiditas untuk maker dan kompetisi dibahas di panduan Polymarket Perps.
Hosting bot
Bot ber-leverage sebaiknya tidak dijalankan di laptop. Bot harus menjaga WebSocket tetap hidup, bereaksi terhadap eksekusi dalam hitungan detik, dan tetap berjalan saat internet rumah putus. Jalankan di VPS di bawah systemd dengan restart otomatis, simpan kunci di file environment yang hanya bisa dibaca oleh user bot, dan sinkronkan jam sistem. Penempatan order tidak diizinkan dari lokasi terlarang, jadi jangan melakukan hosting di AS atau Kanada, dan jangan pernah memakai VPS untuk mengakali pemblokiran yang berlaku bagi Anda. Kami menjalankan bot kami sendiri di TradingVPS (tautan afiliasi: kami mendapat komisi kecil dan harga yang Anda bayar tetap sama). Bab hosting VPS kami membandingkannya dengan penyedia lain dari sisi latensi, harga, dan region.
Setelah paper trading, danai akun dengan jumlah kecil dari halaman Polymarket Perps dan jalankan bot dengan ukuran minimum selama seminggu sebelum memperbesar skala.
Poin-poin penting
- Gunakan SDK resmi,
@polymarket/clientataupolymarket-client. Wallet Anda menandatangani sekali; kunci proxy yang menandatangani order. - Cocokkan pasar berdasarkan simbol live (
BTC-USD, bukanBTC-PERP) dan simpan ID numeriknya. - Tentukan ukuran posisi dari jarak stop, batasi leverage di dalam kode, gunakan margin terisolasi, dan pasang stop di setiap posisi segera setelah tereksekusi.
- TP/SL terpicu berdasarkan harga mark dan keluar dengan harga market. Auto-cancel dapat menghapus stop.
- Bot diizinkan, memanipulasi kompetisi tidak. Jangan pernah melakukan trading dari negara terlarang.





