NBBO na Wetin? National Best Bid and Offer
NBBO na the one consolidated stock quote wey broker must match or beat. Learn how e dey update fast and how real tick data fit show the quote.
NBBO, wey mean national best bid and offer, na consolidated quote for US stock. E show the highest price wey any buyer dey bid and the lowest price wey any seller dey ask, across all exchanges at the same time. Na the quote wey your app dey show, plus benchmark wey your broker must meet or beat when e dey execute your order. Every number wey follow come from real quote records, and query dey attached.
NBBO na wetin? Plain-English meaning
US stock no dey trade for one place only. NYSE, Nasdaq, plus more than a dozen other exchanges each dey run order book for the same ticker. Each one get its own best bid and ask. NBBO dey join the best prices from all of dem together: the highest bid anywhere and the lowest ask anywhere, side by side with the number of shares available for each one. Securities information processors, or SIPs, na market utilities wey the exchanges dey operate together, and na dem dey do this joining work.
Two details matter from the start. Bid and ask fit dey on two different exchanges at the same time. Price no need change before update fit happen: if the size change for the same best price, e still create new record. Traders dey call the two best prices the touch; the gap between dem na the bid-ask spread.
How to read an NBBO quote: prices, sizes and pace
Na ten consecutive NBBO records for Coca-Cola (KO) from 1:30 p.m. Eastern during one recent full session, showing the national quote as e dey arrive.
| ET time | time wey don pass (ms) | bid shares | best bid | best ask | ask shares | spread (cents) |
|---|---|---|---|---|---|---|
| 13:30:00.148 | 0 | 700 | 90.12 | 90.13 | 200 | 1 |
| 13:30:00.254 | 105.58 | 600 | 90.12 | 90.13 | 200 | 1 |
| 13:30:00.254 | 106.19 | 600 | 90.12 | 90.13 | 300 | 1 |
| 13:30:00.255 | 106.71 | 600 | 90.12 | 90.13 | 900 | 1 |
| 13:30:00.255 | 107.03 | 600 | 90.12 | 90.13 | 1100 | 1 |
| 13:30:00.255 | 107.28 | 800 | 90.12 | 90.13 | 1100 | 1 |
| 13:30:00.257 | 108.75 | 800 | 90.12 | 90.13 | 900 | 1 |
| 13:30:00.273 | 124.56 | 800 | 90.12 | 90.13 | 300 | 1 |
| 13:30:00.700 | 552.46 | 900 | 90.12 | 90.13 | 300 | 1 |
| 13:30:00.711 | 562.74 | 800 | 90.12 | 90.13 | 300 | 1 |
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.148 ET. Buyers' best na $90.12 for 700 shares. Sellers' best na $90.13 for 200 shares. The gap between dem measure 1¢.
The share counts na size at the touch — the displayed liquidity for the two best prices. Order wey pass that size go finish the available shares, then fill the remaining part at the next prices for the book. Displayed size na only a floor, no be the complete inventory. More interest fit dey hidden at or between the two prices, including for dark pools.
Now read down the rows. All 10 records land within 562.74 milliseconds of the first one. No fixed schedule dey. The SIP publish whenever the top quote change, and any of four events fit produce a row: better bid, better ask, change in the shares at either best price, or one exchange replacing another at the top. For view wey cover the whole session, see the microstructure deep-dive.
NBBO dey include odd lots?
Mostly, no. National quote na from round-lot interest only. Order wey get fewer shares than round lot na odd lot. E fit rest for exchange book for price wey better pass national best bid, trade for there, and never show for NBBO.
Round lot no longer be flat 100 shares. Under SEC amended Reg NMS quoting rules, e dey tier by share price: 100 shares up to $250, 40 from $250.01 to $1,000, 10 from $1,000.01 to $10,000, and one above that. The tiers dey visible for the tape. The panel dey take the smallest bid size wey ever show for consolidated quote across the bands for six names.
| ticker | share price | smallest quoted size | median bid size |
|---|---|---|---|
| F | 12 | 100 | 8000 |
| KO | 89.93 | 100 | 400 |
| NVDA | 225.82 | 100 | 200 |
| AAPL | 345.92 | 40 | 40 |
| SPY | 777.46 | 40 | 160 |
| AZO | 2895.02 | 10 | 10 |
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, wey dey quote around $12, no ever show bid wey small pass 100 shares for that hour. Coca-Cola at $89.93 too no show smaller bid. Apple, at $345.92, drop to 40. AutoZone, at $2895.02 per share, go down to 10, with median displayed bid of 10. The floor dey follow the price tier. Odd-lot interest for better price fit sit inside the published spread without showing. So, the true best available price sometimes fit better than wetin NBBO imply.
Why your app quote no be exactly live NBBO
The quote wey you read for phone don pass through different stages: exchange matching engine → SIP → data vendor → broker → screen. Every record get two timestamps: the exchange own stamp and SIP stamp when e publish am. The gap between both na the consolidation step.
| ticker | median micros | p99 micros |
|---|---|---|
| SPY | 228 | 492.4 |
| NVDA | 190.5 | 1722.8 |
| AAPL | 27.5 | 539.8 |
| KO | 433.2 | 636 |
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 for SPY na 228 microseconds, meaning millionths of one second, while Apple own na 27.5. The tail gap big pass the middle: Nvidia 99th percentile reach 1722.8 microseconds. SIP no slow for any human sense. Na two other things dey cause the difference.
First, your data fit delay on purpose. Free quote feeds for retail apps and charting sites commonly dey delay 15 minutes under exchange licensing terms. Real-time streaming na paid entitlement.
Second, professional firms no dey wait for SIP. Every exchange also dey sell direct feed from its own matching engine. A colocated firm reads am and calculates its own best bid and offer before SIP version of that event arrive. That microseconds edge fit decide result for a market maker wey dey quote both sides all day.
How often NBBO dey update? Liquid stocks vs thin stocks
Every listed stock get NBBO, and how often e dey update na one measure of liquidity. This table show average for recent full trading sessions of four heavily traded names and two thin small caps, Nathan's Famous (NATH) and Seneca Foods (SENEA).
| ticker | sessions | average updates per session | average updates per session m | multiple of quietest |
|---|---|---|---|---|
| SPY | 5 | 2265035 | 2.265 | 3045 |
| NVDA | 5 | 1358290 | 1.358 | 1826 |
| AAPL | 5 | 561372 | 0.561 | 755 |
| KO | 5 | 306567 | 0.307 | 412 |
| SENEA | 5 | 4032 | 0.004 | 5 |
| NATH | 5 | 744 | 0.001 | 1 |
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, average 2.265 million NBBO updates for each session, 3045 times the quietest name for this table. Nvidia average 1.358 million, while Apple average 0.561 million. Then the sharp drop: Seneca Foods print 4032 updates for one average session, while Nathan's Famous print 744. Nobody fit read screen at SPY rate: NBBO na feed wey software dey use.
When NBBO dey update fastest: market open vs midday
The update rate still dey follow daily pattern. The chart dey show average number of Apple NBBO updates per minute for 30-minute time buckets, based on Eastern Time. The last column show each bucket as percentage of the 9:30 bucket.
| ET time | average updates per minute | percentage of opening bucket |
|---|---|---|
| 04:00 | 36 | 1 |
| 04:30 | 13 | 0.4 |
| 05:00 | 16 | 0.5 |
| 05:30 | 13 | 0.4 |
| 06:00 | 9 | 0.3 |
| 06:30 | 16 | 0.5 |
| 07:00 | 37 | 1.1 |
| 07:30 | 32 | 0.9 |
| 08:00 | 44 | 1.3 |
| 08:30 | 39 | 1.1 |
| 09:00 | 58 | 1.7 |
| 09:30 | 3512 | 100 |
| 10:00 | 1651 | 47 |
| 10:30 | 1234 | 35.1 |
| 11:00 | 1209 | 34.4 |
| 11:30 | 1043 | 29.7 |
| 12:00 | 993 | 28.3 |
| 12:30 | 1060 | 30.2 |
| 13:00 | 1087 | 31 |
| 13:30 | 901 | 25.7 |
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_timeFor the 30 minutes after the opening auction, Apple national quote update 3512 times per minute on average. The 10:00 bucket move at 47% of that pace. After that, trading tape dey thin as day enter middle period. The 2:30 bucket record 1022 updates per minute, or 29.1% of the opening pace. The 3:30 run-in to close climb from that low point, with 2536 updates per minute, or 72.2% of the opening pace. So quoting activity dey highest for both ends of the session and lowest for middle period. Outside regular hours, the tape almost quiet completely, dropping to 15 updates per minute after the 4:00 p.m. bell.
Wetin dey happen to NBBO during trading halt?
When stock move too far too fast, limit-up/limit-down rules go pause am for five minutes. The quote no disappear and e no remain live either: dem stop to refresh am, and the last record before the pause na the frame wey remain for your screen. GameStop (GME) trigger one such pause shortly after 11:00 a.m. Eastern on May 14, 2024; the panel dey track how e quote update and trades happen, minute by minute, across the pause.
| ET time | NBBO updates | trades |
|---|---|---|
| 10:57 | 1812 | 5690 |
| 10:58 | 1275 | 4420 |
| 10:59 | 1491 | 4068 |
| 11:00 | 4511 | 12419 |
| 11:01 | 346 | 1331 |
| 11:02 | 0 | 0 |
| 11:03 | 0 | 0 |
| 11:04 | 1 | 0 |
| 11:05 | 0 | 0 |
| 11:06 | 4155 | 14007 |
| 11:07 | 4627 | 13199 |
| 11:08 | 2941 | 8985 |
| 11:09 | 1613 | 6487 |
| 11:10 | 1741 | 6222 |
| 11:11 | 1447 | 5691 |
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_minFor the minute before the pause, 11:00, GameStop consolidated quote update 4511 times against 12419 trades. Then both columns drop suddenly: 0 quote records at 11:02 and 0 at 11:03, with 0 trades to match. Na quote wey stop to move, for stock wey stop to trade. Quoting return for 11:06, the reopening minute, with 4155 updates and 14007 trades, the busiest trade minute for the table. Last NBBO of halted stock na fossil, e no be tradable price; the January 2021 GameStop session show wetin chain of such pauses fit do to tape.
Why broker suppose fill you for NBBO or better
NBBO na regulatory benchmark. Regulation NMS, wey be SEC market-structure rulebook and dem adopt am for 2005, get order-protection rule, Rule 611. Venue no fit execute trade for price wey worse pass protected quote wey another exchange dey display. Brokers still owe customers best execution, and dem dey measure am against NBBO. Market buy suppose fill for national best ask or lower. Market sell suppose fill for national best bid or higher.
Most retail readers no dey notice this: your market order normally no dey reach exchange. Most retail marketable flow dey route go wholesalers. Dem na off-exchange market makers wey dey pay for the right to fill the orders, then execute dem internally and print the trades to an off-exchange reporting facility. NBBO still control dem. Wholesaler must match or beat the national quote for the exact time wey e execute the trade. E dey compete for the flow by beating that quote and filling a fraction of a cent inside the spread. Dem dey call this price improvement. NBBO na the reference price, no be the venue. Na the order type wey you choose decide whether you go cross the spread at all.
FAQ
NBBO dey include odd lots?
No. National quote na only round-lot quotes dey use. Under Reg NMS, round lot size dey depend on price: 100 shares when price dey below $250, down to one share when price dey above $10,000. The smallest bid wey tape show for Ford above na 100 shares, compared with 10 for AutoZone, wey get much higher share price. Better-priced odd lots fit dey inside the published spread without showing publicly.
Why quote for my brokerage app different from live NBBO?
Two reasons, and SIP no be any of dem. Free retail feeds commonly dey delay 15 minutes under exchange licensing rules, unless you pay for real-time data. Consolidated data na published snapshot. The consolidation step measure 228 microseconds at the median for SPY, while firms wey use direct exchange feeds see the event earlier.
Wetin happen to NBBO when stock halt?
E freeze. During the five-minute limit-up/limit-down pause for GameStop above, tape carry 0 quote updates at 11:03 and 0 trades. The last quote remain for screen, but you no fit trade am. Reopening auction set the next quote.
NBBO dey exist after hours?
Yes. Consolidated quotes dey stream from 4:00 a.m. to 8:00 p.m. ET. So national quote dey available through premarket and after-hours trading, but activity dey much lower than daytime. Apple record 58 updates for one minute inside the last premarket bucket, compared with 3512 after market open.
Data notes: windows and filters
Rolling windows dey end three days before generation. Short ingest lag dey for the front edge of the feed. The count include only full sessions, meaning days wey SPY print all 390 regular-hours minute bars. Coca-Cola panel exclude one-sided and crossed records. The counting panels take every record as e be (locked and crossed markets explain the ones wey dem exclude). GameStop panel tie to one fixed date, with SPY minute bars serving as clock spine, so paused minutes show as zero.
Every panel come with the SQL wey produce am. Open one, replace ticker with your own, and count its NBBO updates for the Strasmore terminal.