Polymarket Bot Tutorial · Chapter 22 of 32
NegRisk multi-outcome bots on Polymarket: sum-to-1 mechanics, leg arbitrage when YES legs dont sum to 1, hedging across legs, and execution pitfalls specific to multi-outcome markets.
What this chapter covers
Multi-outcome NegRisk markets are mutually exclusive - exactly one resolves YES. This chapter is the strategy layer on top of chapter 11's execution mechanics: how to hedge across legs, when sum-to-1 arb is real, and the bugs most NegRisk bots hit on first deploy.
- NegRisk vs binary recap
- Sum-to-1 invariant and arbitrage
- Leg-by-leg hedge construction
- Execution: neg_risk flag in orders
- Common bugs in NegRisk bots
- Code: snapshot all legs and detect under-1.00 sum
NegRisk vs binary recap
Binary: one yes/no market, two tokens, sum to 1.0. NegRisk: N mutually exclusive outcomes, N tokens, all YES legs sum to ~1.0 across the event.
Execution-wise, NegRisk requires negRisk: true on every order (chapter 11) and routes through a separate exchange contract. Strategy-wise, NegRisk offers two unique opportunities binaries don't: cross-leg arb when the sum drifts away from 1.0, and hedge construction by buying multiple YES legs.
The conversion mechanic - the capital-efficiency edge. Because the legs are linked, a No share in any one outcome can be converted into one Yes share in every other outcome, through the Neg Risk Adapter. So holding No on the favourite is economically a Yes basket across the whole field: you can build the same exposure two ways and take whichever is cheaper, or convert instead of buying each leg separately and save the per-leg spread. (Augmented neg-risk events also carry placeholder outcomes for not-yet-named candidates - ignore them until they are named or the event resolves.)
Costs unique to NegRisk: more legs = more spread tax (each leg you trade costs ~0.5-1c spread), wider sum-to-1 deviations on illiquid events (the arb is more often available but smaller).
Sum-to-1 invariant and arbitrage
The arb premise: if buying all N YES legs costs less than $1.00, you have locked in a guaranteed profit at resolution (one leg must pay $1.00; the others go to $0).
In practice, the arb gap is usually 0-3c, eaten by spread + fees on each leg, and disappears within minutes of opening. Capacity is limited by the thinnest leg's liquidity.
The arb is also subject to specific resolution failure modes: a "none of the above" outcome that explicitly resolves YES when no named candidate qualifies. If the event has such a leg and you didn't buy it, your "complete hedge" misses the actual payout.
Leg-by-leg hedge construction
Holding a position on one NegRisk leg, you can hedge by buying YES on competing legs in proportion. If you hold Trump-YES at 0.50 and want to hedge against a Trump loss, you buy a portfolio of the other named legs.
Hedge weight per leg ≈ leg's current implied probability conditional on Trump losing. Approximation: weight_i = price_i / (1 - trump_price).
The hedge is imperfect because the prices used are point-in-time and the conditional probabilities shift as news arrives. Rebalance the hedge weekly or on major news. Don't over-engineer it; the hedge's purpose is reducing variance, not eliminating it.
Execution: neg_risk flag in orders
The single most common NegRisk-specific bug: forgetting negRisk: true in the order placement payload. The order is accepted by the API but settles incorrectly because it routes to the standard CTF exchange instead of the NegRisk exchange.
// CORRECT for NegRisk markets:
await client.createAndPostOrder(
{ tokenID, price, size, side: Side.BUY },
{ tickSize: '0.01', negRisk: true }, // <-- REQUIRED
OrderType.FOK
);
Source of truth: market.negRisk from Gamma API. Read it; pass it through. Never hardcode the flag based on guessing.
Common bugs in NegRisk bots
From production debug logs across multiple bots.
- Missing negRisk flag: orders accepted, settlement fails. Cure: enforce the flag in every wrapper.
- Hedging without the "Other" leg: in events with a "None of the above" outcome, the hedge portfolio that excludes it is incomplete. Cure: always check for the Other leg when constructing hedges.
- Sum-to-1 arb under-sizing: arber realizes the 1c edge but trades 5 shares per leg; total profit is 5 cents before spread, negative net. Cure: size the arb to extract meaningful absolute dollars, not chase headline percentages.
- Stale leg pricing: bot fetches 3 leg prices, takes 200ms total, last leg's price changed during the fetch. Cure: fetch all legs in parallel + treat the snapshot as one observation.
Code: snapshot all legs and detect under-1.00 sum
Reference: snapshot all YES legs of a NegRisk event in parallel, 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 of all legs is the harder problem and requires per-leg FOK + rollback on partial fill (similar pattern to chapter 16's stat-arb code).





