Slippage Protection
PolySimulator uses the same slippage protection model as Polymarket:
the price field on every market order acts as a worst-price limit.
There is no separate slippage parameter — the price is your slippage protection.
How It Works
On Polymarket, all orders are limit orders. A “market order” is simply a
limit order at a marketable price with FOK (Fill-or-Kill) time-in-force.
PolySimulator mirrors this exactly.
The price field is required on market orders and sets the worst price
you’ll accept:
- BUY: fills at the best ask, but never above your
price
- SELL: fills at the best bid, but never below your
price
{
"market_id": "0xabc...",
"side": "BUY",
"outcome": "Yes",
"quantity": "10",
"order_type": "market",
"price": "0.68"
}
BUY Example
Best ask: $0.65
Your limit: $0.68
→ FILLED at $0.65 (price improvement — you pay less than your limit)
Best ask: $0.72
Your limit: $0.68
→ REJECTED — fill price $0.72 exceeds your worst-price limit $0.68
SELL Example
Best bid: $0.60
Your limit: $0.55
→ FILLED at $0.60 (price improvement — you receive more than your limit)
Best bid: $0.50
Your limit: $0.55
→ REJECTED — fill price $0.50 is below your worst-price limit $0.55
Polymarket migration: This is identical to how Polymarket’s price
field works. Your existing slippage logic transfers directly to
PolySimulator — and vice versa when you migrate to live trading.
Price Is Required
Market orders must include a price field. Submitting a market order
without price returns a 400 PRICE_REQUIRED error:
{
"error": "PRICE_REQUIRED",
"message": "Market orders require a 'price' field as a worst-price limit..."
}
This matches Polymarket’s design: there are no “blind” market orders.
You always control the worst price you’ll accept.
Time in Force
Market orders default to FOK (Fill-or-Kill), matching Polymarket.
You can also use FAK (Fill-and-Kill), Polymarket’s term for IOC.
| Value | Behavior | Polymarket Equivalent |
|---|
FOK | All-or-nothing — fill entirely or cancel (default for market orders) | FOK |
FAK | Fill available quantity, cancel remainder | FAK |
IOC | Same as FAK (PolySimulator alias) | FAK |
GTC | Overridden to FOK for market orders | — |
{
"market_id": "0xabc...",
"side": "BUY",
"outcome": "Yes",
"quantity": "10",
"order_type": "market",
"price": "0.68",
"time_in_force": "FAK"
}
If you omit time_in_force on a market order, it defaults to FOK.
If you explicitly set GTC, it is overridden to FOK — market orders
never persist in the book.
Execution Model
Market orders fill at the best available price — BUY at the best ask,
SELL at the best bid — matching Polymarket’s execution model.
The fill-price resolution cascade (each tier falls through to the next on a
miss or a failed sanity guard):
- Order-book walk (VWAP) across live CLOB levels — most realistic
- Best bid/ask from the CLOB order book (cross the spread)
- CLOB midpoint — sub-ms Redis fast-path, then the cached read-through
- Live CLOB midpoint — synchronous fetch (cold-market guard)
- Cached display price — Gamma/SSE fallback (last resort)
See Trade Execution Internals for the full
cascade and the exact price_source labels each tier emits.
Fill Diagnostics
Every market order response includes transparency metadata:
{
"order_id": 42,
"status": "FILLED",
"price": "0.65",
"price_source": "book_walk",
"slippage_bps": 461
}
The slippage_bps field is informational only — it shows how far the
fill price deviated from the cached mid-price, in basis points. It does not
affect order execution. Your price (worst-price limit) is the only
protection mechanism.
Price Sources
price_source is an opaque diagnostic label — log it for post-trade
analysis, but treat it as a non-stable enum (the label set evolves with
engine changes). Some labels carry a transport prefix (ws: / gamma:)
when the underlying snapshot arrived over a WebSocket/poller stream — strip
the prefix or substring-match. The labels the engine actually emits today,
from highest to lowest confidence:
| Label (prefix-stripped) | Path | Quality |
|---|
book_walk | VWAP across live order-book levels | Gold standard — all telemetry fields populated |
best_ask (BUY) / best_bid (SELL) | Single top-of-book quote | Good — spread_bps populated, walk fields null |
clob_midpoint_cached_hft | Sub-ms Redis midpoint cache | Good — quote_age_ms = 0 |
clob_midpoint_cached | Redis CLOB-midpoint read-through | Acceptable |
clob_midpoint_live | Synchronous CLOB midpoint fetch (cold-market guard) | Acceptable |
outcome_yes / outcome_no / outcome_mid / midpoint / last_trade_only / last_trade_spread_fallback | Gamma cached-payload fallback | Lower confidence |
emergency_exit_entry_price / emergency_exit_entry_price_postexpiry | Position-close at entry price (resolved / post-expiry SELL) | Informational — not a market-priced fill |
Monitor price_source in your bot logs. Frequent fills on the
clob_midpoint_* or Gamma-fallback labels (rather than book_walk /
best_ask / best_bid) mean the live book was unavailable — consider
pausing during degraded price quality. (Labels like clob_book or
redis_cache are not emitted — don’t grep for them.)
Recommendations by Strategy
| Strategy | Approach |
|---|
| Scalping / HFT | price = best ask + $0.01 (tight limit, minimal overpay) |
| Swing trading | price = best ask + $0.03 (moderate buffer) |
| Bulk accumulation | price = best ask + $0.05 (wider buffer for fill certainty) |
| Illiquid markets | Use limit orders (GTC) for price certainty |
For illiquid markets with wide spreads, consider limit orders instead
of market orders. Limits give you price certainty at the cost of fill
uncertainty.
Comparison: PolySimulator vs Polymarket
| Feature | Polymarket | PolySimulator |
|---|
| Slippage protection | price field (worst-price limit) | price field (identical) |
| Price required on market orders | Yes | Yes |
| Default time-in-force (market) | FOK | FOK |
| Execution model | BUY at best ask, SELL at best bid | BUY at best ask, SELL at best bid |
| Price improvement | Yes — fill at best available | Yes — fill at best available |
| Order types | FOK, FAK, GTC, GTD | FOK, FAK, IOC, GTC |
| Fees | Per-category taker fees | Per-category taker fees (PM-V2 schedule, mirrored) |
Fees
PolySimulator charges the same per-category taker fee schedule as
Polymarket V2 — fees are not zero. Every taker fill is charged and the
amount is returned in OrderResponse.fee. A bot computing PnL on the
assumption of zero fees will be wrong on most markets.
The fee formula is:
fee = C × feeRate × p × (1 − p)
where C is the number of shares filled, p is the fill price (0–1), and
feeRate is the market’s category rate. Because of the p × (1 − p)
factor, the effective fee is largest near $0.50 and shrinks toward the price
extremes.
| Category | feeRate |
|---|
| Crypto | 7% |
| Finance / Politics / Tech | 4% |
| Sports | 3% |
| Economics / Culture / Weather / Other | 5% |
| Geopolitics | 0% |
| Unknown / missing category | 5% (conservative fallback) |
Makers pay no fee (matching Polymarket V2), and break-even emergency-exit
fills (selling a position on a resolved / expired market) are fee-free.
Maker vs taker is classified by marketability at placement: a GTC limit
that crosses the live book when you place it pays the taker fee even though
it technically fills via the matching loop — only orders that genuinely
rest earn maker (zero-fee) treatment. See Trading Fees
for the full schedule, formula, and divergences.
Always read OrderResponse.fee when computing realized PnL — don’t infer
it from the rate alone, since the p × (1 − p) factor makes the charged
amount price-dependent. To discover a market’s rate up front, call
GET /v1/fee-rate?token_id=… and read the fee_rate_bps field — that is
the effective category rate in bps. (The base_fee field mirrors
Polymarket’s legacy base-fee parameter — 1000 when fees are enabled, 0
when fee-free — not the rate. See Trading Fees.)
Next Steps