Polymarket Bot Tutorial · บทที่ 8 จาก 32
Polymarket CLOB API สำหรับ bots: REST endpoints สำหรับ order book snapshots, WebSocket subscriptions สำหรับการอัปเดตแบบเรียลไทม์, การแยก bids/asks, การคำนวณ mid-price และ depth, ตัวอย่างโค้ด
บทนี้ครอบคลุมอะไรบ้าง
CLOB API คือจุดที่ orders ถูก sign, ส่ง, match และเป็นที่อยู่ของ order book ด้วย Polymarket มี SDK สองเจเนอเรชัน-v1 ที่เลิกใช้แล้ว และ v2 ที่ใช้อยู่ในปัจจุบัน บทนี้ครอบคลุมเฉพาะ v2; v1 ไม่ควรปรากฏใน bot ใด ๆ ที่คุณส่งมอบในปี 2026 เราจะพาไล่ดูเส้นทาง REST snapshot, ช่องทางอัปเดต WebSocket, รายละเอียดการ parsing ที่มักทำให้ builder มือใหม่สะดุด และ logic สำหรับ reconnect ที่หากไม่มีมัน bot ที่รันยาว ๆ จะค่อย ๆ หลุด sync ภายในไม่กี่ชั่วโมง
- CLOB v1 vs v2 (use v2)
- Order book REST snapshot
- WebSocket subscriptions: market and user channels
- Parsing bids/asks/depth
- Computing mid-price and best-bid/ask
- Maker fees, taker fees, rebates
- Code: connect WS and process price-change events
- Reconnect and gap-handling
CLOB v1 vs v2 (use v2)
Polymarket ดูแล SDK สองเจเนอเรชัน v1 (@polymarket/clob-client บน npm, py-clob-client <0.30) เลิกใช้แล้วและขาด order types หลายแบบที่เพิ่มเข้ามาในปี 2024 ส่วน v2 (@polymarket/clob-client-v2 v1.0.6 ใน Node, py-clob-client 0.34.6+ ใน Python) คือมาตรฐานปัจจุบัน
มีความแตกต่างสำคัญ 3 ข้อ v2 รองรับ flag negRisk สำหรับ multi-outcome markets-ซึ่งจำเป็นตั้งแต่เปิดตัว NegRisk exchange ในช่วงปลายปี 2024 v2 มาพร้อม TypeScript types สำหรับรูปแบบข้อความ WebSocket; ส่วน v1 จะคืนค่าเป็น any v2 จัดการ flow การ sign ของ Gnosis Safe ในเดือนสิงหาคม 2025 ได้แบบ native; ส่วน v1 ต้องใช้ custom signing glue
เนื้อหาที่เหลือของบทนี้จะเขียนโดยอิง v2 ตลอดทั้งบท หากคุณเห็นโค้ด v1 ใน tutorial เก่า ให้ถือว่ายังใช้ไม่ได้จนกว่าจะพิสูจน์ได้เป็นอย่างอื่น-โดยเฉพาะการวาง order ในตลาด NegRisk ภายใต้ v1 จะถูกส่งไปยัง exchange contract ที่ผิดแบบเงียบ ๆ
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"}, ...]
}
ราคาเป็น string ที่มีทศนิยม 2-3 ตำแหน่ง; sizes เป็น string ที่แทนจำนวน shares (ไม่ใช่ดอลลาร์) bids เรียงจากสูงไปต่ำ, asks เรียงจากต่ำไปสูง hash คือ marker สำหรับ deduplication-การ poll book เดิมซ้ำ ๆ ที่ไม่มีการเปลี่ยนแปลงจะได้ hash เดิม และ bot ของคุณสามารถข้ามการประมวลผลได้
REST snapshot เหมาะกับการ lookup แบบครั้งเดียว (เช่นเช็กราคาตอนตัดสินใจเข้าเทรด) สำหรับการติดตามแบบต่อเนื่อง ให้ใช้ WebSocket channel ด้านล่าง
WebSocket subscriptions: market and user channels
มี WebSocket channels สองแบบที่สำคัญ
Market channel: wss://ws-subscriptions-clob.polymarket.com/ws/market Subscribe ได้หนึ่ง token หรือหลาย token; รับ order-book updates ทันทีที่เกิดขึ้น
{"type":"Market","markets":["0xCondId1","0xCondId2"]}
ข้อความจะเข้ามาทุกครั้งที่มีการเปลี่ยนแปลง Types ได้แก่ book (full snapshot), price_change (delta), tick_size_change (พบไม่บ่อย), และ last_trade_price (ราคาที่ fill ล่าสุด)
User channel: wss://ws-subscriptions-clob.polymarket.com/ws/user ต้อง auth; ใช้รับ event ของ order คุณเอง-fills, partial fills, cancellations
{"type":"User","auth":{"apiKey":"...","secret":"...","passphrase":"..."}}
User channel เป็นวิธีที่สะอาดที่สุดในการตรวจจับ fill การ poll orders REST endpoint มีต้นทุนสูงกว่าและอาจพลาด state changes ระหว่างการ poll; WebSocket จะ push event ออกมาทันทีที่ matcher ยืนยัน
Parsing bids/asks/depth
Order book คือรายการของ price levels ที่รวม size แล้ว มี conventions ในการ parsing สองข้อที่ต้องทำให้ถูก
ทิศทางของ order: bids คือ buy orders (มีคนต้องการ BUY ที่ราคานี้) เมื่อ bot ของคุณขาย คุณจะไป hit bid เมื่อ bot ของคุณซื้อ คุณจะ lift ask Polymarket UI แสดงทิศทางเดียวกัน; แต่อีกหลาย exchange จะกลับด้าน
การเรียงลำดับ: bids จะมาเรียงจากมากไปน้อย (best bid มาก่อน) asks จะมาเรียงจากน้อยไปมาก (best ask มาก่อน) best bid คือ bids[0]; best ask คือ asks[0] ระวัง: public WebSocket บางครั้งส่ง partial book updates ที่ยังไม่ถูก pre-sort-ให้ re-sort ทุกครั้งอย่างระมัดระวังหลัง merge
Depth ในแต่ละระดับคือมูลค่าเป็นดอลลาร์ที่สามารถเทรดได้: price * size Top-5-level depth เป็น metric ด้านสภาพคล่องที่ใช้บ่อย: sum(b.price * b.size for b in bids[:5]) หาก top-5 depth ต่ำกว่า $100 book นี้ถือว่า illiquid และสมมติฐานของกลยุทธ์ส่วนใหญ่จะเริ่มใช้ไม่ได้
Computing mid-price and best-bid/ask
มี price points ที่ bot ของคุณต้องคำนวณเพิ่มอีก 3 ค่า
- Best bid / best ask:
bids[0].priceและasks[0].priceคือราคาที่คุณเทรดได้จริงสำหรับ 1 share - Mid-price:
(best_bid + best_ask) / 2จุดกึ่งกลางทางคณิตศาสตร์ของ spread ใช้สำหรับ valuation; คุณไม่ได้เทรดที่ mid จริง ๆ - VWAP price สำหรับ size N: เดินไปตาม book จน cumulative size ถึง N แล้วคืนค่า average price แบบถ่วงน้ำหนักด้วย size นี่คือ cost จริงในการ BUY N shares ตอนนี้ โดยคิดรวมการ sweep ไปยังระดับที่ลึกกว่า
กรณีขอบเขต: ถ้าฝั่ง bid หรือ ask ว่างเปล่า (ไม่มีคนขาย หรือไม่มีคนซื้อ) แปลว่า book เป็น one-sided ในโครงสร้างตลาดของ Polymarket สิ่งนี้เกิดกับตลาดที่ปิดแล้วหรือใกล้ปิด โดยฝั่งหนึ่งอยู่ที่ 0.999 และไม่มีใครเสนอ liquidity ฝั่งผู้แพ้ ให้ถือว่า best-bid = 0 หรือ best-ask = 1 เป็นสัญญาณ "do not trade"
Maker fees, taker fees, rebates
ตลอดประวัติศาสตร์ส่วนใหญ่ของมัน Polymarket ไม่ได้เก็บ fee การเทรดเลย สิ่งนี้เปลี่ยนไปในปี 2026: fee ถูกนำมาใช้ช่วงต้นปีในตลาด crypto แบบ 15 นาที ขยายไปยัง Sports เมื่อวันที่ 30 มีนาคม 2026 และนับแต่นั้นก็ทยอยใช้กับหมวดส่วนใหญ่ คู่มือใด ๆ ที่ยังอ้างว่า Polymarket ไม่มี fee ถือว่าล้าสมัย-และใครที่มองข้ามเรื่องนี้ในกลยุทธ์ความถี่สูงจะถูกกัดกินอย่างเงียบ ๆ นี่คือวิธีที่โมเดลทำงานจริง ณ กลางปี 2026
ก่อนอื่นคือสองฝั่งของทุกการเทรด maker คือผู้ที่วาง limit order ค้างไว้ใน book และรออยู่ตรงนั้น; taker คือผู้ที่ส่ง order ที่ถูก execute ทันทีกับ liquidity ที่มีอยู่ maker ยังคงจ่าย fee ศูนย์ และยังได้ rebate เพิ่มอีก; มีเพียง taker เท่านั้นที่จ่าย fee
taker fee ไม่ใช่เปอร์เซ็นต์คงที่ มันเป็นไปตามเส้นโค้งที่ขึ้นกับทั้งขนาดของ order และราคา:
fee = shares × feeRate × price × (1 - price)
พจน์ price × (1 - price) มีค่าสูงสุดที่ราคา 0.50 (ตลาด "โยนเหรียญ" ที่แท้จริง) และหดเข้าหา 0 หรือ 1 กล่าวอีกนัยหนึ่ง ในตลาดที่ไม่แน่นอนที่สุดคุณจ่าย 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 ของตลาด 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 ในตลาด crypto และ sports ที่ผันผวนและเกือบสมดุลเสีย fee มากที่สุด-ดังนั้นตรงนั้นเองที่คุ้มที่สุดที่จะ quote เป็น maker และเก็บ rebate แทนที่จะจ่าย fee
นอกเหนือจาก fee ที่ระบุชัดเจน ทุกครั้งที่คุณข้าม bid-ask spread คุณก็จ่าย spread นั้นด้วย spread คือช่องว่างระหว่างราคาซื้อที่ดีที่สุดและราคาขายที่ดีที่สุด และสำหรับกลยุทธ์ที่เข้าออกแบบ taker ช่องว่างนั้นคือต้นทุนจริงที่เพิ่มจาก fee ให้สมมติราว 1-3 เซนต์ต่อ round-trip สำหรับ book ทั่วไป และมากกว่านั้นในตลาดที่ illiquid ตลาด NegRisk (multi-outcome exchange) ใช้โมเดล fee เดียวกันแต่ settle บน contract แยกต่างหาก ดังนั้น rewards ของมันจึงสะสมแยกกัน Chapter 19 ครอบคลุม liquidity-rewards farming ที่ซึ่งการเก็บ maker rebate คือกลยุทธ์ในตัวเอง ไม่ใช่เพียงผลพลอยได้
Code: connect WS and process price-change events
ตัวอย่าง Node แบบขั้นต่ำ: connect, 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));
Subscribe ได้สบาย ๆ ถึงประมาณ 30 token ต่อ WebSocket connection หากมากกว่านั้น ให้แยกไปหลาย connections-เซิร์ฟเวอร์บางครั้งจะหล่น subscriptions ขนาดใหญ่ออกไปโดยไม่แจ้ง error ซึ่งทำให้เกิด stale book reads แบบเงียบ ๆ
Reconnect and gap-handling
WebSocket connection ที่รันยาว ๆ จะหลุดได้ Cloudflare มีการ cycle connections ทุกไม่กี่ชั่วโมง; เครือข่ายอาจสะดุด; Polymarket ก็มีช่วง deploy ได้เช่นกัน ต้องเตรียมรับมือไว้
กลยุทธ์ reconnect: เมื่อเกิด close หรือ error ให้รอ min(2^attempt, 30) วินาทีพร้อม jitter แล้วค่อย re-subscribe รีเซ็ตตัวนับ attempt เมื่อได้รับข้อความสำเร็จครั้งแรกหลัง reconnect
การจัดการ gap สำคัญยิ่งกว่าความเร็วในการ reconnect ระหว่างที่ WebSocket หลุด book อาจเคลื่อนไหวไปแล้ว เมื่อ reconnect ทุกครั้ง ให้ดึง REST snapshot ของ token ที่ subscribe ไว้อีกครั้งและ reconcile: open positions ใด ๆ ที่ book เคลื่อนไหวอย่างมีนัยสำคัญต้องตรวจ state ใหม่, exits อาจต้อง fire, alarms อาจล้าสมัย เคส "ฉันพลาด book updates ไป 30 วินาที" คือฆาตกรเงียบของ bot ที่รันยาว-มันจะยังทำงานต่อไปบน state เก่า และวาง orders ที่ราคาไม่มีอยู่จริงแล้ว
แพตเทิร์นแบบ defensive: snapshot ของ book ที่ subscribe ไว้ทุกนาทีไม่ว่า WebSocket จะอยู่ในสถานะใด และถือว่า WS เป็นการ optimize แบบ fast-path ที่ทำงานอยู่บน snapshot poll อีกชั้นหนึ่ง












