Skip to content
Recording 4 venues

Kalshi order book data: history and format

Kalshi's API shows a market's order book only as it is now. To see the book at a past moment, you need a recording of every change. Kalshi Eye records every order book change on every Kalshi market, 32 billion rows so far, published each morning as daily Parquet files.

Checked against the files on November 2, 2026.

How Kalshi's order book works

A Kalshi book has two sides, and both are bids: bids to buy YES and bids to buy NO. A NO bid at 31¢ is an offer to sell YES at 69¢, so the best YES ask is 1 minus the best NO bid. Here is the top of the book for SENATETX-26-D (Democrats win the Texas Senate race) at the end of November 1, 2026:

YES bidContractsNO bidContractsSame as a YES ask at
0.68578,3890.31901,9980.69
0.6759,4590.30903,0350.70
  • Prices are dollars from 0 to 1; a contract pays $1 if its side wins. Most markets move in 1¢ steps. Markets with the tapered_deci_cent structure, such as the 15-minute crypto markets, move in 0.1¢ steps below 10¢ and above 90¢. Each market's steps are in the markets file (tick_structure, price_ranges).
  • Sizes are contracts resting at that price.
  • Updates arrive as a snapshot of the whole book, then deltas: a signed change in size at one price on one side, with a sequence number.

What Kalshi's API gives you

GET /markets/{ticker}/orderbook returns the YES and NO bids as they are now. The WebSocket's orderbook_delta channel sends a snapshot and then every change, but only from the moment you connect. Candles give the best bid and ask once a minute. Nothing in the API returns a past book.

Four ways to read the history

DatasetWhat it holdsPlanUse it for
book_l2Every change to the YES and NO bid books, every price level. Each day opens with a snapshot of every book.Full bookThe whole book at any moment: depth, queue position, how the book reacts to trades.
book_snap_1sThe top ten YES bid and ask levels at the end of every second in which the book changed.ResearchSpread and depth over time without replaying anything.
bboThe best YES bid and ask with their sizes, one row each time any of the four changes.ResearchWhat you could have bought or sold at, at any moment.
bbo_1mMinute bars of the YES mid, with the closing bid and ask, trade count and volume.ResearchCharts, long date ranges and minute-level backtests.

Every file is one series for one UTC day. In book_l2, snapshot rows set a level and delta rows add to it, so each day's file replays on its own.

Rebuild the book at any moment

The Python client replays a day of book changes and hands you the book after each one:

from kalshieye import Client, replay_book  # pip install kalshieye

kx = Client()  # reads KALSHIEYE_API_KEY
book = kx.load("book_l2", "2026-11-01", "2026-11-01", series="SENATETX")
for ts, ticker, bids, asks in replay_book(book, ticker="SENATETX-26-D"):
    ...  # bids and asks: the top ten (price, size) levels after every change

The same thing without the client, on a downloaded file:

import duckdb

# Rows are already in time order for each ticker: read them as the file has them.
rows = duckdb.sql("""
  SELECT epoch_ns(ts_recv) AS ts, is_snapshot, side, price, size
  FROM 'kalshieye-data/book_l2/date=2026-11-01/series=SENATETX/*.parquet'
  WHERE ticker = 'SENATETX-26-D'
""").fetchall()

book = {"yes": {}, "no": {}}  # side -> {price: contracts}
snapshot_at = None
for ts, is_snapshot, side, price, size in rows:
    if is_snapshot:
        if ts != snapshot_at:  # the first row of a new snapshot: start over
            book = {"yes": {}, "no": {}}
            snapshot_at = ts
        if side:  # a snapshot with no side: the book was empty
            book[side][price] = size
    else:  # a delta: add the signed change, drop a level at or below zero
        level = book[side].get(price, 0) + size
        if level > 0:
            book[side][price] = level
        else:
            book[side].pop(price, None)

best_bid = max(book["yes"], default=None)
best_ask = round(1 - max(book["no"]), 4) if book["no"] else None  # YES ask = 1 - best NO bid

Spread and depth without replaying

book_snap_1s already holds the top ten levels each second, so most spread and depth questions are one query:

SET TimeZone = 'UTC';

-- Average spread and the contracts bid in the top five levels, hour by hour.
SELECT date_trunc('hour', ts::TIMESTAMPTZ) AS hour,
       round(avg(ask_px_0 - bid_px_0), 4) AS avg_spread,
       round(avg(bid_sz_0 + bid_sz_1 + bid_sz_2 + bid_sz_3 + bid_sz_4)) AS bid_depth_5
FROM 'kalshieye-data/book_snap_1s/date=2026-11-01/series=KXBTC15M/*.parquet'
WHERE bid_px_0 IS NOT NULL AND ask_px_0 IS NOT NULL
GROUP BY hour
ORDER BY hour;

Questions

Does Kalshi have historical order book data?

Not through its API. GET /markets/{ticker}/orderbook returns the book as it is now, and the WebSocket sends changes only while you are connected. Past books exist only where someone recorded the feed as it changed.

Why does a Kalshi order book have no asks?

Every order is a bid on YES or a bid on NO. Selling YES at 69¢ is the same trade as buying NO at 31¢, so Kalshi keeps only bids. The YES ask is 1 minus the best NO bid.

How deep is the recorded book?

book_l2 has every price level on both sides. book_snap_1s keeps the top ten YES bids and asks, which covers most questions about spread and depth.

How big are the files?

It depends on how busy the market is. A day of book_l2 for KXBTC15M, Kalshi's 15-minute bitcoin markets, is about 260 MB; a day for a Senate race is under 1 MB.

Next