Polymarket Bot Tutorial · บทที่ 7 จาก 32
เจาะลึก Polymarket Gamma API: endpoint /events และ /markets, pagination, tag IDs (864 Tennis, 745 NBA เป็นต้น), การกรองตาม volume 24 ชม., rate limits, และตัวอย่างโค้ด Python กับ Node
บทนี้ครอบคลุมอะไรบ้าง
Gamma คือ catalog API ของ Polymarket-มันแสดงทุก event, ทุก market, tag, image และ resolved outcome ที่ฝั่ง front-end แสดงอยู่ ส่วน CLOB API ใช้สำหรับเทรด; Gamma ใช้บอกรายการที่เทรดได้ บั๊กของบอตส่วนใหญ่ในชั้น discovery มักเกิดจากการสับสนสองอย่างนี้ หรือจากการไม่เข้าใจ pagination contract บทนี้คือ field reference ของ endpoint หลักใน Gamma พร้อมพฤติกรรมพารามิเตอร์ที่แม่นยำซึ่งตัว fetcher ใน production ของเราใช้งานอยู่
- Gamma vs CLOB: ควรใช้เมื่อไร
- โครงสร้าง /events endpoint
- โครงสร้าง /markets endpoint
- Tags และ tag IDs (รายการที่ยืนยันแล้ว)
- การกรอง: active, closed, การเรียงตาม volume24hr
- Pagination และ limits
- Rate limits และ caching
- Code: ดึงตลาดที่มี volume 24 ชม. สูงสุด
Gamma vs CLOB: ควรใช้เมื่อไร
มีสองบริการที่ต่างกันสำหรับงานสองแบบที่ต่างกัน
Gamma (gamma-api.polymarket.com): catalog ใช้แสดงรายการ event, market, tags, descriptions, images, resolved outcomes, volume 24 ชั่วโมง, volume รวม, end dates เป็น HTTP แบบ read-only ไม่จำเป็นต้องยืนยันตัวตนสำหรับการอ่านส่วนใหญ่ อัปเดตต่อเนื่องแต่เป็น eventually consistent-market ที่เพิ่งปิดอาจยังแสดง closed: false อยู่สักสองสามวินาที
CLOB (clob.polymarket.com): trading + order book แสดง best bid/ask ปัจจุบัน, book depth top-N, recent trades ต้องยืนยันตัวตนสำหรับ write endpoints (วาง order, ยกเลิก order) มี WebSocket แบบเรียลไทม์สำหรับอัปเดต book
กฎง่าย ๆ: ใช้ Gamma เพื่อหาอะไรที่จะเทรด; ใช้ CLOB เพื่อเทรดมัน บอตที่อ่านราคาจาก Gamma กำลังใช้ข้อมูลที่ล้าสมัย-ฟิลด์ราคาของ Gamma อัปเดตไม่ถี่เท่า order book ของ CLOB บอตที่อ่าน metadata ของ market จาก CLOB กำลังยิง request มากเกินจำเป็น
โครงสร้าง /events endpoint
GET /events ส่งคืนข้อมูลระดับ event “event” คือหน้า Polymarket หนึ่งหน้า; event เดียวอาจมีหลาย market (เช่น event การเลือกตั้งประธานาธิบดีปี 2024 มีหนึ่ง market ต่อผู้สมัครหนึ่งคน)
ฟิลด์สำคัญ:
slug: ตัวระบุที่ใช้ใน URL แบบปลอดภัย ตลอดอายุของ event จะคงที่title,description: ใช้แสดงผลสำหรับมนุษย์endDate(ISO 8601): เวลาที่ event ปิดactive,closed: boolean; ใช้ร่วมกันใน query เช่น?active=true&closed=falseสำหรับ live eventsvolume,volume24hr: ยอดรวมเป็น USDtags: array ของ tag objects (ดูหัวข้อ tags ด้านล่าง)markets: array ของ child market objects (ดูโครงสร้าง/markets)
รูปแบบ discovery ที่พบบ่อยที่สุด: GET /events?active=true&closed=false&order=volume24hr&ascending=false&limit=100. จะคืน 100 event ที่ยัง live อยู่และมี volume สูงที่สุด
โครงสร้าง /markets endpoint
GET /markets ส่งคืนข้อมูลระดับ market market คือสัญญาแบบ Yes/No หรือหลาย outcome หนึ่งรายการ; มันอยู่ภายใน event
ฟิลด์สำคัญ:
slug: ตัวระบุที่ใช้ใน URL แบบปลอดภัยquestion: title ที่แสดงบนหน้าเทรด (เช่น "Will Trump be president on January 1, 2027?")outcomes: JSON string ของชื่อ outcome เช่น'["Yes","No"]'. สำหรับ binary จะมี 2 ค่าเสมอ; ถ้าเป็น NegRisk จะมีมากกว่าoutcomePrices: JSON string ของราคาปัจจุบันในรูป decimals เช่น'["0.62","0.38"]'. ทั้งสองฝั่งรวมกันได้ประมาณ 1.0 ลบด้วย spreadclobTokenIds: JSON string ของ ERC-1155 token IDs ที่จับคู่กับ outcomes นี่คือ token ที่คุณซื้อ/ขายจริงnegRisk: boolean; เป็น true สำหรับตลาดหลาย outcome ที่รวมกันได้ 1.0 มีผลต่อการวาง order (บทที่ 11)
ฟิลด์ outcomes / outcomePrices / clobTokenIds จะมารูปแบบเป็น JSON strings ไม่ใช่ parsed arrays-ต้อง JSON-decode ก่อนใช้งาน
Tags และ tag IDs (รายการที่ยืนยันแล้ว)
Tags คือป้ายกำกับเชิงหมวดหมู่ (Sports, Crypto, Tennis, NBA เป็นต้น) tag IDs ที่ยืนยันแล้วใน production สำหรับหมวดที่ใช้บ่อย:
| Tag | ID | Tag | ID |
|---|---|---|---|
| Sports | 1 | NBA | 745 |
| Crypto | 21 | NFL | 450 |
| Politics | 2 | Tennis | 864 |
| Bitcoin | 100196 | Esports | 702 |
| Ethereum | 100383 | Soccer | 1059 |
| Election | 3 | EPL | 739 |
| Middle East | 1432 | UCL | 2186 |
กรองตาม tag ด้วย ?tag_id=745 สำหรับ tag เฉพาะ หรือใช้ ?tag_slug=nba โดยอ้างอิง slug การกรองด้วย slug อ่านง่ายกว่าในโค้ดแต่ช้ากว่านิดหน่อย; การกรองด้วย ID คือค่าเริ่มต้นใน production
การกรอง: active, closed, การเรียงตาม volume24hr
มิติการกรอง 4 แบบที่คุณจะใช้ 95% ของเวลา
active=true|false:trueจะตัด market ที่ทีม Polymarket ซ่อนไว้จาก UI ออกclosed=true|false:falseจะตัด market ที่ resolved แล้วออก การจับคู่active=true&closed=falseคือ live filter ที่ใช้บ่อยที่สุดorder=volume24hr,order=volume,order=endDate: คีย์สำหรับ sort สิ่งที่มีประโยชน์ที่สุดคือvolume24hrสำหรับหา market ที่ยัง active อยู่ascending=true|false: ค่าเริ่มต้นใน endpoint ส่วนใหญ่คือ true; โดยปกติคุณแทบจะต้องการfalseสำหรับการเรียงตาม volume
ข้อควรระวัง: การกรองด้วย tag_id ร่วมกับ order=volume24hr และ ascending=false บางครั้งอาจคืนหน้าว่างเมื่อ tag นั้นมี live market น้อยมาก ควรดึงมาเกินก่อนเสมอ (request มากกว่าที่จะแสดง) แล้วค่อย post-filter เพื่อรับมือกับกรณีนี้
Pagination และ limits
พารามิเตอร์ limit รับค่า 1-500 ต่อครั้ง ค่าเริ่มต้นคือ 100 ถ้าไม่ระบุ เกิน 500 เซิร์ฟเวอร์จะ cap ให้แบบเงียบ ๆ-คุณจะได้ 500 แต่ response จะไม่มีตัวบอกว่ามีข้อมูลเพิ่ม
Pagination เป็นแบบ offset: ?limit=500&offset=500 สำหรับหน้าที่สอง ไม่มี cursor-based pagination ดังนั้นการ insert พร้อมกันอาจทำให้ขอบเขตของหน้าขยับระหว่างการเรียก สำหรับการ discovery ของบอตส่วนใหญ่สิ่งนี้ยอมรับได้; สำหรับการ scrape archive ให้ sort ด้วยฟิลด์ที่เสถียร (endDate หรือ createdAt) และตรวจหา overlap ด้วย slug
รูปแบบใช้งานจริงสำหรับ "all live markets": ดึง limit=500&order=volume24hr&ascending=false นั่นครอบคลุม top 500 ตาม volume ซึ่งแทบจะเป็นทุก market ที่มี activity จริงจัง การข้ามไป page 1 มักไม่ค่อยมีประโยชน์-market ที่อยู่หางของการกระจาย volume ตามนิยามแล้วไม่ใช่จุดที่มีกิจกรรมหลัก
Rate limits และ caching
Gamma ถูกวางหน้าไว้ด้วย Cloudflare และมี soft rate limits ต่อ IP ค่า threshold ที่สังเกตได้จากการใช้งานใน production:
- สูงสุดราว ~30 req/sec จาก IP เดียวแบบต่อเนื่อง: ปกติ
- burst 100+ req/sec: บางครั้งเจอ 429 แต่ retry แล้วสำเร็จ
- ~500 req/sec แบบต่อเนื่อง: เจอหน้า rate-limit หรือถูกบล็อกชั่วคราว (10-60 วินาที)
response headers ที่เผยแพร่จะมีค่า Cache-Control ประมาณ 30-60 วินาทีสำหรับ endpoint ส่วนใหญ่ ให้เคารพมัน-ไม่มีประโยชน์ที่จะดึง event เดิม 10 ครั้งในหนึ่งนาที รูปแบบ caching ใน production: in-process LRU + TTL โดย key เป็น full URL, TTL 30 วินาที ช่วยลดจำนวน request และลด latency
สำหรับกลยุทธ์ที่ถี่มาก ให้ mirror ข้อมูล Gamma ลง local store ที่อัปเดตโดย process fetcher เดียว; ให้ consumer หลายตัวอ่านจาก store นั้น fetcher หนึ่งตัว × consumer หลายตัว ดีกว่า fetcher หลายตัว × Gamma
Code: ดึงตลาดที่มี volume 24 ชม. สูงสุด
ตัวอย่าง reference fetcher ใน 3 ภาษา โดยคืน top 50 live markets ตาม volume 24 ชั่วโมง
Python:
import requests
r = requests.get("https://gamma-api.polymarket.com/events",
params={"active":"true","closed":"false",
"order":"volume24hr","ascending":"false","limit":50},
timeout=10)
for ev in r.json()[:50]:
print(ev["slug"], ev.get("volume24hr"))
Node:
const url = "https://gamma-api.polymarket.com/events?active=true&closed=false" +
"&order=volume24hr&ascending=false&limit=50";
const events = await fetch(url).then(r => r.json());
for (const ev of events) console.log(ev.slug, ev.volume24hr);
Curl:
curl -s "https://gamma-api.polymarket.com/events?active=true&closed=false&order=volume24hr&ascending=false&limit=50" \
| jq '.[].slug'
endpoint /events ของ Polymarket gamma ไม่รองรับพารามิเตอร์ค้นหาแบบ free-text-ถ้าเพิ่ม ?q=foo หรือ ?search=foo ระบบจะคืนลำดับเริ่มต้นแบบเงียบ ๆ ให้ กรองด้วย slug หรือ tag แทน












