Polymarket Bot Tutorial · Глава 22 из 32
NegRisk multi-outcome bots на Polymarket: механика sum-to-1, leg arbitrage, когда YES legs не суммируются до 1, hedging across legs и подводные камни execution, специфичные для multi-outcome markets.
Что охватывает эта глава
Multi-outcome NegRisk markets взаимно исключающие - ровно один outcome resolves YES. Эта глава посвящена strategy layer поверх execution mechanics из главы 11: как hedge across legs, когда sum-to-1 arb действительно существует и какие bugs чаще всего ловят NegRisk bots при первом deploy.
- NegRisk vs binary recap
- Sum-to-1 invariant и arbitrage
- Leg-by-leg hedge construction
- Execution: флаг neg_risk в orders
- Распространенные bugs в NegRisk bots
- Code: snapshot всех legs и detect under-1.00 sum
NegRisk vs binary recap
Binary: один yes/no market, два tokens, sum до 1.0. NegRisk: N взаимно исключающих outcomes, N tokens, все YES legs суммарно дают ~1.0 на весь event.
С точки зрения execution, для NegRisk требуется negRisk: true в каждом order (глава 11) и routing через отдельный exchange contract. С точки зрения strategy, NegRisk дает два уникальных преимущества, которых нет у binary: cross-leg arb, когда сумма уходит от 1.0, и hedge construction через покупку нескольких YES legs.
Издержки, специфичные для NegRisk: больше legs = больше spread tax (каждый leg, которым вы торгуете, стоит ~0.5-1c spread), более широкие отклонения sum-to-1 на illiquid events (arb чаще доступен, но меньше по размеру).
Sum-to-1 invariant и arbitrage
Логика arb: если покупка всех N YES legs стоит меньше $1.00, вы фиксируете гарантированную прибыль при resolution (один leg обязан выплатить $1.00; остальные уйдут к $0).
На практике arb gap обычно составляет 0-3c и съедается spread + fees на каждом leg; также он исчезает в течение нескольких минут после открытия. Capacity ограничена самым неликвидным leg.
Arb также подвержен специфическим modes of failure при resolution: outcome "none of the above", который явно resolves YES, когда ни один из названных кандидатов не подходит. Если у event есть такой leg, а вы его не купили, ваш "complete hedge" не покрывает фактическую выплату.
Leg-by-leg hedge construction
Держа позицию в одном NegRisk leg, вы можете hedge ее, покупая YES в конкурирующих legs пропорционально. Если у вас Trump-YES по 0.50 и вы хотите hedge against a Trump loss, вы покупаете portfolio из остальных именованных legs.
Вес hedging на leg ≈ текущая implied probability leg при условии, что Trump проиграет. Аппроксимация: weight_i = price_i / (1 - trump_price).
Hedge imperfect, потому что используемые prices взяты на конкретный момент времени, а conditional probabilities меняются по мере появления news. Rebalance hedge раз в неделю или при крупных news. Не усложняйте без необходимости; цель hedging - снизить variance, а не устранить ее полностью.
Execution: флаг neg_risk в orders
Самый распространенный bug, специфичный для NegRisk: забыть negRisk: true в payload при размещении order. Order принимается API, но settles incorrectly, потому что уходит в standard CTF exchange вместо NegRisk exchange.
// CORRECT for NegRisk markets:
await client.createAndPostOrder(
{ tokenID, price, size, side: Side.BUY },
{ tickSize: '0.01', negRisk: true }, // <-- REQUIRED
OrderType.FOK
);
Источник истины: market.negRisk из Gamma API. Считывайте его и передавайте дальше. Никогда не hardcode флаг на основе догадок.
Распространенные bugs в NegRisk bots
Из production debug logs по нескольким bots.
- Missing negRisk flag: orders принимаются, settlement fails. Лечение: enforced flag в каждом wrapper.
- Hedging without the "Other" leg: в events с outcome "None of the above" hedge portfolio, исключающий его, неполный. Лечение: всегда проверяйте Other leg при построении hedges.
- Sum-to-1 arb under-sizing: arber находит edge в 1c, но торгует по 5 shares на leg; общая прибыль - 5 cents до spread, на net получается минус. Лечение: size arb так, чтобы извлекать значимые абсолютные dollars, а не гоняться за headline percentages.
- Stale leg pricing: bot получает 3 leg prices, тратит в сумме 200ms, а price последнего leg уже изменился во время fetch. Лечение: запрашивать все legs параллельно + считать snapshot единым observation.
Code: snapshot всех legs и detect under-1.00 sum
Reference: snapshot всех YES legs NegRisk event параллельно, detect arb.
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
Atomic execution всех legs - более сложная задача, и для нее требуется per-leg FOK + rollback при partial fill (похожий pattern на code stat-arb из главы 16).





