Tutorial Bot Polymarket · Bab 24 daripada 32
Polymarket Perps mempunyai API awam yang lengkap dengan SDK TypeScript dan Python rasmi. Bab ini membina bot di atas antara muka sebenar: pengesahan dengan kunci dagangan proksi, data pasaran melalui REST dan WebSocket, pesanan dengan ambil untung dan henti rugi, serta kawalan risiko yang menghalang bot berleveraj daripada mengosongkan akaun anda.
Kandungan bab ini
- Angka utama untuk pembina bot
- Cara API Perps dibina
- Langkah 1: pasang SDK dan buka sesi
- Langkah 2: baca data pasaran melalui REST dan WebSocket
- Langkah 3: buat pesanan dengan ambil untung dan henti rugi
- Langkah 4: rangka bot dengan kawalan risiko dalam kod
- Ralat, dead man's switch, peraturan untuk bot dan pengehosan
Setiap kaedah SDK dan endpoint di sini diambil daripada dokumentasi Perps Polymarket (docs.polymarket.com/perps, dibaca pada 6 November 2026). Logik kami sendiri ditanda dalam komen, dan fungsi strategi ialah pemegang tempat yang dilabelkan.
Angka utama untuk pembina bot (setakat November 2026)
| Perkara | Nilai |
|---|---|
| URL asas REST | https://api.perpetuals.polymarket.com |
| WebSocket | wss://ws.perpetuals.polymarket.com/v1/ws |
| SDK TypeScript | @polymarket/client (dengan viem untuk tandatangan) |
| SDK Python | polymarket-client (async) |
| Data awam | /v1/info/*, tanpa pengesahan |
| Bacaan peribadi | /v1/account/* dengan pengepala polymarket-proxy dan polymarket-secret |
| Tulisan bertandatangan | /v1/trade/*, tandatangan EIP-712 oleh kunci proksi, sah selama 5 minit |
| Kelayakan sesi | Tamat tempoh selepas satu minggu secara lalai |
| Pesanan minimum | Nilai nosional $10; deposit minimum 10 pUSD |
| Pesanan pasaran terbesar | $1M bagi pasaran 50x dan 20x, $500K bagi kebanyakan pasaran lain, $50K bagi koin 3x dan 5x |
| Format harga dan saiz | Rentetan perpuluhan, tidak melebihi bilangan perpuluhan pasaran dan 5 angka bererti |
| Leveraj maksimum | 50x (BTC, ETH, USA500, US100), selain itu 20x, 10x, 5x atau 3x mengikut pasaran, lebih rendah bagi posisi besar |
| Yuran (peringkat asas) | Taker 0.040%, maker 0.0125%; ANT pada separuh harga |
| Pembiayaan (funding) | Setiap jam, dihadkan pada +/-4% sejam |
| Pencetus TP/SL | Harga tanda (mark price), bukan dagangan terakhir |
| Keepalive WebSocket | Ping kira-kira setiap 25 saat; anggap sambungan basi selepas kira-kira 65 saat senyap |
| Kesegaran ticker | Sehingga 10 saat lama |
Cara API Perps dibina
Perps ialah bursa hibrid: padanan pesanan berlaku di luar rantaian (offchain), manakala jagaan dan penyelesaian berlaku di Polygon. Hanya deposit dan pengeluaran berlaku di rantaian (onchain), jadi tiada gas bagi setiap pesanan. API ini mempunyai tiga lapisan:
- Data awam (
/v1/info/): instrumen, ticker, buku pesanan, lilin, dagangan, pembiayaan, yuran. Tiada kunci diperlukan dan tiada sekatan geografi. - Bacaan peribadi (
/v1/account/): portfolio, baki, pesanan, isian, konfigurasi. - Tulisan bertandatangan (
/v1/trade/): pesanan, pembatalan, leveraj, margin terasing, auto-cancel. Setiap tulisan dikodkan dengan MessagePack, dicincang dengan keccak256 dan ditandatangani sebagai data bertaip EIP-712 oleh kunci proksi.
SDK melakukan tandatangan untuk anda. Terdapat juga spesifikasi OpenAPI (/api-spec/perps-openapi.json di laman dokumentasi) dan spesifikasi AsyncAPI untuk WebSocket.
BTC-PERP. API sebenar memulangkan BTC-USD, SP500-USD (USA500), NAS100-USD (US100), ANT-USD dan sebagainya. Ambil senarai instrumen, padankan symbol sebenar dan simpan id berangka. Jangan salin ID daripada contoh: dalam sistem sebenar, BTC ialah 6 dan SP500 ialah 1.Langkah 1: pasang SDK dan buka sesi
Pasang dengan pip install polymarket-client atau npm install @polymarket/client@latest viem. Bot memerlukan alamat dompet Polymarket anda dan kunci peribadi penandatangan yang memiliki akaun Perps (akaun dikaitkan dengan EOA penandatangan, bukan Safe). Membuka sesi memerlukan satu tandatangan untuk membenarkan kunci dagangan proksi yang baharu, yang kemudiannya menandatangani setiap pesanan, jadi kunci dana anda tidak pernah berdagang. Simpan session.credentials dalam bentuk tersulit untuk menyambung semula kemudian.
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);
Membuat deposit melalui kod (deposit_to_perps / depositToPerps) juga memerlukan kunci API Relayer daripada polymarket.com, Settings, API Keys. Cara yang lebih mudah dan selamat: isi dana akaun melalui aplikasi web dan jauhkan bot daripada pergerakan wang. Sejak 5 November 2026, akaun yang menggunakan buku alamat pengeluaran hanya boleh mengeluarkan wang ke alamat yang disimpan. Untuk papan pemuka, gunakan kunci baca sahaja (proksi berlabel read-only:, yang menolak tulisan bertandatangan), jangan sekali-kali kunci dagangan. Penyimpanan kunci dibincangkan dalam bab dompet dan pengesahan kami.
Langkah 2: baca data pasaran melalui REST dan WebSocket
Ticker mempunyai tiga harga: indeks (nilai saksama daripada suapan seperti Pyth, Chainlink Data Streams dan Hyperliquid), harga tanda (digunakan untuk PnL, margin, likuidasi dan TP/SL) dan terakhir (untuk pelaksanaan sahaja), serta kadar pembiayaan dan masa pembiayaan seterusnya. Asaskan isyarat pada harga tanda atau indeks.
# 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, langgan pada WebSocket mentah dengan {"id": 1, "req": "sub", "chs": ["bbo::6"]}. Saluran termasuk bbo::<id>, book::<id>, trades::<id>, tickers::<id> dan klines::<id>::1m. Setiap kemas kini mempunyai nombor jujukan sq: selepas jurang atau sambungan semula, baca semula keadaan melalui REST. Untuk ujian balik (backtest), list_perps_candles / listPerpsCandles menyenaraikan sejarah mengikut halaman (endpoint REST memulangkan sehingga 1,000 lilin setiap panggilan).
Langkah 3: buat pesanan dengan ambil untung dan henti rugi
Sesuatu pesanan mempunyai side (BUY atau SELL), quantity dalam unit aset asas, price pilihan dalam pUSD, dan tempoh sah (gtc, ioc atau fok). Post-only memerlukan GTC; reduce-only hanya boleh mengecilkan posisi. Tiada bendera long atau short: jika tiada posisi, BUY membuka long dan SELL membuka short.
Pesanan bracket melampirkan satu ambil untung dan satu henti rugi pada pesanan masuk. Kedua-duanya kekal tidak aktif sehingga pesanan masuk diisi sepenuhnya, dan dibatalkan jika pesanan masuk dibatalkan atau hanya diisi separa. Harga di bawah adalah hipotesis.
# 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);
Bagi posisi terbuka, place_position_tp_sl / placePositionTpSl menambah pencetus yang menutup keseluruhan posisi. Peraturannya:
- Pencetus memantau harga tanda dan menghantar pesanan reduce-only. TP/SL posisi keluar pada harga pasaran, jadi jangkakan slippage dalam buku pesanan yang nipis.
- Satu ambil untung dan satu henti rugi posisi bagi setiap pasaran. Mengalihkan henti rugi bermaksud membatalkan kemudian mencipta semula, dan ini tidak atomik.
- Pencetus yang sudah dilintasi oleh harga tanda akan ditolak, dan harga had yang terlalu jauh daripada harga tanda juga ditolak.
Untuk mencuba aliran pesanan secara manual sebelum mengautomasikannya, buka pasaran BTC di Polymarket Perps dan buat pesanan terkecil yang dibenarkan. Melihat isian, yuran dan panel posisi menjadikan respons API lebih mudah difahami.
Langkah 4: rangka bot dengan kawalan risiko dalam kod
Rangka Python di bawah hanya menggunakan panggilan SDK yang didokumenkan; logik risikonya ialah milik kami. Terdapat tiga lapisan perlindungan:
- Had leveraj. Leveraj ditetapkan pada 3x atau maksimum pasaran, yang mana lebih rendah, dengan margin terasing, dan saiz yang jatuh ke dalam peringkat risiko berhad lebih rendah akan ditolak.
- Suis henti (kill switch). Fail HALT, had kerugian harian 5% dan keluar apabila berlaku likuidasi. Suis ini menutup posisi dengan pesanan IOC reduce-only, membatalkan pesanan yang tinggal, kemudian menyemak bahawa kedua-duanya sudah tiada.
- Henti rugi pada setiap posisi. Pesanan masuk fill-or-kill bermaksud tiada isian separa yang tidak dilindungi. Henti rugi dan ambil untung posisi dipasang selepas setiap isian; jika ditolak, posisi terus 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())
Kiraan saiz. Saiz ditentukan oleh jarak henti rugi, bukan oleh leveraj. Dengan harga ask BTC hipotesis 100,000 pUSD dan henti rugi 2%, henti rugi berada pada 98,000. Risiko setiap unit ialah 2,000, jadi kuantitinya ialah 20 / 2,000 = 0.01 BTC, iaitu nilai nosional 1,000 pUSD. Pada 3x, ini memerlukan kira-kira 333 pUSD margin. Kadar margin penyelenggaraan BTC ialah 1% (0.5 / 50x), jadi harga likuidasi posisi long ialah 100,000 x (1 - 1/3) / (1 - 0.01) = kira-kira 67,340, iaitu penurunan 32.7%. Henti rugi pada -2% dicetuskan jauh lebih awal. Jika henti rugi diisi pada harga pencetusnya, kerugian ialah 20 pUSD ditambah yuran: 0.40 untuk membuka dan kira-kira 0.39 untuk menutup pada kadar taker 0.040%, jadi kira-kira 20.79 pUSD sebelum slippage. Leveraj hanya menentukan berapa banyak margin yang terikat. Ia tidak mengubah kerugian pada henti rugi.
Sebelum beroperasi secara langsung, tambah had dagangan harian, amaran untuk isian dan suis henti, serta penyelarasan semasa permulaan (fetch_portfolio, fetch_open_orders, fetch_orders, list_fills). Lihat bab kod risiko dan dagangan latihan kami, panduan leveraj dan likuidasi Perps serta panduan kadar pembiayaan Perps.
Ralat, cubaan semula dan dead man's switch
503 service_unavailabletidak membuktikan permintaan gagal. Cuba semula bacaan dan perubahan leveraj dengan backoff dan patuhiRetry-After. Bagi perubahan baki bertandatangan (margin, pengeluaran), hantar semula hanya permintaan bertandatangan asal yang sama; tandatangan baharu bermaksud pelarasan kedua.signature expired: lebih lama daripada 5 minit. Pastikan jam sentiasa diselaraskan.insufficient_margin/invalid_leverage: semakan menggunakan posisi anda serta semua pesanan yang sedang menunggu.message_rate_limited: bajet WebSocket telah habis. Kunci baca sahaja mendapat 1,000 mesej seminit bagi setiap IP (burst 50) dan empat sambungan bagi setiap akaun bagi setiap gateway; lihat halaman Rate Limits dalam dokumentasi untuk selebihnya.- Ralat format: terlalu banyak perpuluhan atau lebih daripada 5 angka bererti. Itulah sebabnya ada
fit().
Auto-cancel membatalkan setiap pesanan terbuka jika bot anda berhenti mengaktifkannya semula. Tarikh akhir mestilah sekurang-kurangnya 5 saat ke hadapan, dan bilangan pencetusan setiap hari UTC adalah terhad, jadi nyahaktifkan apabila bot ditutup dengan bersih.
// 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();
Pertandingan, kod builder dan peraturan untuk bot
Dokumentasi menyenaraikan "bot dagangan yang bertindak balas terhadap isyarat pasaran" dan pembuat pasaran sebagai kegunaan API. Dua set peraturan terpakai.
- Pertandingan. Wash trading, dagangan sendiri, akaun berbilang, berdagang terutamanya untuk volum dan "bot, skrip atau cara automatik yang bertujuan memanipulasi Program" adalah dilarang. Bot yang berdagang secara tulen dibenarkan; bot yang sekadar menjana volum boleh dibatalkan kelayakan.
- Kod builder. Aplikasi yang berdagang bagi pihak pengguna lain boleh menambah yuran sehingga 10 mata asas dengan kelulusan setiap pedagang. Aplikasi itu mesti mengesahkan lokasi setiap pengguna dan menyekat pesanan dari bidang kuasa terhad, bukan sekadar memberi amaran.
Ganjaran kecairan maker dan pertandingan dibincangkan dalam panduan Polymarket Perps.
Mengehos bot
Bot berleveraj tidak sepatutnya dijalankan pada komputer riba. Ia mesti mengekalkan WebSocket, bertindak balas terhadap isian dalam beberapa saat dan bertahan apabila internet rumah terputus. Jalankan bot pada VPS di bawah systemd dengan mula semula automatik, simpan kunci dalam fail persekitaran yang hanya boleh dibaca oleh pengguna bot, dan selaraskan jam. Pesanan tidak boleh dibuat dari lokasi terhad, jadi jangan mengehos di AS atau Kanada, dan jangan sekali-kali menggunakan VPS untuk memintas sekatan yang terpakai kepada anda. Kami menjalankan bot kami sendiri di TradingVPS (pautan afiliasi: kami menerima komisen kecil dan harga yang anda bayar tetap sama). Bab pengehosan VPS kami membandingkannya dengan penyedia lain dari segi kependaman, harga dan rantau.
Selepas dagangan latihan, isi dana akaun dengan jumlah kecil daripada halaman Polymarket Perps dan jalankan bot pada saiz minimum selama seminggu sebelum menambah saiz.
Rumusan utama
- Gunakan SDK rasmi,
@polymarket/clientataupolymarket-client. Dompet anda menandatangani sekali sahaja; kunci proksi menandatangani pesanan. - Padankan pasaran berdasarkan simbol sebenar (
BTC-USD, bukanBTC-PERP) dan simpan ID berangka. - Tentukan saiz daripada jarak henti rugi, hadkan leveraj dalam kod, gunakan margin terasing, dan pasang henti rugi pada setiap posisi sejurus selepas isian.
- TP/SL dicetuskan pada harga tanda dan keluar pada harga pasaran. Auto-cancel boleh membuang henti rugi.
- Bot dibenarkan, tetapi memanipulasi pertandingan tidak. Jangan sekali-kali berdagang dari negara terhad.





