Real photo by Rob Wingate / Unsplash, used under the Unsplash License.
Source-backed answer: the hard part is not writing the Python loop. A credible Kalshi backtester has to route records across Kalshi's moving live/historical cutoff, distinguish candles and trades from orderbook history, preserve what was knowable at each timestamp, and simulate fills and fees without inventing liquidity.
Critical limitation: Current orderbook calls are not the same as historical orderbook snapshots. Kalshi's documented historical tier includes markets, candles and trades, but a candle does not reconstruct every queue, cancellation or depth change. Collect timestamped REST snapshots or WebSocket messages before claiming a depth-aware backtest.
Store one row per market with its series ticker, market ticker, open time, close time, settlement time, final result, rule text, settlement source and the time each field was observed. A result should not enter the feature table until it was actually knowable. Keeping the rule text matters because two similar headlines can use different thresholds, operators, time zones or settlement sources.
| Data needed | Documented route | Backtest use | Main caveat |
|---|---|---|---|
| Cutoff timestamps | GET /historical/cutoff | Choose live or historical tier | The cutoff advances; never hard-code one date |
| Recent candles | GET /series/{series}/markets/{ticker}/candlesticks | Interval bid, ask, price, volume and open interest | Only 1, 60 and 1440 minute intervals are documented |
| Archived candles | GET /historical/markets/{ticker}/candlesticks | Older settled-market intervals | Route depends on market settlement versus cutoff |
| Trades | GET /markets/trades or GET /historical/trades | Executed price, quantity, side and timestamp | Paginate until the returned cursor is empty |
| Current depth | GET /markets/{ticker}/orderbook | YES and NO bid ladders | It is a present snapshot, not an archive |
Kalshi says markets and their candlesticks that settled before market_settled_ts belong in the historical tier. Trades have a separate trades_created_ts cutoff. A complete dataset may therefore require both live and historical calls, followed by deduplication on stable identifiers and timestamps.
This minimal Python client shows the two candlestick paths. It deliberately takes the route as an explicit decision; production research should fetch the cutoff and market settlement metadata first.
from decimal import Decimal
import requests
BASE_URL = "https://external-api.kalshi.com/trade-api/v2"
def get_json(path, params):
response = requests.get(
f"{BASE_URL}{path}", params=params, timeout=20
)
response.raise_for_status()
return response.json()
def live_candles(series_ticker, market_ticker, start_ts, end_ts, period=60):
return get_json(
f"/series/{series_ticker}/markets/{market_ticker}/candlesticks",
{"start_ts": start_ts, "end_ts": end_ts, "period_interval": period},
)["candlesticks"]
def archived_candles(market_ticker, start_ts, end_ts, period=60):
return get_json(
f"/historical/markets/{market_ticker}/candlesticks",
{"start_ts": start_ts, "end_ts": end_ts, "period_interval": period},
)["candlesticks"]
def decimal_or_none(value):
return None if value is None else Decimal(str(value))
Use Decimal for dollar strings instead of binary floating point. Validate period_interval against the documented values 1, 60 or 1440. Store the raw response beside normalized rows so a later schema or parsing correction can be reproduced.
A candle may summarize last trade, bid, ask, volume and open interest, but that does not guarantee executable size at every point inside the interval. Missing bid or ask values should remain missing. Forward-filling a display price and then treating it as a fill creates liquidity that may never have existed.
For a current orderbook snapshot, Kalshi returns yes_dollars and no_dollars bid ladders. It does not return separate asks. The best YES ask is one dollar minus the best NO bid; the best NO ask is one dollar minus the best YES bid. Larger simulated orders must walk through reciprocal levels and stop when visible quantity is exhausted.
Hypothetical accounting example: 100 YES contracts bought at an executable $0.43 ask cost $43 before fees. If a deliberately hypothetical fee assumption adds $2 total, capital at risk is $45. A YES settlement pays $100, producing $55 net under that assumption; a NO settlement produces a $45 loss. This illustrates the ledger only. The current fee schedule and market-specific fees control a real calculation.
At minimum, report the market universe, observation dates, number of opportunities, number and percentage filled, gross and net P&L, fees, average spread paid, maximum drawdown, capital at risk and separate out-of-sample results. Show a sensitivity table for one or more worse-price assumptions. A strategy that disappears after one extra cent of slippage is evidence about fragility, not a deployable edge.
Version the code, source snapshot, cutoff response, fee schedule and result file together. That turns the backtest into an auditable research artifact instead of a screenshot with no reproducible inputs.
Yes. Kalshi documents historical markets, market candlesticks, trades, fills, and orders behind moving cutoff timestamps. A backtester should fetch GET /historical/cutoff and route older records to the matching historical endpoint instead of assuming one live endpoint contains the full archive.
Start with GET /historical/cutoff, live and historical market metadata, live and historical market candlesticks, live and historical trades, and the current orderbook endpoint. The orderbook call is a current snapshot, not a historical depth archive.
The current market and historical market candlestick references document period_interval values of 1, 60, and 1440 minutes. Each request also needs start_ts and end_ts. Preserve the source timestamps and do not silently forward-fill missing tradable quotes.
Read the current Kalshi fee schedule at run time or version the schedule used by the test. Model marketable buys at the available ask, walk through visible depth for larger quantities, allow partial fills, and stress the result with worse prices. A midpoint fill is not automatically executable.
No. Candlesticks summarize prices, bids, asks, volume, and open interest by interval, but they do not recreate every historical queue and depth change. A depth-sensitive execution study needs snapshots or WebSocket messages collected and timestamped while the market is live.
Boundary: This is an educational data-engineering guide, not trading advice or a claim that any backtested strategy will work. API schemas, cutoffs and fees can change; verify the linked official sources before each research run.