Python API (Reference)
This repo is a normal src/ layout package. The main public entrypoints are exported from:
kalshi_research.api— HTTP clients + Pydantic modelskalshi_research.data— SQLite/SQLAlchemy persistence + repositories + fetcherkalshi_research.analysis— analysis/scanning/visualization utilitieskalshi_research.alerts— alert conditions + monitorkalshi_research.portfolio— portfolio models + sync + P&Lkalshi_research.research— thesis tracking + backtestingkalshi_research.exa— Exa async client + cache + models (optional, requiresEXA_API_KEY)kalshi_research.news— news tracking + sentiment pipeline (Exa-powered, DB-backed)
You can also import the core API clients directly from kalshi_research:
from kalshi_research import Environment, KalshiClient, KalshiPublicClient
Public API client (no auth)
import asyncio
from kalshi_research.api import KalshiPublicClient
async def main() -> None:
async with KalshiPublicClient() as client:
market = await client.get_market("TICKER")
orderbook = await client.get_orderbook("TICKER", depth=5)
markets = [m async for m in client.get_all_markets(status="open", max_pages=1)]
print(market.ticker, orderbook.spread, len(markets))
asyncio.run(main())
Series discovery (public API)
Kalshi’s intended browse pattern is:
get_tags_by_categories() → get_series_list() → get_all_markets(series_ticker=...)
Related endpoints are also available:
get_series(series_ticker)get_series_fee_changes()
import asyncio
from kalshi_research.api import KalshiPublicClient
async def main() -> None:
async with KalshiPublicClient() as client:
tags_by_category = await client.get_tags_by_categories()
series = await client.get_series_list(category="Politics", include_volume=True)
print(len(tags_by_category), len(series))
asyncio.run(main())
Authenticated API client
import asyncio
from kalshi_research.api import KalshiClient
async def main() -> None:
async with KalshiClient(
key_id="...",
private_key_path="/path/to/key.pem",
environment="demo",
) as client:
balance = await client.get_balance()
positions = await client.get_positions()
print(balance, len(positions))
asyncio.run(main())
Exa client (async search + deep research + crash recovery)
import asyncio
from kalshi_research.exa import ExaClient
async def main() -> None:
async with ExaClient.from_env() as exa:
results = await exa.search("Kalshi prediction markets", num_results=5)
# Deep research runs asynchronously via /research/v1.
task = await exa.create_research_task(
instructions="Summarize what could cause this market to resolve YES.",
model="exa-research-fast", # also: exa-research, exa-research-pro
)
# Crash recovery: list tasks, find a likely match, then fetch by ID.
page = await exa.list_research_tasks(limit=10)
recovered = await exa.find_recent_research_task(instructions_prefix="Summarize what could cause")
print(len(results.results), len(page.data), task.status, recovered is not None)
asyncio.run(main())
Data pipeline (DB + fetcher)
import asyncio
from kalshi_research.data import DatabaseManager, DataFetcher
async def main() -> None:
async with DatabaseManager("data/kalshi.db") as db:
await db.create_tables()
async with DataFetcher(db) as fetcher:
await fetcher.sync_markets(status="open", max_pages=1)
await fetcher.take_snapshot(status="open", max_pages=1)
asyncio.run(main())
Analysis utilities
High-level exports live in kalshi_research.analysis:
MarketScanner(opportunity scanning)CalibrationAnalyzer/CalibrationResultEdge/EdgeType- plotting helpers (
plot_probability_timeline,plot_spread_timeline, etc.)
Some analyzers live in submodules (e.g. kalshi_research.analysis.correlation.CorrelationAnalyzer).