> ## Documentation Index
> Fetch the complete documentation index at: https://scalarfield.io/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Kalshi Prediction Markets

> Search Kalshi event contracts, inspect live order books, pull OHLC candlesticks, read the public trade tape, and load market resolution fields — from Kalshi's public Trade API.

<Info>**Python functions:** `screenKalshiMarkets()`, `screenKalshiEvents()`, `getKalshiMarket()`, `getKalshiOrderbook()`, `getKalshiCandles()`, `getKalshiTrades()`</Info>

| Specification      | Value                                                                                                                |
| ------------------ | -------------------------------------------------------------------------------------------------------------------- |
| Delivery Frequency | continuous                                                                                                           |
| Data Frequency     | event-driven (trades), bar-aggregated (candles)                                                                      |
| Coverage           | Live Kalshi markets plus archived settled markets/trades via Kalshi's historical API (rolling \~3-month live window) |
| Candle intervals   | 1 minute, 1 hour, 1 day                                                                                              |
| Availability       | Free (public API; no Kalshi login required)                                                                          |

# Product Overview

Kalshi Data exposes Kalshi's public event-contract markets through Scalar Field. Markets are binary YES/NO contracts priced as probabilities. `screenKalshiMarkets(query=...)` searches live Kalshi public APIs — with a query, Kalshi's `/v1/search/series` index; without, `GET /markets` (one page each). Pass `symbol` (YES) or `symbol_no` (NO) into `venue.trade()` and strategy agents.

Kalshi's order book returns **bids only** on each leg. A YES ask is derived as `1 − best NO bid` (and vice versa). Candlesticks try the live series path first and fail over to `GET /historical/markets/{ticker}/candlesticks` for markets that have rolled off the live set. Intervals with no trades are returned with null OHLC, volume 0, and populated open interest.

<Info>**Timestamps:** Naive request datetimes (`YYYY-MM-DD` / `YYYY-MM-DDTHH:MM:SS`) are **New York time** and converted to UTC before calling Kalshi. Clock-time fields on responses (`close_time`, `created_time`, `settlement_ts`, candle index `end_period`, trade `created_time`) are converted to timezone-naive New York time. **Unix-second values stay Unix:** request ints, candle column `end_period_ts`, and candle `df.attrs["last_ts"]` (that attr is the last `end_period_ts`, not a New York datetime). Trade `df.attrs["last_ts"]` is New York time — same name, different type.</Info>

<Note>Kalshi is a tradable venue on Scalar Field. See [Trading on Kalshi](/docs/trading/venues/kalshi) for connect flow, order semantics, and settlement.</Note>

## Functions at a Glance

| Function                                                | Purpose                                                              |
| ------------------------------------------------------- | -------------------------------------------------------------------- |
| `screenKalshiMarkets(...)`                              | Search/filter markets by series, event, status, close time           |
| `screenKalshiEvents(...)`                               | List events, optionally with nested markets and `mutually_exclusive` |
| `getKalshiMarket(ticker)`                               | Full public Market payload, including settlement / resolution fields |
| `getKalshiOrderbook(ticker)`                            | Live YES/NO book with derived asks                                   |
| `getKalshiCandles(ticker, start, end, period_interval)` | OHLC bars (1 / 60 / 1440 minutes); live then historical              |
| `getKalshiTrades(ticker)`                               | Public trade tape with `is_complete` / `last_ts` / `next_cursor`     |

Kalshi does **not** publish participant-level research APIs (no public wallets, positions, fills-by-trader, or leaderboards). That is a regulated-exchange constraint, not a missing wrapper. Research here is market-level: books, tape, candles, and resolution metadata.

# Querying the Data

## `screenKalshiMarkets()`

```python theme={null}
from scalarlib import screenKalshiMarkets

markets = screenKalshiMarkets(query="bitcoin", closed=False, limit=50)
print(markets[["ticker", "symbol", "title", "yes_bid", "volume"]].head())
```

Returns a DataFrame with `ticker`, `symbol` (YES), `symbol_no` (NO), bids/asks, volume, open interest, `close_time` (expiration, New York time), `created_time` (New York time), `price_ranges`, and settlement columns `result`, `settlement_ts` (New York time), `settlement_value_dollars`, `expiration_value`. Filter expiration with `start_date` / `end_date` (`YYYY-MM-DD`, New York calendar days).

`status` is a **query** filter: `"open"` (default; `None` is the same as omit), `"closed"`, `"settled"`, `"unopened"`, `"paused"`, or aliases `"active"` / `"finalized"`. Other values raise `ValueError`; a non-string raises `TypeError`. Ignored when `closed` is set. Returned rows often have lifecycle labels `"active"` or `"finalized"`; this screener maps those aliases to `"open"` / `"settled"`.

Search-index rows may leave settlement columns null even after a market has determined. Use `getKalshiMarket(ticker)` for the authoritative Market object.

## `screenKalshiEvents()`

```python theme={null}
from scalarlib import screenKalshiEvents

events = screenKalshiEvents(status="open", limit=50)
```

`status` query values: `"open"`, `"closed"`, `"settled"`, or omit/`None` for any. Matches events that have at least one child market in that state. Other values, including `"active"` and `"finalized"`, raise `ValueError` before the request. Event-row `status` is often null.

`mutually_exclusive` is Kalshi's event flag: **true** means only one child market can resolve YES; **false** means more than one child can resolve YES. It is not inferred by Scalar Field.

## `getKalshiMarket()`

```python theme={null}
from scalarlib import getKalshiMarket

market = getKalshiMarket("KXHIGHNY-26AUG10-T60")
print(market["status"], market["result"], market["settlement_ts"])
print(market["settlement_value_dollars"], market["expiration_value"])
print(market["rules_primary"])
```

This is the resolution dataset. It calls `GET /markets/{ticker}`, then `GET /historical/markets/{ticker}` when the live ticker has rolled off Kalshi's \~3-month settled-market cutoff.

Fields come from [Kalshi's Market schema](https://docs.kalshi.com/api-reference/market/get-market) and [market lifecycle](https://docs.kalshi.com/getting_started/market_lifecycle):

| Field                               | Meaning                                                                                         |
| ----------------------------------- | ----------------------------------------------------------------------------------------------- |
| `result`                            | `yes` / `no` / `scalar` after determination; empty string before                                |
| `settlement_value_dollars`          | YES/LONG payout in dollars; filled after determination. Binary winners pay **\$1**              |
| `settlement_ts`                     | Timestamp when the market reached `finalized` (New York time, naive)                            |
| `expiration_value`                  | Observed value considered for settlement (not a news-source URL)                                |
| `status`                            | `initialized`, `active`, `inactive`, `closed`, `determined`, `disputed`, `amended`, `finalized` |
| `rules_primary` / `rules_secondary` | Current settlement-rule text                                                                    |

`disputed` / `amended` are lifecycle states on this object, not a separate dispute feed. Kalshi does not publish a settlement-rule revision log — `rules_*` is the current text. There is no `resolution_source` field in the Trade API.

## `getKalshiOrderbook()`

```python theme={null}
from scalarlib import getKalshiOrderbook

book = getKalshiOrderbook("KXHIGHNY-26AUG10-T60")
yes, no = book["yes"], book["no"]
print(yes["bid"], yes["ask"])
if yes["bid"] is not None and yes["ask"] is not None:
    spread = yes["ask"] - yes["bid"]
```

Kalshi's raw orderbook is bids only ([Orderbook responses](https://docs.kalshi.com/getting_started/orderbook_responses)):

* YES bid at `X` ≡ NO ask at `1 − X` (same size)
* NO bid at `Y` ≡ YES ask at `1 − Y` (same size)

The data server already applies that conversion. `yes.bids` / `no.bids` are `[[price, size], ...]` best-first.

| Situation           | Result                                                                                                                                                                |
| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Both legs have bids | `yes.ask = 1 − best NO bid`; YES spread = `yes.ask − yes.bid`                                                                                                         |
| Empty NO bids       | `yes.ask` and `no.bid` are `None` — do not invent a quote                                                                                                             |
| Empty YES bids      | `yes.bid` and `no.ask` are `None`                                                                                                                                     |
| Multi-level book    | Each YES bid level `[p, s]` is a NO ask at `1 − p` for size `s` (and vice versa). Sizes match; do not assume the same number of displayed levels after a `depth` trim |
| Rounding            | Complement is `round(1 − p, 4)` dollars. Kalshi still rejects off-grid limit prices on submit — snap with the market's `price_ranges`                                 |
| Fees                | Derived asks are **pre-fee**. A complementary pair that sums to \$1 is not a fee-adjusted arbitrage signal                                                            |

## `getKalshiCandles()`

```python theme={null}
from scalarlib import getKalshiCandles

df = getKalshiCandles(
    "KXHIGHNY-26AUG10-T60",
    start="2026-08-01",
    end="2026-08-10",
    period_interval=60,
)
print(df.attrs["source"], df.attrs["is_complete"])
```

`period_interval` must be `1`, `60`, or `1440`. Columns: `end_period_ts`, `open`, `high`, `low`, `close`, `volume`, `open_interest`. The index is `end_period` (New York time, timezone-naive). `end_period_ts` remains Unix seconds (UTC instant). Naive `start` / `end` strings are New York time. Prices are the **YES trade** distribution from Kalshi (`price.open_dollars` / `high` / `low` / `close`, or historical `price.open` / `high` / `low` / `close` — both map into the same columns).

### Sparse / zero-trade intervals

Kalshi returns a row for intervals with no trades. That is valid, not a gap in the series.

| Field                          | Empty interval                                                                                   |
| ------------------------------ | ------------------------------------------------------------------------------------------------ |
| `open`, `high`, `low`, `close` | `null` (pandas `NaN`) — Kalshi documents these as nullable when there was no trade in the period |
| `volume`                       | `0`                                                                                              |
| `open_interest`                | Populated (OI at `end_period_ts`)                                                                |

Do **not** forward-fill null OHLC unless that is an explicit choice for your analysis. This wrapper does not substitute `previous_dollars` into OHLC.

Kalshi can also prepend a synthetic candle when `include_latest_before_start=true` (null OHLC, `previous_price` set). `getKalshiCandles` does not pass that flag.

### Live vs historical coverage

Candlesticks for markets that **settled before** Kalshi's rolling live cutoff (`market_settled_ts` from `GET /historical/cutoff`, target window **\~3 months**) are only on `GET /historical/markets/{ticker}/candlesticks`. `getKalshiCandles` calls the live series path first and **fails over** to that historical endpoint when live is 404/empty.

There is no documented hard row cap on the candlestick endpoint (it is a time-range query, not a `limit` page). `df.attrs["is_complete"]` is True after a successful time-range response. `df.attrs["source"]` is `"live"` or `"historical"`. `df.attrs["last_ts"]` is the last `end_period_ts` in the frame — **Unix seconds, not New York time**. Use the `end_period` index when you want the clock time. This is not the same as trade `last_ts` (a New York datetime).

## `getKalshiTrades()`

```python theme={null}
from scalarlib import getKalshiTrades

tape = getKalshiTrades("KXHIGHNY-26AUG10-T60", limit=100)
if not tape.attrs["is_complete"]:
    more = getKalshiTrades(
        "KXHIGHNY-26AUG10-T60",
        limit=100,
        cursor=tape.attrs["next_cursor"],
        source=tape.attrs["source"],
    )
```

Public tape from `GET /markets/trades`, then `GET /historical/trades` when the live cursor is exhausted and `limit` still has room (`include_historical=True` by default). Columns: `trade_id`, `ticker`, `symbol`, `outcome`, `price`, `yes_price`, `no_price`, `count`, `taker_side`, `created_time` (New York time, naive).

`yes_price` + `no_price` = 1. `outcome` / `price` are the **taker's** `taker_outcome_side` and that leg's price — the same complement encoding as private fills (sell-YES prints as NO at `1 - yes_price`). `taker_side` is the legacy YES/NO of the aggressive order.

Optional filters: `min_ts` / `max_ts` (naive New York time or Unix seconds; converted to UTC for Kalshi), `cursor`, `source` (`live` / `historical` / omit for auto).

### Pagination, completeness, retention

| Rule                      | Behavior                                                                                                                                                                                                           |
| ------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `limit`                   | Default 100, **capped at 1000**. Kalshi's page size is 1–1000 (default 100).                                                                                                                                       |
| What `limit=1000` means   | Up to 1,000 matching trades from live, then historical if live is exhausted. It is not a silent “lifetime tape” dump — check `is_complete`.                                                                        |
| Cursor                    | Kalshi is cursor-paginated (`cursor` empty ⇒ no further page on that source). Resume with `df.attrs["next_cursor"]` and `df.attrs["source"]`.                                                                      |
| `df.attrs["is_complete"]` | True when the last queried source returned no further cursor.                                                                                                                                                      |
| `df.attrs["last_ts"]`     | Oldest `created_time` in this page, as naive **New York** datetime (not Unix seconds — unlike candle `last_ts`). When incomplete, pass it as `max_ts` (same `min_ts`) to continue backwards, or use `next_cursor`. |
| Live vs archive           | Trades older than `trades_created_ts` (\~3 months) live on `GET /historical/trades`. This helper **does** query that endpoint unless `include_historical=False` or `source="live"`.                                |
| User fills                | `GET /historical/fills` is authenticated and **not** the public tape. Use `venue.history(account="KALSHI")`.                                                                                                       |
| Latency                   | Kalshi documents a short delay before REST reflects matching-engine events. No numeric SLA.                                                                                                                        |

# Related

* [Trading on Kalshi](/docs/trading/venues/kalshi)
* [Polymarket Data](/docs/market-data/polymarket-data)
