Unified Python SDK for Kalshi and Polymarket.
What’s inside
- Unified, no‑auth REST façade over Kalshi and Polymarket public endpoints.
- Normalization helpers and Pydantic models for events, markets, search, and history.
- A multiplexed WebSocket client that streams from Kalshi and Polymarket concurrently.
Supported Python: 3.9+
From PyPI (preferred):
pip install dataventsFrom a local wheel (e.g., CI artifact):
pip install /path/to/datavents-0.1.0-py3-none-any.whlEditable install for contributors:
pip install -e .[dev]List events (both providers) and print a few titles:
from datavents import DataVentsNoAuthClient, DataVentsProviders, DataVentsOrderSortParams, DataVentsStatusParams
client = DataVentsNoAuthClient()
events = client.list_events(
provider=DataVentsProviders.ALL,
limit=5,
page=0,
status_params=DataVentsStatusParams.OPEN_MARKETS,
order_sort_params=DataVentsOrderSortParams.ORDER_BY_TRENDING,
)
for payload in events:
print(payload["provider"], "→ keys:", list(payload["data"].keys()))Normalize provider search into one schema:
from datavents import normalize_search_response, Provider, OrderSort, StatusFilter
raw = events[0]["data"] # e.g., Kalshi payload
normalized = normalize_search_response(
Provider.kalshi,
raw,
q="election",
page=1,
limit=5,
order=OrderSort.trending,
status=StatusFilter.open,
)
print("Normalized results:", len(normalized.results))Explore more complete runnable samples under examples/.
The DataVentsNoAuthClient exposes a small set of high‑leverage methods. All network calls hit live provider public APIs (no credentials needed for REST). Each method returns a list of provider‑tagged dictionaries like:
[{"provider": "kalshi", "data": {...}}, {"provider": "polymarket", "data": {...}}]search_events(
provider: DataVentsProviders,
query: str,
limit: int,
page: int,
order_sort_params: DataVentsOrderSortParams,
status_params: DataVentsStatusParams,
**params
) -> list[dict]- Provider‑specific mapped sorts and statuses (see Enums below).
- When
provider=ALL, Kalshi and Polymarket are called in parallel. - Returns raw provider search payloads (use normalization helpers to unify shape).
Common extra params
kalshi_params: dict– forwarded to Kalshi search. Ifscope="series"with a status other than ALL, client falls back to Kalshi events search (series endpoint doesn’t support status).polymarket_params: dict– forwarded to Polymarket public‑search call.
list_events(
provider: DataVentsProviders,
limit: int = 50,
page: int = 0,
status_params: DataVentsStatusParams = DataVentsStatusParams.ALL_MARKETS,
query: str = "",
order_sort_params: DataVentsOrderSortParams = DataVentsOrderSortParams.ORDER_BY_TRENDING,
) -> list[dict]- Thin wrapper around provider search tuned for event discovery.
- Use with
normalize_search_responsefor unified typing.
list_markets(
provider: DataVentsProviders,
limit: int = 50,
page: int = 0,
status_params: DataVentsStatusParams = DataVentsStatusParams.OPEN_MARKETS,
query: str = "",
order_sort_params: DataVentsOrderSortParams = DataVentsOrderSortParams.ORDER_BY_TRENDING,
) -> list[dict]- Kalshi path currently uses events search (markets are nested under events).
- Polymarket path uses public‑search with
type="markets"and exposes a top‑levelmarketslist (flattened if needed).
get_event(provider, kalshi_event_ticker=None, polymarket_id=None, polymarket_slug=None, *, with_nested_markets=False, include_chat=False, include_template=False) -> list[dict]
get_market(provider, kalshi_ticker=None, polymarket_id=None, polymarket_slug=None, *, include_tag=False) -> list[dict]- Supply the proper identifier for each provider (Kalshi uses tickers; Polymarket supports id or slug).
- With
provider=ALL, only providers with identifiers provided are called.
While the client is "no‑auth" by default, it can lazily spin up a signed Kalshi REST client for auth‑only routes:
from datavents import DataVentsNoAuthClient, DataVentsProviders
from datavents.providers.config import Config as ProviderConfig
dv = DataVentsNoAuthClient()
# Direct helper (returns raw provider JSON)
ob = dv.get_kalshi_market_orderbook("ABC-24-XYZ-T50", depth=50, env=ProviderConfig.LIVE)
# Unified facade (list with provider tag)
res = dv.get_market_orderbook(DataVentsProviders.KALSHI, kalshi_ticker="ABC-24-XYZ-T50", depth=50)Set environment variables for Kalshi auth:
- LIVE:
KALSHI_API_KEY,KALSHI_PRIVATE_KEY(PEM path) - PAPER:
KALSHI_API_KEY_PAPER,KALSHI_PRIVATE_KEY_PAPER
get_event_metadata(event_ticker: str)– Kalshi event metadata.get_event_tags(event_id: int),get_market_tags(market_id: int)– Polymarket tags.
Convert raw provider payloads into consistent Pydantic models located in datavents.schemas.
Key entry points
normalize_search_response(provider, raw, q, order, status, page, limit)→SearchResponseNormalizednormalize_event(provider, raw)→Eventnormalize_market(provider, raw)→Marketnormalize_market_history(provider, identifiers=..., start, end, interval, raw|points)→MarketHistoryResponseNormalized
Highlights
- Prices normalized to probability in [0,1] (accepts common provider units such as 0..1, percent, or bps‑like).
- Timestamps normalized to epoch milliseconds.
statusmapped toStatusNormalized(open/closed/settled/upcoming/unknown) while retainingstatus_raw.- Provider snapshots preserved under
vendor_rawwith optional structuredvendor_fields.
Example (market → binary outcomes):
from datavents import normalize_market, Provider
raw_market = {"id": 123, "slug": "will-something-happen", "bestBid": 0.47, "bestAsk": 0.49}
m = normalize_market(Provider.polymarket, raw_market)
print(m.mid_price, [o.name for o in m.outcomes])Use DvWsClient to multiplex Kalshi and Polymarket streams with a single async loop.
import asyncio
from datavents import DvWsClient, DvSubscription, DvVendors
async def main():
dv = DvWsClient()
async def on_event(evt): # evt is datavents.ws.NormalizedEvent
print(evt.vendor, evt.event, evt.market)
sub = DvSubscription(
vendors=(DvVendors.POLYMARKET,),
polymarket_assets_ids=["asset-id-1", "asset-id-2"],
)
await dv.run(sub, on_event)
asyncio.run(main())Kalshi WS requires valid API credentials in your environment. Polymarket market WS is public and needs only asset ids.
Environment variables (Kalshi; see provider docs)
KALSHI_API_KEY(live) orKALSHI_API_KEY_PAPER(paper)KALSHI_PRIVATE_KEY(live) orKALSHI_PRIVATE_KEY_PAPER(paper) – path to PEM file used for RSA‑PSS signing
Order sorts (DataVentsOrderSortParams) map to provider‑specific semantics, e.g.:
ORDER_BY_TRENDING→ Kalshitrending, Polymarketvolume_24hr(proxy).ORDER_BY_CLOSING_SOON→ Kalshiclosing, Polymarketend_date.
Status filters (DataVentsStatusParams):
OPEN_MARKETS→ Kalshiopened,unopened; Polymarketactive.CLOSED_MARKETS→ Kalshiclosed,settled; Polymarketclosed.ALL_MARKETS→ no status filter.
Note: Kalshi series search does not accept a status filter; when you request scope="series" with a non‑ALL status, the client uses events search under the hood.
- HTTP timeout (seconds):
HTTP_TIMEOUT_SECONDSenv (default 15s). - Basic pacing between requests via
RateLimitConfig(default ~100ms between calls). - Logging: the SDK uses the standard library’s
loggingmodule; enable debug logs by configuring handlers in your app.
See examples/ for small, runnable scripts:
search_events.py– search across providers.list_markets.py– get Polymarket markets and Kalshi event‑nested markets.get_event.py/get_market.py– fetch single resources by id/ticker/slug.ws_multiplex.py– stream from Polymarket and/or Kalshi in one loop.normalize_examples.py– convert raw payloads into normalized Pydantic models.
Run like:
python -m venv .venv && source .venv/bin/activate
pip install -e .
python examples/search_events.py --q "election" --limit 5extract_vendors(value, *, strict=False)→[DvVendors, ...]- Accepts flexible input such as
"kalshi","poly,kalshi","all",Provider.kalshi, or a list of mixed tokens. - Returns a stable, de‑duplicated order:
[DvVendors.KALSHI, DvVendors.POLYMARKET]when both. - Back‑compat alias:
_extract_vendors(...).
- Accepts flexible input such as
from datavents import extract_vendors, DvVendors
assert extract_vendors("all") == [DvVendors.KALSHI, DvVendors.POLYMARKET]
assert extract_vendors(["k", "pm"]) == [DvVendors.KALSHI, DvVendors.POLYMARKET]build_ws_info(subscription)→ dict summarizing aDvSubscription(WS URLs, channels, identifiers).- Back‑compat alias:
_send_ws_info(subscription)for legacy callers.
- Back‑compat alias:
from datavents import DvSubscription, DvVendors, build_ws_info
sub = DvSubscription(
vendors=(DvVendors.KALSHI, DvVendors.POLYMARKET),
kalshi_market_tickers=["SER-ABC-123"],
polymarket_assets_ids=["asset-1"],
)
info = build_ws_info(sub)
print(info["kalshi"]["ws_url"], info["polymarket"]["ws_url"]) # ready for UIs/logging- This SDK targets provider public endpoints. Some features (e.g., private Kalshi WS) require valid credentials.
list_marketson Kalshi returns markets nested inside events; flatten as needed.- Provider APIs evolve; enums map to best‑available sorts/filters and may be updated across releases.
python -m venv .venv
source .venv/bin/activate
pip install -e .[dev]
pytestTo publish artifacts:
make build # dist/*.whl + dist/*.tar.gz
make check # twine check dist
make publish # twine upload dist/*MIT