Wetín Be NBBO? National Best Bid and Offer
NBBO na the best bid and offer across US exchanges, wey your broker must match or beat. See how e updates fast with real tick data and why e matter.
NBBO, wey mean national best bid and offer, na the combined 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, and na the 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.
Wetín be NBBO? Plain-English meaning
US stock no dey trade for one place only. NYSE, Nasdaq, and more than a dozen other exchanges each dey run order book for the same ticker, and each get im own best bid and ask. NBBO dey join all those top prices 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, market utilities wey the exchanges dey operate together, na dem dey do the joining.
Two details matter from the start. Bid and ask fit dey for two different exchanges at the same time. And update no require price change: 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
Here na ten NBBO records wey follow one another for Coca-Cola (KO), from 1:30 p.m. Eastern during one recent full session. Na so the national quote dey arrive for real time.
| ET time | time wey don pass (ms) | bid shares | best bid | best ask | ask shares | spread (cents) |
|---|---|---|---|---|---|---|
| 13:30:00.141 | 0 | 200 | 88.11 | 88.12 | 300 | 1 |
| 13:30:00.141 | 0.26 | 200 | 88.11 | 88.12 | 700 | 1 |
| 13:30:00.257 | 115.81 | 200 | 88.11 | 88.12 | 600 | 1 |
| 13:30:01.017 | 876.55 | 200 | 88.11 | 88.12 | 700 | 1 |
| 13:30:01.307 | 1166.37 | 200 | 88.11 | 88.12 | 600 | 1 |
| 13:30:01.491 | 1349.95 | 200 | 88.11 | 88.12 | 700 | 1 |
| 13:30:01.575 | 1433.69 | 200 | 88.11 | 88.12 | 600 | 1 |
| 13:30:01.729 | 1588.01 | 100 | 88.11 | 88.12 | 600 | 1 |
| 13:30:01.729 | 1588.16 | 100 | 88.11 | 88.12 | 700 | 1 |
| 13:30:01.729 | 1588.16 | 100 | 88.11 | 88.12 | 800 | 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.141 ET. Buyers' best offer na $88.11 for 200 shares. Sellers' best offer na $88.12 for 300 shares. The gap between dem measure 1¢.
The share counts na size at the touch — the liquidity wey dey show for the two best prices. If order pass that size, e go finish the available shares, then fill the balance at the next prices for the book. Displayed size na only the minimum wey you fit see, no be the full inventory. More interest fit dey hidden at or between the two prices, including for dark pools.
Now read down the rows. All 10 records enter within 1588.16 milliseconds of the first one. No fixed schedule dey. The SIP publishes whenever a winner changes. Any of four events fit create one row: better bid, better ask, change for the shares at either best price, or one exchange replacing another at the top. For a view wey cover the whole session, see the microstructure deep-dive.
NBBO dey include odd lots?
Mostly, no. National quote na only round-lot interest dey build am. Order wey get fewer shares than round lot na odd lot: e fit rest for exchange book at price wey better pass national best bid, trade for there, and never show for NBBO.
Round lot no be fixed 100 shares again. Under SEC amended Reg NMS quoting rules, e dey depend on 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. These tiers dey visible for tape. Panel dey show the smallest bid size wey ever appear for consolidated quote across six names for the different bands.
| ticker | share price | smallest quoted size | median bid size |
|---|---|---|---|
| F | 13.96 | 100 | 7300 |
| KO | 89.05 | 100 | 200 |
| NVDA | 217.87 | 100 | 200 |
| AAPL | 324.53 | 40 | 80 |
| SPY | 763.53 | 40 | 240 |
| AZO | 2950.37 | 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 $13.96, no show bid wey small pass 100 shares during that hour; Coca-Cola at $89.05 no do am too. Apple, at $324.53, drop reach 40; AutoZone, at $2950.37 per share, go down reach 10, with median displayed bid of 10. Na price tier dey determine the floor. Odd-lot interest at 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 dey fully match 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 timestamp and the SIP timestamp when e publish am. The gap between both na the consolidation step.
| ticker | median micros | p99 micros |
|---|---|---|
| SPY | 347.4 | 480.4 |
| NVDA | 196.7 | 414.1 |
| AAPL | 178.1 | 433.7 |
| KO | 392.6 | 551.4 |
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 measure 347.4 microseconds, wey be millionths of one second, while Apple own na 178.1. The tail gap dey much wider than the middle: Nvidia 99th percentile reach 414.1 microseconds. SIP no slow for any human sense. Na two other things dey cause delay.
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 still dey sell direct feed from its own matching engine. Colocated firm fit read am and calculate its own best bid and offer before SIP version of that event arrive. Na microseconds edge, and e fit decide result for a market maker wey dey quote both sides throughout the day.
NBBO dey update how often? Liquid stocks vs thin stocks
Every listed stock get NBBO, and how often e dey update na one measure of liquidity. The table take average recent complete sessions for 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 | 2697406 | 2.697 | 3641 |
| NVDA | 5 | 1939655 | 1.94 | 2618 |
| AAPL | 5 | 745508 | 0.746 | 1006 |
| KO | 5 | 312847 | 0.313 | 422 |
| SENEA | 5 | 3782 | 0.004 | 5 |
| NATH | 5 | 741 | 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.697 million NBBO updates for each session, 3641 times the quietest name for the table. Nvidia average 1.94 million, while Apple average 0.746 million. Then the sharp drop: Seneca Foods record 3782 updates for an average session, while Nathan's Famous record 741. Nobody fit read screen at SPY rate: NBBO na feed wey software dey use.
When NBBO dey update fastest: market open versus midday
Update rate sef dey follow daily pattern. The chart dey show Apple NBBO updates per minute on average, using half-hour time buckets for Eastern Time. The last column dey show each bucket as percentage of the 9:30 one.
| ET time | average updates per minute | % of opening bucket |
|---|---|---|
| 04:00 | 20 | 0.6 |
| 04:30 | 12 | 0.3 |
| 05:00 | 14 | 0.4 |
| 05:30 | 16 | 0.5 |
| 06:00 | 11 | 0.3 |
| 06:30 | 10 | 0.3 |
| 07:00 | 22 | 0.6 |
| 07:30 | 18 | 0.5 |
| 08:00 | 24 | 0.7 |
| 08:30 | 24 | 0.7 |
| 09:00 | 43 | 1.2 |
| 09:30 | 3552 | 100 |
| 10:00 | 2685 | 75.6 |
| 10:30 | 2155 | 60.7 |
| 11:00 | 1983 | 55.8 |
| 11:30 | 1773 | 49.9 |
| 12:00 | 1674 | 47.1 |
| 12:30 | 1586 | 44.7 |
| 13:00 | 1182 | 33.3 |
| 13:30 | 1043 | 29.4 |
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 half hour after the opening auction, Apple national quote update 3552 times per minute on average. The 10:00 bucket maintain that pace at 75.6% of am. Tape come thin as day reach middle: the 2:30 bucket record 1280 updates per minute, or 36% of the opening pace. The 3:30 run-in to close recover only part of the difference, with 3130 updates per minute, or 88.1% of the open. So, the heaviest quoting happen for the first hour, no be the last one. Outside regular hours, tape nearly quiet. E reach just 20 updates per minute after the 4:00 p.m. bell.
What happens to NBBO when trading halt happen?
When stock price move too far too fast, limit-up/limit-down rules go pause trading for five minutes. The quote no disappear, and e no remain live either. E stop getting fresh updates, while the last record before the pause na the frame wey remain for your screen. GameStop (GME) trigger one of these pauses shortly after 11:00 a.m. Eastern on May 14, 2024. The panel follow the quote updates and trades minute by minute through 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 don stop moving, for stock wey don stop trading. Quoting return for 11:06, the reopening minute, with 4155 updates and 14007 trades. Na the minute wey get the heaviest trading for the table. Last NBBO for halted stock na fossil, no be price wey person fit trade. the January 2021 GameStop session show wetin a chain of pauses like this fit do to a tape.
Why your broker must fill you at the NBBO or better
NBBO na regulatory benchmark. Regulation NMS, wey be SEC rulebook for market structure and dem adopt for 2005, get order-protection rule, Rule 611: venue no fit execute order 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, while market sell suppose fill for national best bid or higher.
Most retail readers no dey notice this: your market order usually no dey reach exchange at all. Most retail marketable flow dey go to wholesalers, na off-exchange market makers wey dey pay for the right to fill am, then dem execute am internally and print am to off-exchange reporting facility. NBBO still control dem: wholesaler must match or beat the national quote for the exact time wey execution happen. E fit compete for the flow by beating that quote and fill am a fraction of one cent inside the spread. Dem dey call this price improvement. NBBO na reference price, no be venue; the order type wey you choose decide whether you go cross the spread at all.
FAQ
NBBO include odd lots?
No. National quote only dey use round-lot quotes. Reg NMS dey set round lot based on price: 100 shares when price dey below $250, and e go reduce reach one share when price pass $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. Odd lots with better prices fit dey inside the published spread without anybody displaying dem.
Why quote for my brokerage app different from live NBBO?
Two reasons, and SIP no be any of dem. Free retail feeds commonly dey 15 minutes late under exchange licensing rules, unless you pay for real-time data. Consolidated data na published snapshot too. For SPY, consolidation step measure 347.4 microseconds at the median, while firms wey use exchange direct feeds dey 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 go set the next quote.
NBBO still dey 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 much lower than daytime. Apple get 43 updates per minute for the last premarket bucket, compared with 3552 after market open.
Data notes: windows and filters
Rolling windows dey end three days before generation, because short ingest lag dey for the front edge of the feed. Dem count only full sessions, meaning days wey SPY print all 390 regular-hours minute bars. Coca-Cola panel no include one-sided and crossed records. Counting panels dey take every record as e be (locked and crossed markets cover the ones wey dem exclude). GameStop panel dey tied to one fixed date. SPY minute bars serve as clock spine, so paused minutes show as zeros.
Every panel come with the SQL wey produce am. Open one, replace the ticker with your own, and count its NBBO updates on the Strasmore terminal.