Polymarket Bot Tutorial · Chapter 13 of 32
Market making on Polymarket: how to quote bid and ask, capture the spread, earn maker rebates (20-25% of taker fees), inventory risk math, and when MM works on prediction markets.
What this chapter covers
Market making means posting both a buy price and a sell price on the same market at the same time, over and over, and earning the small gap between them (the spread) every time both sides fill. You are not betting on who wins; you are renting out liquidity and collecting a fee for it. The strategy is well understood in traditional finance. The Polymarket-specific wrinkles are the maker-rebate program (Polymarket pays you a bonus for providing liquidity) and a higher rate of adverse selection than most financial venues - adverse selection means the trader who takes your quote often knows something you do not, and you are on the wrong side the moment they fill you. This chapter is the honest math behind whether it actually pays.
- Market making in plain English
- The spread + rebate edge
- Inventory risk and skew
- When MM works on Polymarket (and when not)
- Code skeleton: quote both sides at +/- N cents
- Adjusting quotes on news flow
- Killing the bot when adverse selection spikes
Market making in plain English
A market maker posts a buy price (the bid) below the market's middle price and a sell price (the ask) above it. That middle point, halfway between the best bid and the best ask, is called the mid. When another trader sells to you, they "hit your bid" and you buy cheap; when another trader buys from you, they "lift your ask" and you sell dear. The gap between your bid and your ask is your revenue on each completed round trip.
Here is the whole edge in one example. Suppose the mid is $0.46 and you quote a bid at $0.45 and an ask at $0.47. Someone sells you a share at $0.45; a little later someone buys that share from you at $0.47. You are flat again and two cents richer, without ever having an opinion on whether the outcome happens. Do that a few hundred times a day and the pennies add up.
The catch is that you do not control which side fills, or when. The strategy is order-flow-driven, not directional: you are betting that trading will keep flowing and the spread will keep being paid, not that YES or NO wins. The real risk is adverse selection - the traders who fill you often know something you do not, so you keep buying right before the price drops. Whether market making pays comes down to one question: is your spread wide enough to cover the losses from those informed traders?
The spread + rebate edge
Two revenue streams for a Polymarket maker.
Spread capture: quote bid 0.45 and ask 0.47 around a 0.46 mid. Every fill on the bid that you can later exit on the ask earns 2 cents. Net edge depends on fill imbalance and price drift.
Maker rebate: Polymarket pays you for filled maker orders. Each fill earns a fee-equivalent (shares × feeRate × p × (1-p) - the same curve as the taker fee), and your daily rebate is your slice of that market's pool: rebate = (your_fee_equivalent / market_total) × pool. The pool is funded from collected taker fees - about 20% in crypto and ~25% in the other categories (geopolitics is fee-free, so it pays no rebate). It settles daily in pUSD once you have accrued $1, and you compete only with other makers in the same market - so heavy taker flow with few competing makers is where the rebate is richest.
For most markets, spread capture is the larger revenue stream. The rebate is meaningful when you can quote wide markets (election year, major sports playoffs) where Polymarket boosts rebates to attract liquidity.
Inventory risk and skew
An MM that gets repeatedly hit on the bid accumulates a long position. The risk is that the mid drops while inventory is long; the maker realizes a loss on the position even after the spread revenue.
Defenses: quote skew (move the bid lower when inventory is long, ask higher when short, to discourage one-sided fills); inventory cap (stop quoting on the side where you are already too long); active rebalancing (occasionally cross the spread to reduce position when inventory is at limit).
Work the math to see why this matters. Your edge per round trip is the 2-cent spread. Now say you buy 100 shares on the bid, but 60 of them never get sold back before the price drifts 2 cents against you - those 60 shares lose 2 cents each, which is $1.20, exactly wiping out the 2 cents you earned on the 40 that did round-trip ($0.80). One lopsided session and the whole day's spread income is gone. That is why you skew: when fills are landing far more on one side than the other (say worse than a 65/35 split), lean your quotes hard the other way so you stop accumulating a position the market is about to move against.
When MM works on Polymarket (and when not)
MM works on Polymarket when three conditions hold.
- Liquid book: enough quote competition that your spread is competitive but not zero. The 2024 election markets, major NFL/NBA games, BTC up/down 5m all qualify.
- Two-sided flow: both buyers and sellers active. One-sided markets (resolved-ish at 0.95+) have nothing for the maker to capture.
- Bounded price moves: spread captures are eaten by 5-cent jumps. Stable mid-range markets (0.40-0.60) are friendliest.
MM fails on: news-driven markets where mid jumps faster than you can re-quote; illiquid books where you are the only quote and the next trade walks 5 levels; resolution-imminent markets where one side is converging to 0 or 1.
Code skeleton: quote both sides at +/- N cents
Pseudocode for the simplest viable maker.
SPREAD_CENTS = 2
INVENTORY_CAP_SHARES = 50
def make_loop(token_id):
while True:
book = fetch_book(token_id)
mid = (book.best_bid + book.best_ask) / 2
inv = chain_balance(token_id)
# Skew: pull the side we are too long on
bid_px = mid - SPREAD_CENTS/200 - (0.005 if inv > INVENTORY_CAP_SHARES * 0.6 else 0)
ask_px = mid + SPREAD_CENTS/200 + (0.005 if inv < -INVENTORY_CAP_SHARES * 0.6 else 0)
cancel_my_existing_quotes(token_id)
if inv < INVENTORY_CAP_SHARES:
place_gtc(token_id, side="BUY", price=bid_px, size=5)
if inv > -INVENTORY_CAP_SHARES:
place_gtc(token_id, side="SELL", price=ask_px, size=min(5, inv))
time.sleep(2)
Production makers add: per-side inventory tracking, cancel-before-place ordering, jitter on the re-quote interval to avoid being predictable, kill-switch on adverse selection (next section).
Adjusting quotes on news flow
When a news event hits, the fair value moves before your quotes do. An MM that does not pull quotes during news flow gets picked off.
The signal: cancel rate of incoming fills jumping above ~3x baseline within 30 seconds, or a wider event-stream cross-check (Polymarket Twitter/Discord, Bloomberg headline feed). When detected, the maker pulls all quotes for 60-120 seconds, lets the new mid stabilize, then re-quotes around the new center.
The simplest implementation watches the last-trade-price stream for the token. A jump of more than 2 standard deviations from the rolling-window mean triggers a pause. The bot re-engages when the price has stabilized for 30+ seconds.
Killing the bot when adverse selection spikes
The hard exit. If the bot's fill PnL over the last 50 fills turns sharply negative, something is wrong: either the market is now news-driven and you should not be making, or your spread is set too tight for the current adverse-selection level.
Kill conditions to encode:
- 5 consecutive bid fills with no ask fill, mid down > 1c since first fill.
- Realized PnL on last 25 round-trip fills below -25% of expected.
- WebSocket disconnect or stale book detected.
- Inventory at cap on either side for > 5 minutes.
When triggered, cancel everything, flatten inventory at market, halt for 15+ minutes. A market maker that does not have a kill switch will lose money during volatile periods until the trader notices manually - which always takes longer than you think.





