Trade Execution Internals
PolySimulator aims to replicate Polymarket’s execution semantics as closely as possible while providing instant fills for paper trading. This document describes exactly how fill prices are determined.Execution Priority Cascade
When you submit a trade, the execution engine evaluates price sources in this order: Each layer has sanity guards that reject fills diverging too far from the cached display price. The threshold is relative and market-kind-aware: 15% for ordinary markets, but wider for fast binary Up/Down markets that legitimately swing near resolution (25% for hourly/daily Up/Down, 30% for 15-minute, 50% for 5-minute). If all layers fail, the trade is rejected withPrice unavailable for market.
Layer 1: Order Book Walk (VWAP)
For size-aware execution, the engine walks the order book to compute a Volume-Weighted Average Price (VWAP).How It Works
In every case the walk consumes the best price first, then steps to the next-best level until your quantity is filled:
The engine accumulates fills level-by-level until the requested quantity is satisfied:
Complement-Aware Execution
PolySimulator replicates Polymarket’s dual-book matching. In binary markets, you can fill a BUY by:- Buying the primary token from its ask side, OR
- Selling the complementary token from its bid side
effective_ask = 1 - complementary_bideffective_bid = 1 - complementary_ask
Sanity Guards
The book walk is rejected if the VWAP falls outside a symmetric relative band around the cached display price,cached × [1 − t, 1 + t], where t
is the market-kind-aware threshold (default 0.15; wider for Up/Down — see
above). Both sides use the same band:
(An earlier revision mixed a multiplicative upper bound with an absolute
cached − 0.15 lower bound; that asymmetric form was replaced because the
absolute term effectively no-op’d the guard for low-priced outcomes. The
guard is now purely relative so it scales correctly across [0.01, 0.99].)
Layer 2: Best Bid/Ask
If the order book walk is unavailable, the engine uses the top-of-book prices:Complement Merging
The best bid/ask is computed from both the primary and complementary order books:Sanity Guards
If the selected fill price diverges beyond the same relative threshold from the cached outcome price (15% default; wider for Up/Down):- First, try the primary-only best bid/ask (ignoring complement)
- If that also diverges, reject the fill and fall through to the next layer
Layer 3: CLOB Midpoint Cache
The engine caches the latest CLOB midpoint for each token. The midpoint is(best_bid + best_ask) / 2 from Polymarket’s live order book, refreshed roughly every 30 seconds.
Latency: Sub-millisecond.
Layer 4: Cached Outcome Price
The final fallback uses the cached display price — the same price visible in the UI.Label Matching
The engine matches the requested outcome label ("Yes", "Up", "Down", etc.) against the market’s outcome array:
buy/sell fields don’t correspond positionally to outcomes[0]/outcomes[1].
Fallback Chain
If direct label matching fails:- Try conventional aliases (
yes/no→ first/second outcome) - Compute midpoint from
best_bid+best_askfields - Use
last_tradeprice if spread is wide (>10%) - Average
yes_price+no_price
Limit Order Enforcement
For limit orders, the fill price is validated against your specified limit:Price Source Tracking
Every fill records which price source was used:
This is returned in the order response and recorded server-side for auditing.
Spread & Impact Metrics
Each fill computes:
These are available in the order response and help you understand execution quality.
Market Validation
Before execution, the engine validates:- Not closed:
closed=false - Active:
active=true - Not resolved:
resolved_outcomeis null - Not expired:
end_dateis in the future (or SELL for emergency exit) - Price sanity: Outcome prices don’t sum > 1.5 (post-expiry detection)
Emergency Exits: When selling a position on an expired market with no live price, the engine allows a break-even exit at your entry price rather than blocking the trade.
Idempotency
Every trade requires aclient_order_id (or Idempotency-Key header). Duplicate submissions return the original order without re-executing.
This is critical for:
- Retry-safe bot execution
- Preventing accidental double-fills on network timeouts
- Audit trail integrity