Explainer
Bid-ask spread explained: bid, ask, mid price and spread in bps
Every quote is two prices. The gap between them is the cheapest signal of liquidity you will ever get, and most dashboards throw it away.
On this page
- Bid vs ask: two prices, one market
- The arithmetic: pips, cents and basis points
- How wide is normal?
- Why spreads widen at night and at the rollover
- NBBO: the best bid and offer across US venues
- Slippage: when the spread is not the whole cost
- Track the spread yourself in Python
- What the spread tells your code
- Questions
Key takeaways
- The bid is the best price a buyer will pay, the ask is the best price a seller will accept, and the bid-ask spread is the gap between them.
- Compare spreads in basis points (spread divided by mid price, times 10,000), not in raw price units, or a yen pair will always look wider than it is.
- Spreads widen when fewer people are trading: pre-market and after-hours sessions, the daily forex rollover and the minutes around big releases.
- Slippage is the extra cost when an order is bigger than the size shown at the best price and fills deeper in the book.
- A zero, crossed or suddenly huge spread is a data-quality alarm. Check it before you chart or act on a quote.
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 will accept (the ask). Open any quote from a market data API and you get both numbers. On a Monday morning in late September 2026, EURUSD quoted 1.136965 to sell and 1.137005 to buy: a spread of 0.00004, which forex traders call 0.4 pips.
That gap tells you how easy it is to trade at this moment, what a round trip costs before any fee, and whether the quote in front of you is healthy. This guide covers how to express the spread so markets can be compared, why it changes through the day, what NBBO and slippage mean, and how to track it in a few lines of Python.
Bid vs ask: two prices, one market
If you want to buy right now you pay the ask (also called the offer). If you want to sell right now you receive the bid. The mid price halfway between them is what most charts draw, but nobody actually trades at the mid. Every quote endpoint returns both sides and the size available at each:
A live forex quote from the REST API
{
"symbol": "EURUSD",
"bid": 1.136965,1
"ask": 1.137005,2
"bid_size": 85,3
"ask_size": 61,
"timestamp": 17905911140024
}
bidBest price a buyer is offering. You sell here.askBest price a seller is asking. You buy here.bid_sizeSize available at the bid. Units depend on the market.timestampWhen the quote was observed, in Unix milliseconds (UTC).
A quote is not a trade. A quote is an offer that is still standing; a trade is a deal that already happened. The last trade price can sit anywhere inside, or briefly outside, the current bid and ask. The OHLC explainer shows how trades become the bars on a chart, and why a bar never tells you what the spread was.
The arithmetic: pips, cents and basis points
spread = ask − bidmid = (bid + ask) ÷ 2spread in bps = spread ÷ mid × 10,000
- spread
- The gap, in units of the quote currency.
- mid
- The midpoint most charts plot.
- bps
- Basis points: hundredths of a percent, comparable across markets.
Forex traders usually quote spreads in pips. For most pairs a pip is the fourth decimal (0.0001); for yen pairs it is the second (0.01), because the yen trades at a much larger number per dollar. Our pip explainer has the full rule and a calculator. Stocks quote spreads in cents, and crypto in whatever the quote currency is.
None of those units compare across markets. Divide the spread by the mid and express it in basis points instead: one basis point is 0.01%, and a 2 bps spread costs the same fraction of a trade whether the instrument is priced at 1.13 or at 82,000. Here are five live quotes taken a few seconds apart:
| Instrument | Bid | Ask | Spread | In bps |
|---|---|---|---|---|
| EURUSD | 1.136965 | 1.137005 | 0.4 pips | 0.35 |
| USDJPY | 157.0982 | 157.1052 | 0.7 pips | 0.45 |
| XAUUSD | 4,155.95 | 4,156.47 | $0.52 | 1.25 |
| BTCUSD | 82,820.00 | 82,820.01 | $0.01 | 0.001 |
| US:KO (pre-market) | 88.11 | 88.30 | $0.19 | 21.54 |
The last row is the interesting one. The same stock that trades a cent or two wide in the middle of a regular session was quoting 19 cents wide before the open: seventeen times wider than gold and sixty times wider than EURUSD in relative terms. Nothing was wrong with the data; there were simply very few people quoting it at 06:25 in New York. The after-hours and pre-market guide goes deeper on why.
How wide is normal?
There is no universal "good" spread. It depends on how many people trade the instrument and how much risk a market maker takes by standing in the middle. The most traded currency pairs and the largest stocks sit at a basis point or less; thinly traded shares, exotic currencies and small coins can be fifty times wider.
Typical spread in busy hours, basis points
Why spreads widen at night and at the rollover
The same pair can be tight at 14:00 UTC and wide at 22:00 UTC. Liquidity follows people: when London and New York are both open, many banks and funds quote currencies and competition squeezes the spread. When only one region is awake, fewer quotes compete and the gap opens up. The forex market hours guide has a live session clock.
Forex sessions on a UTC clock
Hours in UTC
The widest moment of the forex day is usually the rollover at 17:00 New York time, when dealers settle positions and roll them to the next value date. For a few minutes liquidity thins out and spreads can jump to several times their daytime width. If your alerting fires on price moves, it will fire at the rollover for reasons that have nothing to do with the market.
EURUSD spread across a trading day
Stocks follow the same logic on a different clock. Spreads are widest in the first minutes after the open, while the order book is still filling, and in pre-market and after-hours sessions. Crypto trades all week, so it has no open, but weekend nights are visibly thinner than weekday afternoons.
NBBO: the best bid and offer across US venues
A US stock trades on many venues at once, and each has its own best bid and ask. The national best bid and offer (NBBO) is the highest bid and the lowest ask across all of them. It is the reference brokers are measured against, and the tightest spread anyone can see for that stock at that instant.
- Venue A88.40 / 88.43
- Venue B88.41 / 88.44
- Venue C88.39 / 88.42
- NBBO88.41 / 88.42
TickerLayer quotes are derived from aggregated sources rather than being the official NBBO, so we publish how close they come: the US stocks data quality benchmark compares our quotes with a consolidated US market reference, state for state, including the share of samples where our midpoint sits inside the NBBO.
Slippage: when the spread is not the whole cost
The spread is the cost of trading a small amount. The size fields tell you how small. The pre-market US:KO quote above showed 200 shares at the 88.30 ask. Buy 1,000 shares and the first 200 fill at the ask; the rest fill at worse prices deeper in the book. That difference between the price you expected and the average price you got is slippage.
| Ask level | Shares available | Shares filled | Cost |
|---|---|---|---|
| 88.30 | 200 | 200 | $17,660 |
| 88.34 | 300 | 300 | $26,502 |
| 88.39 | 500 | 500 | $44,195 |
| Total | 1,000 | $88,357 |
Quote data does not show the levels behind the best price, but it tells you when to worry: when your order is several times the displayed size, or when the spread is already wide, expect to pay more than the ask.
Track the spread yourself in Python
You need a key and one endpoint. GET /{asset}/quote/{symbol} returns the current bid and ask for any asset class, with the same shape for forex, stocks, crypto, indices, ETFs and commodities. This script prints the spread in basis points for a few instruments:
import os
import requests
API = "https://api.tickerlayer.com"
HEADERS = {"x-api-key": os.environ["TICKERLAYER_API_KEY"]}
def spread_bps(asset: str, symbol: str) -> float:
r = requests.get(f"{API}/{asset}/quote/{symbol}", headers=HEADERS, timeout=5)
r.raise_for_status()
q = r.json()
bid, ask = float(q["bid"]), float(q["ask"])
mid = (bid + ask) / 2
return (ask - bid) / mid * 10_000
for asset, symbol in [
("forex", "EURUSD"),
("forex", "USDJPY"),
("commodities", "XAUUSD"),
("crypto", "BTCUSD"),
("stocks", "US:KO"),
]:
print(f"{symbol:<8} {spread_bps(asset, symbol):7.2f} bps")EURUSD 0.35 bps
USDJPY 0.45 bps
XAUUSD 1.25 bps
BTCUSD 0.00 bps
US:KO 21.54 bpsPolling is fine for a spot check. To watch a spread change tick by tick, subscribe to the quote channel over WebSocket instead. Stream frames carry the same bid and ask, sent as strings on the forex, crypto and stocks channels, so convert them before doing arithmetic. The WebSocket market data guide walks through connecting, subscribing and reconnecting, and the exchange rate API guide covers forex quotes in depth.
What the spread tells your code
- The cost of tradingCrossing the spread is a real cost. A strategy that makes 1 bp per trade on a 2 bps spread loses money before fees.
- A liquidity gaugeA spread three times its usual width means the book is thin. Size down, or wait.
- A quality checkZero, negative or absurd spreads point at a stale or broken quote. Drop it instead of plotting it.
- A calmer alertAlert on the mid, but only when the spread is normal. Most false alarms happen when it is not.
The quality check deserves code of its own. A crossed quote (bid above ask) or a locked one (bid equal to ask) is not a price anyone can trade. It is usually a moment where the two sides were updated at slightly different times, and a reason to wait for the next update. The bad ticks guide extends this into a full stale-price and spike filter.
def usable(quote: dict, max_bps: float = 50.0) -> bool:
bid, ask = float(quote["bid"]), float(quote["ask"])
if bid <= 0 or ask <= 0 or ask <= bid:
return False # empty, crossed or locked
mid = (bid + ask) / 2
return (ask - bid) / mid * 10_000 <= max_bpsDo
- Compare spreads in basis points across markets.
- Keep bid and ask in storage; derive the mid.
- Expect wider spreads before the open, overnight and at the rollover.
- Filter crossed and locked quotes before charting.
Avoid
- Comparing a yen pair and a euro pair in raw price units.
- Backtesting on mid prices and ignoring the cost of crossing.
- Firing price alerts during the daily rollover.
- Assuming a large order fills at the ask.
Questions
What is a good bid-ask spread?
It depends on the instrument. Major currency pairs, large coins and the largest stocks trade at around one basis point or less in busy hours; small caps, exotic currencies and minor coins can be tens of basis points wide. Compare an instrument with its own history rather than with other markets.
Is it better to buy at the bid or the ask?
A market order to buy pays the ask and a market order to sell receives the bid. A limit order placed at the bid may get a better price, but it only fills if a seller comes to you.
Why does the spread widen at night?
Fewer participants are quoting, so less competition keeps prices close. In forex the widest point is usually the daily rollover at 17:00 New York time; in US stocks it is the pre-market and after-hours sessions.
What is the difference between spread and slippage?
The spread is the gap between the best bid and the best ask. Slippage is the extra cost when an order is larger than the size at the best price and fills at worse levels.
What is the mid price?
The average of the bid and the ask. Charts usually plot it, but nobody trades at it; buyers pay the ask and sellers receive the bid.
What does NBBO mean?
National best bid and offer: the highest bid and the lowest ask for a US stock across all trading venues at a given moment.