Polymarket Bot Tutorial · 第32章中の22章
PolymarketのNegRiskマルチアウトカムbot: sum-to-1の仕組み、YESレッグの合計が1にならないときのレッグ裁定、レッグ間ヘッジ、そしてマルチアウトカム市場特有の実行上の落とし穴。
この章で扱う内容
マルチアウトカムのNegRisk市場は相互排他的です。つまり、ちょうど1つだけがYESで決済されます。この章は、11章の実行メカニクスの上にある戦略レイヤーです。レッグ間でどうヘッジするか、sum-to-1の裁定が本当に成立するのはいつか、そしてNegRisk botが初回デプロイで最もハマりやすいバグを解説します。
- NegRiskとbinaryの復習
- sum-to-1の不変条件と裁定
- レッグごとのヘッジ構築
- 実行: 注文におけるneg_riskフラグ
- NegRisk botでよくあるバグ
- コード: 全レッグをスナップショットして1.00未満の合計を検知する
NegRiskとbinaryの復習
Binary: 1つのyes/no市場、2つのトークン、合計は1.0。NegRisk: 相互排他的なN個の結果、N個のトークン、すべてのYESレッグの合計はイベント全体でおよそ1.0になります。
実行面では、NegRiskではすべての注文にnegRisk: trueが必要で(11章参照)、別のexchange contractを経由します。戦略面では、NegRiskにはbinaryにはない2つの独自機会があります。合計が1.0からずれたときのレッグ間裁定と、複数のYESレッグを買うことによるヘッジ構築です。
NegRisk固有のコスト: レッグが多いほどスプレッド税も増える(取引する各レッグで約0.5〜1cのスプレッドコスト)、流動性の低いイベントではsum-to-1の乖離が大きくなりやすい(裁定機会は見つけやすいが、規模は小さい)。
sum-to-1の不変条件と裁定
裁定の前提: すべてのN個のYESレッグを買うのに$1.00未満しかかからないなら、決済時に保証された利益をロックできます(1つのレッグは必ず$1.00を払い、他は$0になります)。
実際には、裁定ギャップは通常0〜3cで、各レッグのスプレッドと手数料に食われます。また、公開後数分で消えることがほとんどです。取り切れる量は、最も薄いレッグの流動性に制限されます。
この裁定は、特有の決済失敗モードにも影響されます。たとえば、どの候補も条件を満たさない場合に明示的にYESで決済される「none of the above」の結果です。イベントにそのレッグがあり、あなたがそれを買っていなければ、「完全ヘッジ」は実際のペイアウトを取り逃がします。
レッグごとのヘッジ構築
あるNegRiskレッグでポジションを持っている場合、競合するレッグのYESを比率に応じて買うことでヘッジできます。Trump-YESを0.50で保有していてTrump敗北に対するヘッジをしたいなら、他の候補レッグのポートフォリオを買います。
レッグごとのヘッジ比率 ≈ そのレッグの現在の暗示確率を、Trump敗北を条件にした値。近似式: weight_i = price_i / (1 - trump_price)。
このヘッジは不完全です。使う価格はある時点のスナップショットであり、ニュース到来に応じて条件付き確率が変わるからです。ヘッジは週次、または大きなニュースのたびにリバランスしてください。やりすぎないこと。ヘッジの目的は分散を減らすことであり、完全に消すことではありません。
実行: 注文におけるneg_riskフラグ
NegRisk固有で最もよくあるバグは、注文送信ペイロードにnegRisk: trueを入れ忘れることです。注文自体はAPIで受理されますが、NegRisk exchangeではなく標準のCTF exchangeにルーティングされるため、決済が正しく行われません。
// CORRECT for NegRisk markets:
await client.createAndPostOrder(
{ tokenID, price, size, side: Side.BUY },
{ tickSize: '0.01', negRisk: true }, // <-- REQUIRED
OrderType.FOK
);
真のソースはGamma APIのmarket.negRiskです。必ず読み、それをそのまま渡してください。推測でフラグをハードコードしてはいけません。
NegRisk botでよくあるバグ
複数のbotにわたる本番デバッグログから。
- negRiskフラグの未設定: 注文は受理されるが、決済に失敗する。対策: すべてのラッパーでフラグを強制する。
- 「Other」レッグを使わないヘッジ: 「none of the above」の結果があるイベントでは、それを除外したヘッジポートフォリオは不完全です。対策: ヘッジ構築時は必ずOtherレッグを確認する。
- sum-to-1裁定のサイズ不足: 裁定機会の1cのエッジは見つけたが、各レッグ5株しか取らない。総利益は手数料前で5セント、ネットではマイナス。対策: 見出しのパーセンテージを追うのではなく、意味のある絶対ドル額を取れるサイズにする。
- 古いレッグ価格: botが3つのレッグ価格を取得するのに合計200msかかり、取得中に最後のレッグ価格が変わっていた。対策: すべてのレッグを並列取得し、スナップショットを1つの観測値として扱う。
コード: 全レッグをスナップショットして1.00未満の合計を検知する
参考: NegRiskイベントのすべてのYESレッグを並列でスナップショットし、裁定を検知します。
import asyncio, aiohttp
async def fetch_leg_ask(session, token_id):
async with session.get(f"https://clob.polymarket.com/book?token_id={token_id}") as r:
d = await r.json()
asks = d.get("asks", [])
return float(asks[0]["price"]) if asks else None
async def check_arb(event_slug):
event = await fetch_event(event_slug)
if not event["markets"][0]["negRisk"]: return None
legs = []
for m in event["markets"]:
toks = json.loads(m["clobTokenIds"])
yes_token = toks[0]
legs.append(yes_token)
async with aiohttp.ClientSession() as s:
asks = await asyncio.gather(*[fetch_leg_ask(s, t) for t in legs])
if any(a is None for a in asks): return None
total = sum(asks)
if total < 0.97:
return {"edge": 1 - total, "legs": list(zip(legs, asks))}
return None
すべてのレッグを原子的に実行するのはさらに難しく、レッグごとのFOKと部分約定時のロールバックが必要です(chapter 16のstat-arbコードと同様のパターンです)。





