آموزش Polymarket Bot · فصل 8 از 32

Polymarket CLOB API برای botها: REST endpointها برای order book snapshotها، WebSocket subscriptionها برای real-time updateها، parsing of bids/asks، محاسبه mid-price و depth، نمونه‌کدها.

این فصل چه چیزهایی را پوشش می‌دهد

CLOB API جایی است که orderها امضا می‌شوند، ارسال می‌شوند، match می‌شوند، و order book آنجا قرار دارد. Polymarket دو نسل SDK دارد - نسخه deprecated v1 و نسخه current v2. این فصل فقط v2 را پوشش می‌دهد؛ v1 نباید در هیچ botی که در 2026 منتشر می‌کنید ظاهر شود. ما مسیر REST snapshot، کانال به‌روزرسانی WebSocket، جزئیات parsing که برای builderهای تازه‌کار دردسرساز می‌شوند، و منطق reconnect را بررسی می‌کنیم؛ منطقی که بدون آن یک bot طولانی‌مدت ظرف چند ساعت از sync خارج می‌شود.

  • CLOB v1 در برابر v2 (از v2 استفاده کنید)
  • Order book REST snapshot
  • WebSocket subscriptionها: market و user channelها
  • Parsing bids/asks/depth
  • محاسبه mid-price و best-bid/ask
  • Maker fee، taker fee، rebate
  • Code: اتصال WS و پردازش price-change eventها
  • Reconnect و gap-handling

CLOB v1 در برابر v2 (از v2 استفاده کنید)

Polymarket دو نسل SDK را نگه می‌دارد. v1 (@polymarket/clob-client در npm، py-clob-client <0.30) deprecated شده و چندین order type اضافه‌شده در 2024 را ندارد. v2 (@polymarket/clob-client-v2 نسخه 1.0.6 در Node، py-clob-client نسخه 0.34.6+ در Python) استاندارد فعلی است.

سه تفاوت مشخص. v2 از flagِ negRisk برای marketهای multi-outcome پشتیبانی می‌کند - که از زمان launch شدن NegRisk exchange در اواخر 2024 لازم است. v2 typeهای TypeScript را برای شکل پیام‌های WebSocket همراه دارد؛ v1 مقدار any برمی‌گرداند. v2 جریان امضای Gnosis Safe مربوط به August 2025 را به‌صورت native مدیریت می‌کند؛ v1 به glue code سفارشی برای signing نیاز دارد.

بقیه این فصل با فرض استفاده از v2 نوشته شده است. اگر در یک tutorial قدیمی کد v1 دیدید، آن را تا زمانی که خلافش ثابت نشده خراب فرض کنید - به‌ویژه order placement در marketهای NegRisk تحت v1 به‌صورت silent به contract exchange اشتباه route می‌شود.

Order book REST snapshot

REST snapshot endpoint کل book را برای یک token در یک لحظه برمی‌گرداند.

GET https://clob.polymarket.com/book?token_id=<ERC1155_TOKEN_ID>

شکل response:

{
  "market": "0x...",
  "asset_id": "5413...",
  "timestamp": "1715600000000",
  "hash": "0x...",
  "bids": [{"price":"0.45","size":"120"}, {"price":"0.44","size":"380"}, ...],
  "asks": [{"price":"0.47","size":"85"}, {"price":"0.48","size":"210"}, ...]
}

قیمت‌ها رشته‌هایی با 2 تا 3 رقم اعشار هستند؛ sizeها رشته‌هایی هستند که تعداد share را نشان می‌دهند (نه دلار). bids از بالا به پایین مرتب شده‌اند، asks از پایین به بالا. hash یک نشانگر deduplication است - polling تکراریِ book بدون تغییر همان hash را برمی‌گرداند و bot شما می‌تواند پردازش را رد کند.

REST snapshot انتخاب درست برای lookupهای یک‌باره است (مثلاً بررسی قیمت هنگام تصمیم ورود). برای monitoring پیوسته، از WebSocket channel زیر استفاده کنید.

WebSocket subscriptionها: market و user channelها

دو WebSocket channel مهم‌اند.

Market channel: wss://ws-subscriptions-clob.polymarket.com/ws/market. به یک یا چند token subscribe کنید؛ order-book updateها را به‌محض وقوع دریافت می‌کنید.

{"type":"Market","markets":["0xCondId1","0xCondId2"]}

پیام‌ها با هر تغییر می‌رسند. typeها شامل book (full snapshot)، price_change (delta)، tick_size_change (نادر)، و last_trade_price (آخرین fill) هستند.

User channel: wss://ws-subscriptions-clob.polymarket.com/ws/user. احراز هویت‌شده است؛ eventهای مربوط به orderهای خودتان را دریافت می‌کنید - fillها، partial fillها، cancellationها.

{"type":"User","auth":{"apiKey":"...","secret":"...","passphrase":"..."}}

User channel تمیزترین راه برای تشخیص fill است. polling کردن orders REST endpoint هزینه بیشتری دارد و ممکن است state changeها را بین pollingها از دست بدهد؛ WebSocket event را همان لحظه‌ای که matcher آن را تأیید می‌کند push می‌کند.

Parsing bids/asks/depth

Order book فهرستی از price levelها با size تجمیع‌شده است. دو convention برای parsing را باید درست انجام دهید.

جهت order: bids orderهای خرید هستند (یعنی کسی می‌خواهد در این قیمت BUY کند). وقتی bot شما SELL می‌کند، به bid می‌زند. وقتی bot شما BUY می‌کند، ask را lift می‌کند. رابط کاربری Polymarket هم همین جهت را نشان می‌دهد؛ بعضی exchangeهای دیگر برعکس عمل می‌کنند.

Sorting: bids به‌صورت نزولی می‌رسند (best bid اول). asks به‌صورت صعودی می‌رسند (best ask اول). best bid برابر bids[0] است؛ best ask برابر asks[0]. مراقب باشید: WebSocket عمومی گاهی partial book updateهایی می‌فرستد که از قبل sort نشده‌اند - بعد از هر merge همیشه به‌صورت defensive دوباره sort کنید.

Depth در یک level برابر ارزش دلاری قابل معامله است: price * size. depth پنج level اول یک metric رایج برای liquidity است: sum(b.price * b.size for b in bids[:5]). اگر top-5 depth زیر 100 دلار باشد، book illiquid است و بیشتر فرض‌های strategy از کار می‌افتند.

محاسبه mid-price و best-bid/ask

سه price point مشتق‌شده که bot شما به آن‌ها نیاز دارد.

  • Best bid / best ask: bids[0].price و asks[0].price. قیمتی که واقعاً می‌توانید با آن معامله کنید، برای یک share.
  • Mid-price: (best_bid + best_ask) / 2. مرکز ریاضی spread. برای valuation مفید است؛ شما هیچ‌وقت در mid معامله نمی‌کنید.
  • VWAP price for size N: book را آن‌قدر طی کنید تا cumulative size به N برسد، سپس average price وزن‌دار با size را برگردانید. هزینه واقعی BUY کردن N سهم در همین لحظه، با در نظر گرفتن sweep شدن به levelهای عمیق‌تر.

حالت مرزی: خالی بودن سمت bid یا ask (یعنی کسی نمی‌فروشد یا کسی نمی‌خرد) به این معناست که book یک‌طرفه است. در ساختار market پلی‌مارکت این حالت برای marketهای resolved یا نزدیک به resolved رخ می‌دهد، جایی که یک سمت روی 0.999 است و کسی برای سمت بازنده liquidity ارائه نمی‌کند. best-bid = 0 یا best-ask = 1 را به‌عنوان سیگنال «معامله نکن» در نظر بگیرید.

Maker fee، taker fee، rebate

Polymarket در بیشتر تاریخ خود اصلاً هیچ fee معامله‌ای نمی‌گرفت. این در 2026 تغییر کرد: feeها در ابتدای سال روی marketهای crypto پانزده‌دقیقه‌ای معرفی شدند، در 30 مارس 2026 به Sports گسترش یافتند و از آن پس روی بیشتر دسته‌ها اجرا شدند. هر آموزشی که هنوز ادعا کند Polymarket بدون fee است، منسوخ است - و هرکس این را در یک strategy پُربسامد نادیده بگیرد، بی‌سروصدا خورده می‌شود. مدل واقعاً این‌طور کار می‌کند، تا اواسط 2026.

نخست دو سوی هر trade. maker کسی است که یک limit order در حال انتظار در book می‌گذارد که همان‌جا منتظر می‌ماند؛ taker کسی است که orderی می‌فرستد که بی‌درنگ در برابر liquidity موجود اجرا می‌شود. makerها همچنان صفر fee می‌پردازند و افزون بر آن rebate می‌گیرند؛ تنها takerها fee می‌پردازند.

taker fee یک درصد ثابت نیست. از یک منحنی پیروی می‌کند که هم به اندازهٔ order و هم به قیمت بستگی دارد:

fee = shares × feeRate × price × (1 - price)

جملهٔ price × (1 - price) در قیمت 0.50 (یک market واقعی «شیر یا خط») بیشینه است و به سمت 0 یا 1 کوچک می‌شود. به بیان دیگر، در نامطمئن‌ترین marketها بیشترین fee و در marketهای تقریباً تعیین‌شده تقریباً هیچ fee نمی‌پردازید. feeRate برای هر دسته تعیین می‌شود:

  • Crypto: feeRate 0.07 (بالاترین، اوج مؤثر حدود 1.8%)، maker rebate 20%.
  • Sports: feeRate 0.03 (اوج حدود 0.75%)، maker rebate 25%.
  • Finance، Politics، Tech، Mentions: feeRate 0.04، maker rebate 25%.
  • Economics، Culture، Weather، عمومی: feeRate 0.05، maker rebate 25%.
  • Geopolitics و رویدادهای بزرگ جهانی: 0، همچنان بدون fee.

یک مثال محاسباتی. فرض کنید bot شما 100 shares از یک market crypto را با قیمت 0.50 می‌گیرد. fee برابر است با 100 × 0.07 × 0.50 × (1 - 0.50) = 100 × 0.07 × 0.25 = $1.75. اگر همان 100 shares را با 0.90 بگیرید، fee به 100 × 0.07 × 0.90 × 0.10 = $0.63 کاهش می‌یابد، چون قیمت از میانهٔ نامطمئن دور است. درس برای bot روشن است: گرفتن liquidity در marketهای crypto و sports پُرنوسان و تقریباً متعادل بیشترین fee را دارد - پس دقیقاً همان‌جا به‌صرفه‌تر است که به‌عنوان maker quote بدهید و rebate بگیرید تا اینکه fee بپردازید.

افزون بر fee صریح، هر بار که از bid-ask spread عبور می‌کنید، آن spread را می‌پردازید. spread فاصلهٔ میان بهترین قیمت خرید و بهترین قیمت فروش است و برای strategyیی که به‌صورت taker وارد و خارج می‌شود، آن فاصله هزینه‌ای واقعی روی fee است. برای bookهای معمولی round-trip را 1 تا 3 سنت و برای illiquidها بیشتر فرض کنید. marketهای NegRisk (همان multi-outcome exchange) از همان مدل fee استفاده می‌کنند اما روی یک contract جداگانه تسویه می‌شوند، پس rewardهایشان جداگانه انباشته می‌شود. فصل 19 به liquidity-rewards farming می‌پردازد، جایی که گرفتن maker rebate خودِ strategy است نه صرفاً یک اثر جانبی.

Code: اتصال WS و پردازش price-change eventها

نمونه Node حداقلی: اتصال، subscribe، و log کردن هر price-change event برای یک token.

import WebSocket from "ws";
const ws = new WebSocket("wss://ws-subscriptions-clob.polymarket.com/ws/market");
ws.on("open", () => {
  ws.send(JSON.stringify({ type: "Market", markets: ["<CONDITION_ID>"] }));
});
ws.on("message", (data) => {
  const msg = JSON.parse(data.toString());
  if (msg.event_type === "price_change") {
    console.log("price_change", msg.asset_id, msg.changes);
  } else if (msg.event_type === "book") {
    console.log("book snapshot", msg.bids?.[0], msg.asks?.[0]);
  }
});
ws.on("close", () => console.log("closed"));
ws.on("error", (e) => console.error("err", e.message));

تا حدود 30 token را می‌توانید به‌راحتی در هر WebSocket connection subscribe کنید. فراتر از آن، اتصال‌ها را بین چند connection تقسیم کنید - server گاهی subscriptionهای بزرگ را بدون error رها می‌کند که باعث stale book readهای silent می‌شود.

Reconnect و gap-handling

یک WebSocket connection طولانی‌مدت قطع خواهد شد. Cloudflare هر چند ساعت connectionها را می‌چرخاند؛ networkها flicker می‌کنند؛ Polymarket هم گاهی deploy می‌کند. برای این وضعیت برنامه داشته باشید.

استراتژی reconnect: در صورت close یا error، با jitter به‌مدت min(2^attempt, 30) ثانیه صبر کنید، سپس دوباره subscribe کنید. شمارنده attempt را پس از اولین پیام موفق بعد از reconnect reset کنید.

gap-handling از سرعت reconnect مهم‌تر است. در زمانی که WebSocket قطع بوده، book تغییر کرده است. در هر reconnect، snapshot REST هر token subscribed را دوباره بگیرید و reconcile کنید: هر position باز که book آن به‌طور معناداری حرکت کرده نیاز به state re-check دارد، exitها ممکن است باید اجرا شوند، alarmها ممکن است stale باشند. حالت «30 ثانیه update book را از دست دادم» قاتل خاموش botهای طولانی‌مدت است - آن‌ها با state قدیمی ادامه می‌دهند و orderها را در قیمت‌هایی می‌گذارند که دیگر وجود ندارند.

الگوی دفاعی: هر book subscribed را صرف‌نظر از وضعیت WebSocket، هر دقیقه یک‌بار snapshot بگیرید و WS را به‌عنوان یک optimization مسیر سریع روی polling snapshot در نظر بگیرید.

پرسش‌های متداول

Polymarket CLOB API endpoint چیست؟
Base CLOB endpoint برابر https://clob.polymarket.com (REST) و wss://ws-subscriptions-clob.polymarket.com/ws/market (WebSocket) است. این‌ها همان V2 endpointهایی هستند که توسط @polymarket/clob-client-v2 و py-clob-client استفاده می‌شوند.
برای خواندن order book به API key نیاز دارم؟
خیر. خواندن order book (snapshotها و WebSocket subscriptionها) عمومی است و هیچ authenticationی نمی‌خواهد. فقط برای ثبت/لغو order و خواندن داده‌های مخصوص account (positionها، fillها) به API key نیاز دارید.
CLOB WebSocket با چه سرعتی price update را push می‌کند؟
به همان سرعتی که orderها match می‌شوند. marketهای فعال هر چند صد میلی‌ثانیه یک‌بار update می‌بینند؛ marketهای کم‌عمق فقط هنگام order واقعی update می‌شوند. هم تغییرات depth و هم eventهای trade از همان WS channel عبور می‌کنند - برای مدیریت درست هر مورد، event type را parse کنید.
چطور mid-price یک Polymarket order book را محاسبه کنم؟
mid = (best_bid + best_ask) / 2 اگر هر دو وجود داشته باشند؛ در غیر این صورت از last_trade_price به‌عنوان fallback استفاده کنید. در bookهای کم‌عمق که best_bid بسیار پایین‌تر از best_ask است دقت کنید - mid ممکن است بی‌معنا باشد. همیشه قبل از اینکه mid را price منصفانه فرض کنید spread را هم در نظر بگیرید.
Maker fee در Polymarket در 2026 چقدر است؟
makerها 0% می‌پردازند و rebate می‌گیرند (20% روی crypto، 25% روی بیشتر دسته‌های دیگر). تنها takerها fee می‌پردازند و آن ثابت نیست: از fee = shares × feeRate × price × (1 - price) پیروی می‌کند، پس در marketهای «شیر یا خط» نزدیک 0.50 به اوج می‌رسد و به سمت لبه‌ها کوچک می‌شود. feeRateها برحسب دسته از 0.03 (Sports، اوج مؤثر حدود 0.75%) تا 0.07 (Crypto، اوج مؤثر حدود 1.8%) می‌روند، در حالی که Geopolitics همچنان بدون fee است. همین نابرابری maker-taker است که باعث می‌شود botهای فعال تقریباً همیشه با limit orderهای در حال انتظار quote بدهند تا اینکه با market order از spread عبور کنند.
چطور WebSocket disconnect را مدیریت کنم؟
با exponential backoff reconnect کنید (1s، 2s، 4s، حداکثر 30s)، دوباره به همان marketها subscribe شوید، و برای پر کردن هر gap یک REST snapshot دوباره بگیرید. هرگز به order book stale اعتماد نکنید - اگر بیش از 5 ثانیه disconnect بوده‌اید، قبل از گذاشتن order یک snapshot تازه درخواست کنید.