Edges
Market Data for Algo Traders: IEX, NBBO, and SIP
Oyamori
Market Data for Algo Traders: IEX, NBBO, and SIP
Before you wire price data into your bot, you need to understand what that data actually represents — and what it silently leaves out. Most free API feeds give you a structurally incomplete view of the market. For swing trading that is acceptable. For any bot that times entries at the second or sub-second level, it is not.
The US Market is 16 Exchanges Running Simultaneously
The US stock market is not one venue. It is 16+ separate exchanges operating in parallel, each with its own order book and its own trade feed.
| Group | Exchanges |
|---|---|
| NYSE | NYSE, ARCA, American |
| NASDAQ | NASDAQ, PHLX, BX |
| Cboe | BZX, BYX, EDGX, EDGA |
| Others | IEX, MEMX, MIAX Pearl, LTSE |
When your bot places a market order for AAPL, the broker routes it to whichever exchange has the best available price at that moment. The execution happens on that specific exchange — not on all of them.
What Free API Feeds Actually Give You
Most algo traders start with a free feed: IEX Cloud, Alpaca's market data, or a free Polygon tier. Nearly all of them source from IEX or a similarly limited exchange feed.
IEX is one exchange out of 16. Its feed shows only the trades that executed on IEX specifically.
During active trading, a single second on AAPL looks like this:
09:31:00 AAPL $185.20 300 shares NASDAQ ← not in your feed
09:31:00 AAPL $185.19 100 shares NYSE ← not in your feed
09:31:01 AAPL $185.21 50 shares IEX ← you see this
09:31:01 AAPL $185.18 500 shares BATS ← not in your feed
09:31:02 AAPL $185.22 200 shares IEX ← you see this
Your feed returned 2 trades. The real market had 6. IEX typically captures 5–15% of total volume on large-cap stocks. Your bot is making decisions on a 15% sample and treating it as the complete picture.
NBBO: The Price Your Bot Should Be Using
NBBO stands for National Best Bid and Offer. It is the best available buy price and best available sell price across all 16 exchanges combined, updated continuously.
NASDAQ asking $185.25
NYSE asking $185.24
IEX asking $185.23
NBBO best ask = $185.23. That is the actual market price — the lowest price at which AAPL can be bought anywhere, right now.
The gap: if NASDAQ drops to $185.10 but that trade executes on NASDAQ (not IEX), your IEX feed still shows $185.23. Your bot thinks current price is $185.23. The real market moved to $185.10 thirteen cents ago.
The execution consequence: your bot's entry logic is comparing its signal threshold against a stale price. The fill it receives will be at the real NBBO — not at the price the bot used to make the decision.
Why This Breaks Scalp Bots Specifically
Scalp logic targets 5–15 cent moves. Entry timing must be accurate to the second.
Bot signal fires: sees $185.23 → entry condition met
Real NBBO: $185.10 → already moved $0.13
Bot places order: fills at $185.10
Bot's target: $185.33 (+10 cents from signal price)
Real target needed: $185.20 (+10 cents from fill price)
The bot is not wrong in logic. It is working with wrong data. The edge it was designed to capture does not exist in the feed it is subscribed to.
For strategies with holding periods of minutes or longer, the lag matters less — prices converge and the 13-cent miss averages out. For sub-minute scalping, it is a structural problem that cannot be tuned away.
SIP: The Feed That Covers All Exchanges
Securities Information Processor (SIP) is the regulatory aggregator. It collects every trade and every quote from all 16 exchanges and publishes a single consolidated stream — the official market tape.
SIP provides:
- Every trade from every venue, in time sequence
- The NBBO updated in real-time
- The legal record of US equity prices
This is what Bloomberg terminals, institutional desks, and professional quant systems consume.
Choosing the Right Feed for Your Bot
| Feed | Coverage | Latency | Cost | Best For |
|---|---|---|---|---|
| IEX free / Alpaca free | IEX only (~5–15% volume) | Low | Free | Paper trading, learning, swing signals |
| Polygon Starter | SIP-consolidated | ~1–5ms | $29/mo | Live swing / position bots |
| Polygon Advanced | SIP + full order book | Sub-ms | $79–199/mo | Day trade bots, momentum scalpers |
| IBKR data feed | Aggregated (near-SIP) | Low | Included with account | Any bot running through IBKR |
| Direct SIP (CTA/UTP) | All exchanges, official tape | Lowest | $1,000+/mo | Institutional / HFT infrastructure |
Practical rule:
- Paper trading or swing bots with multi-day holds → free IEX-based feed is fine
- Any bot entering and exiting within the same session → Polygon Starter minimum
- Scalp bot with sub-minute targets → Polygon Advanced or IBKR feed, validate NBBO on every signal
- Sub-second execution → direct SIP or co-located infrastructure; free feeds are not in the conversation
Alpaca and NBBO: What the Docs Actually Say
Alpaca is a common starting point for algo traders. Understanding exactly how it handles order pricing matters.
Key finding: Alpaca matches orders against the NBBO, not the last trade price.
This is documented across three order types:
Market orders Alpaca fills market orders at the best available current market price — the NBBO at the time of routing. The expected fill price is the NBBO ask at order submission. The docs note: "the fill price may vary from the expected price due to market liquidity and price fluctuations during order routing."
Stop orders From Alpaca's Order Handling Standards: "Stop orders and trailing stops are elected based on the consolidated print. A sell stop order triggers only when a trade occurs at or below the stop price, provided the trade is within the National Best Bid and Offer (NBBO)."
Stop triggers are validated against the NBBO — not just any print on any exchange.
Fractional trading "The expected fill price is the NBBO quote at the time of order submission."
What this means for your bot architecture:
| Layer | Alpaca Behavior | Your Responsibility |
|---|---|---|
| Order execution | Uses NBBO ask — correct | Nothing to change |
| Stop election | Validated against NBBO — correct | Nothing to change |
| Data feed (free) | IEX only — incomplete | Upgrade to Polygon or IBKR for signal data |
| Fill slippage | Can still vary from NBBO at submission due to routing delay | Size conservatively; use limit orders for precision entries |
The practical implication: if you are building a scalp bot on Alpaca, upgrade the data feed before optimizing the strategy. Execution quality is already handled. Signal quality is where the work needs to happen.
What to Check in Your Bot's Data Pipeline
Before going live with any strategy, verify these three things:
1. What exchange does your feed source from? Read the API documentation. "Real-time" does not mean consolidated. Check whether it specifies IEX, SIP, or aggregated.
2. Does your entry logic use last trade price or NBBO ask? Last trade price is historical — it tells you where the last fill happened. NBBO ask is the price your market order will actually fill at. These diverge under fragmented conditions.
3. What is the actual latency between market event and your bot receiving data? Websocket feeds vary. IEX publishes its own latency stats. Polygon documents its SIP delay. Know the number before you design signal timing.
Getting the data layer right does not guarantee a profitable strategy. Getting it wrong guarantees that the edge you measured in backtesting does not exist in live trading.
A minimal Alpaca bot that checks these three things before placing any order:
import alpaca_trade_api as tradeapi
api = tradeapi.REST(API_KEY, SECRET_KEY, base_url='https://paper-api.alpaca.markets')
def safe_entry_check(symbol: str, signal_price: float) -> bool:
# 1. Get NBBO ask — not last trade
quote = api.get_latest_quote(symbol)
nbbo_ask = float(quote.ask_price)
# 2. Check data lag — reject if signal price diverges more than 0.15%
lag = abs(signal_price - nbbo_ask) / nbbo_ask
if lag > 0.0015:
print(f"Data lag too high: signal={signal_price} nbbo={nbbo_ask} lag={lag:.4%}")
return False
# 3. Confirm market is open
clock = api.get_clock()
if not clock.is_open:
return False
return True