Polymarket Bot Tutorial · 第32章中15章目
Polymarketにおけるスポーツのマイクロストラクチャーボット:試合中の優位性、スコアライン起因の誤価格設定、NBAタグ(745)とTennisタグ(864)、ライブデータソース、そして高速スポーツ市場向けの執行パターン。
この章で扱う内容
スポーツ市場は、Polymarketで最も一貫して活発な非政治セグメントです。うまく機能するボットは、きれいに2つの系統に分かれます。ラインが確定した後に取引する試合前のラインキャッチャーと、試合中の板の動きに反応する試合中のマイクロストラクチャーボットです。この章では、それぞれに適用される具体的なタグID、データソース、レイテンシー予算とともに両方を扱います。
スポーツ市場は、Polymarketで最も忙しい非政治セグメントです。機能する執行パターンは、ライブスコアフィード(ESPN、PandaScore)とオーダーブックのマイクロストラクチャーシグナルを組み合わせたものです。この章では、NFL、NBA、サッカー、テニスで具体的に何が機能するのか、そしてeスポーツがどう異なるのかを扱います。
- なぜスポーツ市場は取引可能なのか
- 試合前 vs 試合中(別々のボット)
- 検証済みタグID(NBA 745、Tennis 864)
- データソース:ESPN、公式API、オンスクリーン
- 試合中向けのレイテンシー予算
- 0.99 / 0.01 の罠
- コード:games bookにサブスクライブして反応する
なぜスポーツ市場は取引可能なのか
スポーツ市場は、定義された時間枠(数時間から数日)で清算され、公開されたライブデータがあり、試合中に継続的な注文フローが発生します。取引可能な市場に必要なのはこの3つすべてです。政治市場には「定義された時間枠」がなく、天候市場には「継続的なフロー」がなく、マイナーな大会には「公開ライブデータ」がありません。
スポーツ市場のトレーダー層は、たとえば選挙市場よりも多様です。カジュアルなスポーツベッターは感情で価格付けし、知識のあるトレーダーは試合の進行に応じて公正価値へと修正します。この2者のギャップこそがボットの優位性です。
出来高の分布は不均一です。NFLの日曜には、Polymarketのスポーツ市場全体で数億ドルが回転する一方、火曜夜のサウジ・プロリーグの試合は5万ドル未満になることもあります。実際に動いているところに合わせて戦略の規模を決めてください。
試合前 vs 試合中(別々のボット)
根本的に異なる2つのボット設計があります。
試合前のラインキャッチャー:開いたばかりの市場をスキャンし、自分のモデルまたはより鋭いベッティング市場の数値に対して、誤価格のラインを特定してFOK買いを出します。保有は試合開始後、場合によっては決着まで。速度:秒ではなく分。優位性:モデル+ラインショッピング。
試合中のマイクロストラクチャー:ライブ試合のオーダーブックWebSocketにサブスクライブし、数秒以内に板の偏りシグナル+得点イベントに反応します。速度:分ではなく秒。優位性:レイテンシー+注文フローの読み取り。
この2つで共有するコードはほとんどありません。リスクプロファイルも、データソースも、出口戦略も異なります。両方をやろうとするボットは、結局どちらも上手くできなくなります。どちらか一方を選んでください。
検証済みタグID(NBA 745、Tennis 864)
2026年5月に、主要スポーツカテゴリ向けの本番タグIDを検証済みです。/events呼び出しを効率的に絞り込むために使用してください。
| スポーツ / リーグ | タグID | タグスラッグ | 備考 |
|---|---|---|---|
| NBA | 745 | nba | 10月〜6月に最大出来高 |
| NFL | 450 | nfl | 9月〜2月の日・月にピーク |
| Tennis(全体) | 864 | tennis | 通年、トーナメント単位で変動 |
| Soccer(一般) | 1059 | soccer | 下のサブタグと組み合わせる |
| EPL | 739 | epl | |
| UCL | 2186 | uefa-champions-league | |
| Esports(全体) | 702 | esports | LoL+CS2+Valorant+Dota |
| MLB | 1245 | mlb | 4月〜10月にピーク |
| NHL | 823 | nhl | 10月〜6月にピーク |
タグIDは年をまたいでも安定しています。新しいタグ(サウジ・プロリーグ、IPL)は追加されますが、古いタグの番号が変更されることはありません。
データソース:ESPN、公式API、オンスクリーン
従来型スポーツでは、無料のESPNスコアボードAPIで必要なものはほぼすべて揃います。スコア、ピリオド/時計、勝率、場合によってはショット位置まで取得できます。キーは不要で、レート制限はIP単位のみです。エンドポイントのパターンはhttps://site.api.espn.com/apis/site/v2/sports/<sport>/<league>/scoreboardです。
eスポーツにはESPNのカバレッジがありません。選択肢は、PandaScore(月額30〜60ドル、業界標準)、HLTV(CS2専用、スクレイプ可能、APIなし)、Liquipedia(コミュニティ管理、スクレイプ可能、更新頻度は遅め)です。
オンスクリーンのフィード(TVストリームを契約し、OCRでスコアバグを読む)も使えますが、運用負荷が高いです。リアルタイムでAPIがカバーしていないスポーツで、3秒未満の更新が必要な戦略がある場合にのみ推奨します。
試合中向けのレイテンシー予算
試合中に反応するボットのエンドツーエンドのレイテンシー予算です。
- 得点イベント発生: t=0
- ソースフィードに反映: t+3-15秒(ESPN: 約10秒、PandaScore: 約3秒)
- ボットがフィードを読む: t+10-16秒
- ボットがアクションを決定: +50ms
- FOK注文を発注: +200-500ms
- CLOBで約定: +300-1000ms(ネットワーク+マッチング)
合計:11〜17秒。最速のプロフェッショナル企業は、有料のプレミアムフィードとコロケーション済みVPSでエンドツーエンド3〜5秒を達成しています。標準ホストと無料のESPNで動くリテールボットは、遅い側に位置します。
5秒未満が必要な戦略は、リテールには実行不可能です。10〜17秒のウィンドウで機能する戦略は、得点後のラインキャッチ、過剰反応の逆張り、終盤の確定性プレイです。
0.99 / 0.01 の罠
試合中スポーツボットで最もよくある失敗は、残り1分で大本命を0.99で買い、簡単な+1¢を期待することです。これが失敗する理由は3つあります。
第一に、1%と見積もられる大穴側の確率はゼロではありません。終盤の逆転劇は無視できない頻度で起こります。99.5%確実な勝利に200回賭ければ、フルサイズのポジションで1回は負けます。
第二に、0.99/0.01のスプレッドでは1株あたり99セントを払い、成功しても1セントしか勝てず、まれな逆転で99セントを失います。リスク・リターンは非常に厳しいです。
第三に、GTC売りを0.999で置くボットは、ほとんど約定しません。その価格では買い手がいないからです。ポジションは決着まで持ち越されます。勝てば1セント、逆転が起きれば99セントの損失です。
この罠は、計算をしなかったビルダーが実際に資金を失う原因です。償還アービトラージ型に特化した戦略でない限り、0.95以上の価格帯には手を出さないでください。
コード:games bookにサブスクライブして反応する
参考:特定のNBA試合のWebSocketにサブスクライブし、板更新をログに出し、偏りシグナルでFOKを発射します。
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()
本番追加事項:発注間のクールダウン、トークンごとの在庫上限、古い板で停止(30秒メッセージなしでkill)。





