Scalpel
FeaturesCockpitPricingGuidesBlogSign inDownload
Scalpelscalpeltrade.comA keyboard-first scalping cockpit for Interactive Brokers.
ProductFeaturesCockpitPricingDownloadFAQ
ResourcesGuidesTroubleshootingGlossaryBlogCompareChangelogAccountRefer a trader
CompanyAboutFor AI agentsContactTermsPrivacyRefundsRisk

Trading involves substantial risk of loss. Scalpel is software, not investment advice. LIVE mode places real orders in your Interactive Brokers account. No warranty. Simulated or replayed results do not guarantee future results. Scalpel is independent software and is not affiliated with, endorsed by or sponsored by Interactive Brokers LLC. IBKR and TWS are trademarks of their respective owners. Figures shown on this site are demo data, not live quotes. Scalpel is sold by QUITE RIGHT LIMITED, registered in England and Wales, company number 17053815. Copyright 2026.

    Blog/Building Scalpel

    TWS API market depth: reqMktDepth and SMART depth in practice

    By Scalpel Team. May 14, 2026, updated October 22, 2026. 10 min read.

    A Level 2 bid ladder with numbered rows, one row being inserted, one updated and one deleted, labeled with IBKR's operation codes 0, 1 and 2.

    In short

    TWS API market depth comes from reqMktDepth(reqId, contract, numRows, isSmartDepth, mktDepthOptions). Set isSmartDepth to True on a SMART-routed stock and IBKR merges every venue you subscribe to into one book, sent as updateMktDepthL2 messages that insert, update or delete a numbered row on the bid or ask. You rebuild the book yourself from those messages, and empty it when error 317 says IBKR reset it. A default account can stream only 3 symbols' depth at once, and you need a depth subscription such as Nasdaq TotalView for rows to arrive.

    Level 2 is the reason most traders want the API, and the part of the TWS API with the least forgiving rules. We stream it on every desk in Scalpel, so this is the version of IBKR's documentation we wish we had read first: each parameter, keeping a book correct from a stream of row edits, the limits, and how our montage is built from it. Everything about the API was checked against IBKR's reference and its 10.51.01 Python client on October 22, 2026. Everything about Scalpel comes from its source code. Scalpel is independent and not affiliated with or endorsed by Interactive Brokers.

    How TWS API market depth works: reqMktDepth

    IBKR's market depth reference defines one request and two callbacks. Python, Java and C++ call the request reqMktDepth; C# and Visual Basic call it reqMarketDepth.

    ParameterWhat it does
    reqIdYour number for this book. Every update carries it, and you cancel with it
    contractThe stock. For SMART depth its exchange must be SMART
    numRowsHow many rows to keep on each side
    isSmartDepthTrue: one book merged across venues. False: one venue's book
    mktDepthOptionsInternal use. Pass an empty list

    Each update arrives as updateMktDepthL2(reqId, position, marketMaker, operation, side, price, size, isSmartDepth). side is 1 for the bid and 0 for the ask. position is the row number, 0 at the inside. operation says what to do with that row: 0 insert, 1 update, 2 delete. With SMART depth, marketMaker is the exchange holding the order, such as NSDQ or ARCA; IBKR's montage shows venues, not firms (MMID). Cancel with cancelMktDepth(reqId, isSmartDepth).

    One thing IBKR says plainly and many first versions miss: depth "is sent without sampling or filtering". The ordinary Level 1 quote is the opposite. IBKR's streaming data page lists US stock quotes as snapshots every 250 ms. So the book can show a level being hit before your Level 1 last price moves, and your code must apply every message, because each one edits the row the previous one left.

    SMART depth vs direct depth in the IBKR API

    SMART depthDirect depth
    RequestisSmartDepth=True, contract on SMARTisSmartDepth=False, contract on one exchange
    What you getEvery venue you subscribe to, in one bookThat one exchange's book
    marketMakerThe exchange codeThe market maker's MPID
    Like in TWSBookTraderThe Market Depth window

    For a scalper, SMART depth is the useful one: it is the whole lit book in one stream, and it costs one depth slot for the symbol however many venues feed it. Which venues appear depends on your subscriptions. Nasdaq TotalView and NYSE ArcaBook are the usual pair for small caps; our guide to IBKR market data for day trading walks through subscribing, and Nasdaq TotalView covers what that feed adds.

    Two contradictions in IBKR's own material, so you do not lose an afternoon to them:

    • The reference page's "Receive Market Depth" summary pairs SMART depth with updateMktDepth and direct depth with updateMktDepthL2, yet the same page describes updateMktDepth as "used for direct routed requests only" and gives updateMktDepthL2 a marketMaker field that holds "the exchange holding the order if isSmartDepth is True". The safe answer is to implement both callbacks and route them through one function, which is what the code below and Scalpel both do.
    • The docstring inside IBKR's Python client still says depth "must be direct-routed to an exchange and not smart-routed". The isSmartDepth flag and the reference page say otherwise, and SMART depth works.

    To see what direct depth looks like before you code it, IBKR's lesson on the TWS Market Depth window is a nine-minute tour, including the per-exchange buttons that mirror direct requests:

    TWS Market Depth Trader (Level II). Interactive Brokers, 8:51. Watch on YouTube

    Building the book from updateMktDepthL2

    The book is a list per side, best first, and every message edits one numbered row. The rule that matters: an insert at a position pushes the rows below it down one, and a delete pulls them up one. Treat an insert as an overwrite and you lose a row; skip the shift on a delete and the rest of that side is off by one until the next reset.

    Four snapshots of a bid side as IBKR depth messages arrive: an insert at row 1 pushes three rows down, a delete at row 3 pulls the last row up, and an update at row 0 changes a size in place.
    Three depth messages applied to one bid side. The insert and the delete move every row below them, which is why each message must be applied in order to the book the previous one left.

    This Python script keeps both sides correct and prints the top three rows a second for ten seconds:

    import threading
    import time
    
    from ibapi.client import EClient
    from ibapi.contract import Contract
    from ibapi.wrapper import EWrapper
    
    REQ_ID, ROWS = 2001, 10
    
    
    class DepthApp(EWrapper, EClient):
        def __init__(self):
            EClient.__init__(self, self)
            self.ready = threading.Event()
            self.bids, self.asks = [], []  # rows of [venue, price, size], best first
    
        def nextValidId(self, orderId: int):
            self.ready.set()
    
        def updateMktDepthL2(self, reqId, position, marketMaker, operation, side, price, size, isSmartDepth):
            book = self.bids if side == 1 else self.asks  # side: 1 = bid, 0 = ask
            row = [marketMaker, price, size]
            if operation == 0:  # insert: the rows below move down one
                book.insert(min(position, len(book)), row)
            elif operation == 1 and position < len(book):  # update this row
                book[position] = row
            elif operation == 2 and position < len(book):  # delete: the rows below move up
                del book[position]
    
        def updateMktDepth(self, reqId, position, operation, side, price, size):
            self.updateMktDepthL2(reqId, position, "", operation, side, price, size, False)
    
        def error(self, reqId, errorTime, errorCode, errorString, advancedOrderRejectJson=""):
            if errorCode == 317:  # TWS reset its book: empty ours, the updates rebuild it
                self.bids.clear()
                self.asks.clear()
            else:
                print(f"[{errorCode}] req {reqId}: {errorString}")
    
    
    app = DepthApp()
    app.connect("127.0.0.1", 7497, 44)
    threading.Thread(target=app.run, daemon=True).start()
    if not app.ready.wait(5):
        raise SystemExit("No reply: is TWS or IB Gateway running with the API on this port?")
    
    stock = Contract()
    stock.symbol, stock.secType, stock.exchange, stock.currency = "ABCD", "STK", "SMART", "USD"
    
    app.reqMktDepth(REQ_ID, stock, ROWS, True, [])  # True = SMART depth
    for _ in range(10):
        time.sleep(1)
        bids, asks = list(app.bids), list(app.asks)
        print("bids", bids[:3], "asks", asks[:3])
    app.cancelMktDepth(REQ_ID, True)
    app.disconnect()
    

    ABCD is a placeholder that returns error 200, no security definition: put in a real symbol you have depth for.

    Three details matter, two of them from IBKR's error list:

    • 317, "Market depth data has been RESET". IBKR's reference calls handling it "an integral part of processing the incoming data": empty both sides, and the updates that follow rebuild the book. Ignore it and new rows land on top of stale ones.
    • 316, "Market depth data has been HALTED". The stream has stopped and you must subscribe again.
    • Bounds checks. An update or delete for a row you do not have is skipped: one bad index should cost you a row, not the reader thread.

    The callbacks run on the API thread, so copy the lists before you draw or compute from them, as the loop does.

    The same book with ib_async

    ib_async keeps the book for you on a ticker:

    from ib_async import IB, Stock
    
    ib = IB()
    ib.connect("127.0.0.1", 7497, clientId=45)
    stock = Stock("ABCD", "SMART", "USD")  # put a real symbol here
    if ib.qualifyContracts(stock)[0] is None:
        ib.disconnect()
        raise SystemExit("IBKR does not know that symbol.")
    
    ticker = ib.reqMktDepth(stock, numRows=10, isSmartDepth=True)
    for _ in range(10):
        ib.sleep(1)
        for level in ticker.domBids[:3]:
            print("bid", level.marketMaker, level.price, level.size)
        for level in ticker.domAsks[:3]:
            print("ask", level.marketMaker, level.price, level.size)
    ib.cancelMktDepth(stock, isSmartDepth=True)
    ib.disconnect()
    

    Note that isSmartDepth defaults to False in ib_async, so pass it. Also read how a library applies inserts before you trust it. ib_async 2.1.0 clears the book on error 317, but it stores rows by position and treats an insert as "insert or overwrite" rather than shifting the rows below. We shift, because IBKR describes 0 as inserting a new row. If you build on either approach, check your book against TWS's BookTrader on a busy stock for a few minutes.

    IBKR API Level 2 limits

    Depth is the scarcest data IBKR sells through the API. From IBKR's specialized market data lines table, checked on October 22, 2026:

    Market data linesLevel 2 books at once
    100 (the default)3
    101 to 4003
    401 to 5004
    601 to 7006
    1001 to 110010

    So three Quote Booster packs, which take you to 400 lines, still leave you with 3 books. Ask for a fourth symbol and IBKR's error 309 comes back: "Max number (3) of market depth requests has been reached". The same entry adds that API clients "may make multiple market depth requests for the same security", so the cap counts distinct symbols, not requests. IBKR applies the same limit to TWS's own depth windows (BookTrader, Market Depth and the Integrated Stock Window) as to API clients, and we treat the slots as first come, first served: close any depth window in TWS you are not reading before your code asks for its books.

    Two gaps to design around:

    • Odd lots are not in the depth feed. IBKR says so on the reference page, along with a warning that it cannot guarantee every quoted price is shown.
    • Not every venue offers SMART depth. reqMktDepthExchanges() returns each venue and whether it supports SMART depth or only direct depth. Run it once when a book looks thinner than you expect.
    Depth unavailable for ABCD: [309] Max number (3) of market depth requests has been reached
    The amber toast when IBKR refuses a fourth book, with IBKR's own reason after the symbol. Demo data.

    How Scalpel builds its montage from SMART depth

    Here is what our code does with all this, read from Scalpel's source:

    • One connection per desk. Each desk has its own API client id, 10 for the first desk and up from there, and asks for 15 rows a side of SMART depth on a SMART-routed US stock. Orders travel on a different connection, so a burst of depth never queues in front of a sell. Our pillar on what the TWS API can and cannot do for scalping explains the one-connection-per-job design.
    • One row per venue, not per price. The book keeps IBKR's rows in IBKR's order, best first, each with its venue. Inserts shift, deletes close the gap, and an index outside the book is ignored. Error 317 empties both sides.
    • Depth fails alone. If IBKR refuses the book (a fourth symbol on a default account, no subscription), only the depth stops: the quote, the tape and the chart keep streaming, and the desk shows Depth unavailable for the symbol with IBKR's reason, as in the toast above.
    • Background feeds never ask for depth. When session recording is on, Scalpel records quotes, prints and bars for the scanner's names on extra connections, but never their depth. With 3 slots handed out first come, first served, a background request could take a book from a desk you are trading.
    • Drawn at about 16 frames a second. Every message is applied to the book, but the screen gets a fresh copy at most every 60 ms. Redrawing per message wastes CPU the order path needs.
    • Grouping happens on screen, not in the book. The Tick stepper merges rows into wider price buckets, bids rounded down and asks up. The stored book stays one row per venue, so the iceberg detection still sees each venue's rows as IBKR sent them.
    • The same book prices SIM fills. In SIM, a marketable buy walks the ask rows for a volume-weighted fill, and the thin bid exit warning walks the bid rows to estimate what selling your position would cost.
    LEVEL 2
    -Tick+
    ABCDAbcd Therapeutics Inc+0.42  +7.95%5.70
    O 5.21H 5.84L 5.12VWAP 5.47VOL 12.4M
    BID VOL9,381
    BEST BID5.69
    SPREAD0.02 (0.35%)
    BEST ASK5.71
    ASK VOL7,345
    IMBALANCE+34%DELTA10s +8501m +4.2K5m +18K
    Hidden seller refilling at the offer
    LONG 400@ 5.50+$80.00
    MMIDSIZEPRICE
    NSDQ1,2005.69
    IEX6005.69
    EDGX4005.69
    MEMX2315.68
    ARCA9005.68
    BATS1,5005.67
    NYSE3005.67
    IEX8005.66
    ARCA2,0005.65
    NSDQ5005.65
    EDGX7005.64
    BYX2505.63
    PRICESIZEMMID
    5.71100DRCTEDGE
    5.71145IEX
    5.72700NSDQ
    5.72300MEMX
    5.732,000ARCA
    5.74450EDGX
    5.74600BATS
    5.75200NYSE
    5.76900IEX
    5.771,100NSDQ
    5.78350ARCA
    5.79500MEMX
    PRICESIZE
    5.712,500
    5.71400
    5.70300
    5.711,200
    5.69800
    5.71100
    5.713,600
    5.69200
    5.70500
    5.71900
    5.691,500
    5.71600
    5.71300
    5.68400
    5.711,000
    5.70200
    The Level 2 panel for ABCD, built from SMART depth: bids left, asks right, one row per venue, the tape on the right. Demo data.

    How to read the result, rather than build it, is in our guide to reading Level 2 and the pillar how to read Level 2. Our section video shows the montage, tape and chart working together:

    Reading the market | Scalpel guide 3 of 15. Scalpel Trade, 1:58. Watch on YouTube

    If you are writing your first script, start with our IBKR API Python starter for the connection basics, and see the TWS API scanner for finding which symbols deserve your three books. API access itself is switched on as in our enable TWS API guide.

    Trading carries a significant risk of loss. Nothing here is financial advice.

    Questions

    How do I get Level 2 data from the IBKR API?
    Call reqMktDepth with the contract, the number of rows per side, and isSmartDepth set to True on a SMART-routed stock. Rows arrive as updateMktDepthL2 messages that insert, update or delete a numbered row, and you need a depth subscription such as Nasdaq TotalView on the account.
    What is SMART depth in the IBKR API?
    A market depth request with isSmartDepth set to True. IBKR merges the books of every venue you subscribe to into one stream, labels each row with its exchange, and returns the same data as TWS's BookTrader.
    How many market depth subscriptions does IBKR allow through the API?
    Three distinct symbols at once on accounts with up to 400 market data lines, rising to 4 at 401 to 500 lines and 10 at 1,001 to 1,100. Several requests for the same symbol use one slot, and TWS's own depth windows share the same limit.
    What does IBKR error 317 mean?
    Market depth data has been reset. Empty both sides of your book and let the updates that follow rebuild it, or new rows will mix with stale ones.
    What does error 309 mean in the TWS API?
    You have reached the maximum number of market depth requests, 3 distinct symbols on a default account. Cancel a book you no longer need or close a depth window in TWS.
    Is IBKR market depth data sampled?
    No. IBKR sends depth without sampling or filtering, unlike the Level 1 quote, which is a snapshot every 250 ms for US stocks. Odd lots are not included in the depth feed.

    Related guides

    Guide 1.5Enable the API in TWS and IB GatewayTurn on Enable ActiveX and Socket Clients in TWS or IB Gateway, set the socket port, allow 127.0.0.1 and switch off read-only API so Scalpel can trade.Guide 3.1How to read Level 2 in ScalpelRead Scalpel's DAS-style Level 2: bids left, asks right, optional colour bands per price, venues, spread, imbalance and delta, and reading players by behaviour.Guide 1.6The IBKR market data you need for Level 2The IBKR market data that gives Scalpel live quotes, Level 2 and the tape, with no third-party feed to pay for, and how delayed or missing data shows.

    Keep reading

    • Scalping through the IBKR API: what the TWS API can and cannot doIBKR API scalping, from building a trading app on the TWS API: market data lines, Level 2 and tick-by-tick caps, pacing, scanner limits, pre-market stops.
    • IBKR API with Python: a working starter for tradersIBKR API Python starter: ibapi vs ib_async, connecting to TWS, a quote stream and a guarded paper order, with code tested against TWS API 10.51.
    • IBKR API scanner: reqScannerSubscription in practiceIBKR API scanner in practice: scan codes from reqScannerParameters, filters, 50 rows and 10 scans, errors 165, 322 and 10360, and a live pre-market run.
    Practice in SIM, against the live book.A lifetime license, every update included, with a 7-day refund guarantee.Ask in DiscordGet Scalpel
    Sign in