บทเรียนบอท โพลีมาร์เก็ต · บทที่ 15 จาก 32
Sports microstructure bots บน โพลีมาร์เก็ต: in-game edge, mispricing ที่ขับเคลื่อนด้วย scoreline, แท็ก NBA (745) และ Tennis (864), live data sources และรูปแบบการ execution สำหรับ high-frequency sports markets
บทนี้ครอบคลุมอะไรบ้าง
Sports markets เป็นกลุ่มที่คึกคักที่สุดในฝั่งที่ไม่เกี่ยวกับการเมืองบน โพลีมาร์เก็ต บอทที่ใช้งานได้จริงจะแบ่งเป็น 2 กลุ่มชัดเจน: pre-game line-catchers ที่เทรดหลังจาก line ถูกตั้งแล้ว และ in-game microstructure bots ที่ตอบสนองต่อการเคลื่อนไหวของ order book ระหว่างเกม บทนี้ครอบคลุมทั้งสองแบบ พร้อม tag IDs, data sources และ latency budgets ที่ใช้กับแต่ละแบบ
Sports markets เป็นกลุ่มที่คึกคักที่สุดในฝั่งที่ไม่เกี่ยวกับการเมืองบน โพลีมาร์เก็ต execution pattern ที่ได้ผลคือการผสาน live-score feed (ESPN, PandaScore) เข้ากับสัญญาณ microstructure ของ order book บทนี้ครอบคลุมสิ่งที่ใช้ได้จริงสำหรับ NFL, NBA, soccer และ tennis โดยเฉพาะ รวมถึงความแตกต่างของ esports
- ทำไมตลาดกีฬาถึงเทรดได้
- ก่อนเกมกับระหว่างเกม (บอทคนละแบบ)
- tag IDs ที่ตรวจสอบแล้ว (745 NBA, 864 Tennis)
- แหล่งข้อมูล: ESPN, official APIs, ภาพจากหน้าจอ
- งบเวลาหน่วงสำหรับระหว่างเกม
- กับดัก 0.99 / 0.01
- โค้ด: subscribe เข้ากับ book ของเกมแล้วตอบสนอง
ทำไมตลาดกีฬาถึงเทรดได้
Sports markets ปิดตลาดในกรอบเวลาที่ชัดเจน (หลายชั่วโมงถึงหลายวัน), มี live data สาธารณะ และมี order flow ต่อเนื่องระหว่างการแข่งขัน ทั้งสามอย่างนี้จำเป็นสำหรับ market ที่เทรดได้ - ตลาดการเมืองไม่มี "defined timeframe", ตลาดสภาพอากาศไม่มี "continuous flow", ส่วนทัวร์นาเมนต์ที่ไม่เป็นที่รู้จักมักไม่มี "public live data"
กลุ่มผู้เล่นใน sports markets ก็หลากหลายกว่าตลาดอย่าง election markets ผู้เล่นทั่วไปที่ชอบเดิมพันกีฬาอาจตั้งราคาตามอารมณ์; ส่วน trader ที่มีข้อมูลจะปรับราคาให้เข้าใกล้ fair value ไปตลอดทั้งเกม ช่องว่างระหว่างสองฝั่งนี้คือ bot edge
การกระจายตัวของ volume ไม่เท่ากัน: NFL Sunday อาจหมุนเงินหลายร้อยล้านดอลลาร์ผ่าน โพลีมาร์เก็ต sports markets; ขณะที่เกม Saudi Pro League คืนวันอังคารอาจมีเงินไม่ถึง $50k คุณต้องขนาดกลยุทธ์ให้เหมาะกับจุดที่ activity อยู่จริง
ก่อนเกมกับระหว่างเกม (บอทคนละแบบ)
นี่คือ bot design ที่ต่างกันอย่างสิ้นเชิง 2 แบบ
Pre-game line-catcher: สแกนตลาดที่เพิ่งเปิด, ระบุ line ที่ตั้งราคาไว้ผิดเมื่อเทียบกับโมเดลของคุณหรือราคาจาก venue ที่ sharper กว่า, แล้วส่ง FOK buy ถือสถานะไปจนถึง in-play และบางครั้งจนจบเกม ความเร็ว: เป็นนาทีไม่ใช่วินาที edge: model + line-shopping
In-game microstructure: subscribe WebSocket ของ order book ของเกมสด, ตอบสนองต่อ imbalance signals + score events ภายในไม่กี่วินาที ความเร็ว: เป็นวินาทีไม่ใช่นาที edge: latency + การอ่าน order flow
สองแบบนี้แทบไม่ใช้โค้ดร่วมกัน มี risk profile, data sources และ exit strategies ต่างกัน บอทที่พยายามทำทั้งสองอย่างมักทำได้ไม่ดีสักอย่าง เลือกอย่างใดอย่างหนึ่ง
tag IDs ที่ตรวจสอบแล้ว (745 NBA, 864 Tennis)
tag IDs สำหรับ production ที่ตรวจสอบแล้วในพฤษภาคม 2026 สำหรับหมวดกีฬาหลัก ใช้สิ่งเหล่านี้เพื่อ filter การเรียก /events ให้มีประสิทธิภาพ
| กีฬา / ลีก | Tag ID | Tag slug | หมายเหตุ |
|---|---|---|---|
| NBA | 745 | nba | volume สูงสุด ต.ค.-มิ.ย. |
| NFL | 450 | nfl | พีค อา./จ. ก.ย.-ก.พ. |
| Tennis (ทั้งหมด) | 864 | tennis | ตลอดทั้งปี ตามรอบทัวร์นาเมนต์ |
| Soccer (ทั่วไป) | 1059 | soccer | ใช้ร่วมกับ sub-tags ด้านล่าง |
| EPL | 739 | epl | |
| UCL | 2186 | uefa-champions-league | |
| Esports (ทั้งหมด) | 702 | esports | LoL+CS2+Valorant+Dota |
| MLB | 1245 | mlb | พีค เม.ย.-ต.ค. |
| NHL | 823 | nhl | พีค ต.ค.-มิ.ย. |
tag IDs มีความเสถียรข้ามปี มีการเพิ่มแท็กใหม่เข้ามา (Saudi Pro League, IPL) แต่แท็กเก่าไม่ได้ถูกเปลี่ยนเลข
แหล่งข้อมูล: ESPN, official APIs, ภาพจากหน้าจอ
สำหรับกีฬาทั่วไป ESPN scoreboard API แบบฟรีครอบคลุมทุกอย่างที่คุณต้องใช้: scores, period/clock, win-probability และบางครั้ง shot location ไม่ต้องใช้ key; จำกัด rate แค่ระดับ IP endpoint pattern คือ https://site.api.espn.com/apis/site/v2/sports/<sport>/<league>/scoreboard
สำหรับ esports, ESPN ไม่มี coverage ทางเลือกคือ PandaScore ($30-60/mo, มาตรฐานในอุตสาหกรรม), HLTV (เฉพาะ CS2, scrape ได้, ไม่มี API), Liquipedia (ดูแลโดยชุมชน, scrape ได้, อัปเดตช้ากว่า)
feeds แบบ on-screen (จ่ายค่า TV stream แล้วใช้ OCR อ่าน scorebug) ใช้งานได้ แต่ภาระด้าน operation สูง แนะนำเฉพาะกรณีที่คุณมีกลยุทธ์ที่ต้องการอัปเดตเร็วกว่า 3 วินาทีในกีฬาที่ไม่มี API ใดครอบคลุมแบบ real time
งบเวลาหน่วงสำหรับระหว่างเกม
End-to-end latency budget สำหรับบอท reactive แบบ in-game
- Score event เกิดขึ้น: t=0
- Source feed สะท้อนเหตุการณ์: t+3-15s (ESPN: ~10s; PandaScore: ~3s)
- บอทของคุณอ่าน feed: t+10-16s
- บอทตัดสินใจ: +50ms
- ส่ง FOK order: +200-500ms
- Matched ที่ CLOB: +300-1000ms (network + matching)
รวม: 11-17 วินาที firms มืออาชีพที่เร็วที่สุดทำได้ 3-5 วินาที end-to-end ด้วย premium feeds แบบจ่ายเงินและ VPS ที่ co-located บอทรายย่อยที่รันบน host มาตรฐานและใช้ฟรี ESPN จะอยู่ฝั่งที่ช้ากว่า
กลยุทธ์ที่ต้องใช้เวลาน้อยกว่า 5 วินาทีไม่เหมาะกับ retail กลยุทธ์ที่ใช้ได้ในช่วง 10-17 วินาทีคือ: line-catching หลังมี score, fading overreactions, late-game certainty plays
กับดัก 0.99 / 0.01
ความล้มเหลวที่พบบ่อยที่สุดของบอท sports แบบ in-play: ซื้อ heavy favorite ที่ 0.99 ตอนเหลือ 1 นาที คิดว่าจะได้ +1¢ ง่าย ๆ มี 3 เหตุผลที่มันล้มเหลว
อย่างแรก ความน่าจะเป็นโดยนัย 1% ของ underdog ไม่ใช่ศูนย์ - การไล่กลับมาชนะในช่วงท้ายเกิดขึ้นบ่อยพอสมควร เกมที่มั่นใจ 99.5% ถ้าเล่น 200 ครั้ง จะมี 1 ครั้งที่ขาดทุนเต็มขนาด position
อย่างที่สอง spread ที่ 0.99/0.01 หมายความว่าคุณจ่าย 99c ต่อ share, ได้กำไร 1c เมื่อสำเร็จ, ขาดทุน 99c เมื่อเกิด reversal ที่เกิดขึ้นน้อย ความคุ้มค่า risk-reward แย่มาก
อย่างที่สาม บอทที่ใช้ GTC sell ที่ 0.999 แทบจะไม่ถูก fill - ไม่มีผู้ซื้อที่ราคานั้น position จะไหลไปจนจบเกม ถ้าชนะ คุณได้ 1c ถ้าเกิด reversal คุณเสีย 99c
กับดักนี้คือเงินจริงที่ผู้สร้างบอทเสียไปเพราะไม่ได้คำนวณ Stay out ของตลาดที่ราคา 0.95+ เว้นแต่กลยุทธ์ของคุณจะถูกสร้างมาโดยเฉพาะสำหรับ redemption-arbitrage profile
โค้ด: สมัครรับ order book ของแมตช์แล้วตอบสนอง
Reference: subscribe ไปยัง WebSocket ของเกม NBA เฉพาะเกม, log การอัปเดต book, แล้วส่ง FOK เมื่อมี imbalance signal
import websocket, json
THRESHOLD = 0.5 # imbalance level to trigger
def on_message(ws, message):
msg = json.loads(message)
if msg.get("event_type") != "book": return
bids = msg.get("bids", [])
asks = msg.get("asks", [])
bid_depth = sum(float(b["price"]) * float(b["size"]) for b in bids[:5])
ask_depth = sum(float(a["price"]) * float(a["size"]) for a in asks[:5])
total = bid_depth + ask_depth
if total < 100: return # too illiquid
imb = (bid_depth - ask_depth) / total
if abs(imb) > THRESHOLD:
print(f"signal imb={imb:.2f} bid={bid_depth:.0f} ask={ask_depth:.0f}")
# fire FOK here
ws = websocket.WebSocketApp(
"wss://ws-subscriptions-clob.polymarket.com/ws/market",
on_open=lambda ws: ws.send(json.dumps({"type":"Market","markets":["<CONDITION_ID>"]})),
on_message=on_message
)
ws.run_forever()
สิ่งที่ต้องเพิ่มใน production: cooldown ระหว่างการยิงแต่ละครั้ง, per-token inventory cap, kill เมื่อ book stale (ไม่มีข้อความภายใน 30 วินาที)


