The Bid-Ask Spread
The Cost of Immediacy in Every Market

What Is the Spread?

The spread is the gap between the highest price a buyer is willing to pay (the best bid) and the lowest price a seller is willing to accept (the best ask). If the best bid for ETH is $1,998 and the best ask is $2,001, the spread is $3. This gap is the most fundamental cost of trading. Every time you execute a market order, you cross the spread, buying at the ask or selling at the bid.

The spread exists because market makers need compensation for providing liquidity. They sit on both sides of the market, willing to buy and sell at any moment, and the spread is how they get paid for that service.

Order Book with Spread Highlighted Bids Price Asks 5.2 ETH $1,998 3.8 ETH $1,997 2.1 ETH $1,996 1.0 ETH $1,995 $2,001 4.5 ETH $2,002 6.1 ETH $2,003 2.7 ETH $2,005 0.9 ETH SPREAD = $2,001 - $1,998 = $3 ~0.15% relative spread (15 bps) midpoint = ($1,998 + $2,001) / 2 = $1,999.50 buy at $2,001 (ask) · sell at $1,998 (bid) · the spread is the round-trip cost of immediacy

Why the Spread Exists

Market makers provide a service. They stand ready to buy and sell at any time, absorbing the timing mismatch between buyers and sellers. The spread compensates them for three risks.

Spread in Traditional Markets

Highly liquid assets like Apple stock or EUR/USD have extremely tight spreads, often just one cent or one pip. The spread on AAPL might be $0.01 on a $180 stock, which is about 0.006%. There are so many market makers competing that the spread gets compressed to almost nothing.

Illiquid assets tell a different story. A small-cap stock might have a spread of $0.50 on a $10 stock (5%). An exotic currency pair or a thinly traded commodity future can have spreads measured in percentage points. The less competition among market makers and the less volume, the wider the spread.

Spread in DeFi

AMMs do not have explicit bid and ask prices. There is no order book with visible spread. Instead, the spread emerges implicitly from two sources.

First, the bonding curve itself creates a spread. When you buy a token from an AMM, you push the price up along the curve. If you immediately tried to sell the same amount back, you would get less than you paid because selling pushes the price back down. The round-trip cost is the implicit spread.

Second, swap fees widen the spread further. Uniswap V3 offers multiple fee tiers that LPs choose when creating pools.

The fee is applied on top of the curve impact, so the effective spread on a Uniswap V3 ETH/USDC pool with a 0.30% fee tier is at minimum 0.60% for a round trip (0.30% to buy, 0.30% to sell), plus whatever additional curve impact the trade size causes.

AMM Implicit Spread (Buy vs Sell Price) Price (USDC per ETH) ETH in pool (reserve x) x · y = k buy price you remove ETH, price goes up spot price sell price you add ETH, price goes down implicit spread + 0.30% swap fee per side widens effective spread further

Measuring the Spread

There are several ways to quantify the spread.

For AMM pools, we can compute the implied spread by simulating a buy and a sell of the same size against the reserves.

Go
// Read Uniswap V2 pool reserves and compute the implied spread

import (
    "context"
    "fmt"
    "math"
    "math/big"

    "github.com/ethereum/go-ethereum"
    "github.com/ethereum/go-ethereum/common"
    "github.com/ethereum/go-ethereum/crypto"
    "github.com/ethereum/go-ethereum/ethclient"
)

func getImpliedSpread(client *ethclient.Client, pairAddr common.Address, tradeSize float64) {
    // getReserves() selector
    selector := crypto.Keccak256([]byte("getReserves()"))[:4]

    result, _ := client.CallContract(context.Background(), ethereum.CallMsg{
        To:   &pairAddr,
        Data: selector,
    }, nil)

    // Decode ABI-packed (uint112, uint112, uint32)
    reserve0 := new(big.Int).SetBytes(result[0:32])
    reserve1 := new(big.Int).SetBytes(result[32:64])

    x := float64(reserve0.Int64()) / math.Pow(10, 18) // ETH (18 decimals)
    y := float64(reserve1.Int64()) / math.Pow(10, 6)  // USDC (6 decimals)
    k := x * y

    spotPrice := y / x

    // Buy tradeSize ETH -- remove ETH from pool, add USDC
    newX := x - tradeSize
    buyPrice := (k/newX - y) / tradeSize

    // Sell tradeSize ETH -- add ETH to pool, remove USDC
    newX2 := x + tradeSize
    sellPrice := (y - k/newX2) / tradeSize

    absoluteSpread := buyPrice - sellPrice
    relativeSpreadBps := (absoluteSpread / spotPrice) * 10000

    fmt.Printf("Spot price:       %.2f\n", spotPrice)
    fmt.Printf("Buy price:        %.2f\n", buyPrice)
    fmt.Printf("Sell price:       %.2f\n", sellPrice)
    fmt.Printf("Absolute spread:  %.2f\n", absoluteSpread)
    fmt.Printf("Relative spread:  %.1f bps\n", relativeSpreadBps)
}

What Moves the Spread

The spread is not static. It widens and tightens in response to market conditions.

Understanding the spread is the first step. But the spread only tells you the cost of an infinitely small trade. For real trades of meaningful size, you also need to understand slippage, which measures how much worse your execution gets as your trade size increases.

For the broader framework of how these mechanics fit together, see Market Microstructure. For how swaps are executed as on-chain transactions through smart contracts, see the contracts deep dive.