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

# Market Graph

> Logical relations between Kalshi and Polymarket markets, queried with Cypher over the REST API

> [!NOTE]
>
> The market graph is an **early access** feature and is not included in standard plans. To request access, email [support@probalytics.io](mailto:support@probalytics.io).

## Shape

The market graph links markets whose outcomes depend on each other, within a platform and across Kalshi and Polymarket. It covers five domains: `crypto`, `sports`, `finance`, `weather`, and `elections`. Every relation comes with a price rule that consistent prices must satisfy.

```
(:Market)-[:ASSERTS]->(:Underlying)          the number the market is about
(:Market)-[:MEMBER_OF]->(:OutcomeSet)        options of one event that split one outcome
(:Market)-[:RELATION]->(:Market)             how two markets' outcomes are linked
(:Underlying)-[:OF_GAME]->(:Game)            sports: the game or competition
```

### Market

One node per market. `id` is the Probalytics market ID and joins to [`markets.id`](https://docs.probalytics.io/docs/datasets/markets).

| Field | Meaning |
| - | - |
| `id`, `platform`, `platform_id`, `slug`, `url` | Identity |
| `family` | The platform's product: Kalshi series, or Polymarket event slug with numbers masked |
| `domain` | `crypto`, `sports`, `finance`, `weather`, or `elections` |
| `title`, `status`, `created_at`, `closes_at`, `resolved_yes` | Listing and settlement. `resolved_yes` is `null` while open. |
| `underlying`, `measure` | Key of the number the market asserts and how it is observed. `null` if not modelled. |
| `direction`, `lo`, `hi`, `lo_closed`, `hi_closed` | The Yes condition as an interval of that number. `direction` is `above`, `below`, `range`, `up`, or (sports) `set`. |
| `coverage` | Why a market has no underlying. `null` when modelled. |
| `category`, `subject`, `shape`, `stat`, `predicate`, `outcome_role`, `mechanism`, `payout` | Taxonomy labels, present on every market |

Sports markets also carry `game_id`, `segment`, `unit`, `tie_push`, and settlement facts (`tie`, `postponed`, `cancelled`, `stopped`, `player_out`). A column a domain does not use is `null`.

### Underlying

One observable number: `key`, `domain`, `asset`, `feed`, `measure`, `obs_start`, `obs_end`, `markets`. Markets on the same underlying ask about the same number, which is what makes exact relations possible.

| Domain | Key | `asset` |
| - | - | - |
| crypto | `asset \| feed \| measure \| start \| end` | Coin, e.g. `BTC` |
| sports | `game \| segment \| measure \| unit` | — (use the `Game` node) |
| finance | `instrument \| source \| measure \| start \| end` | Instrument, e.g. `SPX`, `UST10Y`, `NVDA` |
| weather | `station \| source \| measure \| start \| end` | Station or area, e.g. `KNYC`, `usdm:Texas` |
| elections | `race \| source \| measure` | Race, e.g. `US\|2026\|house\|CA-38\|general` |

### OutcomeSet

The options of one event. `kind` is:

* `brackets` — ranges tiling a line; exactly one wins, prices sum to 1
* `selection` — options covering every outcome; exactly one wins, prices sum to 1
* `exclusive` — at most one wins; prices sum to at most 1

Also `members`, `resolved`, and `winners`.

### RELATION

| Field | Meaning |
| - | - |
| `relation` | `IMPLIES`, `EQUIVALENT`, `MUTEX`, `INVERSE`, or `NEAR` (see below) |
| `basis` | The rule that produced the edge |
| `exact` | `true` when the relation holds by construction |
| `cross_platform` | `true` for Kalshi ↔ Polymarket edges |
| `divergence`, `divergence_n` | Share of settled pairs of this kind that resolved inconsistently, and the number of pairs measured |
| `margin`, `margin_bps` | `NEAR` only: the gap between the two levels |
| `holds_if` | Condition under which the edge is guaranteed (sports, elections). `null` means it always holds. |

## Relations

| Relation | Meaning | Price rule |
| - | - | - |
| `IMPLIES` | If A resolves Yes, B resolves Yes | P(A) ≤ P(B) |
| `EQUIVALENT` | A and B always resolve the same way | P(A) = P(B) |
| `MUTEX` | A and B cannot both resolve Yes | P(A) + P(B) ≤ 1 |
| `INVERSE` | Exactly one of A and B resolves Yes | P(A) + P(B) = 1 |
| `NEAR` | Same question on different data sources (e.g. Binance vs. CF Benchmarks) | P(A) ≈ P(B), with risk given by `divergence` |

`IMPLIES` edges are stored only to the nearest neighbour: a 150-rung threshold ladder is 149 edges, and the full chain is a path query. `MUTEX` edges are likewise stored to the nearest conflicting market on each side. Brackets of one event are exclusive through their `OutcomeSet`, not pairwise edges.

Sports `holds_if` values:

* `completed` — the platforms settle a disrupted game differently, so the edge is guaranteed when the game is played to completion
* `played` — they also settle a player who does not play differently
* `played_second_half` — one side is a Kalshi "(Protected)" prop

Elections `holds_if` values (`seated`, `call_stands`, `official_count`, `winner_inaugurated`) mark edges where the platforms settle on different results.

## Coverage

| Domain | Cross-platform edges |
| - | - |
| crypto | `NEAR` (different price feeds), with measured divergence |
| sports | Exact (same game, same quantity), with `holds_if` for disruptions |
| finance | Exact where both platforms read the same publisher (Treasury par yields, AAA gas) |
| weather | `NEAR` only: Kalshi reads the NWS climate day, Polymarket a station's local calendar day; `margin` is in degrees |
| elections | Exact on one race's result, with `holds_if` where settlement sources differ |

The graph covers the full history of Kalshi and Polymarket markets in the five domains, open and resolved. Each Kalshi series or Polymarket event family belongs to exactly one domain, so no market appears twice. Markets outside the five domains are not included; markets inside a domain that could not be modelled are included with `coverage` set.

The graph is rebuilt from the [markets](https://docs.probalytics.io/docs/datasets/markets.md) dataset roughly every 30 minutes.

## Querying the graph

The graph is served over the REST API with the same API key as other endpoints. Requests from accounts without access return `403`.

### Preconfigured queries

```bash theme={null}
curl "https://api.probalytics.io/api/graph/q/relations?id=<market id>" \
  -H "Authorization: Bearer <credential_id>:<credential_secret>"
```

| Name | Returns |
| - | - |
| `market` | The market node |
| `relations` | Every relation to or from the market, with the peer's `id`, `platform`, `title`, `status`, `url` |
| `underlying` | All markets on the same underlying, with `direction`, `lo`, `hi` |
| `outcome_set` | All markets in the same outcome set, with the set's `kind` and `members` |

### Cypher

```bash theme={null}
curl -X POST "https://api.probalytics.io/api/graph/cypher" \
  -H "Authorization: Bearer <credential_id>:<credential_secret>" \
  -H "Content-Type: application/json" \
  -d '{"query": "MATCH (a:Market {id: $id})-[r:RELATION]-(b:Market) WHERE r.cross_platform RETURN r.relation, r.exact, r.divergence, b.platform, b.title", "params": {"id": "<market id>"}}'
```

The response is `{"rows": [...]}`. Each request runs one read-only statement and returns at most 10,000 rows. Queries time out after 30 seconds; when the server is busy with other Cypher queries it returns `429`, so retry with backoff.

Example queries:

```cypher theme={null}
// Everything a market implies (follows the nearest-neighbour chain)
MATCH (a:Market {id: $id})-[r:RELATION* 1..30 (e, n | WHERE e.relation = 'IMPLIES')]->(b:Market)
RETURN DISTINCT b.id, b.title

// All markets on one instrument
MATCH (m:Market)-[:ASSERTS]->(u:Underlying) WHERE u.asset = 'UST10Y'
RETURN m.platform AS platform, u.measure AS measure, count(*) AS markets

// Both platforms' markets for one game
MATCH (g:Game {id: $game})<-[:OF_GAME]-(u:Underlying)<-[:ASSERTS]-(m:Market)
RETURN m.platform, m.title, u.measure, m.lo, m.hi

// Open cross-platform pairs with low divergence
MATCH (a:Market)-[r:RELATION]->(b:Market)
WHERE r.cross_platform AND r.divergence < 0.005 AND a.status = 'ACTIVE' AND b.status = 'ACTIVE'
RETURN a.title, b.title, r.relation, r.divergence
LIMIT 100
```

`UNION ALL` is not supported; run separate queries instead. Combine graph results with [orderbook](https://docs.probalytics.io/docs/datasets/orderbook.md) or [markets](https://docs.probalytics.io/docs/datasets/markets.md) data in SQL by market `id` to check price rules against live prices.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.