All articles
Polymarket·May 27, 2026·6 min read

Building a Polymarket CLOB Market-Making Bot: Full Guide

Build a two-sided Polymarket market making bot on the CLOB: inventory skew, tick sizing, queue position and rebate capture, with code and the gotchas that matter.

Market making on Polymarket means you post both a bid and an ask on a binary outcome token and earn the spread when both sides fill — plus whatever maker rebate the venue is running that week. It sounds like a passive strategy. It isn't. On a market that resolves to exactly $0 or $1, every quote you leave sitting is a free option you've written to the rest of the book, and the person picking you off usually knows something you don't.

This is a build guide for a two-sided quoting bot against Polymarket's Central Limit Order Book (CLOB). It's a different animal from an arbitrage bot that hunts price gaps across correlated markets — here you're a liquidity provider, and your P&L comes from spread capture and rebates minus adverse selection, not from crossing mispriced legs.

What you're actually quoting

Polymarket markets are binary. Each market has two ERC-1155 outcome tokens (YES and NO) whose prices sum to ~$1. The CLOB is off-chain matching with on-chain settlement: you sign orders with an EIP-712 signature, the operator matches them, and fills settle against the CTF Exchange contract on Polygon. If you haven't dealt with the signing scheme before, the mechanics of nonces, salt, and the maker/taker fee flags are covered in this walkthrough of CLOB order signing — get that right first, because a malformed signature just gets rejected and you'll burn an afternoon wondering why nothing fills.

A few numbers that shape everything downstream:

  • Tick size is usually $0.01, but liquid markets drop to $0.001. Your quote granularity is capped by the tick; you can't undercut by a fraction of a cent where the tick is a full cent.
  • Prices live in [0.01, 0.99] for most markets. Near the extremes, the book gets thin and skew matters enormously.
  • Maker orders rest on the book; taker orders cross the spread. You want to be a maker almost always — that's where the rebate is and where you avoid paying the spread yourself.

The core loop

The bot is a state machine that re-quotes on every meaningful book change. Skeleton:

def on_book_update(book, inventory, params):
    mid = (book.best_bid + book.best_ask) / 2
    # fair value: start from mid, then skew for inventory
    skew = params.skew_coeff * inventory.net_position
    fair = clamp(mid - skew, 0.01, 0.99)

    half_spread = max(params.min_edge, params.k_vol * book.recent_vol)
    bid = round_to_tick(fair - half_spread, book.tick)
    ask = round_to_tick(fair + half_spread, book.tick)

    return [
        Quote(side="BUY",  price=bid, size=size_for(inventory, "BUY")),
        Quote(side="SELL", price=ask, size=size_for(inventory, "SELL")),
    ]

The two parts that earn or lose money are the skew and the half-spread. Everything else is plumbing.

Inventory skew is the whole game

If you quote symmetrically around mid and the market drifts, you accumulate a position on the losing side and get run over. The fix is to bias your fair value against your current inventory. Long 400 YES shares? Push both quotes down so your ask fills more eagerly (unloading) and your bid fills more reluctantly (stop buying).

A linear skew works fine to start: fair = mid - skew_coeff * net_position. On a market where a full position swing is ~1000 shares, a skew_coeff around 0.00005 shifts fair by half a cent at 100 shares net — enough to nudge fills without constantly crossing yourself. Tune it against realized inventory variance, not against a backtest of spread captured, because the spread capture number always looks great right up until a resolution event turns your inventory into a directional bet.

The gotcha nobody mentions: near resolution, or when a market has real news, the mid you're skewing around is stale. Your linear model happily keeps quoting a two-sided market while informed flow eats your bid on the way to $1. This is exactly the adverse-selection tax that separates a real desk from a script, and it's why you either widen aggressively or pull quotes entirely around known events — the same instinct behind a news-driven Polymarket bot that reacts to headlines.

Tick sizing and where you sit in the queue

On a $0.01-tick market, if the book is 0.42 / 0.44 you can join at 0.42, improve to 0.43 (becoming the whole spread on the bid), or sit behind. Improving to 0.43 gets you fills faster but earns one tick less and puts you first in line to be adversely selected. Joining at 0.42 means queue priority behind existing size — you might never fill in a slow market.

My default: quote one tick inside the far side only when my inventory wants that fill anyway. If I'm flat, I join the existing best rather than improving, because improving is paying for flow I don't need. This queue-position logic is the difference between a purpose-built Polymarket market maker and a naive bid = best_bid + tick loop that donates edge to every taker.

Rebate capture without gaming it

Polymarket has run maker incentive programs that pay rebates on maker volume. The temptation is to wash — quote tight on both sides and let your own orders trade against each other to farm volume. Don't. Self-trade detection exists, programs claw back, and you can get banned. Legitimate rebate capture means being a genuine maker: rest orders, get filled by real takers, and let the rebate turn a break-even spread into a positive one. On a 2-cent spread with a 1-cent effective edge after adverse selection, a rebate of even 0.2 cents per share is the margin between viable and not.

Worked example

Book: YES at 0.42 / 0.44, tick 0.01, you're long 200 YES.

  • Mid = 0.43. Skew (coeff 0.00005 × 200) = 0.01. Fair = 0.42.
  • Half-spread from min_edge = 0.01. Raw bid 0.41, raw ask 0.43.
  • You quote BUY 0.41 (reluctant — you're already long) and SELL 0.43 (joining the ask, eager to unload).

If your ask fills, inventory drops to ~150, skew shrinks, fair drifts back toward mid, and the next re-quote loosens. That self-correcting behavior is the point.

Testing before you risk real USDC

Paper-trade against the live WebSocket feed first — subscribe to book updates, run the full quoting logic, log the fills you would have gotten, and reconcile against actual trade prints. Watch two metrics: realized spread per round-trip and inventory half-life (how fast a position decays back to flat). If inventory half-life climbs during volatile sessions, your skew is too weak. If realized spread is negative on filled round-trips, you're getting picked off and need wider quotes or an event filter.

Resolution risk deserves its own kill-switch. A market that looks like a clean 50/50 can gap to a corner when the UMA optimistic oracle proposes an outcome, and if you're quoting into that you'll hold inventory on the wrong side at settlement. Some desks treat the dispute window itself as tradable rather than something to hide from — there's genuine edge around contested resolutions — but for a pure market maker the safe default is to flatten and stand down before resolution timestamps.

Start narrow: one liquid market, small size, tight loss limits, and a hard cap on net inventory. Watch it for a week, read every fill, and only widen your universe once the inventory half-life and realized-spread numbers hold up. If you'd rather have that quoting engine, skew model, and event kill-switch built and tuned for your capital, that's exactly what our Polymarket market-making build delivers.

Need a bot like this built?

We design, build and run trading bots on Solana, Hyperliquid and Polymarket.

Start a project
#Polymarket#market making#CLOB#trading bots#liquidity provision