> "The market is a device for transferring money from the impatient to the patient." — Warren Buffett
In This Chapter
- 10.1 Understanding the Bid-Ask Spread
- 10.2 Components of Transaction Costs
- 10.3 Platform Fee Structures
- 10.4 Breakeven Edge Calculations
- 10.5 Effective Odds and the Overround
- 10.6 Market Impact and Slippage
- 10.7 Minimizing Transaction Costs
- 10.8 The Economics of Spreads in Prediction Markets
- 10.9 Empirical Analysis of Prediction Market Spreads
- 10.10 Practical Considerations and Chapter Summary
- What's Next
Chapter 10: Bid-Ask Spreads, Transaction Costs, and Fees
"The market is a device for transferring money from the impatient to the patient." — Warren Buffett
Every prediction market trade carries a cost. Some costs are visible — the platform fee deducted from your winnings, the gas fee on a blockchain transaction. Others are hidden — the spread you cross when you buy at the ask instead of the bid, the price slippage when your order is large enough to move the market, the adverse selection you face when a better-informed trader takes the other side. Together, these costs form a tax on every trade you make, and understanding them is the difference between a strategy that looks profitable on paper and one that actually makes money in practice.
This chapter provides an exhaustive treatment of transaction costs in prediction markets. We will decompose every cost a trader faces, build tools to measure and calculate them, and develop strategies to minimize them. By the end, you will be able to look at any prediction market trade and immediately assess whether the expected edge justifies the total cost of execution.
10.1 Understanding the Bid-Ask Spread
10.1.1 Definition and Fundamentals
The bid-ask spread is the difference between the highest price a buyer is willing to pay (the bid) and the lowest price a seller is willing to accept (the ask). In a prediction market where contracts pay $1 if an event occurs and $0 otherwise, you might see:
- Bid: $0.52 (someone will buy "Yes" at 52 cents)
- Ask: $0.55 (someone will sell "Yes" at 55 cents)
- Spread: $0.03 (3 cents, or 3 percentage points)
The midpoint of this spread — $0.535 in our example — is often used as the best estimate of the "true" price. If you want to buy immediately, you pay the ask price of $0.55. If you want to sell immediately, you receive the bid price of $0.52. Either way, you lose half the spread relative to the midpoint.
In prediction markets, prices are typically quoted as probabilities between 0 and 1 (or equivalently, as cents per dollar of face value). A spread of 3 cents on a contract priced near 50 cents is fundamentally different from a spread of 3 cents on a contract priced near 95 cents. We will formalize these distinctions below.
10.1.2 Why Spreads Exist
Spreads are not arbitrary — they arise from three fundamental forces that affect anyone willing to provide liquidity (stand ready to buy or sell):
Adverse Selection Cost
The most important driver of spreads in prediction markets is adverse selection. When you post a limit order to buy "Yes" at $0.52, you are offering to trade with anyone. Some of those counterparties know more than you do. A political insider may know that a candidate is about to drop out of a race. A weather modeler may have a superior hurricane forecast. When these informed traders take your order, you systematically lose money.
To compensate for this risk, liquidity providers widen their spreads. The greater the information asymmetry in a market, the wider the spread. This is why you observe:
- Wide spreads in markets where private information is valuable (political elections near election day, breaking news events)
- Narrow spreads in markets with mostly public information (weather markets based on publicly available forecasts, sports markets close to tip-off when lineups are known)
The mathematical framework for adverse selection in market making was developed by Glosten and Milgrom (1985), which we cover in Section 10.8.
Inventory Risk
A market maker who accumulates a large position in one direction faces inventory risk — the risk that the position moves against them before they can unwind it. If a market maker has sold many "Yes" contracts and the event is looking increasingly likely, they are exposed to significant losses.
In prediction markets, inventory risk has a unique character. Unlike equities, prediction market contracts have bounded payoffs ($0 or $1), which limits the maximum loss. However, prediction markets are often illiquid, making it difficult to offload inventory quickly. Market makers respond by:
- Widening spreads when they have accumulated a large directional position
- Skewing their quotes (moving the bid down and ask down if they are long, or bid up and ask up if they are short)
Order Processing Costs
Even in the absence of informed traders and inventory risk, market makers incur costs for operating: technology infrastructure, capital requirements, time, and attention. These order processing costs form the floor of the spread — even in the most liquid, informationally symmetric market, the spread cannot go to zero because someone must be compensated for providing the service.
In blockchain-based prediction markets like Polymarket, order processing costs include gas fees for on-chain settlement, which can be substantial during periods of network congestion.
10.1.3 Measuring Spreads
There are three primary measures of the spread, each capturing a different aspect of trading costs:
Quoted Spread
The quoted spread is simply the difference between the best ask and best bid at any given moment:
$$S_{\text{quoted}} = P_{\text{ask}} - P_{\text{bid}}$$
The relative quoted spread normalizes by the midpoint:
$$S_{\text{relative}} = \frac{P_{\text{ask}} - P_{\text{bid}}}{M}, \quad \text{where } M = \frac{P_{\text{ask}} + P_{\text{bid}}}{2}$$
For our example: $S_{\text{relative}} = \frac{0.55 - 0.52}{0.535} \approx 5.6\%$
The quoted spread is a snapshot — it tells you the cost of trading right now for a small order. It does not account for how much you can actually trade at those prices.
Effective Spread
The effective spread measures the actual cost of a trade relative to the midpoint at the time of execution:
$$S_{\text{effective}} = 2 \times |P_{\text{trade}} - M_t|$$
where $P_{\text{trade}}$ is the execution price and $M_t$ is the midpoint at the time of the trade. The factor of 2 is conventional — it represents the round-trip cost (buying and selling).
The effective spread is often larger than the quoted spread because: - Large orders may walk through multiple price levels in the order book - The midpoint may move between the time you decide to trade and execution - You may receive partial fills at worse prices
Realized Spread
The realized spread measures the profit that the liquidity provider actually earns, calculated after some time interval $\tau$:
$$S_{\text{realized}} = 2 \times D \times (P_{\text{trade}} - M_{t+\tau})$$
where $D = +1$ for a buy and $D = -1$ for a sell, and $M_{t+\tau}$ is the midpoint $\tau$ seconds (or minutes) after the trade.
The realized spread is typically smaller than the effective spread because some of the spread compensates for adverse selection. If the price moves against the liquidity provider after the trade (because the taker was informed), the realized spread shrinks. The difference between the effective and realized spread is a direct measure of adverse selection:
$$\text{Adverse Selection Cost} = S_{\text{effective}} - S_{\text{realized}}$$
10.1.4 Prediction Market Spreads vs. Traditional Finance
Prediction market spreads differ from traditional financial markets in several important ways:
| Feature | Traditional Finance | Prediction Markets |
|---|---|---|
| Spread width | Often <0.01% for liquid stocks | Typically 1-10% for liquid markets |
| Price bounds | Unbounded (stocks can go to any price) | Bounded [0, 1] |
| Spread near boundaries | N/A | Spreads compress near 0 and 1 |
| Terminal date | No expiry for stocks | All contracts have resolution date |
| Spread dynamics | Mean-reverting around equilibrium | Widen near news events, narrow at resolution |
| Tick size | $0.01 for stocks | $0.01 on Kalshi, $0.001 on Polymarket | |
| Participants | Professional market makers + algorithms | Mix of professional and retail |
The bounded payoff structure of prediction markets has important implications. When a contract trades near 95 cents, the maximum upside for a buyer is 5 cents, while the maximum downside is 95 cents. This asymmetry means that market makers must charge wider relative spreads near the boundaries to compensate for the skewed risk.
10.2 Components of Transaction Costs
10.2.1 A Taxonomy of Costs
Every prediction market trade incurs multiple layers of cost. We categorize them into explicit costs (directly observable charges) and implicit costs (embedded in the price you receive):
Total Transaction Cost
├── Explicit Costs
│ ├── Platform trading fee (maker/taker)
│ ├── Settlement/resolution fee
│ ├── Gas/network fee (blockchain platforms)
│ ├── Deposit/withdrawal fees
│ └── Currency conversion fees
└── Implicit Costs
├── Half the bid-ask spread
├── Slippage (price movement during execution)
├── Market impact (your order moving the price)
└── Opportunity cost (price movement while waiting)
10.2.2 Explicit Costs
Platform Trading Fees
Most prediction market platforms charge a fee on each trade. The fee structure varies significantly:
- Percentage of notional: A fee based on the total value of the trade (e.g., 2% of the amount spent)
- Percentage of profit: A fee charged only on winning trades, based on the profit amount (e.g., 10% of winnings)
- Maker/taker model: Different fees for providing liquidity (maker) vs. taking liquidity (taker)
- Flat fee per trade: A fixed dollar amount regardless of trade size
Gas Fees (Blockchain Platforms)
On blockchain-based prediction markets like Polymarket (built on Polygon), every transaction requires a network fee. These fees vary with network congestion:
- Polygon (Polymarket): Typically $0.001 - $0.05 per transaction
- Ethereum mainnet: Can range from $1 - $100+ during high congestion
- Layer 2 solutions: Generally $0.01 - $0.50
Gas fees create a minimum viable trade size — if the gas fee is $0.02 and your expected profit is $0.01, the trade is not worth making.
Settlement and Withdrawal Fees
Some platforms charge fees when a market resolves or when you withdraw funds:
- PredictIt: 5% fee on profits at resolution, plus 5% withdrawal fee
- Kalshi: No withdrawal fee for ACH, $10 for wire transfers
- Polymarket: Gas fee for on-chain withdrawals
10.2.3 Implicit Costs
Spread Cost
As discussed in Section 10.1, crossing the spread costs you half the bid-ask spread on each side of a round trip. For a market with a 4-cent spread:
$$\text{Spread cost (one way)} = \frac{S}{2} = \frac{0.04}{2} = 0.02 \text{ per contract}$$
$$\text{Spread cost (round trip)} = S = 0.04 \text{ per contract}$$
Slippage
Slippage is the difference between the price you expected and the price you actually received. It occurs when:
- The market moves between when you decide to trade and when your order executes
- Your order is large relative to the available liquidity at the best price
- Other traders' orders arrive ahead of yours
In prediction markets, slippage can be particularly severe around news events, when prices can gap by 10-20 cents in seconds.
Market Impact
Market impact is the permanent price change caused by your trade. When you buy a large number of "Yes" contracts, you:
- Consume all the available "ask" orders at the best price
- Push into less favorable price levels
- Signal to the market that someone wants to buy, potentially causing other participants to adjust their prices upward
Market impact is a function of order size relative to market liquidity. We cover this in detail in Section 10.6.
10.2.4 Total Cost of a Trade: A Worked Example
Let us calculate the total cost of buying 100 "Yes" contracts on a Kalshi market:
Market conditions: - Bid: $0.52, Ask: $0.55, Midpoint: $0.535 - Order book depth: 50 contracts at $0.55, 50 contracts at $0.56 - Kalshi taker fee: $0.01 per contract (on both entry and exit, capped)
Execution: - 50 contracts filled at $0.55 - 50 contracts filled at $0.56 - Average execution price: $0.555
Cost breakdown:
| Cost Component | Calculation | Amount |
|---|---|---|
| Spread cost | $(0.555 - 0.535) \times 100$ | $2.00 |
| Slippage | $(0.555 - 0.55) \times 100$ | $0.50 |
| Entry fee | $0.01 \times 100$ (capped rules may apply) | $1.00 |
| Exit fee (estimated) | $0.01 \times 100$ | $1.00 |
| Total one-way cost | $3.00 | |
| Total round-trip cost | (approx., depends on exit price) | ~$5.00 - $6.00 |
Cost as percentage of notional: $3.00 / ($0.555 \times 100) = 5.4\%$
This means you need at least 5.4% edge on this trade just to break even. That is a substantial hurdle.
10.2.5 Python Transaction Cost Analyzer
"""
Transaction Cost Analyzer for Prediction Markets
Calculates total cost of a trade across multiple cost components.
"""
from dataclasses import dataclass, field
from typing import List, Tuple, Optional
@dataclass
class OrderBookLevel:
"""A single price level in the order book."""
price: float
quantity: int
@dataclass
class TradeExecution:
"""Result of executing a trade against the order book."""
fills: List[Tuple[float, int]] # (price, quantity) pairs
total_quantity: int
total_cost: float
average_price: float
midpoint: float
@dataclass
class CostBreakdown:
"""Complete breakdown of transaction costs."""
spread_cost: float
slippage_cost: float
platform_fee: float
gas_fee: float
total_cost: float
cost_per_contract: float
cost_as_pct_of_notional: float
implied_breakeven_edge: float
def simulate_execution(
bid: float,
ask: float,
ask_levels: List[OrderBookLevel],
quantity: int
) -> TradeExecution:
"""
Simulate executing a buy order against the order book.
Parameters
----------
bid : float
Best bid price
ask : float
Best ask price (should match first ask_level)
ask_levels : list of OrderBookLevel
Available ask-side liquidity
quantity : int
Number of contracts to buy
Returns
-------
TradeExecution
Details of the simulated execution
"""
midpoint = (bid + ask) / 2
fills = []
remaining = quantity
for level in ask_levels:
if remaining <= 0:
break
fill_qty = min(remaining, level.quantity)
fills.append((level.price, fill_qty))
remaining -= fill_qty
if remaining > 0:
raise ValueError(
f"Insufficient liquidity: {remaining} contracts unfilled"
)
total_cost = sum(p * q for p, q in fills)
total_qty = sum(q for _, q in fills)
avg_price = total_cost / total_qty
return TradeExecution(
fills=fills,
total_quantity=total_qty,
total_cost=total_cost,
average_price=avg_price,
midpoint=midpoint
)
def calculate_costs(
execution: TradeExecution,
fee_per_contract: float = 0.0,
fee_pct_of_notional: float = 0.0,
fee_pct_of_profit: float = 0.0,
gas_fee: float = 0.0,
estimated_true_prob: Optional[float] = None
) -> CostBreakdown:
"""
Calculate complete cost breakdown for a trade.
Parameters
----------
execution : TradeExecution
The simulated execution result
fee_per_contract : float
Fixed fee per contract (e.g., Kalshi style)
fee_pct_of_notional : float
Fee as percentage of notional value
fee_pct_of_profit : float
Fee as percentage of profit (charged on winning trades)
gas_fee : float
Fixed gas/network fee for the transaction
estimated_true_prob : float, optional
Estimated true probability for breakeven calculation
Returns
-------
CostBreakdown
Detailed cost breakdown
"""
n = execution.total_quantity
avg_price = execution.average_price
mid = execution.midpoint
# Spread cost: difference between execution price and midpoint
spread_cost = (avg_price - mid) * n
# Slippage: additional cost from walking through the book
best_ask = execution.fills[0][0]
slippage_cost = (avg_price - best_ask) * n
# Platform fee
if fee_per_contract > 0:
platform_fee = fee_per_contract * n
elif fee_pct_of_notional > 0:
platform_fee = fee_pct_of_notional * execution.total_cost
else:
platform_fee = 0.0
total = spread_cost + slippage_cost + platform_fee + gas_fee
cost_per = total / n
cost_pct = total / execution.total_cost
# Breakeven edge: how much the true probability must exceed
# the midpoint for this trade to be profitable
breakeven_edge = cost_per
return CostBreakdown(
spread_cost=spread_cost,
slippage_cost=slippage_cost,
platform_fee=platform_fee,
gas_fee=gas_fee,
total_cost=total,
cost_per_contract=cost_per,
cost_as_pct_of_notional=cost_pct,
implied_breakeven_edge=breakeven_edge
)
# Example usage
if __name__ == "__main__":
# Define order book
ask_levels = [
OrderBookLevel(price=0.55, quantity=50),
OrderBookLevel(price=0.56, quantity=50),
OrderBookLevel(price=0.58, quantity=100),
]
# Simulate buying 100 contracts
execution = simulate_execution(
bid=0.52, ask=0.55,
ask_levels=ask_levels, quantity=100
)
print(f"Average execution price: ${execution.average_price:.4f}")
print(f"Midpoint: ${execution.midpoint:.4f}")
print()
# Calculate costs (Kalshi-style fees)
costs = calculate_costs(
execution,
fee_per_contract=0.01,
gas_fee=0.0
)
print("=== Cost Breakdown ===")
print(f"Spread cost: ${costs.spread_cost:.2f}")
print(f"Slippage cost: ${costs.slippage_cost:.2f}")
print(f"Platform fee: ${costs.platform_fee:.2f}")
print(f"Gas fee: ${costs.gas_fee:.2f}")
print(f"Total cost: ${costs.total_cost:.2f}")
print(f"Cost/contract: ${costs.cost_per_contract:.4f}")
print(f"Cost % notional: {costs.cost_as_pct_of_notional:.2%}")
print(f"Breakeven edge: {costs.implied_breakeven_edge:.4f}")
10.3 Platform Fee Structures
10.3.1 Overview of Major Platforms
Each prediction market platform has evolved its own fee structure, reflecting different business models, regulatory environments, and target user bases. Understanding these structures is essential for choosing where to trade and for accurately calculating your expected returns.
10.3.2 Polymarket
Business model: Decentralized prediction market on Polygon blockchain (formerly on Ethereum).
Fee structure (as of 2024-2025): - Trading fees: 0% — Polymarket does not charge trading fees on most markets - Gas fees: Polygon network fees, typically $0.001 - $0.05 per transaction - Deposit fees: Free for USDC deposits - Withdrawal fees: Gas fee for on-chain withdrawal (~$0.01 - $0.10)
Key characteristics: - Zero trading fees make Polymarket attractive for active traders - The order book model (CLOB — Central Limit Order Book) means costs come primarily from the spread - Market makers are incentivized through the spread itself rather than maker rebates - Low gas costs on Polygon make small trades viable
Effective cost calculation: $$C_{\text{Polymarket}} = \frac{S}{2} + \text{slippage} + G_{\text{gas}}$$
where $G_{\text{gas}}$ is typically negligible for trades above $10.
10.3.3 Kalshi
Business model: Regulated U.S. exchange (CFTC-regulated designated contract market).
Fee structure (as of 2024-2025): - Taker fee: The lesser of $0.01 per contract or 1/15th of the contract price (approximately 6.67% of price) - Maker fee: $0.00 (no fee for limit orders that add liquidity) - Settlement fee: No additional settlement fee - Deposit: Free for ACH; wire transfer fees apply - Withdrawal: Free for ACH; $10 for wire
Key characteristics: - Maker-taker model incentivizes liquidity provision - Fee cap at $0.01 means fees are capped for contracts priced above $0.15 - The 1/15th rule means that for cheap contracts (priced below $0.15), the fee is proportionally lower - Regulatory compliance adds trust but limits some market types
Effective cost calculation: $$C_{\text{Kalshi}} = \frac{S}{2} + \text{slippage} + \min\left(0.01, \frac{P}{15}\right) \text{ per contract}$$
10.3.4 PredictIt
Business model: Academic research platform with no-action letter from CFTC (note: regulatory status has changed; included for educational comparison).
Fee structure: - Trading fee: 0% on trades themselves - Profit fee: 10% of profits on each market - Withdrawal fee: 5% of withdrawal amount - Position limit: Maximum $850 per contract per market
Key characteristics: - The 10% profit fee is extremely significant — it creates a strong asymmetry - The 5% withdrawal fee is applied to total withdrawal, not just profits - Position limits create unique pricing dynamics - These fees stack: effective cost can exceed 15% for profitable trades
Effective cost per winning trade: $$C_{\text{PredictIt}} = \frac{S}{2} + 0.10 \times \max(0, P_{\text{payout}} - P_{\text{entry}}) + 0.05 \times P_{\text{withdrawal}}$$
The PredictIt fee drag problem: Consider buying "Yes" at $0.60 with a true probability of $0.70. Your expected profit before fees is $0.10 per contract. But: - When you win (70% of the time), you pay 10% of your $0.40 profit = $0.04 - When you lose (30% of the time), you pay nothing on profits (no profit) - Expected fee: $0.70 \times 0.04 = $0.028$ per contract - Plus 5% withdrawal fee on your total withdrawal
This makes PredictIt dramatically more expensive than the other platforms.
10.3.5 Manifold Markets
Business model: Play-money prediction market with optional real-money sweepstakes markets (Manifold Sweepcash).
Fee structure (play money / Mana): - Trading fee: Dynamic based on liquidity pool mechanism - No explicit fees: Costs embedded in the AMM (Automated Market Maker) curve - Loan system: Users receive loans on long-dated positions
Fee structure (Sweepcash): - Redemption fee: Varies by redemption method - Trading cost: Embedded in AMM pricing
Key characteristics: - AMM-based pricing means there is no traditional order book - The "cost" of trading is the price impact on the bonding curve - For small trades, the effective cost can be very low - For large trades relative to the pool, the cost increases quadratically
10.3.6 Fee Comparison Table
| Platform | Maker Fee | Taker Fee | Profit Fee | Withdrawal Fee | Min Effective Cost |
|---|---|---|---|---|---|
| Polymarket | 0% | 0% | 0% | Gas (~$0.01) | Spread only |
| Kalshi | 0% | $0.01/contract (capped) | 0% | $0 (ACH) | Spread + $0.01 | ||
| PredictIt | 0% | 0% | 10% of profit | 5% of amount | Spread + ~3-5% |
| Manifold (Mana) | 0% | 0% | 0% | N/A | AMM slippage |
10.3.7 Python Fee Calculator
"""
Fee Calculator for Major Prediction Market Platforms
Compare trading costs across Polymarket, Kalshi, PredictIt, and Manifold.
"""
from dataclasses import dataclass
from typing import Optional
from enum import Enum
class Platform(Enum):
POLYMARKET = "Polymarket"
KALSHI = "Kalshi"
PREDICTIT = "PredictIt"
MANIFOLD = "Manifold"
@dataclass
class TradeParams:
"""Parameters for a single trade."""
entry_price: float # Price paid per contract (0 to 1)
num_contracts: int # Number of contracts
is_maker: bool = False # True if limit order adding liquidity
true_probability: float = 0.5 # Estimated true probability for EV calc
exit_price: Optional[float] = None # Exit price if closing before resolution
@dataclass
class FeeResult:
"""Fee calculation result for a platform."""
platform: str
entry_fee: float
exit_fee: float
profit_fee: float
withdrawal_fee: float
total_fees: float
expected_pnl_before_fees: float
expected_pnl_after_fees: float
fee_as_pct_of_notional: float
breakeven_probability: float
def calculate_polymarket_fees(
trade: TradeParams,
gas_fee_per_tx: float = 0.005,
withdrawal_gas: float = 0.01
) -> FeeResult:
"""Calculate Polymarket fees (zero trading fees, gas only)."""
notional = trade.entry_price * trade.num_contracts
entry_fee = gas_fee_per_tx
exit_fee = gas_fee_per_tx # For closing trade or resolution claim
profit_fee = 0.0
withdrawal_fee = withdrawal_gas
total = entry_fee + exit_fee + profit_fee + withdrawal_fee
# Expected P&L
expected_payout = trade.true_probability * 1.0 * trade.num_contracts
expected_pnl_before = expected_payout - notional
expected_pnl_after = expected_pnl_before - total
# Breakeven: need true_prob * 1 * N >= entry_price * N + total_fees
breakeven = (notional + total) / trade.num_contracts
return FeeResult(
platform="Polymarket",
entry_fee=entry_fee,
exit_fee=exit_fee,
profit_fee=profit_fee,
withdrawal_fee=withdrawal_fee,
total_fees=total,
expected_pnl_before_fees=expected_pnl_before,
expected_pnl_after_fees=expected_pnl_after,
fee_as_pct_of_notional=total / notional if notional > 0 else 0,
breakeven_probability=min(breakeven, 1.0)
)
def calculate_kalshi_fees(trade: TradeParams) -> FeeResult:
"""Calculate Kalshi fees (maker-taker model)."""
notional = trade.entry_price * trade.num_contracts
# Taker fee: min($0.01, price/15) per contract
if trade.is_maker:
per_contract_fee = 0.0
else:
per_contract_fee = min(0.01, trade.entry_price / 15)
entry_fee = per_contract_fee * trade.num_contracts
# Exit fee applies on the other side too
exit_price = trade.exit_price if trade.exit_price else (1.0 - trade.entry_price)
if trade.is_maker:
exit_per_contract = 0.0
else:
exit_per_contract = min(0.01, exit_price / 15)
exit_fee = exit_per_contract * trade.num_contracts
profit_fee = 0.0
withdrawal_fee = 0.0
total = entry_fee + exit_fee + profit_fee + withdrawal_fee
expected_payout = trade.true_probability * 1.0 * trade.num_contracts
expected_pnl_before = expected_payout - notional
expected_pnl_after = expected_pnl_before - total
breakeven = (notional + total) / trade.num_contracts
return FeeResult(
platform="Kalshi",
entry_fee=entry_fee,
exit_fee=exit_fee,
profit_fee=profit_fee,
withdrawal_fee=withdrawal_fee,
total_fees=total,
expected_pnl_before_fees=expected_pnl_before,
expected_pnl_after_fees=expected_pnl_after,
fee_as_pct_of_notional=total / notional if notional > 0 else 0,
breakeven_probability=min(breakeven, 1.0)
)
def calculate_predictit_fees(
trade: TradeParams,
withdrawal_amount: Optional[float] = None
) -> FeeResult:
"""Calculate PredictIt fees (10% profit fee + 5% withdrawal fee)."""
notional = trade.entry_price * trade.num_contracts
entry_fee = 0.0
exit_fee = 0.0
# 10% profit fee: only on positive profit
# Expected profit fee = P(win) * 10% * profit_if_win
profit_if_win = (1.0 - trade.entry_price) * trade.num_contracts
expected_profit_fee = trade.true_probability * 0.10 * profit_if_win
profit_fee = expected_profit_fee
# 5% withdrawal fee on total amount withdrawn
if withdrawal_amount is None:
# Estimate: expected payout
expected_payout = trade.true_probability * trade.num_contracts
withdrawal_amount = expected_payout
withdrawal_fee = 0.05 * withdrawal_amount
total = entry_fee + exit_fee + profit_fee + withdrawal_fee
expected_payout = trade.true_probability * 1.0 * trade.num_contracts
expected_pnl_before = expected_payout - notional
expected_pnl_after = expected_pnl_before - total
# Breakeven is more complex due to conditional profit fee
# p * (1 - 0.10*(1-entry)) * N * 0.95 >= entry * N
# Solve for p:
net_per_win = (1.0 - 0.10 * (1.0 - trade.entry_price)) * 0.95
breakeven = (trade.entry_price * 0.95) / net_per_win if net_per_win > 0 else 1.0
return FeeResult(
platform="PredictIt",
entry_fee=entry_fee,
exit_fee=exit_fee,
profit_fee=profit_fee,
withdrawal_fee=withdrawal_fee,
total_fees=total,
expected_pnl_before_fees=expected_pnl_before,
expected_pnl_after_fees=expected_pnl_after,
fee_as_pct_of_notional=total / notional if notional > 0 else 0,
breakeven_probability=min(breakeven, 1.0)
)
def compare_platforms(trade: TradeParams) -> None:
"""Compare fees across all platforms for the same trade."""
results = [
calculate_polymarket_fees(trade),
calculate_kalshi_fees(trade),
calculate_predictit_fees(trade),
]
print(f"Trade: Buy {trade.num_contracts} contracts @ ${trade.entry_price:.2f}")
print(f"Estimated true probability: {trade.true_probability:.1%}")
print(f"Notional: ${trade.entry_price * trade.num_contracts:.2f}")
print()
print(f"{'Platform':<12} {'Total Fee':>10} {'Fee %':>8} {'E[PnL] Pre':>12} "
f"{'E[PnL] Post':>12} {'BE Prob':>8}")
print("-" * 72)
for r in results:
print(f"{r.platform:<12} ${r.total_fees:>8.2f} {r.fee_as_pct_of_notional:>7.1%} "
f"${r.expected_pnl_before_fees:>10.2f} "
f"${r.expected_pnl_after_fees:>10.2f} "
f"{r.breakeven_probability:>7.1%}")
if __name__ == "__main__":
# Example: Buy 100 contracts at $0.55, true prob = 0.62
trade = TradeParams(
entry_price=0.55,
num_contracts=100,
is_maker=False,
true_probability=0.62
)
compare_platforms(trade)
10.4 Breakeven Edge Calculations
10.4.1 The Fundamental Question
The most important practical question in prediction market trading is: How much edge do I need to overcome transaction costs? If your model estimates a probability of 62% but the market asks you to buy at 55 cents with 3% in total fees, is the trade worth making?
The breakeven edge is the minimum difference between the true probability and the market price at which you break even after all costs.
10.4.2 Formula Derivation
Consider buying a "Yes" contract at price $P_{\text{ask}}$ with total round-trip costs of $C$ per contract. The contract pays $1 if the event occurs (probability $p$) and $0 otherwise.
Expected profit per contract: $$E[\pi] = p \cdot (1 - P_{\text{ask}}) - (1 - p) \cdot P_{\text{ask}} - C$$ $$E[\pi] = p - P_{\text{ask}} - C$$
Setting $E[\pi] = 0$ and solving for the breakeven probability:
$$p_{\text{breakeven}} = P_{\text{ask}} + C$$
The breakeven edge is:
$$\text{Edge}_{\text{breakeven}} = p_{\text{breakeven}} - M = (P_{\text{ask}} - M) + C = \frac{S}{2} + C$$
where $M$ is the midpoint and $S$ is the spread. In other words, your edge must exceed half the spread plus all other costs.
10.4.3 Breakeven with Percentage-of-Profit Fees
When the platform charges a percentage of profit (like PredictIt's 10%), the calculation changes because the fee is asymmetric — you pay it only when you win:
$$E[\pi] = p \cdot (1 - P_{\text{ask}}) \cdot (1 - f) - (1 - p) \cdot P_{\text{ask}} - C_{\text{other}}$$
where $f$ is the profit fee rate. Setting to zero:
$$p_{\text{breakeven}} = \frac{P_{\text{ask}} + C_{\text{other}}}{1 - f \cdot (1 - P_{\text{ask}}) + P_{\text{ask}} \cdot f}$$
Simplifying:
$$p_{\text{breakeven}} = \frac{P_{\text{ask}} + C_{\text{other}}}{1 - f + f \cdot P_{\text{ask}}}$$
For PredictIt ($f = 0.10$), buying at $P = 0.55$:
$$p_{\text{breakeven}} = \frac{0.55}{1 - 0.10 + 0.10 \times 0.55} = \frac{0.55}{0.955} \approx 0.576$$
So you need the true probability to be at least 57.6% to break even when the market price is 55 cents — an edge of 2.6 cents just to overcome the profit fee.
10.4.4 Breakeven with Withdrawal Fees
If you also face a withdrawal fee of rate $w$ (PredictIt: $w = 0.05$), your net payout per dollar of profit is further reduced:
$$\text{Net per win} = (1 - f)(1 - w) \cdot (1 - P_{\text{ask}})$$ $$\text{Net per loss} = (1 - w) \cdot (-P_{\text{ask}}) \quad \text{(you still lose, but get less back)}$$
The full breakeven with both fees becomes significantly more demanding.
10.4.5 Worked Examples
Example 1: Polymarket (Zero Fees)
- Market price: $0.60 (bid $0.58, ask $0.60)
- Spread cost: $\frac{0.02}{2} = 0.01$
- Gas: ~$0.005 per contract (negligible for $100+ trades)
- Breakeven edge: ~$0.01$ above midpoint ($0.59$)
- Breakeven probability: $\approx 0.60$
Example 2: Kalshi (Taker Fee)
- Market price: $0.60 (bid $0.58, ask $0.60)
- Spread cost: $0.01$
- Taker fee: $\min(0.01, 0.60/15) = 0.01$ per contract on each side
- Total cost per contract: $0.01 + 0.01 + 0.01 = 0.03$
- Breakeven probability: $0.60 + 0.02 = 0.62$
Example 3: PredictIt (Profit + Withdrawal Fee)
- Market price: $0.60
- Profit fee: 10% of profit if win
- Withdrawal fee: 5%
- Breakeven probability: $\frac{0.60}{0.955 \times 0.95} \approx 0.661$
- Required edge: 6.1 cents — more than triple Kalshi's requirement
10.4.6 Sensitivity Analysis
The breakeven edge is sensitive to the price level. Near the extremes (close to 0 or 1), percentage fees have different impacts:
| Entry Price | Polymarket BE | Kalshi BE (Taker) | PredictIt BE |
|---|---|---|---|
| $0.10 | $0.10 | $0.11 | $0.12 | ||
| $0.30 | $0.31 | $0.32 | $0.35 | ||
| $0.50 | $0.51 | $0.52 | $0.56 | ||
| $0.70 | $0.71 | $0.72 | $0.78 | ||
| $0.90 | $0.91 | $0.92 | $0.95 |
Note: These are approximate, assuming 2-cent spreads and typical fee structures.
The table reveals a critical insight: PredictIt's fee structure makes it nearly impossible to profitably trade high-probability events. When the true probability is 90%, PredictIt requires you to believe it is at least 95% — there is very little room for error.
10.4.7 Python Breakeven Calculator
"""
Breakeven Edge Calculator with Sensitivity Analysis
"""
import numpy as np
def breakeven_simple(ask_price: float, spread: float,
cost_per_contract: float) -> float:
"""
Calculate breakeven probability for simple fee structures.
Returns the minimum true probability needed for a profitable
"Yes" buy at the ask price.
"""
midpoint = ask_price - spread / 2
return ask_price + cost_per_contract
def breakeven_profit_fee(ask_price: float, profit_fee_rate: float,
other_costs: float = 0.0) -> float:
"""
Calculate breakeven probability with percentage-of-profit fee.
"""
numerator = ask_price + other_costs
denominator = 1.0 - profit_fee_rate + profit_fee_rate * ask_price
return min(numerator / denominator, 1.0)
def breakeven_full_predictit(ask_price: float, profit_fee: float = 0.10,
withdrawal_fee: float = 0.05) -> float:
"""
Calculate breakeven for PredictIt-style fee structure.
Accounts for both profit fee and withdrawal fee.
"""
# Net payout if win: (1 - profit_fee * (1-P)) after withdrawal fee
# Net cost: P after withdrawal fee adjustment
# p * [(1 - profit_fee*(1-P))] * (1-w) = P * (1-w) + p*0 ...
# Simplification: withdrawal fee applies to everything withdrawn
net_win_payout = (1.0 - profit_fee * (1.0 - ask_price)) * (1.0 - withdrawal_fee)
net_cost = ask_price # Capital is already on platform
# p * net_win_payout - (1-p) * ask_price * (1-withdrawal_fee) >= 0
# Actually: you deposit ask_price. If win, get back
# (1 - profit_fee*(1-ask_price))*(1-withdrawal_fee)
# If lose, get back 0.
# Breakeven: p * net_win_payout >= ask_price
return min(ask_price / net_win_payout, 1.0)
def sensitivity_table(prices: list, platforms: dict) -> None:
"""Print a sensitivity table of breakeven probabilities."""
header = f"{'Price':>7}"
for name in platforms:
header += f" {name:>14}"
print(header)
print("-" * len(header))
for p in prices:
row = f"${p:>5.2f}"
for name, func in platforms.items():
be = func(p)
edge = be - p
row += f" {be:>6.3f} (+{edge:.3f})"
print(row)
if __name__ == "__main__":
print("=== Breakeven Probability Analysis ===\n")
prices = [0.10, 0.20, 0.30, 0.40, 0.50, 0.60, 0.70, 0.80, 0.90]
platforms = {
"Polymarket": lambda p: breakeven_simple(p, spread=0.02, cost_per_contract=0.005),
"Kalshi": lambda p: breakeven_simple(p, spread=0.02, cost_per_contract=min(0.01, p/15)),
"PredictIt": lambda p: breakeven_full_predictit(p),
}
sensitivity_table(prices, platforms)
10.5 Effective Odds and the Overround
10.5.1 From Market Prices to True Probabilities
In a prediction market with multiple outcomes (e.g., "Who will win the election?" with candidates A, B, C), the sum of all contract prices should theoretically equal 1.00 (or $1). In practice, it rarely does. The overround (also called the vig or vigorish) is the amount by which the sum of all prices exceeds 1:
$$\text{Overround} = \sum_{i=1}^{n} P_i - 1$$
where $P_i$ is the ask price for outcome $i$.
For example, in a three-candidate race: - Candidate A: Ask $0.45 - Candidate B: Ask $0.38 - Candidate C: Ask $0.22 - Sum: $1.05 - Overround: $0.05$ or 5%
The overround represents the "tax" extracted by the market from traders as a whole. If you bought all outcomes at the ask, you would pay $1.05 and receive exactly $1.00 when one outcome resolves — a guaranteed 5% loss.
10.5.2 Why the Overround Exists
The overround arises from several sources:
- The bid-ask spread: When you use ask prices, each includes half the spread as a markup
- Market maker profit: Market makers set prices to guarantee a profit margin
- Platform fees: Fees embedded in prices create additional overround
- Risk aversion: Traders may demand a premium for taking on binary risk
In traditional bookmaking, the overround is explicitly set by the bookmaker. In prediction markets with order books, it emerges organically from the spread and the behavior of traders.
10.5.3 Removing the Overround
To convert market prices to "true" implied probabilities, we must remove the overround. Several methods exist:
Method 1: Proportional (Multiplicative) Normalization
The simplest approach divides each price by the sum of all prices:
$$\hat{p}_i = \frac{P_i}{\sum_{j=1}^{n} P_j}$$
For our example: - $\hat{p}_A = 0.45 / 1.05 = 0.4286$ - $\hat{p}_B = 0.38 / 1.05 = 0.3619$ - $\hat{p}_C = 0.22 / 1.05 = 0.2095$ - Sum: $1.0000$
Limitation: This assumes the overround is distributed proportionally across all outcomes, which may not be accurate.
Method 2: Additive (Shin) Method
Shin's method (1993) assumes the overround comes from informed trading and is distributed unevenly, with more overround on less likely outcomes:
$$\hat{p}_i = \frac{\sqrt{z^2 + 4(1-z) \cdot \frac{P_i^2}{\sum P_j}} - z}{2(1-z)}$$
where $z$ is the proportion of informed traders (estimated from the overround). This is more accurate but computationally more complex.
Method 3: Power Method
The power method finds an exponent $k$ such that the adjusted probabilities sum to 1:
$$\hat{p}_i = \frac{P_i^k}{\sum_{j=1}^{n} P_j^k}, \quad \text{where } k \text{ is chosen so } \sum \hat{p}_i = 1$$
In practice, $k$ is close to 1 for small overrounds and requires numerical optimization.
Method 4: Using Midpoints
The most practical approach for prediction markets with order books is to use midpoint prices instead of ask prices:
$$M_i = \frac{\text{Bid}_i + \text{Ask}_i}{2}$$
Midpoint prices often sum to much closer to 1.00 since the spread-induced overround is largely eliminated.
10.5.4 Overround in Binary Markets
For a simple Yes/No binary market, the overround is:
$$\text{Overround} = P_{\text{Yes,ask}} + P_{\text{No,ask}} - 1 = P_{\text{Yes,ask}} + (1 - P_{\text{Yes,bid}}) - 1 = P_{\text{Yes,ask}} - P_{\text{Yes,bid}} = S$$
The overround in a binary market is exactly equal to the bid-ask spread! This provides a clean connection between the concepts.
10.5.5 Python Implementation
"""
Overround Calculation and Probability Extraction
"""
import numpy as np
from scipy.optimize import brentq
def calculate_overround(prices: list) -> float:
"""Calculate the overround (vig) from a set of outcome prices."""
return sum(prices) - 1.0
def normalize_proportional(prices: list) -> list:
"""Remove overround using proportional normalization."""
total = sum(prices)
return [p / total for p in prices]
def normalize_power(prices: list, tol: float = 1e-8) -> list:
"""Remove overround using the power method."""
def objective(k):
powered = [p ** k for p in prices]
return sum(p / sum(powered) for p in powered) - 1.0
# For the power method, we need to find k such that
# sum(p_i^k) / sum(p_j^k) sums to 1 — which is always true.
# Instead, we find k such that the re-normalized probabilities
# are "fair" by minimizing overround in the k-powered space.
def obj2(k):
powered = [p ** k for p in prices]
return sum(powered) - 1.0
try:
k_star = brentq(obj2, 0.01, 10.0)
return [p ** k_star for p in prices]
except ValueError:
# Fall back to proportional
return normalize_proportional(prices)
def normalize_midpoints(bids: list, asks: list) -> list:
"""Calculate implied probabilities from bid-ask midpoints."""
midpoints = [(b + a) / 2 for b, a in zip(bids, asks)]
total = sum(midpoints)
return [m / total for m in midpoints]
def display_overround_analysis(
outcomes: list,
asks: list,
bids: list = None
) -> None:
"""Display complete overround analysis."""
overround = calculate_overround(asks)
print(f"Market Overround: {overround:.4f} ({overround:.2%})")
print()
prop_probs = normalize_proportional(asks)
print(f"{'Outcome':<15} {'Ask':>6} {'Proportional':>14}", end="")
if bids:
mid_probs = normalize_midpoints(bids, asks)
print(f" {'Midpoint':>14}", end="")
print()
print("-" * 55)
for i, outcome in enumerate(outcomes):
line = f"{outcome:<15} {asks[i]:>6.3f} {prop_probs[i]:>14.4f}"
if bids:
line += f" {mid_probs[i]:>14.4f}"
print(line)
totals = f"{'Total':<15} {sum(asks):>6.3f} {sum(prop_probs):>14.4f}"
if bids:
mids = [(b + a) / 2 for b, a in zip(bids, asks)]
totals += f" {sum(normalize_midpoints(bids, asks)):>14.4f}"
print(totals)
if __name__ == "__main__":
# Three-candidate election market
outcomes = ["Candidate A", "Candidate B", "Candidate C"]
asks = [0.45, 0.38, 0.22]
bids = [0.42, 0.35, 0.19]
print("=== Overround Analysis: Election Market ===\n")
display_overround_analysis(outcomes, asks, bids)
10.6 Market Impact and Slippage
10.6.1 Understanding Market Impact
Market impact is the effect that executing a trade has on the market price. It has two components:
-
Temporary impact: The short-lived price displacement caused by consuming liquidity in the order book. After your order fills, new limit orders may replenish the book and the price partially recovers.
-
Permanent impact: The lasting price change caused by the informational content of your trade. If you are buying because you have superior information, the market rationally adjusts to incorporate your signal.
For a prediction market trader, the total market impact of a trade is:
$$\Delta P_{\text{total}} = \Delta P_{\text{temporary}} + \Delta P_{\text{permanent}}$$
10.6.2 The Square-Root Impact Model
Empirical research in traditional finance has established that market impact follows an approximate square-root law:
$$\Delta P \approx \sigma \cdot \sqrt{\frac{Q}{V}}$$
where: - $\sigma$ is the price volatility - $Q$ is the order size (number of contracts) - $V$ is the average daily volume
This means that doubling your order size increases market impact by a factor of $\sqrt{2} \approx 1.41$, not by a factor of 2. This sub-linear relationship arises because:
- Larger orders are expected and partially anticipated
- Liquidity providers partially replenish the order book as it is consumed
- The informational content of an order does not scale linearly with size
For prediction markets, we can adapt this model:
$$\Delta P_{\text{pred}} = \alpha \cdot \sigma_{\text{contract}} \cdot \sqrt{\frac{Q}{V_{\text{daily}}}} + \beta$$
where $\alpha$ is a market-specific scaling parameter and $\beta$ captures fixed costs (minimum tick size effects).
10.6.3 Impact in Order Book vs. AMM Markets
The mechanism of market impact differs depending on the market structure:
Order Book Markets (Polymarket, Kalshi): - Impact is discrete: your order "walks" through price levels - Impact depends on the specific shape of the order book - You can observe the book and predict your impact before trading - Temporary impact is high; permanent impact depends on information content
AMM Markets (Manifold, some DeFi platforms): - Impact is continuous along the bonding curve - Impact is deterministic: $x \cdot y = k$ (constant product) or similar formula - No hidden liquidity — you can calculate exact impact before trading - All impact is "mechanical" — the formula determines the new price
For a constant-product AMM with reserves $x$ (Yes tokens) and $y$ (No tokens):
$$P_{\text{after}} = \frac{y - \Delta y}{x + \Delta x}$$
The impact of buying $\Delta x$ tokens is fully determined by the pool size.
10.6.4 Optimal Execution Strategies
When you need to trade a large position, splitting the order can reduce total market impact:
Strategy 1: Time-Weighted Average Price (TWAP)
Divide the order into $N$ equal slices and execute one slice every $T/N$ time units:
$$Q_i = \frac{Q_{\text{total}}}{N}, \quad t_i = t_0 + i \cdot \frac{T}{N}$$
Advantages: - Simple to implement - Reduces temporary impact by allowing the book to replenish - Does not require market microstructure knowledge
Disadvantages: - Does not adapt to market conditions - Suffers from price drift if the market is trending - Execution is predictable, making you vulnerable to front-running
Strategy 2: Volume-Weighted Average Price (VWAP)
Execute proportionally to historical volume patterns:
$$Q_i = Q_{\text{total}} \times \frac{V_i}{\sum V_j}$$
where $V_i$ is the historical volume in time bucket $i$.
In prediction markets, volume often spikes around: - News events (press conferences, data releases) - Market opening hours (for platforms with defined sessions) - Approaching resolution (increased activity in final hours/days)
Trading when volume is naturally high means your order is a smaller fraction of total flow, reducing market impact.
Strategy 3: Implementation Shortfall (Almgren-Chriss)
The Almgren-Chriss framework optimizes the trade-off between market impact (which favors slow execution) and price risk (which favors fast execution):
$$\text{Minimize: } E[\text{Impact Cost}] + \lambda \cdot \text{Var}[\text{Execution Cost}]$$
where $\lambda$ is the trader's risk aversion parameter. This produces an optimal execution trajectory that trades aggressively at the start (to reduce price risk) and more slowly later (as the remaining quantity and hence risk decreases).
For prediction markets, this framework must be modified to account for: - Bounded prices (the optimization changes near 0 and 1) - Event-driven volatility (volatility is not constant) - Low liquidity environments (discrete order book effects)
10.6.5 Practical Prediction Market Execution
For most prediction market traders, the following simple rules suffice:
-
For orders < 10% of daily volume: Execute immediately. Market impact will be minimal.
-
For orders 10-50% of daily volume: Split into 3-5 pieces executed over hours or a few days. Use limit orders when possible.
-
For orders > 50% of daily volume: You are the market. Split over days, use aggressive limit orders, and accept that you will move the price significantly.
-
Near news events: Execute before the news if you have advance knowledge of timing. Liquidity typically improves during high-activity periods, but so does adverse selection.
10.6.6 Python Market Impact Simulator
"""
Market Impact Simulator for Prediction Markets
Models the cost of executing large orders.
"""
import numpy as np
from typing import List, Tuple
def square_root_impact(
order_size: float,
daily_volume: float,
volatility: float,
alpha: float = 0.5,
beta: float = 0.001
) -> float:
"""
Estimate market impact using the square-root model.
Parameters
----------
order_size : float
Number of contracts to trade
daily_volume : float
Average daily volume in the market
volatility : float
Daily price volatility (standard deviation)
alpha : float
Impact scaling parameter (market-specific)
beta : float
Fixed cost component
Returns
-------
float
Estimated price impact in price units
"""
participation_rate = order_size / daily_volume
return alpha * volatility * np.sqrt(participation_rate) + beta
def simulate_order_splitting(
total_quantity: int,
num_slices: int,
daily_volume: float,
volatility: float,
alpha: float = 0.5,
initial_price: float = 0.50,
price_drift: float = 0.0
) -> Tuple[float, List[Tuple[int, float, float]]]:
"""
Simulate splitting a large order into smaller pieces.
Returns total cost and execution details for each slice.
"""
slice_size = total_quantity / num_slices
executions = []
total_cost = 0.0
current_price = initial_price
for i in range(num_slices):
# Impact of this slice
impact = square_root_impact(
slice_size, daily_volume, volatility, alpha
)
exec_price = current_price + impact / 2 # Average execution within slice
cost = slice_size * exec_price
total_cost += cost
executions.append((i + 1, slice_size, exec_price))
# Permanent impact shifts the price
permanent_fraction = 0.5 # 50% of impact is permanent
current_price += impact * permanent_fraction + price_drift
return total_cost, executions
def compare_execution_strategies(
total_quantity: int,
daily_volume: float,
volatility: float,
initial_price: float = 0.50
) -> None:
"""Compare different execution strategies."""
print(f"Order: {total_quantity} contracts")
print(f"Daily volume: {daily_volume}")
print(f"Participation rate: {total_quantity/daily_volume:.1%}")
print(f"Price volatility: {volatility:.4f}")
print()
strategies = {
"All at once (1 slice)": 1,
"5 slices (TWAP)": 5,
"10 slices (TWAP)": 10,
"20 slices (TWAP)": 20,
"50 slices (TWAP)": 50,
}
baseline_cost = total_quantity * initial_price
print(f"{'Strategy':<25} {'Avg Price':>10} {'Total Cost':>12} "
f"{'Impact Cost':>12} {'Impact %':>10}")
print("-" * 72)
for name, n_slices in strategies.items():
total_cost, executions = simulate_order_splitting(
total_quantity, n_slices, daily_volume, volatility,
initial_price=initial_price
)
avg_price = total_cost / total_quantity
impact_cost = total_cost - baseline_cost
impact_pct = impact_cost / baseline_cost
print(f"{name:<25} {avg_price:>10.4f} ${total_cost:>10.2f} "
f"${impact_cost:>10.2f} {impact_pct:>9.2%}")
if __name__ == "__main__":
print("=== Market Impact Analysis ===\n")
# Scenario: Large order in a moderately liquid market
compare_execution_strategies(
total_quantity=500,
daily_volume=2000,
volatility=0.03,
initial_price=0.50
)
print("\n=== Highly Liquid Market ===\n")
compare_execution_strategies(
total_quantity=500,
daily_volume=20000,
volatility=0.02,
initial_price=0.50
)
10.7 Minimizing Transaction Costs
10.7.1 Limit Orders vs. Market Orders
The single most effective way to reduce transaction costs is to use limit orders instead of market orders. A limit order that rests in the order book:
- Earns the spread instead of paying it
- Pays maker fees (often $0) instead of taker fees
- Avoids slippage — you get your exact price or nothing
- Provides a free option to the market (you can cancel before it fills)
The cost of using limit orders is execution risk — your order may never fill, or it may fill at the worst possible time (when an informed trader picks you off).
Optimal limit order placement:
Consider placing a buy limit order at price $P_{\text{limit}}$ in a market with midpoint $M$ and ask $P_{\text{ask}}$:
- At the bid ($P_{\text{limit}} = P_{\text{bid}}$): Maximum cost savings, lowest fill probability
- At the midpoint ($P_{\text{limit}} = M$): Moderate savings, moderate fill probability
- One tick below the ask ($P_{\text{limit}} = P_{\text{ask}} - \text{tick}$): Small savings, high fill probability
- At the ask (market order) ($P_{\text{limit}} = P_{\text{ask}}$): No savings, guaranteed fill
The optimal placement depends on your urgency and edge decay. If your informational edge will decay quickly (e.g., you are trading on breaking news), you should use market orders. If your edge is persistent (e.g., a long-term model), limit orders are strongly preferred.
10.7.2 Timing Strategies
Transaction costs vary over time, and trading at the right moment can significantly reduce costs:
Trade during high liquidity periods: - Spreads are tightest when many participants are active - On Polymarket, this typically coincides with U.S. business hours - On Kalshi, spreads tighten during their most active trading hours - Major political or sporting events attract temporary liquidity
Avoid trading immediately after news: - Spreads widen dramatically after major news events - Market makers pull their quotes to avoid adverse selection - Wait 5-30 minutes for liquidity to return before trading
Trade early in a market's lifecycle: - New markets often have temporarily wide spreads as market makers calibrate - However, early markets may also have less adverse selection (no one has private information yet)
Be aware of resolution timing: - As resolution approaches, spreads may widen (less time for market makers to manage risk) - Or narrow (if the outcome becomes obvious and risk decreases) - The optimal trading window depends on the specific market
10.7.3 Platform Selection
Different platforms are optimal for different trading strategies:
| Strategy | Best Platform | Reason |
|---|---|---|
| High-frequency, small trades | Polymarket | Zero fees, tight spreads on liquid markets |
| Large positions, patient execution | Kalshi | Maker rebate, regulated exchange |
| Arbitrage across platforms | Both | Need accounts on multiple platforms |
| Long-term holds | Polymarket | No ongoing fees, no profit fee |
| Small speculative bets | Manifold (play money) | Free to use, good for learning |
10.7.4 Fee Optimization Techniques
Maker Rebate Strategy
On platforms with maker-taker pricing, you can systematically earn the maker rebate:
- Identify markets where you have a view
- Place limit orders on the side you want to trade (buy at or slightly above the bid)
- Wait for natural order flow to fill your orders
- Benefit from zero (or negative) maker fees
This strategy works best when you are not in a hurry and the market has regular two-way flow.
Netting and Hedging
If you hold positions in correlated markets, you can sometimes reduce trading by netting:
- If you are long "Candidate A wins" and want to buy "Party X wins" (and A is from Party X), the positions are partially redundant
- Instead of two separate trades, look for a single market that captures your combined view
- Fewer trades means fewer transaction costs
Tax-Lot Optimization
On platforms that charge profit fees (PredictIt), the order in which you close positions matters:
- FIFO (First In, First Out): Close oldest positions first
- LIFO (Last In, First Out): Close newest positions first
- Highest Cost: Close the highest-cost positions first to minimize realized gains
The optimal choice depends on your specific positions and the platform's rules.
10.7.5 Position Sizing Adjusted for Costs
Transaction costs should be directly incorporated into position sizing. The Kelly Criterion (covered in later chapters) gives the optimal fraction of bankroll to bet:
$$f^* = \frac{p(b+1) - 1}{b}$$
where $p$ is the true probability and $b$ is the net odds. When costs $c$ are included:
$$f^*_{\text{adjusted}} = \frac{(p - c)(b+1) - 1}{b}$$
This always reduces the optimal position size. For marginal edges, costs can reduce the optimal bet to zero — meaning you should not trade at all.
10.8 The Economics of Spreads in Prediction Markets
10.8.1 Who Earns the Spread?
The bid-ask spread is not "lost" — it is earned by liquidity providers, who can be:
- Professional market makers: Firms or individuals who systematically post two-sided quotes
- Informed traders with limit orders: Traders who believe the price will move to their limit order level
- Casual participants: Retail traders who happen to post limit orders
- Automated bots: Algorithmic systems that provide liquidity based on models
In prediction markets, market making is accessible to anyone with capital and a basic understanding of pricing. There are no regulatory barriers to entry (unlike equities, where you need broker-dealer registration to make markets on exchanges).
10.8.2 Market Maker Profitability
A market maker who posts a bid at $0.48$ and an ask at $0.52$ earns $0.04$ per round trip (buying at the bid and selling at the ask). However, the market maker's actual profitability depends on:
Revenue: - Spread earned per contract: $S$ - Volume of contracts traded: $V$ - Gross revenue: $S \times V$
Costs: - Adverse selection losses: When informed traders pick off stale quotes - Inventory risk: Directional losses when positions accumulate - Platform fees: Maker fees (if any) - Technology costs: Bots, APIs, monitoring - Capital costs: Opportunity cost of capital tied up in positions
The fundamental equation of market making profitability:
$$\Pi_{\text{MM}} = S \times V - \text{AS losses} - \text{Inventory losses} - \text{Fees} - \text{Costs}$$
A market maker is only profitable if the spread exceeds the expected adverse selection cost plus all other costs.
10.8.3 The Glosten-Milgrom Model
The Glosten-Milgrom (1985) model is the foundational framework for understanding how spreads arise from adverse selection. The core insight is elegant:
Setup: - A security has true value $V$, which is either $V_H$ (high) or $V_L$ (low) - A market maker does not know $V$ but has prior beliefs: $\Pr(V = V_H) = \mu$ - Traders arrive sequentially. With probability $\pi$, a trader is informed (knows $V$). With probability $1 - \pi$, the trader is uninformed (trades randomly for liquidity reasons). - Informed traders always buy if $V = V_H$ and sell if $V = V_L$ - Uninformed traders buy or sell with equal probability
The market maker's pricing:
The ask price (at which the market maker sells) must equal the expected value of the security conditional on someone wanting to buy:
$$A = E[V \mid \text{buy order}]$$
Using Bayes' rule:
$$A = V_H \cdot \Pr(V = V_H \mid \text{buy}) + V_L \cdot \Pr(V = V_L \mid \text{buy})$$
Since buys come from informed traders (who buy only when $V = V_H$) and uninformed traders (who buy randomly):
$$\Pr(\text{buy} \mid V = V_H) = \pi + (1 - \pi) \cdot 0.5$$ $$\Pr(\text{buy} \mid V = V_L) = (1 - \pi) \cdot 0.5$$
Applying Bayes' rule:
$$A = V_H \cdot \frac{\mu[\pi + (1-\pi)/2]}{\mu[\pi + (1-\pi)/2] + (1-\mu)(1-\pi)/2} + V_L \cdot \frac{(1-\mu)(1-\pi)/2}{\mu[\pi + (1-\pi)/2] + (1-\mu)(1-\pi)/2}$$
Similarly for the bid:
$$B = E[V \mid \text{sell order}]$$
Key predictions: - The spread $A - B$ is increasing in $\pi$ (more informed traders → wider spread) - The spread is increasing in $V_H - V_L$ (more uncertainty → wider spread) - The spread is zero when $\pi = 0$ (no informed traders) - The midpoint converges to the true value over time as the market maker learns from order flow
Application to prediction markets:
In a binary prediction market with $V_H = 1$ (event occurs) and $V_L = 0$ (event does not occur), and current probability $\mu$:
$$A = \frac{\mu[\pi + (1-\pi)/2]}{\mu[\pi + (1-\pi)/2] + (1-\mu)(1-\pi)/2}$$
$$B = \frac{\mu(1-\pi)/2}{\mu(1-\pi)/2 + (1-\mu)[\pi + (1-\pi)/2]}$$
For $\mu = 0.50$ and $\pi = 0.20$ (20% informed traders):
$$A = \frac{0.50 \times 0.60}{0.50 \times 0.60 + 0.50 \times 0.40} = \frac{0.30}{0.50} = 0.60$$
$$B = \frac{0.50 \times 0.40}{0.50 \times 0.40 + 0.50 \times 0.60} = \frac{0.20}{0.50} = 0.40$$
Spread: $0.60 - 0.40 = 0.20$ — a 20% spread when 20% of traders are informed. This is a wide spread, illustrating how information asymmetry creates significant trading costs.
10.8.4 Adverse Selection in Prediction Markets
Prediction markets are particularly susceptible to adverse selection because:
-
Information arrives in bursts: Political events, court decisions, and economic data releases create sudden information advantages for those who learn first.
-
Expertise varies widely: A domain expert (pollster, meteorologist, policy analyst) may have far better probability estimates than a generalist market maker.
-
No mandatory disclosure: Unlike in stock markets, there are no insider trading regulations in most prediction markets. Anyone can trade on private information legally.
-
Low barriers to entry for informed trading: You do not need to be a large institution to trade on information in prediction markets.
The practical implication: Prediction market spreads are wider than you might expect based on the relatively simple payoff structure, precisely because the adverse selection risk is high.
10.9 Empirical Analysis of Prediction Market Spreads
10.9.1 Typical Spreads Across Platforms
Based on observation of major prediction market platforms, typical quoted spreads (for liquid markets) are:
| Platform | Typical Spread (Liquid) | Typical Spread (Illiquid) |
|---|---|---|
| Polymarket | 1-3 cents | 5-15 cents |
| Kalshi | 2-5 cents | 5-20 cents |
| PredictIt (historical) | 3-7 cents | 10-20+ cents |
| Manifold (play money) | 1-5% (AMM-dependent) | 5-20% |
10.9.2 Factors Affecting Spreads
Market liquidity: The single strongest predictor of spread width. Markets with high volume and many active participants have narrower spreads.
Time to resolution: Spreads tend to follow a U-shaped pattern: - Wide at market creation (uncertainty about fair value) - Narrow in the middle of a market's lifecycle (active trading, established pricing) - Variable near resolution (can narrow if outcome is clear, or widen if outcome is uncertain)
Contract price level: Spreads tend to be narrower in absolute terms near the boundaries (0 and 1) because: - The maximum loss for a market maker is bounded - The tick size constrains the minimum spread - Fewer informed traders operate at extremes (the information is already in the price)
However, spreads as a percentage of the price are often wider near the boundaries.
Market type: - Binary markets (Yes/No): Typically have the tightest spreads - Multiple-outcome markets: Wider spreads due to more complex pricing and hedging - Conditional markets: Widest spreads due to illiquidity and complexity
News environment: - Spreads widen dramatically during news events - The widening can persist for hours after a major announcement - This is rational: market makers face increased adverse selection risk when news is breaking
10.9.3 Spread Dynamics Over Time
Let us analyze how spreads evolve using synthetic data that reflects typical patterns:
"""
Empirical Analysis of Prediction Market Spreads
Using synthetic data that reflects typical market patterns.
"""
import numpy as np
from typing import List, Dict
def generate_synthetic_spread_data(
n_days: int = 90,
base_spread: float = 0.03,
base_volume: float = 1000,
base_price: float = 0.50,
news_events: List[int] = None,
resolution_day: int = 90,
seed: int = 42
) -> Dict:
"""
Generate synthetic spread data for a prediction market.
Models realistic patterns:
- Spreads widen during low volume
- Spreads widen during news events
- Spreads narrow as resolution approaches (if outcome becomes clear)
- Volume increases near resolution
"""
rng = np.random.RandomState(seed)
if news_events is None:
news_events = [15, 30, 60, 85]
days = np.arange(n_days)
prices = np.zeros(n_days)
spreads = np.zeros(n_days)
volumes = np.zeros(n_days)
# Price path: random walk with drift toward eventual outcome
outcome = 1.0 # Assume event occurs (price drifts toward 1)
price = base_price
for d in range(n_days):
# Random walk with mean reversion toward outcome
days_left = resolution_day - d
drift = (outcome - price) / max(days_left, 1) * 0.1
noise = rng.normal(0, 0.02)
price = np.clip(price + drift + noise, 0.02, 0.98)
prices[d] = price
# Volume: increases near resolution, spikes at news events
base_vol = base_volume * (1 + 2 * (d / n_days) ** 2)
if d in news_events:
base_vol *= 5
volume = max(base_vol * (1 + rng.normal(0, 0.3)), 100)
volumes[d] = volume
# Spread: inversely related to volume, wider at news events
vol_factor = np.sqrt(base_volume / volume)
news_factor = 3.0 if d in news_events else 1.0
# Spread also depends on price level (wider near 0.5, narrower near 0/1)
price_factor = 4 * price * (1 - price) # Peaks at 0.5
spread = base_spread * vol_factor * news_factor * (0.5 + price_factor)
spread = max(spread, 0.01) # Minimum 1-cent spread
spread = min(spread, 0.20) # Maximum 20-cent spread
spreads[d] = spread
return {
'days': days,
'prices': prices,
'spreads': spreads,
'volumes': volumes,
'news_events': news_events
}
def analyze_spread_statistics(data: Dict) -> None:
"""Print summary statistics of spread data."""
spreads = data['spreads']
volumes = data['volumes']
prices = data['prices']
print("=== Spread Summary Statistics ===")
print(f"Mean spread: {np.mean(spreads):.4f}")
print(f"Median spread: {np.median(spreads):.4f}")
print(f"Min spread: {np.min(spreads):.4f}")
print(f"Max spread: {np.max(spreads):.4f}")
print(f"Std spread: {np.std(spreads):.4f}")
print()
# Correlation analysis
corr_vol = np.corrcoef(volumes, spreads)[0, 1]
corr_price = np.corrcoef(prices, spreads)[0, 1]
print("=== Correlation Analysis ===")
print(f"Spread vs Volume: {corr_vol:+.3f} (expected negative)")
print(f"Spread vs Price: {corr_price:+.3f}")
print()
# Relative spread analysis
relative_spreads = spreads / prices
print("=== Relative Spread (Spread/Price) ===")
print(f"Mean relative spread: {np.mean(relative_spreads):.4f}")
print(f"Median relative spread: {np.median(relative_spreads):.4f}")
print()
# Cost analysis
print("=== Trading Cost Analysis ===")
avg_half_spread = np.mean(spreads) / 2
print(f"Average one-way cost (half spread): {avg_half_spread:.4f}")
print(f"Average round-trip cost (full spread): {np.mean(spreads):.4f}")
# News event impact
news_days = data['news_events']
normal_days = [d for d in range(len(spreads)) if d not in news_days]
print(f"\nSpread on news days: {np.mean(spreads[news_days]):.4f}")
print(f"Spread on normal days: {np.mean(spreads[normal_days]):.4f}")
print(f"News day multiplier: {np.mean(spreads[news_days])/np.mean(spreads[normal_days]):.2f}x")
if __name__ == "__main__":
data = generate_synthetic_spread_data()
analyze_spread_statistics(data)
10.9.4 Spread Patterns by Market Category
Different types of prediction markets exhibit characteristic spread patterns:
Political Markets: - Average spreads: 2-5 cents on major races, 5-10 cents on minor races - Spreads widen before debates, primary elections, and polling releases - Spreads narrow after definitive results in related races - Highest adverse selection risk around insider information (endorsements, campaign strategy changes)
Sports Markets: - Average spreads: 1-3 cents on major events (Super Bowl, World Cup) - Spreads tighten significantly close to game time (less uncertainty about lineups, conditions) - Spreads widen at halftime or during injury timeouts - Relatively low adverse selection (information is mostly public)
Financial/Economic Markets: - Average spreads: 2-4 cents (will the Fed raise rates?) - Spreads tighten leading up to announcements as markets price in expectations - Significant spread widening during the announcement itself - Moderate adverse selection (some traders may have early access to data)
Weather Markets: - Average spreads: 3-7 cents - Spreads narrow as the forecast date approaches (weather models converge) - Relatively low adverse selection (weather data is publicly available) - Spreads can be wide for rare events (hurricanes, extreme temperatures)
10.10 Practical Considerations and Chapter Summary
10.10.1 Building a Cost-Aware Trading Framework
Every prediction market trader should maintain a personal cost model. Here is a framework:
Step 1: Identify All Costs For each platform you trade on, catalog every cost: - Entry fees (maker/taker) - Exit fees (closing trade or resolution) - Profit fees (if applicable) - Withdrawal fees - Gas/network fees - Spread cost (varies by market) - Expected slippage (varies by order size)
Step 2: Calculate Breakeven Edge For every trade you consider, calculate the minimum edge needed: $$\text{Edge}_{\text{min}} = \frac{S}{2} + \text{fees} + \text{expected slippage}$$
Step 3: Apply a Cost Filter Reject any trade where your estimated edge is less than 1.5x the breakeven edge. The 1.5x buffer accounts for estimation error in both your probability estimates and the cost estimates.
Step 4: Track Actual Costs Maintain a trading log that records: - Expected cost (pre-trade estimate) - Actual cost (post-trade calculation) - Fill rate (what percentage of limit orders filled) - Slippage (difference between expected and actual execution price)
Step 5: Optimize Continuously Use your cost data to improve: - Choose platforms with lower effective costs for your trading style - Adjust limit order aggressiveness based on fill rates - Shift trading to time periods with better liquidity
10.10.2 Common Mistakes
-
Ignoring the spread: Many traders calculate edge as "my probability minus the midpoint" but then execute at the ask. The edge is against the execution price, not the midpoint.
-
Underestimating PredictIt-style fees: The 10% profit fee plus 5% withdrawal fee is far more expensive than it appears. It can consume 15-20% of profits on winning trades.
-
Over-trading: Each trade incurs costs. If you have 2 cents of edge and 1.5 cents of cost per trade, you need a very high win rate to overcome the cumulative drag of costs.
-
Neglecting gas fees on small trades: A $5 gas fee on a $20 trade is a 25% cost — catastrophic for profitability.
-
Market orders in thin books: Placing a market order in a prediction market with a thin order book can result in fills at extremely unfavorable prices.
-
Not accounting for the exit: Many traders calculate the cost of entering a position but forget that they will also face costs when exiting (either by selling before resolution or through resolution fees).
10.10.3 Key Formulas Reference
| Formula | Description |
|---|---|
| $S = P_{\text{ask}} - P_{\text{bid}}$ | Quoted spread |
| $S_{\text{eff}} = 2 \times \|P_{\text{trade}} - M\|$ | Effective spread |
| $S_{\text{rel}} = S / M$ | Relative spread |
| $\text{Edge}_{\text{BE}} = \frac{S}{2} + C$ | Breakeven edge |
| $\Delta P = \sigma\sqrt{Q/V}$ | Square-root impact |
| $\text{Overround} = \sum P_i - 1$ | Market overround |
| $\hat{p}_i = P_i / \sum P_j$ | Proportional normalization |
10.10.4 Chapter Summary
This chapter has provided an exhaustive treatment of the costs of trading in prediction markets:
-
Spreads are the primary implicit cost, arising from adverse selection, inventory risk, and order processing. In prediction markets, typical spreads range from 1-10 cents depending on liquidity.
-
Transaction costs include both explicit fees (platform fees, gas fees) and implicit costs (spread, slippage, market impact). Total costs can easily reach 3-8% of notional value.
-
Platform fee structures vary dramatically. Polymarket's zero-fee model makes it cheapest for active trading, while PredictIt's profit-and-withdrawal fee structure can consume 15%+ of profits.
-
Breakeven edge calculations show that you need significantly more edge than the raw price difference suggests. A trade that "looks" like 5 cents of edge may actually have only 1-2 cents after costs.
-
The overround in multi-outcome markets represents the aggregate cost of trading. Removing it to extract true probabilities requires careful normalization.
-
Market impact follows an approximate square-root law, meaning splitting large orders can significantly reduce execution costs.
-
Cost minimization strategies include using limit orders, timing trades around liquidity, choosing the right platform, and incorporating costs into position sizing.
-
The economics of spreads are fundamentally driven by adverse selection, as formalized by the Glosten-Milgrom model. Prediction markets face particularly high adverse selection risk due to the prevalence of informed traders and the absence of insider trading regulations.
What's Next
In Chapter 11: Automated Market Makers (AMMs) and Bonding Curves, we will explore the alternative to order-book-based trading: automated market makers. We will study how AMMs like the Logarithmic Market Scoring Rule (LMSR) and constant-product formulas create prices mechanically, how they relate to the cost concepts from this chapter, and how traders can exploit or provide liquidity through these mechanisms. The AMM framework provides a particularly elegant connection between probability estimation and market microstructure.
Chapter 10 is part of Part II: Market Microstructure & Pricing of "Learning Prediction Markets — From Concepts to Strategies."