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:

  1. 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.
  2. 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.
  3. 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:

DimensionPolymarket CLOBAMM (Constant Product)AMM (LMSR)
Price settingTraders set pricesFormula x*y=kFormula C(q)=b*ln(…)
SpreadEndogenous (trader competition)Endogenous (pool size)Parameter b
DepthVariable, depends on LP activityUniform along curveUniform, set by b
SlippageNon-linear, depends on book shapePredictable from formulaPredictable from b
Capital efficiencyHigh (concentrated at best prices)Low (spread across curve)Moderate
Zero-liquidity riskYes (book can empty)No (always provides price)No (always provides price)

Platform Comparison: Polymarket vs. Kalshi vs. Sportsbooks

FeaturePolymarket CLOBKalshi Matching EngineTraditional Sportsbook
Order typesLimit, Market, GTC, IOCLimit, MarketMarket only (bet at posted line)
Maker fee0%0%N/A
Taker fee0%0%N/A (vig baked into odds)
Settlement fee~2% on net winningsBuilt into spreadN/A (vig is the fee)
Typical overround0-1% (spread)2-5%4-10%
Depth visibilityFull Level 2 via APIFull Level 2 via APINone (single price)
SettlementOn-chain (Polygon, USDC)USD (regulated)USD (account balance)
Latency~2s block time + matchingSub-second matchingInstant (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.