Polymarket Bot Tutorial · Chapter 10 of 32

Polymarket order types explained for bot builders: Fill-or-Kill (FOK), Fill-and-Kill (FAK), Good-til-Cancelled (GTC), and limit-vs-market trade-offs. With production-grade decision rules.

What this chapter covers

Order-type confusion is the single most expensive class of bug for new bot builders. Sending FOK when GTC was needed produces missed entries; sending GTC when FOK was needed leaves resting orders that fill at terrible prices hours later. This chapter is the decision tree and the production defaults that have held up across thousands of orders.

  • Quick decision tree
  • FOK: when you must fill or skip
  • FAK: when partials are acceptable
  • GTC: when you want to rest on the book
  • GTD: orders that expire on their own
  • Post-only: guarantee maker status
  • Limit vs market and the spread tax
  • Our production defaults (FOK buys, GTC sells)
  • Code: place each order type

Quick decision tree

Three questions decide every order placement.

  1. Do you need a guaranteed fill right now, and not at all if you cannot get it now? → FOK.
  2. Do you want as much fill as you can get right now, accept partials, no resting order? → FAK.
  3. Do you want to rest on the book at your price and wait for someone to come to you? → GTC.

That's it. Most bot bugs around order types come from picking #1 when you wanted #3 (a "buy" turns into "no position because the spread was too wide") or picking #3 when you wanted #1 (a "buy" turns into a resting order that fills hours later at the wrong moment).

FOK: when you must fill or skip

Fill-or-Kill matches the entire order at the requested price or better, instantly. If the full size cannot be filled instantly, the order is rejected and nothing happens. No resting, no partial.

Use FOK for: news-arbitrage entries (you only want in at the news price, not at where the market is in 30s); take-profit exits at a specific target where partials would muddy bookkeeping; any time the strategy assumes atomic execution.

The trade-off: FOK rejects more often than other order types, especially on illiquid books. Always have a fallback path - re-evaluate the strategy condition and retry if still valid, or move on.

FAK: when partials are acceptable

Fill-and-Kill (also called "immediate or cancel") matches as much as it can right now, then cancels the unfilled remainder. You may get the full size, a partial, or zero.

Use FAK for: market-buy with a specific price ceiling (lift the ask up to N cents above mid); sweep-the-book sells when reducing inventory urgently; any strategy where "some position is better than none."

Operationally trickier than FOK because the bot has to know whether it got 100% or 30% before deciding the next step. The fill response includes a filled_size field - always read it.

GTC: when you want to rest on the book

Good-til-Cancelled rests on the book at your price until filled or you cancel. No timeout (other order types in the v2 API include GTD with an expiry).

Use GTC for: take-profit sells at +Nc above entry; stop-loss sells at -Nc below entry (with caveats - see below); market-making both-sided quotes; any position where the bot is willing to wait for a better price.

The hard rule: GTC requires ≥ 5 shares. Orders below 5 shares are rejected by the CLOB with Size (X) lower than the minimum: 5. A bot that posts a 4-share GTC sell silently fails to set the exit and rides the position to resolution. Always check inventory ≥ 5 before posting GTC; fall back to FAK or ride-to-resolve if smaller.

GTD: orders that expire on their own

Good-til-Date is a resting order, like GTC, with a built-in expiry. It sits on the book until it fills, you cancel it, or its expiration timestamp passes - whichever comes first. Once the timestamp passes the CLOB drops it for you; you never have to send a cancel.

Use GTD for: a maker quote you only want live until a known event (a data release, a game kickoff, a debate); a take-profit that should not outlive the session; any "post it and forget it, but not forever" order. It is the clean alternative to posting GTC and running your own timer to cancel later - one fewer moving part to get wrong.

The expiration field is a UTC seconds timestamp (Unix epoch seconds, not milliseconds). The CLOB enforces a security buffer of about a minute, so the order must expire at least ~60 seconds in the future: for a real lifetime of N seconds, set now + 60 + N. An expiry in the past is rejected with INVALID_ORDER_EXPIRATION.

Post-only: guarantee maker status

Post-only is a flag on a resting order that says "only add liquidity, never take it." If the order would cross the spread and match immediately - making you the taker - the CLOB rejects it instead of filling. That guarantees you stay the maker on every fill: no taker fee, and you keep the maker rebate.

Use post-only for market-making and passive accumulation, where being the maker is the whole point. It removes the race where the market moves into your price between your read of the book and your post, silently turning a maker order into a taker that pays the spread plus the taker fee.

Two rules. Post-only works only with GTC and GTD (the resting types); combine it with FOK or FAK and the order is rejected with INVALID_POST_ONLY_ORDER_TYPE. And if a post-only order would cross the book at submission, it is rejected with INVALID_POST_ONLY_ORDER - catch that, re-read the book, and re-post behind the spread.

Limit vs market and the spread tax

Every Polymarket order is technically a limit order - even what bots call a "market buy" specifies a price ceiling. The distinction is whether that price is at the best ask (effectively a market order, will fill against the book) or below it (will rest on the book).

The spread tax is the cost of crossing - bid 0.45, ask 0.47, mid 0.46. A round trip that buys ask and sells bid pays 2 cents per share. On a 60% win-rate strategy with +3c/-4c targets, that 2c spread is the difference between profit and loss.

Maker pattern (post GTC at the bid or below, wait to be hit) collects the spread instead of paying it. The cost is uncertain fill - you may never get hit. For high-conviction trades, pay the spread. For passive accumulation, work the book.

Our production defaults (FOK buys, GTC sells)

The pattern most of our production bots converge on:

  • Entries: FOK at ask + 0-2 cents. If the bot decided to buy, it should buy now or skip. Resting an entry order is rarely worth it - the situation that triggered the buy decision changes faster than the order will rest.
  • Take-profit exits: GTC at target price. Posted immediately after entry fills. We let the market come to us; we don't chase the bid down. With ≥ 5 shares.
  • Stop-loss: case-by-case. GTC works for slow strategies where price changes are bounded. For fast-moving markets a GTC stop won't fill if price flies through it; we ride to resolution in option-D fashion (memory: trader-gtc-sell.md).

The pattern is conservative - fewer fills, less slippage. A more aggressive variant uses FAK entries and FAK exits, accepting partial fills. Pick one and stay consistent; mixing per-trade decisions invites confusion.

Code: place each order type

All five in the official V2 clients - py-clob-client-v2 (Python) and @polymarket/clob-client-v2 (Node/TS). Switch tabs for your language. Both follow the same shape: build the order args, pass tick_size in the options, and choose the order type. Post-only is the last argument on create_and_post_order; FOK/FAK go through create_and_post_market_order.

from py_clob_client_v2 import (
    ClobClient, ApiCreds, OrderArgs, MarketOrderArgs,
    OrderType, PartialCreateOrderOptions, Side,
)
import os, time

# L2 client - creds go in the constructor (no set_api_creds in v2)
creds = ApiCreds(api_key=os.environ["CLOB_API_KEY"],
                 api_secret=os.environ["CLOB_SECRET"],
                 api_passphrase=os.environ["CLOB_PASS_PHRASE"])
c = ClobClient(host="https://clob.polymarket.com", chain_id=137,
               key=os.environ["PK"], creds=creds)
opts = PartialCreateOrderOptions(tick_size="0.01")  # neg_risk=True for multi-outcome

# FOK market buy: spend the amount now in full, or cancel
c.create_and_post_market_order(
    order_args=MarketOrderArgs(token_id=TOKEN, amount=100, side=Side.BUY,
                               order_type=OrderType.FOK),
    options=opts, order_type=OrderType.FOK)

# FAK market buy: take what's available now, cancel the rest
c.create_and_post_market_order(
    order_args=MarketOrderArgs(token_id=TOKEN, amount=100, side=Side.BUY,
                               order_type=OrderType.FAK),
    options=opts, order_type=OrderType.FAK)

# GTC limit sell: rest 100 shares at 0.85 until filled or cancelled
c.create_and_post_order(
    order_args=OrderArgs(token_id=TOKEN, price=0.85, side=Side.SELL, size=100),
    options=opts, order_type=OrderType.GTC)

# GTD limit buy: auto-expire. expiration = UTC seconds, must be >= ~60s ahead
c.create_and_post_order(
    order_args=OrderArgs(token_id=TOKEN, price=0.40, side=Side.BUY, size=100,
                         expiration=int(time.time()) + 70),
    options=opts, order_type=OrderType.GTD)

# Post-only (maker-only): rejected if it would cross. GTC/GTD only.
c.create_and_post_order(
    order_args=OrderArgs(token_id=TOKEN, price=0.46, side=Side.BUY, size=100),
    options=opts, order_type=OrderType.GTC, post_only=True)

The neg_risk / negRisk flag (chapter 11) must be set in the options for multi-outcome markets - missing it routes the order to the wrong exchange contract.

Frequently asked questions

What is FOK on Polymarket?
Fill-or-Kill. The order must fill in full immediately or it is cancelled - no partial fills, no resting on the book. We use FOK by default for buys in our production trader because it eliminates phantom-fill ambiguity (the order is either fully filled or fully gone, never half-stuck).
What is FAK on Polymarket?
Fill-and-Kill (also called IOC, Immediate-or-Cancel). The order takes whatever liquidity is available immediately and cancels the unfilled remainder. Useful when you accept partial fills but never want a rest. Faster than FOK in fragmented order books.
What is GTC on Polymarket?
Good-til-Cancelled. The order rests on the book until filled or you cancel it. GTC is what you use to be a maker (provide liquidity), earn rebates, and avoid taker fees. We use GTC for sells in our production setup so we capture the spread on exits.
What is GTD on Polymarket?
Good-til-Date. A resting order, like GTC, but with a built-in expiry: it lives on the book until it fills, you cancel it, or its expiration timestamp passes - then the CLOB drops it automatically. The expiration is a UTC seconds timestamp and must be at least about 60 seconds in the future, so for a lifetime of N seconds use now + 60 + N. Ideal for quotes you only want live until a known event.
What is a post-only order on Polymarket?
A flag on a resting order that says only add liquidity, never take it. If the order would cross the spread and match immediately, the CLOB rejects it instead of filling, so you always stay the maker and never pay the taker fee. Post-only works only with GTC and GTD; combining it with FOK or FAK is rejected with INVALID_POST_ONLY_ORDER_TYPE, and a post-only order that would cross is rejected with INVALID_POST_ONLY_ORDER.
Should my bot use limit orders or market orders?
Limit orders almost always. Market orders pay the taker fee (0.75% to 1.80%) and the spread; limit orders earn the maker rebate (20-25% of taker fees). The only good reason to use a market order is when news has hit and the price is about to move beyond the spread before your limit can fill.
Does Polymarket support stop-loss orders natively?
No. Stop-loss is a client-side concept: your bot watches the price, and when the trigger condition is met, places a market or FAK sell order. The exchange has no native stop primitive, so you must build the logic in your bot.
What are the order minimums?
Market orders: 1 USD minimum notional. Limit orders: 5 shares minimum. Some thin markets reject very small orders - the SDK returns a specific error code you can detect and re-size against.