Market microstructure is the difference between theoretical edge and realized profit. Effective spread = 2 x |trade_price - midpoint|, slippage = VWAP(size) - midpoint. An agent that ignores orderbook depth, spread costs, and price impact is donating money to market makers.
Why This Matters for Agents
An autonomous betting agent can have a perfect probability model and still lose money. The gap between model edge and realized profit is microstructure cost — the spread, slippage, and fees the agent pays every time it trades.
This is Layer 3 — Trading in the Agent Betting Stack. Layer 4 (Intelligence) tells the agent what to trade. Layer 3 tells it how to execute without destroying its own edge. A 3% expected value on a Polymarket position means nothing if the agent market-buys into a thin orderbook, pays 2% in slippage, and loses another 1% to fees and spread. Microstructure-aware execution is what separates profitable agents from expensive noise generators.
The Prediction Market Math 101 guide established that price equals probability. This guide explains what happens between the moment an agent decides to trade and the moment it actually owns a position — and how much that journey costs.
The Math
Orderbook Structure
A Central Limit Order Book (CLOB) is a two-sided queue of resting limit orders, sorted by price-time priority. The Polymarket CLOB, which runs on Polygon, maintains separate orderbooks for each binary outcome contract.
Polymarket Orderbook — "Will Fed cut rates in June 2026?"
ASKS (sellers) BIDS (buyers)
Price Size Cumulative Price Size Cumulative
$0.67 $800 $800 $0.64 $1,200 $1,200
$0.68 $2,100 $2,900 $0.63 $3,500 $4,700
$0.69 $5,400 $8,300 $0.62 $2,800 $7,500
$0.70 $8,000 $16,300 $0.61 $6,000 $13,500
$0.72 $12,000 $28,300 $0.59 $4,200 $17,700
Best Ask = $0.67 Best Bid = $0.64
Midpoint = ($0.67 + $0.64) / 2 = $0.655
Quoted Spread = $0.67 - $0.64 = $0.03
Three key observations:
- Depth is uneven. The bid side has $17,700 within 5 levels; the ask side has $28,300. This asymmetry signals directional pressure — more sellers are willing to offer than buyers are willing to absorb.
- Gaps exist. There is no ask at $0.71 — the book jumps from $0.70 to $0.72. Gaps create non-linear slippage: a large market buy that consumes through $0.70 suddenly pays $0.72 for the next tranche.
- Cumulative depth determines execution cost. A $5,000 market buy consumes $800 at $0.67, $2,100 at $0.68, and $2,100 at $0.69. The VWAP is not $0.67 — it is $0.6832.
Bid-Ask Spread Decomposition
The quoted spread — the raw difference between best ask and best bid — is the most visible but least useful spread metric. Three spread measures matter for agent execution:
Quoted Spread:
S_quoted = P_ask - P_bid
Where P_ask is the best ask price and P_bid is the best bid price. Simple, but only reflects the cost of trading the minimum size at the top of book.
Effective Spread:
S_effective = 2 × |P_trade - P_mid|
Where P_trade is the actual execution price and P_mid = (P_ask + P_bid) / 2 is the midpoint. The effective spread captures the real cost of a trade, including any price improvement from limit orders or price disimprovement from market orders.
For a market buy at $0.67 when the midpoint is $0.655:
S_effective = 2 × |$0.67 - $0.655| = $0.03
Realized Spread:
S_realized = 2 × (P_trade - P_mid(t+Δ)) × direction
Where P_mid(t+Δ) is the midpoint some time period Δ after the trade, and direction is +1 for buys and -1 for sells. Realized spread measures how much of the quoted spread the market maker actually captures after adverse selection. If a large buy pushes the midpoint up, the realized spread is smaller than the effective spread — the buyer’s information moved the price.
Volume-Weighted Average Price (VWAP)
VWAP is the single most important execution metric for agents. It tells you the average price you will pay for a given order size, accounting for all orderbook levels consumed.
For a market buy order of size Q dollars against the ask side of the book:
VWAP(Q) = Σ(Pᵢ × min(Sᵢ, Qᵢ_remaining)) / Q
Where Pᵢ is the price at level i, Sᵢ is the size available at level i, and Qᵢ_remaining is the unfilled portion of Q when the matching engine reaches level i.
Slippage is the difference between VWAP and midpoint:
Slippage(Q) = VWAP(Q) - P_mid
For our example orderbook, a $5,000 market buy:
Level 1: $800 at $0.67 → fills $800
Level 2: $2,100 at $0.68 → fills $2,100
Level 3: $2,100 at $0.69 → fills $2,100 (of $5,400 available)
Total: $5,000
VWAP = ($800 × $0.67 + $2,100 × $0.68 + $2,100 × $0.69) / $5,000
= ($536 + $1,428 + $1,449) / $5,000
= $3,413 / $5,000
= $0.6826
Slippage = $0.6826 - $0.655 = $0.0276 = 2.76%
That 2.76% slippage on a $5,000 order wipes out most edges. An agent needs to know this before submitting.
Price Impact — Kyle’s Lambda
Kyle’s lambda (λ) quantifies the permanent price impact of a trade. In Kyle’s (1985) model, the price moves linearly with order flow:
ΔP = λ × Q
Where ΔP is the price change, Q is the signed order flow (positive for buys), and λ is the price impact coefficient. Lambda depends on the ratio of informed to uninformed trading — higher informed flow means higher lambda.
To estimate lambda empirically from historical trades:
λ = Cov(ΔPₜ, Qₜ) / Var(Qₜ)
This is a simple OLS regression of price changes on signed trade volume. Higher λ means the market is more sensitive to order flow — each dollar of buying pressure moves the price more.
Amihud Illiquidity Ratio
The Amihud ratio measures illiquidity as the average absolute price change per dollar of volume:
ILLIQ = (1/N) × Σ |rₜ| / Vₜ
Where rₜ is the return in period t and Vₜ is the dollar volume. Higher values mean less liquidity — prices move more per unit of trading activity.
For prediction markets, compute this over 5-minute or hourly intervals. An agent uses Amihud to compare liquidity across markets and prioritize execution in deeper venues.
AMM vs. CLOB: The Mathematical Difference
Not all prediction markets use orderbooks. Some use Automated Market Makers (AMMs). The two dominant AMM models:
Constant Product AMM (Uniswap-style):
x × y = k
Where x is the quantity of YES tokens, y is the quantity of NO tokens, and k is a constant. The price of YES = y/x. Buying YES reduces y and increases x, pushing the YES price up. The slippage is baked into the formula — larger orders move the price more.
For a trade of Δx YES tokens:
Price_impact = Δx / (x + Δx)
Logarithmic Market Scoring Rule (LMSR):
Hanson’s LMSR, used by some prediction market platforms, defines a cost function:
C(q) = b × ln(Σ exp(qᵢ / b))
Where qᵢ is the number of shares outstanding for outcome i and b is the liquidity parameter. The price of outcome j is:
P(j) = exp(qⱼ / b) / Σ exp(qᵢ / b)
This is a softmax function. The liquidity parameter b controls how much prices move per trade — higher b means deeper liquidity but more capital at risk for the market maker. The LMSR guarantees that prices always sum to 1.0 and never go below 0 or above 1. The LMSR and AMM Math guide derives these properties from first principles.
CLOB vs. AMM comparison:
| Dimension | Polymarket CLOB | AMM (Constant Product) | AMM (LMSR) |
|---|---|---|---|
| Price setting | Traders set prices | Formula x*y=k | Formula C(q)=b*ln(…) |
| Spread | Endogenous (trader competition) | Endogenous (pool size) | Parameter b |
| Depth | Variable, depends on LP activity | Uniform along curve | Uniform, set by b |
| Slippage | Non-linear, depends on book shape | Predictable from formula | Predictable from b |
| Capital efficiency | High (concentrated at best prices) | Low (spread across curve) | Moderate |
| Zero-liquidity risk | Yes (book can empty) | No (always provides price) | No (always provides price) |
Platform Comparison: Polymarket vs. Kalshi vs. Sportsbooks
| Feature | Polymarket CLOB | Kalshi Matching Engine | Traditional Sportsbook |
|---|---|---|---|
| Order types | Limit, Market, GTC, IOC | Limit, Market | Market only (bet at posted line) |
| Maker fee | 0% | 0% | N/A |
| Taker fee | 0% | 0% | N/A (vig baked into odds) |
| Settlement fee | ~2% on net winnings | Built into spread | N/A (vig is the fee) |
| Typical overround | 0-1% (spread) | 2-5% | 4-10% |
| Depth visibility | Full Level 2 via API | Full Level 2 via API | None (single price) |
| Settlement | On-chain (Polygon, USDC) | USD (regulated) | USD (account balance) |
| Latency | ~2s block time + matching | Sub-second matching | Instant (no matching needed) |
For a full sportsbook comparison with live vig data, see the AgentBets Vig Index.
Worked Examples
Example 1: Execution Cost on Polymarket
Consider the Polymarket market “Will Bitcoin exceed $150K by July 2026?” with this orderbook snapshot:
ASKS (YES sellers):
$0.42 × $2,500
$0.43 × $6,200
$0.44 × $8,800
$0.45 × $15,000
$0.47 × $20,000
BIDS (YES buyers):
$0.39 × $3,800
$0.38 × $5,100
$0.37 × $9,400
$0.36 × $12,000
$0.34 × $7,500
Midpoint = ($0.42 + $0.39) / 2 = $0.405
Quoted Spread = $0.42 - $0.39 = $0.03 (7.4% relative)
An agent wants to buy $10,000 worth of YES contracts.
Level 1: $2,500 at $0.42 → $2,500 filled
Level 2: $6,200 at $0.43 → $6,200 filled
Level 3: $1,300 at $0.44 → $1,300 filled (of $8,800)
Total: $10,000
VWAP = ($2,500 × 0.42 + $6,200 × 0.43 + $1,300 × 0.44) / $10,000
= ($1,050 + $2,666 + $572) / $10,000
= $4,288 / $10,000
= $0.4288
Slippage = $0.4288 - $0.405 = $0.0238 = 2.38%
Effective Spread = 2 × |$0.4288 - $0.405| = $0.0476
If the agent believes true probability is 48% (edge = 48% - 42.88% = 5.12%), the net edge after slippage is:
Net edge = Model probability - VWAP = 0.48 - 0.4288 = 0.0512 = 5.12%
But after the 2% settlement fee on winnings, the fee-adjusted expected cost is:
Fee cost = 0.02 × (1 - 0.4288) × 0.48 = $0.00548 per contract
Net edge after fees = 5.12% - 0.55% = 4.57%
Still profitable. But if the agent’s edge were only 3%, the 2.38% slippage would leave just 0.62% before fees — barely worth the risk.
Example 2: Comparing Liquidity Across Markets
An agent is evaluating whether to trade “Fed rate cut June 2026” on Polymarket vs. Kalshi vs. BetOnline:
Polymarket: Midpoint $0.655, Quoted spread $0.03, Top-of-book depth $2,000
Kalshi: Midpoint $0.66, Quoted spread $0.04, Top-of-book depth $800
BetOnline: Fed cut YES at -180 (implied 64.3%), no orderbook visibility
Amihud (Polymarket, hourly): 0.0015
Amihud (Kalshi, hourly): 0.0042
Polymarket is 2.8x more liquid by Amihud. For a $5,000 order, the agent should route to Polymarket. For a $500 order, the difference is negligible — the agent optimizes for other factors (settlement speed, regulatory status).
Implementation
import numpy as np
from dataclasses import dataclass, field
from typing import Optional
@dataclass
class OrderLevel:
"""Single price level in an orderbook."""
price: float
size: float # in dollars
@dataclass
class Orderbook:
"""Representation of a prediction market orderbook."""
market_id: str
bids: list[OrderLevel] = field(default_factory=list) # sorted descending by price
asks: list[OrderLevel] = field(default_factory=list) # sorted ascending by price
@property
def best_bid(self) -> Optional[float]:
return self.bids[0].price if self.bids else None
@property
def best_ask(self) -> Optional[float]:
return self.asks[0].price if self.asks else None
@property
def midpoint(self) -> Optional[float]:
if self.best_bid is not None and self.best_ask is not None:
return (self.best_bid + self.best_ask) / 2
return None
@property
def quoted_spread(self) -> Optional[float]:
if self.best_bid is not None and self.best_ask is not None:
return self.best_ask - self.best_bid
return None
@property
def relative_spread(self) -> Optional[float]:
"""Quoted spread as fraction of midpoint."""
mid = self.midpoint
qs = self.quoted_spread
if mid and qs is not None and mid > 0:
return qs / mid
return None
def compute_vwap(levels: list[OrderLevel], order_size: float) -> dict:
"""
Compute VWAP and slippage for a given order size against orderbook levels.
Args:
levels: List of OrderLevel, sorted by price (ascending for asks, descending for bids).
order_size: Total dollar size of the order.
Returns:
Dict with vwap, total_filled, slippage details, and levels consumed.
"""
filled = 0.0
cost = 0.0
levels_consumed = 0
for level in levels:
remaining = order_size - filled
if remaining <= 0:
break
fill_at_level = min(level.size, remaining)
cost += fill_at_level * level.price
filled += fill_at_level
levels_consumed += 1
if filled == 0:
return {"vwap": None, "filled": 0, "levels_consumed": 0}
vwap = cost / filled
return {
"vwap": round(vwap, 6),
"filled": round(filled, 2),
"cost": round(cost, 2),
"levels_consumed": levels_consumed,
"fully_filled": filled >= order_size,
}
def estimate_slippage(
book: Orderbook,
order_size: float,
side: str = "buy"
) -> dict:
"""
Estimate slippage for a market order of given size.
Args:
book: Orderbook object.
order_size: Dollar size of the order.
side: 'buy' (consumes asks) or 'sell' (consumes bids).
Returns:
Dict with vwap, midpoint, slippage, and effective_spread.
"""
levels = book.asks if side == "buy" else book.bids
result = compute_vwap(levels, order_size)
if result["vwap"] is None or book.midpoint is None:
return {"error": "Insufficient data"}
mid = book.midpoint
vwap = result["vwap"]
slippage = abs(vwap - mid)
effective_spread = 2 * slippage
return {
"side": side,
"order_size": order_size,
"midpoint": round(mid, 6),
"vwap": vwap,
"slippage_abs": round(slippage, 6),
"slippage_pct": round(slippage / mid * 100, 4),
"effective_spread": round(effective_spread, 6),
"levels_consumed": result["levels_consumed"],
"fully_filled": result["fully_filled"],
}
def compute_vwap_curve(
book: Orderbook,
sizes: list[float],
side: str = "buy"
) -> list[dict]:
"""
Compute VWAP and slippage for multiple order sizes.
Useful for plotting slippage curves.
Args:
book: Orderbook object.
sizes: List of dollar order sizes to evaluate.
side: 'buy' or 'sell'.
Returns:
List of slippage estimates, one per size.
"""
return [estimate_slippage(book, size, side) for size in sizes]
def amihud_illiquidity(
returns: np.ndarray,
volumes: np.ndarray
) -> float:
"""
Compute Amihud illiquidity ratio.
Args:
returns: Array of period returns (e.g., hourly midpoint returns).
volumes: Array of period dollar volumes.
Returns:
Amihud ratio (higher = less liquid).
"""
mask = volumes > 0
if mask.sum() == 0:
return float("inf")
ratios = np.abs(returns[mask]) / volumes[mask]
return float(np.mean(ratios))
def estimate_kyle_lambda(
price_changes: np.ndarray,
signed_volumes: np.ndarray
) -> dict:
"""
Estimate Kyle's lambda via OLS regression of price changes on signed volume.
Args:
price_changes: Array of midpoint changes per period.
signed_volumes: Array of net signed dollar volume per period
(positive = net buying).
Returns:
Dict with lambda estimate, r_squared, and interpretation.
"""
n = len(price_changes)
if n < 10:
return {"error": "Need at least 10 observations"}
cov = np.cov(price_changes, signed_volumes)
lambda_hat = cov[0, 1] / cov[1, 1]
residuals = price_changes - lambda_hat * signed_volumes
ss_res = np.sum(residuals**2)
ss_tot = np.sum((price_changes - np.mean(price_changes))**2)
r_squared = 1 - ss_res / ss_tot if ss_tot > 0 else 0
return {
"lambda": round(float(lambda_hat), 8),
"r_squared": round(float(r_squared), 4),
"interpretation": (
f"Each $1 of net buying pressure moves the price by "
f"${abs(lambda_hat):.6f}"
),
}
def execution_cost_analysis(
book: Orderbook,
order_size: float,
side: str = "buy",
settlement_fee_rate: float = 0.02,
model_probability: Optional[float] = None
) -> dict:
"""
Full execution cost breakdown for an agent evaluating a trade.
Args:
book: Orderbook object.
order_size: Dollar size of the order.
side: 'buy' or 'sell'.
settlement_fee_rate: Platform fee on net winnings (Polymarket ~2%).
model_probability: Agent's estimated true probability (optional).
Returns:
Dict with cost breakdown and net edge estimate.
"""
slip = estimate_slippage(book, order_size, side)
if "error" in slip:
return slip
vwap = slip["vwap"]
mid = slip["midpoint"]
# Fee cost per contract (expected fee payment)
if model_probability is not None and side == "buy":
# Expected fee = fee_rate * expected_profit_per_contract * prob_of_winning
expected_profit = 1.0 - vwap
fee_cost = settlement_fee_rate * expected_profit * model_probability
gross_edge = model_probability - vwap
net_edge = gross_edge - fee_cost
else:
fee_cost = None
gross_edge = None
net_edge = None
return {
"order_size": order_size,
"midpoint": mid,
"vwap": vwap,
"slippage_cost_pct": slip["slippage_pct"],
"quoted_spread": book.quoted_spread,
"effective_spread": slip["effective_spread"],
"settlement_fee_rate": settlement_fee_rate,
"expected_fee_cost_per_contract": round(fee_cost, 6) if fee_cost else None,
"model_probability": model_probability,
"gross_edge": round(gross_edge, 6) if gross_edge else None,
"net_edge_after_costs": round(net_edge, 6) if net_edge else None,
"profitable": net_edge > 0 if net_edge is not None else None,
}
# --- Demo: build a sample orderbook and analyze ---
if __name__ == "__main__":
book = Orderbook(
market_id="btc-150k-july-2026",
asks=[
OrderLevel(0.42, 2500),
OrderLevel(0.43, 6200),
OrderLevel(0.44, 8800),
OrderLevel(0.45, 15000),
OrderLevel(0.47, 20000),
],
bids=[
OrderLevel(0.39, 3800),
OrderLevel(0.38, 5100),
OrderLevel(0.37, 9400),
OrderLevel(0.36, 12000),
OrderLevel(0.34, 7500),
],
)
print(f"Market: {book.market_id}")
print(f"Midpoint: ${book.midpoint}")
print(f"Quoted Spread: ${book.quoted_spread}")
print(f"Relative Spread: {book.relative_spread:.2%}")
print()
# Slippage curve
sizes = [1000, 2500, 5000, 10000, 25000]
print(f"{'Size':>8} {'VWAP':>8} {'Slippage':>10} {'Levels':>6}")
print("-" * 40)
for size in sizes:
result = estimate_slippage(book, size, "buy")
print(
f"${size:>7,} ${result['vwap']:.4f} "
f"{result['slippage_pct']:>8.2f}% "
f"{result['levels_consumed']:>6}"
)
print()
# Full execution cost with model probability
analysis = execution_cost_analysis(
book, order_size=10000, side="buy",
settlement_fee_rate=0.02, model_probability=0.48
)
print("Execution Cost Analysis ($10,000 buy, model prob = 48%):")
for k, v in analysis.items():
print(f" {k}: {v}")
Limitations and Edge Cases
Stale orderbooks. Polymarket’s CLOB runs on Polygon with ~2-second block times. An agent that snapshots the orderbook and executes 5 seconds later may find the book has changed — especially during high-volatility events (election night, Fed announcements). The orderbook snapshot is a point-in-time estimate, not a guarantee.
Hidden orders. Some matching engines support iceberg or hidden orders that do not appear in the public orderbook. The visible depth understates true liquidity. An agent that estimates slippage from visible depth alone may overestimate execution cost. Polymarket’s CLOB currently shows all resting orders, but this is not guaranteed to remain the case.
Minimum tick size effects. Polymarket prices are in increments of $0.01. At prices near $0.50, the minimum spread is $0.01 (2% relative spread). At prices near $0.01 or $0.99, the minimum spread is $0.01 but the relative spread is 1% or 100% respectively. Effective tick size distorts microstructure analysis for extreme-probability events.
Cross-market latency. An agent running cross-platform arbitrage between Polymarket and Kalshi faces different settlement speeds, fee structures, and orderbook update frequencies. A price discrepancy visible in a synchronized snapshot may not be tradeable due to execution lag on one side.
Market maker withdrawal. During high-uncertainty events, market makers pull liquidity. The orderbook that showed $50,000 of depth at 3pm may show $500 at 3:01pm when breaking news hits. Agents must monitor depth in real time and adjust order sizes dynamically, not rely on cached depth snapshots.
AMM impermanent loss. For prediction markets using constant product AMMs, liquidity providers face impermanent loss as the market resolves toward 0 or 1. An LMSR market maker faces bounded loss (set by parameter b), but a constant product LP can lose their entire position if the outcome resolves to the opposite side. This is why most serious prediction markets have moved to CLOB models.
FAQ
How does the Polymarket orderbook work?
Polymarket uses a Central Limit Order Book (CLOB) built on Polygon. Traders submit limit orders at specific prices (0.01 to 0.99). The matching engine fills market orders against resting limit orders by price-time priority. Polymarket charges 0% maker and taker fees but takes ~2% on net winnings at settlement.
What is bid-ask spread in a prediction market?
The bid-ask spread is the difference between the highest price a buyer will pay (best bid) and the lowest price a seller will accept (best ask). In prediction markets, a spread of $0.02 on a YES contract means you pay a $0.01 implicit cost on each side of the trade. Tighter spreads indicate higher liquidity and lower trading costs for agents.
How do you calculate slippage on a prediction market order?
Slippage is the difference between the expected execution price and the actual price paid. Calculate it as VWAP(order_size) - midpoint, where VWAP is the volume-weighted average price across all orderbook levels consumed by the order. A $1,000 market buy on a thin book might show 3-5% slippage; the same order on a deep market shows under 0.5%.
What is the difference between an AMM and a CLOB in prediction markets?
A CLOB (Central Limit Order Book) matches individual limit orders by price-time priority — Polymarket and Kalshi use this model. An AMM (Automated Market Maker) uses a mathematical formula like constant product (x*y=k) or LMSR to set prices algorithmically based on outstanding shares. CLOBs offer tighter spreads on liquid markets; AMMs guarantee liquidity but with wider spreads.
How does market microstructure affect prediction market agent profitability?
Microstructure determines execution cost, which directly reduces edge. An agent with 3% expected edge but 2% execution cost (spread + slippage + fees) nets only 1%. Agents must model depth of book, estimate VWAP for their order size, and factor in maker-taker fees before submitting orders. See the Agent Betting Stack for how microstructure fits into Layer 3.
What’s Next
This guide covered the mechanics of how prediction market orders execute. The next step is understanding the pricing formulas behind automated market makers in full mathematical detail.
- Next in the series: LMSR and Automated Market Maker Math — full derivation of the logarithmic market scoring rule cost function and its properties.
- Price equals probability: Prediction Market Math 101 establishes the foundational price-probability identity this guide builds on.
- The API layer: Prediction Market API Reference documents every Polymarket and Kalshi endpoint for pulling orderbook data.
- Execution in practice: The py_clob_client reference covers the Python SDK for interacting with Polymarket’s CLOB programmatically.
- Sharp execution matters: Sharp Betting covers how professional bettors minimize execution cost across sportsbooks and prediction markets.
