Polymarket Bot Tutorial · 32장 중 22장

Polymarket의 NegRisk multi-outcome bot: sum-to-1 mechanics, YES legs가 1을 합산하지 않을 때의 leg arbitrage, legs 간 hedging, 그리고 multi-outcome market에 특화된 execution pitfalls.

이 장에서 다루는 내용

Multi-outcome NegRisk market는 상호 배타적입니다. 정확히 하나만 YES로 resolve됩니다. 이 장은 11장의 execution mechanics 위에 얹는 strategy layer입니다. legs 간 hedge하는 방법, sum-to-1 arb가 실제로 성립하는 경우, 그리고 대부분의 NegRisk bot이 첫 배포에서 맞닥뜨리는 버그를 다룹니다.

  • NegRisk vs binary 요약
  • Sum-to-1 invariant와 arbitrage
  • Leg별 hedge 구성
  • Execution: orders의 neg_risk flag
  • NegRisk bot의 흔한 버그
  • Code: 모든 leg snapshot 및 under-1.00 sum 감지

NegRisk vs binary 요약

Binary: 하나의 yes/no market, 두 개의 token, 합은 1.0입니다. NegRisk: N개의 상호 배타적 outcome, N개의 token, 모든 YES leg의 합은 이벤트 전반에서 약 1.0입니다.

Execution 측면에서 NegRisk는 모든 order에 negRisk: true가 필요하며(11장), 별도의 exchange contract를 통해 라우팅됩니다. Strategy 측면에서 NegRisk는 binary에는 없는 두 가지 고유한 기회를 제공합니다. 합이 1.0에서 벗어날 때의 cross-leg arb, 그리고 여러 YES leg를 매수해 hedge를 구성하는 방법입니다.

NegRisk에만 있는 비용: leg가 많을수록 spread tax도 커집니다(거래하는 각 leg마다 약 0.5~1c의 spread 비용). 또한 유동성이 낮은 이벤트에서는 sum-to-1 deviation이 더 크게 벌어지며, arb 기회는 더 자주 나타나지만 규모는 더 작습니다.

Sum-to-1 invariant와 arbitrage

Arb의 전제는 이렇습니다. 모든 N개의 YES leg를 사는 비용이 $1.00 미만이면, resolve 시점에 확정 수익이 잠깁니다. 반드시 하나의 leg는 $1.00을 지급하고, 나머지는 $0으로 가기 때문입니다.

실전에서는 arb gap이 보통 0~3c 수준이며, 각 leg의 spread와 fee에 의해 대부분 사라집니다. 또한 개장 후 몇 분 안에 없어집니다. 용량은 가장 얇은 leg의 liquidity에 의해 제한됩니다.

이 arb는 특정 resolution failure mode의 영향을 받기도 합니다. 예를 들어 명시적으로 "none of the above" outcome가 있고, 어떤 명명된 후보도 해당되지 않을 때 YES로 resolve되는 경우입니다. 이벤트에 그런 leg가 있는데 그걸 사지 않았다면, 당신의 "완전 hedge"는 실제 payout을 놓치게 됩니다.

Leg별 hedge 구성

한 NegRisk leg에 position을 보유하고 있다면, 비례해서 경쟁 leg들의 YES를 매수하여 hedge할 수 있습니다. Trump-YES를 0.50에 보유하고 있고 Trump 패배에 대비해 hedge하고 싶다면, 나머지 명명된 leg들의 포트폴리오를 매수합니다.

Leg당 hedge weight는 대략 Trump가 지는 조건부 상황에서의 현재 implied probability와 같습니다. 근사식은 다음과 같습니다. weight_i = price_i / (1 - trump_price).

이 hedge는 point-in-time price를 사용하고, 뉴스가 들어오면 conditional probability가 변하기 때문에 완벽하지 않습니다. Hedge는 매주 또는 큰 뉴스가 있을 때 재조정하세요. 지나치게 복잡하게 만들 필요는 없습니다. hedge의 목적은 variance를 줄이는 것이지, 완전히 없애는 것이 아니기 때문입니다.

Execution: orders의 neg_risk flag

NegRisk 전용으로 가장 흔한 버그는 order placement payload에 negRisk: true를 빼먹는 것입니다. API에서는 order가 받아들여지지만, standard CTF exchange가 아니라 NegRisk exchange로 라우팅되어야 하는데 잘못 settlement됩니다.

// CORRECT for NegRisk markets:
await client.createAndPostOrder(
  { tokenID, price, size, side: Side.BUY },
  { tickSize: '0.01', negRisk: true },   // <-- REQUIRED
  OrderType.FOK
);

Source of truth는 Gamma API의 market.negRisk입니다. 이를 읽고 그대로 전달하세요. 추측으로 flag를 hardcode하지 마세요.

NegRisk bot의 흔한 버그

여러 bot의 production debug logs에서 나온 내용입니다.

  1. negRisk flag 누락: order는 받아들여지지만 settlement가 실패합니다. 해결: 모든 wrapper에서 flag를 강제하세요.
  2. "Other" leg 없이 hedge하기: "None of the above" outcome가 있는 이벤트에서 이를 제외한 hedge portfolio는 불완전합니다. 해결: hedge를 구성할 때 항상 Other leg가 있는지 확인하세요.
  3. Sum-to-1 arb의 규모를 너무 작게 잡음: arber가 1c edge는 알아냈지만 leg당 5 shares만 거래합니다. 총 profit은 spread 전 5센트에 불과하고, net 기준으로는 마이너스입니다. 해결: headline percentage를 쫓지 말고, 의미 있는 절대 달러 수익이 나오도록 arb 규모를 잡으세요.
  4. 오래된 leg pricing: bot이 3개 leg 가격을 가져오는 데 총 200ms가 걸리고, 마지막 leg 가격은 그 사이에 변합니다. 해결: 모든 leg를 parallel로 가져오고, snapshot을 하나의 observation으로 취급하세요.

Code: 모든 leg snapshot 및 under-1.00 sum 감지

참고: NegRisk 이벤트의 모든 YES leg를 병렬로 snapshot하고 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

모든 leg의 atomic execution은 더 어려운 문제이며, leg별 FOK와 부분 체결 시 rollback이 필요합니다(16장의 stat-arb code와 유사한 패턴).

자주 묻는 질문

NegRisk market에서 sum-to-1 invariant는 무엇인가요?
NegRisk multi-outcome market의 모든 YES leg를 합치면, 정확히 하나의 outcome만 이기기 때문에 YES price의 합은 1 USD 근처에 유지됩니다. 수수료를 제외한 합이 1.00 아래로 떨어지면, 모든 leg를 비례해서 매수해 arbitrage profit을 잠글 수 있습니다. 이 arb는 드물고 빠르게 사라집니다. primary strategy가 아니라 흥미로운 예외로 보는 것이 좋습니다.
NegRisk pricing은 binary와 어떻게 다른가요?
Binary: YES price는 직접적인 probability estimate입니다. NegRisk: 한 leg의 YES price는 N개의 alternative 중 특정 outcome이 이길 확률입니다. N이 커질수록 각 개별 price는 작아집니다(확률 합이 1이기 때문). NegRisk trading은 절대적인 Yes/No가 아니라 상대적인 probability로 생각해야 합니다.
NegRisk bot에서 가장 흔한 버그는 무엇인가요?
Order placement에서 neg_risk: true flag를 잊는 것입니다. order가 거부되거나 잘못된 CTF position으로 라우팅됩니다. 우리는 production에서 이 문제를 겪었습니다. trader history의 commit 06deaef가 바로 이 수정이었습니다. NegRisk order에는 항상 Python에서는 neg_risk=true, Node에서는 negRisk: true를 설정하세요.
Leg를 hedge해서 NegRisk에서 돈을 벌 수 있나요?
이론적으로는 가능합니다(두 leg 사이의 spread를 잠그는 방식). 하지만 실전에서는 fee가 대부분의 retail bot에서 hedge edge를 갉아먹습니다. Hedging은 market making 중 inventory를 neutral하게 유지하는 데는 유용하지만, 단독 전략으로는 적합하지 않습니다.
NegRisk market는 어떻게 찾나요?
Gamma /events에서 market 수가 2보다 크고 negRisk flag가 설정된 이벤트를 필터링하세요. 흔한 category는 championship winners(NBA Finals MVP), election fields(next Speaker), tournament brackets입니다. 각 Gamma event에는 child markets array가 포함됩니다.
NegRisk market는 binary보다 유동성이 더 좋나요, 나쁜가요?
leg당으로는 더 낮고, 전체 합계로는 더 높습니다. 30팀 NBA Champion 이벤트는 24시간 총 volume이 50K일 수 있지만, 각 팀 market은 1.6K에 불과할 수 있습니다. 그래서 leg별 trading은 더 어렵습니다. aggregate liquidity는 실제로 존재하지만, 분산되어 있을 뿐입니다.