Kalshi L2 depth and trade data answer different questions. Depth records resting prices and sizes at a captured snapshot; a trade print records a transaction. To study them together, you need a clear timestamp rule and a way to handle missing or stale book observations.
This guide checks ZenHodl's free depth CSV, explains the archive's clocks and quantity limits, and shows what to inspect before using the full tape. ZenHodl sells that archive.
Kalshi's official historical API supplies trades and candles. Those do not reconstruct a past bid/ask ladder. Our captured trade table contains retrieved venue trade records; the separately recorded depth supplies sampled historical book states.
Current inventory and scope
The current inventory is catalog metadata for the 2026-10-02 artifact cutoff, not a new raw recount of every delivered file. The published catalog lists 821,979,759 depth rows and 29,984,492 deduplicated trade prints. Those figures are in the public catalog and on the tape product page:
This is a capture of selected high-volume markets. It is not a complete exchange tape, and it is not guaranteed season coverage. Depth is capped at 20 levels per side even if Kalshi exposed more.
What the public sample actually shows
The free sample is a small 400-row depth CSV checked on 2026-10-07:
Download the 400-row depth CSV
The sample contains 400 depth rows, 12 ticker-snapshot groups, and one observation time: 2026-07-12T15:20:09.015955Z. Of the 12 groups, 11 have a complete level-zero bid and ask pair. The median ask-minus-bid spread on those 11 pairs is 1 cent, with sorted gaps of 1, 1, 1, 1, 1, 1, 1, 1, 2, 2, 13 cents.
Those are 11 paired quotes at one poll. They are not an exchange-wide spread estimate, a fill study, or a comparison across days or venues. One group lacks a complete level-zero pair in this example, but that does not imply an outage or a missing book in the full archive. A truncated sample cannot support that inference. The CSV contains no trade prints, so it cannot be used to evaluate trade lag or tape completeness.
Save the CSV as kalshi_micro_tape_sample.csv beside this script, then run Python from that directory. The hash check stops the example if the file differs from the one analyzed here.
import csv
import hashlib
from collections import defaultdict
from decimal import Decimal
from pathlib import Path
from statistics import median
path = Path("kalshi_micro_tape_sample.csv")
expected = "271c0aad444c88523852c14309f040df23d81eb1aecf42799f6bd367cea92de5"
if hashlib.sha256(path.read_bytes()).hexdigest() != expected:
raise ValueError("Sample changed; recheck the published result")
with path.open(encoding="utf-8-sig", newline="") as handle:
rows = list(csv.DictReader(handle))
best = defaultdict(dict)
groups = {(row["timestamp"], row["ticker"]) for row in rows}
for row in rows:
if row["side"] not in {"bid", "ask"}:
raise ValueError("Unexpected side")
if int(row["level"]) == 0:
key = (row["timestamp"], row["ticker"])
if row["side"] in best[key]:
raise ValueError("Duplicate level-zero side")
best[key][row["side"]] = Decimal(row["price_c"])
paired = [v for v in best.values() if set(v) == {"bid", "ask"}]
gaps = [v["ask"] - v["bid"] for v in paired]
print("depth rows:", len(rows))
print("ticker-snapshot groups:", len(groups))
print("complete level-zero pairs:", len(paired))
print("median paired gap (cents):", median(gaps))
Expected printed output:
depth rows: 400
ticker-snapshot groups: 12
complete level-zero pairs: 11
median paired gap (cents): 1
Reading the fields
The dated public schema defines the fields and capture limits.
Depth rows use an epoch-seconds timestamp in UTC. The stored side is bid or ask, price_c is price in cents, and size is a float64 contract quantity. This normalized archive layout differs from the live API's YES/NO bid representation. That float64 size has a known limitation for older depth: stored historical depth through July 19 had integer-truncated fractions upstream. Rebuilding the archive as float64 cannot recover fractions that were already lost. Trade rebuilds are different; trades.count preserves fractional quantities from the raw JSON.
For trade prints, created_time is the exchange trade time. capture_ts is when the recorder retrieved the print. The game-context fields in prints describe retrieval time, not exchange event time. For event-time research, join created_time to a separately verified score timeline. Align each print with a prior depth snapshot, and apply a declared maximum age; selecting a future book can leak information into the comparison. The difference capture_ts - created_time is observed retrieval lag, not Kalshi latency. Polls and backfills may arrive much later than the exchange timestamp.
This article does not recompute a lag distribution for the full October 2 tape. The tryout walkthrough reports a separate, dated retrieval-lag analysis; its results apply to that smaller vintage.
Coverage timing is global, not per-ticker
The current schema and capture stats document a median global depth interval of 30.5 seconds before July 12 and 43.0 seconds from July 12. The word "global" matters. These medians describe the recorder's global snapshot cycles. They do not establish continuous observation of every ticker.
For per-market gaps, inspect the product's coverage or current product documentation. Do not infer a complete-market observation schedule from the global median. The sample cannot measure coverage over time at all.
Questions the tape can support
- What was recorded at a particular poll? Inspect that ticker's depth rows, retained levels and snapshot time. A ladder calculation describes those sampled sizes; it does not prove your order would have filled.
- Which captured transactions preceded a score event? Use exchange
created_timeand a verified score timeline. Retrieval-time game context cannot substitute for that timeline. - How often did a market appear? Measure presence and gaps for the ticker and research window. The global cadence and this one-snapshot CSV cannot answer that per-market question.
Define these rules before computing the result, and report the observations excluded by them.
Start with the free sample
Use the free CSV above to test your parser. Check the price units, sides, level numbering and timestamp handling against the schema.
If that works, the $9 tryout is the next step: Kalshi micro tryout product. A walkthrough exists at Kalshi tryout first insight guide. The tape product page has current inventory and schema references: Kalshi historical data.
Method note, October 7, 2026: DeepSeek assisted drafting. We reran the displayed Python check against the public CSV and checked the field descriptions against the dated schema. Inventory totals are published metadata for the October 2 artifact, not a fresh scan of its table payloads.