Unified prediction market model.Documentation Index
Fetch the complete documentation index at: https://docs.openpx.trade/llms.txt
Use this file to discover all available pages before exploring further.
Coverage
| Exchange | Sourced | Synthetic | Omitted |
|---|---|---|---|
| kalshi | 15 | 6 | 4 |
| polymarket | 19 | 6 | 0 |
Synthetic — computed by OpenPX, not present upstream.
Omitted — upstream does not expose this concept.
Field crosswalk
| Unified field | Type | kalshi source | polymarket source | Transform | Notes |
|---|---|---|---|---|---|
ticker | string | Market.ticker (string) | Market.slug (string) | direct | polymarket: Polymarket exposes both a slug and a numeric id; we use slug as the unified ticker because it’s stable and human-readable. The on-chain conditionId lives in condition_id; the numeric id lives in numeric_id for REST-only deep links. |
openpx_id | string | computed — {exchange}:{ticker} | computed — {exchange}:{ticker} | synthetic | Composite primary key, never sourced from upstream. |
exchange | string | computed — literal exchange identifier (“kalshi” or “polymarket”) | computed — literal exchange identifier (“kalshi” or “polymarket”) | synthetic | |
numeric_id | string? | not exposed | Market.id (string) | direct | kalshi: Kalshi has no separate numeric id surface. |
event_ticker | string? | Market.event_ticker (string) | computed — events[0].slug from the embedded events relation | direct / synthetic | kalshi: Kalshi exposes the parent event identifier as event_ticker. polymarket: Polymarket’s parent event is embedded under events[] on each market object; we read events[0].slug (the human-readable identifier matching the unified event_ticker semantics). Falls back to events[0].id only when slug is missing on the upstream payload. |
title | string | computed — {yes_sub_title} | {no_sub_title} | Market.question (string) | synthetic / direct | kalshi: Composed from the spec-canonical short titles for each side of the binary contract. The deprecated title field is ignored. |
rules | string? | computed — {rules_primary} | {rules_secondary} | Market.description (string) | synthetic / direct | kalshi: Concatenation of the two upstream prose fields with ” | ” separator. Sources: rules_primary, rules_secondary. |
status | MarketStatus | Market.status (string) | Market.active (boolean) | enum_remap | kalshi: Kalshi values: initialized, inactive, active, closed, determined, disputed, amended, finalized → unified MarketStatus. polymarket: Polymarket exposes active, closed, archived as booleans; combined into unified MarketStatus enum. |
market_type | MarketType | Market.market_type (string) | computed — derived from outcomes.len() (binary if 2, else categorical) | enum_remap / synthetic | kalshi: binary | scalar → MarketType::Binary / MarketType::Scalar |
result | string? | Market.result (string) | computed — outcomes[i] where outcomePrices[i] == 1.0 (resolved markets only) | direct / synthetic | polymarket: Polymarket has no single result field; the winning outcome is derived from outcomePrices after settlement. None for unresolved markets. Sources: outcomes, outcomePrices. |
open_time | string? (date-time) | Market.open_time (string (date-time)) | Market.startDate (string (date-time)) | parse_datetime | |
close_time | string? (date-time) | Market.close_time (string (date-time)) | Market.endDate (string (date-time)) | parse_datetime | |
created_at | string? (date-time) | Market.created_time (string (date-time)) | Market.createdAt (string (date-time)) | parse_datetime | |
settlement_time | string? (date-time) | Market.settlement_ts (string (date-time)) | Market.closedTime (string) | parse_datetime | polymarket: closedTime is the actual on-chain settlement timestamp (distinct from endDate, which is the scheduled close). Populated only after settlement. |
best_bid | number? (double) | Market.yes_bid_dollars (FixedPointDollars) | Market.bestBid (number) | fixed_point_dollars / direct | polymarket: Polymarket already in [0,1] decimal. |
best_ask | number? (double) | Market.yes_ask_dollars (FixedPointDollars) | Market.bestAsk (number) | fixed_point_dollars / direct | |
last_trade_price | number? (double) | Market.last_price_dollars (FixedPointDollars) | Market.lastTradePrice (number) | fixed_point_dollars / direct | |
tick_size | number? (double) | Market.price_ranges (array) | Market.orderPriceMinTickSize (number) | tick_size / direct | kalshi: Kalshi exposes a tiered structure (PriceRange array, each with start/end/step strings); the unified value is the smallest step across all tiers — the finest precision the market accepts anywhere on its price curve. Coarser tiers still require their own step, but the single-number unified surface answers “what’s the tightest tick I can ever quote?”. The deprecated scalar tick_size integer is ignored. polymarket: Polymarket publishes a single scalar. |
min_order_size | number? (double) | computed — 1.0 (or 0.01 when fractional_trading_enabled) | Market.orderMinSize (number) | synthetic / direct | kalshi: Kalshi has no upstream min_order_size field. Synthesized from fractional_trading_enabled. Source: fractional_trading_enabled. |
volume | number (double) | Market.volume_fp (FixedPointCount) | Market.volumeNum (number) | fixed_point_count / direct | |
volume_24h | number? (double) | Market.volume_24h_fp (FixedPointCount) | Market.volume24hr (number) | fixed_point_count / direct | |
outcomes | array | computed — per-exchange Vec<Outcome{label, price, token_id}> | computed — per-exchange Vec<Outcome{label, price, token_id}> | synthetic | Kalshi binary: two entries with labels “Yes” and “No”; price computed from yes_ask_dollars/last_price_dollars (No price = 1 - Yes); token_id always None (no per-outcome token surface). Polymarket: zipped element-wise from the stringified-JSON arrays outcomes, outcomePrices, and clobTokenIds. Replaces the previous trio of outcome_prices map + outcome_tokens vec + binary-only token_id_yes/token_id_no. |
condition_id | string? | not exposed | Market.conditionId (string) | direct | |
neg_risk | boolean? | not exposed | negRisk — spec gap | direct | polymarket: Spec gap: the gamma OpenAPI defines negRisk on Event and Template, not on Market, but live /markets and /markets/keyset responses include it on each market object. The parser reads it directly. Re-check on each daily upstream refresh — when Polymarket adds it to Market properties, promote to a regular ref: here. |
neg_risk_market_id | string? | not exposed | negRiskMarketID — spec gap | direct | polymarket: Spec gap (same pattern as neg_risk). |
Source specs
- kalshi ·
schema/upstream/kalshi.openapi.yaml - polymarket ·
schema/upstream/polymarket-gamma.openapi.yaml
Tables are auto-generated fromschema/mappings/. CI fails on unresolved $refs and on type mismatches fortransform: direct. Drift in upstream specs surfaces here on the daily refresh PR.

