What Is the NBBO? National Best Bid and Offer
The NBBO is the national best bid and offer, the one consolidated stock quote your broker must match or beat. See how fast it updates, from real tick data.
The NBBO, the national best bid and offer, is the consolidated quote for a US stock: the highest price any buyer is bidding and the lowest any seller is asking, across every exchange at once. It is the quote your app shows and the benchmark your broker must meet or beat on your order. Every number below comes from real quote records, query attached.
What is the NBBO? A plain-English definition
A US stock does not trade in one place. NYSE, Nasdaq, and more than a dozen other exchanges each run an order book for the same ticker, each with its own best bid and ask. The NBBO stitches those tops together: the highest bid anywhere, the lowest ask anywhere, side by side with the shares available at each. The stitching is done by the securities information processors, or SIPs, market utilities the exchanges operate jointly.
Two details matter early. The bid and the ask can sit on two different exchanges at once. And a price change is not required for an update: a size change at the same best price prints a new record too. Traders call the two best prices the touch; the gap between them is the bid-ask spread.
How to read an NBBO quote: prices, sizes and pace
Here are ten consecutive NBBO records for Coca-Cola (KO) from 1:30 p.m. Eastern on a recent full session, the national quote as it actually arrives.
The exact SQL behind every number
WITH (
SELECT max(session_date)
FROM (
SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS session_date,
countIf((toHour(toTimeZone(window_start, 'America/New_York')) * 60 + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959) AS regular_bars
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND window_start >= toDateTime(today() - 10)
AND window_start < toDateTime(today() - 3)
GROUP BY session_date
HAVING regular_bars = 390
)
) AS last_session
SELECT substring(toString(toTimeZone(sip_timestamp, 'America/New_York')), 12, 12) AS et_time,
round((toUnixTimestamp64Nano(sip_timestamp) - min(toUnixTimestamp64Nano(sip_timestamp)) OVER ()) / 1e6, 2) AS elapsed_ms,
bid_size AS bid_shares,
toFloat64(bid_price) AS best_bid,
toFloat64(ask_price) AS best_ask,
ask_size AS ask_shares,
round((toFloat64(ask_price) - toFloat64(bid_price)) * 100, 1) AS spread_cents
FROM global_markets.cache_stocks_quotes
WHERE ticker = 'KO'
AND sip_timestamp >= toDateTime(last_session, 'America/New_York') + INTERVAL 13 HOUR + INTERVAL 30 MINUTE
AND sip_timestamp < toDateTime(last_session, 'America/New_York') + INTERVAL 14 HOUR
AND bid_price > 0
AND ask_price > bid_price
ORDER BY sip_timestamp, sequence_number
LIMIT 10Read the first row as two standing offers, stamped 13:30:00.057 ET. Buyers' best: $88.74 for 100 shares. Sellers' best: $88.75 for 300 shares. The gap between them measured 1¢.
The share counts are the size at the touch, displayed liquidity at the two best prices. An order larger than that size exhausts it and fills the rest at the next prices in the book. Displayed size is a floor, not a full inventory: more interest often rests un-displayed at or between the two prices, including in dark pools.
Now read down the rows. All 10 records landed within 0.83 milliseconds of the first. There is no schedule, the SIP publishes whenever a winner changes, and any of four events prints a row: a better bid, a better ask, a change in the shares at either best price, or one exchange replacing another at the top. For a session-length view, see the microstructure deep-dive.
Does the NBBO include odd lots?
Mostly, no. The national quote is built from round-lot interest only. An order for fewer shares than a round lot is an odd lot: it can rest on an exchange book at a better price than the national best bid, trade there, and never appear in the NBBO.
The round lot is no longer a flat 100 shares. Under the SEC's amended Reg NMS quoting rules it is tiered by share price: 100 shares up to $250, 40 from $250.01 to $1,000, 10 from $1,000.01 to $10,000, one above that. The tiers are visible in the tape, the panel takes the smallest bid size that ever appears in the consolidated quote for six names across the bands.
The exact SQL behind every number
WITH (
SELECT max(session_date)
FROM (
SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS session_date,
countIf((toHour(toTimeZone(window_start, 'America/New_York')) * 60 + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959) AS regular_bars
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND window_start >= toDateTime(today() - 10)
AND window_start < toDateTime(today() - 3)
GROUP BY session_date
HAVING regular_bars = 390
)
) AS last_session
SELECT ticker,
round(quantileDeterministic(0.5)(toFloat64(bid_price), toUInt64(sequence_number)), 2) AS share_price,
min(bid_size) AS smallest_quoted_size,
round(quantileDeterministic(0.5)(toFloat64(bid_size), toUInt64(sequence_number)), 0) AS median_bid_size
FROM global_markets.cache_stocks_quotes
WHERE ticker IN ('F', 'KO', 'NVDA', 'AAPL', 'SPY', 'AZO')
AND sip_timestamp >= toDateTime(last_session, 'America/New_York') + INTERVAL 10 HOUR
AND sip_timestamp < toDateTime(last_session, 'America/New_York') + INTERVAL 11 HOUR
AND bid_price > 0
AND bid_size > 0
GROUP BY ticker
ORDER BY share_priceFord, quoting around $13.97, never shows a bid smaller than 100 shares in that hour; nor does Coca-Cola at $88.5. Apple, at $309.76, drops to 40; AutoZone, at $3090.02 a share, goes down to 10, with a median displayed bid of 20. The floor tracks the price tier. Better-priced odd-lot interest can sit inside the published spread undisplayed, so the true best available price is sometimes better than the NBBO implies.
Why your app's quote isn't quite the live NBBO
The quote you read on a phone has travelled: exchange matching engine → SIP → data vendor → broker → screen. Every record carries two clocks, the exchange's own stamp and the SIP's stamp at publication, and the gap between them is the consolidation step.
The exact SQL behind every number
WITH (
SELECT max(session_date)
FROM (
SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS session_date,
countIf((toHour(toTimeZone(window_start, 'America/New_York')) * 60 + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959) AS regular_bars
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND window_start >= toDateTime(today() - 10)
AND window_start < toDateTime(today() - 3)
GROUP BY session_date
HAVING regular_bars = 390
)
) AS last_session
SELECT ticker,
round(quantileDeterministic(0.5)((toUnixTimestamp64Nano(sip_timestamp) - toUnixTimestamp64Nano(participant_timestamp)) / 1000.0, toUInt64(sequence_number)), 1) AS median_micros,
round(quantileDeterministic(0.99)((toUnixTimestamp64Nano(sip_timestamp) - toUnixTimestamp64Nano(participant_timestamp)) / 1000.0, toUInt64(sequence_number)), 1) AS p99_micros
FROM global_markets.cache_stocks_quotes
WHERE ticker IN ('SPY', 'NVDA', 'AAPL', 'KO')
AND sip_timestamp >= toDateTime(last_session, 'America/New_York')
AND sip_timestamp < toDateTime(last_session + 1, 'America/New_York')
AND participant_timestamp > toDateTime64('2020-01-01 00:00:00', 9)
GROUP BY ticker
ORDER BY indexOf(['SPY', 'NVDA', 'AAPL', 'KO'], ticker)The median gap on SPY measured 241.1 microseconds, millionths of a second, and Apple's 192.6. The tail is fatter than the middle: Nvidia's 99th percentile came in at 580.3 microseconds. The SIP is not slow in any human sense. Two other things are.
First, your data may be delayed on purpose. Free quote feeds in retail apps and charting sites are commonly delayed 15 minutes under exchange licensing terms; real-time streaming is a paid entitlement.
Second, professional firms do not wait for the SIP. Each exchange also sells a direct feed from its own matching engine. A colocated firm reads it and computes its own best bid and offer before the SIP's version of that event arrives, an edge of microseconds, decisive for a market maker quoting both sides all day.
How often does the NBBO update? Liquid vs thin stocks
Every listed stock has an NBBO, and its update rate is a liquidity gauge. The table averages recent full sessions for four heavily traded names and two thin small caps, Nathan's Famous (NATH) and Seneca Foods (SENEA).
The exact SQL behind every number
SELECT ticker,
uniqExact(toDate(toTimeZone(sip_timestamp, 'America/New_York'))) AS sessions,
round(count() / uniqExact(toDate(toTimeZone(sip_timestamp, 'America/New_York')))) AS avg_updates_per_session,
round(count() / uniqExact(toDate(toTimeZone(sip_timestamp, 'America/New_York'))) / 1e6, 3) AS avg_updates_per_session_m,
round((count() / uniqExact(toDate(toTimeZone(sip_timestamp, 'America/New_York')))) / min(count() / uniqExact(toDate(toTimeZone(sip_timestamp, 'America/New_York')))) OVER (), 0) AS multiple_of_quietest
FROM global_markets.cache_stocks_quotes
WHERE ticker IN ('SPY', 'NVDA', 'AAPL', 'KO', 'SENEA', 'NATH')
AND sip_timestamp >= toDateTime(today() - 10)
AND sip_timestamp < toDateTime(today() - 3)
AND toDate(toTimeZone(sip_timestamp, 'America/New_York')) IN (
SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS session_date
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND window_start >= toDateTime(today() - 10)
AND window_start < toDateTime(today() - 3)
GROUP BY session_date
HAVING countIf((toHour(toTimeZone(window_start, 'America/New_York')) * 60 + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959) = 390
)
GROUP BY ticker
ORDER BY indexOf(['SPY', 'NVDA', 'AAPL', 'KO', 'SENEA', 'NATH'], ticker)SPY, the S&P 500 ETF, averaged 2.057 million NBBO updates per session, 866 times the quietest name on the table. Nvidia averaged 1.534 million, Apple 0.797 million. Then the cliff: Seneca Foods printed 5083 updates in an average session, Nathan's Famous 2377. No person reads a screen at SPY's rate: the NBBO is a feed built for software.
When the NBBO updates fastest: the open vs. midday
The update rate also keeps a daily rhythm. The chart averages Apple's NBBO updates per minute in half-hour clock buckets (Eastern Time); the last column reads each bucket as a percentage of the 9:30 one.
The exact SQL behind every number
SELECT et_time,
avg_updates_per_minute,
round(100 * avg_updates_per_minute / maxIf(avg_updates_per_minute, et_time = '09:30') OVER (), 1) AS pct_of_opening_bucket
FROM (
SELECT formatDateTime(toStartOfInterval(toTimeZone(sip_timestamp, 'America/New_York'), INTERVAL 30 MINUTE), '%H:%i') AS et_time,
round(count() / (uniqExact(toDate(toTimeZone(sip_timestamp, 'America/New_York'))) * 30)) AS avg_updates_per_minute
FROM global_markets.cache_stocks_quotes
WHERE ticker = 'AAPL'
AND sip_timestamp >= toDateTime(today() - 10)
AND sip_timestamp < toDateTime(today() - 3)
AND toDate(toTimeZone(sip_timestamp, 'America/New_York')) IN (
SELECT toDate(toTimeZone(window_start, 'America/New_York')) AS session_date
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND window_start >= toDateTime(today() - 10)
AND window_start < toDateTime(today() - 3)
GROUP BY session_date
HAVING countIf((toHour(toTimeZone(window_start, 'America/New_York')) * 60 + toMinute(toTimeZone(window_start, 'America/New_York'))) BETWEEN 570 AND 959) = 390
)
GROUP BY et_time
HAVING et_time >= '04:00' AND et_time < '20:00'
)
ORDER BY et_timeIn the half hour after the opening auction, Apple's national quote updated 3901 times a minute on average, and the 10:00 bucket held that pace at 79.7% of it. The tape then thins through the middle of the day: the 2:30 bucket ran 1440 updates a minute, 36.9% of the opening pace. The 3:30 run-in to the close recovers only part of the way back, 3330 a minute, 85.4% of the open, so the heaviest quoting sat in the first hour, not the last. Outside regular hours the tape is near-silent, down to 18 updates a minute after the 4:00 p.m. bell.
What happens to the NBBO during a trading halt?
When a stock moves too far too fast, the limit-up/limit-down rules pause it for five minutes. The quote neither vanishes nor stays live: it stops being refreshed, and the last record before the pause is the frame left on your screen. GameStop (GME) tripped such a pause just after 11:00 a.m. Eastern on May 14, 2024; the panel tracks its quote updates and trades, minute by minute, across it.
The exact SQL behind every number
WITH spine AS (
SELECT toStartOfMinute(toTimeZone(window_start, 'America/New_York')) AS et_min
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'SPY'
AND window_start >= toDateTime('2024-05-14 10:57:00', 'America/New_York')
AND window_start < toDateTime('2024-05-14 11:12:00', 'America/New_York')
),
gme_trades AS (
SELECT toStartOfMinute(toTimeZone(window_start, 'America/New_York')) AS et_min,
sum(transactions) AS trades
FROM global_markets.delayed_stocks_minute_aggs
WHERE ticker = 'GME'
AND window_start >= toDateTime('2024-05-14 10:57:00', 'America/New_York')
AND window_start < toDateTime('2024-05-14 11:12:00', 'America/New_York')
GROUP BY et_min
),
gme_quotes AS (
SELECT toStartOfMinute(toTimeZone(sip_timestamp, 'America/New_York')) AS et_min,
count() AS nbbo_updates
FROM global_markets.cache_stocks_quotes
WHERE ticker = 'GME'
AND sip_timestamp >= toDateTime('2024-05-14 10:57:00', 'America/New_York')
AND sip_timestamp < toDateTime('2024-05-14 11:12:00', 'America/New_York')
GROUP BY et_min
)
SELECT formatDateTime(spine.et_min, '%H:%i') AS et_time,
ifNull(gme_quotes.nbbo_updates, 0) AS nbbo_updates,
toUInt64(ifNull(gme_trades.trades, 0)) AS trades
FROM spine
LEFT JOIN gme_quotes ON gme_quotes.et_min = spine.et_min
LEFT JOIN gme_trades ON gme_trades.et_min = spine.et_min
ORDER BY spine.et_minIn the minute before the pause, 11:00, GameStop's consolidated quote updated 4511 times against 12419 trades. Then both columns fall off a shelf: 0 quote records at 11:02 and 0 at 11:03, with 0 trades to match, a quote that has stopped moving, in a stock that has stopped trading. Quoting returns at 11:06, the reopening minute, with 4155 updates and 14007 trades, the heaviest trade minute in the table. A halted stock's last NBBO is a fossil, not a tradable price; the January 2021 GameStop session shows what a chain of such pauses does to a tape.
Why your broker must fill you at the NBBO or better
The NBBO is a regulatory benchmark. Regulation NMS, the SEC's market-structure rulebook, adopted in 2005, carries an order-protection rule, Rule 611: a venue may not execute at a price worse than a protected quote displayed on another exchange. Brokers also owe customers best execution, measured against the NBBO: a market buy should fill at the national best ask or lower, a market sell at the national best bid or higher.
Most retail readers miss this: your market order usually never reaches an exchange. Most retail marketable flow is routed to wholesalers, off-exchange market makers who pay for the right to fill it, and executed internally, printed to an off-exchange reporting facility. The NBBO still rules them: the wholesaler must match or beat the national quote at the moment of execution, and competes for the flow by beating it, fills a fraction of a cent inside the spread, known as price improvement. The NBBO is the reference price, not the venue; the order type you choose decides whether you cross the spread at all.
FAQ
Does the NBBO include odd lots?
No. The national quote is assembled from round-lot quotes only, and Reg NMS tiers the round lot by price, 100 shares below $250, down to one share above $10,000. The smallest bid the tape showed for Ford above was 100 shares, against 10 for AutoZone, whose shares cost far more. Better-priced odd lots rest inside the published spread undisplayed.
Why is the quote in my brokerage app different from the live NBBO?
Two reasons, neither of them the SIP. Free retail feeds are commonly delayed 15 minutes under exchange licensing rules unless you pay for real-time. And consolidated data is a published snapshot: the consolidation step measured 241.1 microseconds at the median on SPY, while firms on exchange direct feeds see the event sooner.
What happens to the NBBO when a stock is halted?
It freezes. During GameStop's five-minute limit-up/limit-down pause above, the tape carried 0 quote updates at 11:03 and 0 trades. The last quote stays on screen but is not tradable; a reopening auction sets the next one.
Does the NBBO exist after hours?
Yes. Consolidated quotes stream from 4:00 a.m. to 8:00 p.m. ET, so a national quote exists through premarket and after-hours trading, at a fraction of the daytime pace: 52 Apple updates a minute in the last premarket bucket against 3901 after the open.
Data notes: windows and filters
Rolling windows end three days before generation (a short ingest lag sits at the feed's front edge) and count only full sessions, days on which SPY prints all 390 regular-hours minute bars. The Coca-Cola panel excludes one-sided and crossed records; the counting panels take every record as-is (locked and crossed markets covers the excluded ones). The GameStop panel is pinned to a fixed date, with SPY's minute bars as a clock spine, so paused minutes show as zeros.
Every panel ships with the SQL that produced it, open one, swap in your own ticker, and count its NBBO updates on the Strasmore terminal.