Mapping markets
How a market on the exchange corresponds to a real game and a real outcome, and which fields to key your own records on.
It covers the core game markets: winners, draws, totals, handicaps, and team and
event stat lines. Player props follow a different specifier grammar and are not
covered here; see What this page does not cover.
For the symbol grammar see Market symbols, and for the lifecycle values see Market and order status.
Start here: one game, twelve markets, nine books
Section titled “Start here: one game, twelve markets, nine books”Markets do not have a “both sides” object. Each outcome is its own market with its own order book. A real NCAAF game carries twelve, and those twelve form nine sets of mutually exclusive outcomes: the moneyline pair is one set, the three handicap lines are one set, and each stat question is its own.
grouping_id is the field that says which set a market belongs to. Every value
below is prefixed by the event’s id, elided here as {event} to keep the table
readable.
rules |
specifier |
grouping_id |
Settles yes if |
|---|---|---|---|
home_winner |
null |
{event}:moneyline:full |
Duke win |
away_winner |
null |
{event}:moneyline:full |
Tulane win |
spread |
"-4.5" |
{event}:spread:full |
Duke win by more than 4.5 |
spread |
"-7.5" |
{event}:spread:full |
Duke win by more than 7.5 |
spread |
"-13.5" |
{event}:spread:full |
Duke win by more than 13.5 |
event_stat_line |
"home|FIRST_SCORE|NA" |
{event}:esl:full:FIRSTSCORE:HOME |
Duke score first |
event_stat_line |
"away|FIRST_SCORE|NA" |
{event}:esl:full:FIRSTSCORE:AWAY |
Tulane score first |
event_stat_line |
"home|LAST_SCORE|NA" |
{event}:esl:full:LASTSCORE:HOME |
Duke score last |
event_stat_line |
"away|LAST_SCORE|NA" |
{event}:esl:full:LASTSCORE:AWAY |
Tulane score last |
event_stat_line |
"both|FIRST_SCORE_TOUCHDOWN|NA" |
{event}:esl:full:FIRSTSCORETOUCHDOWN:BOTH |
The first score is a touchdown |
event_stat_line |
"both|ANY_SCORE|39.5" |
{event}:esl:full:ANYSCORE:BOTH |
Either team scores 40+ |
event_stat_line |
"both|OVERTIME|NA" |
{event}:esl:full:OVERTIME:BOTH |
The game goes to overtime |
Four things to take from that list.
A moneyline is two markets, not one market with two sides. Their books are
independent, and their prices need not sum to max_price. They share a
grouping_id, which is how you know they are the same question seen from both
ends.
One rules value can appear several times on the same game, at different
specifiers: three handicap lines above, all one set. (event_id, rules) does
not identify a market; (event_id, rules, specifier) does.
A shared grouping_id does not identify a market either; that is the
point of it. Five of the twelve markets above sit on a grouping_id they share
with another market.
Not every family exists on every game. That game has no totals market at all. Build your mapping from what the API returns, not from a template of what a game “should” have.
If that table makes sense, the rest of this page is detail. The three fields below and the six steps after them are the whole job; everything later is reference for the moments when one of those steps is not obvious.
The three fields that map a market
Section titled “The three fields that map a market”Mapping has two levels, and conflating them is the most expensive mistake on this page. Your system almost certainly holds an instrument (“the Duke/Tulane handicap ladder”) with several legs. Ours holds one market per leg. Three fields carry that structure, and each answers a different question.
| Field | Question it answers | Unique per market? |
|---|---|---|
grouping_id |
Which set of mutually exclusive outcomes? The book. | No, deliberately: siblings share it |
rules + specifier |
Which outcome within that set? The leg. | Yes, with event_id |
market_id |
What do I send back to trade? The handle. | Yes |
A complete mapping stores all three: grouping_id to line our book up against
your instrument, rules and specifier to line up the leg, market_id to act.
{ "market_id": "0a5f9c31-6d24-4b17-9e83-c1f7a0d5b862", "event_id": "272b75e2-77d2-408b-b9f6-d7f1090646fc", "grouping_id": "272b75e2-77d2-408b-b9f6-d7f1090646fc:spread:full", "grouping_name": "Spread", "rules": "spread", "specifier": "-7.5"}Why grouping_id is worth reading rather than deriving
Section titled “Why grouping_id is worth reading rather than deriving”For game markets you could work the set out yourself: the family and the scope
both follow from rules, and the strike from specifier. For season futures
you cannot. A division_winner market’s set is its division, which comes from
the standings we hold and you do not, and the filter fields that used to carry
it are empty on every settled market. One NFL season event holds 96 markets in
11 sets; nothing else in the payload separates them.
grouping_id is the only part of the market relation that is not computable
from the rest of the payload. That is the reason it exists.
Map a game in six steps
Section titled “Map a game in six steps”-
Pull markets for the competitions you follow with
GET /api/v1/markets.GET /api/v1/markets?competitions=NCAAF&status=open&trading=trueResults are ordered tradeable-first, and the response is paginated: follow
cursoruntil it comes backnull. -
Group by
event_id. Every market on one game shares it.event_start,event_titleandparticipantson any of them describe the game. -
Group by
grouping_id. Each group is one set of mutually exclusive outcomes, one of your instruments. This works on an unfiltered bulk pull too:grouping_idcarries the event id, so grouping a whole dump by it alone will not merge two fixtures’ moneylines. -
Within a set, key each market on
(rules, specifier)and store itsmarket_id. That pair is the leg:rulesfor winners and draws, thehome/away/bothprefix inspecifierfor stat lines, home-relative for handicaps, over for totals.market_idis what you send back when you trade. -
Read
max_priceper market and convert prices into your own representation, minding the units table below. -
Subscribe for changes.
GET /api/v1/marketsis a snapshot; themarketschannel pushes only the fields that change afterwards. New markets on a game you already track arrive asmarket_created, so a live client can map them without another REST call.
Identifying a market
Section titled “Identifying a market”| Field | What it is | Use it for |
|---|---|---|
market_id |
UUID, fixed for the life of the market | The key in your own store. Every order, cancel and channel topic takes it |
grouping_id |
Opaque string shared by every market in one set of mutually exclusive outcomes | Lining our book up against your instrument. Compare for equality; never parse |
event_id |
UUID shared by every market on the game | Grouping a game’s markets. Needs no string handling |
rules |
What the market asks, machine-readable | Deciding how to interpret specifier, and which leg of a set this is |
specifier |
The strike: the line, the handicap, the stat | The threshold the result grades against |
grouping_name |
The set in words, e.g. "Spread", "AL East Division" |
Display and logs. Not a key |
symbol |
Readable name, e.g. STXNCAAF-26SEP051530TULNDUKE-SPREADDUKEMINUS7.5 |
Logs, dashboards, anything a person reads |
The mapping key is event_id + rules + specifier, which identifies
exactly one market. grouping_id sits above it and says which markets belong
together. Store market_id alongside whatever
you match on, and you can act on the market without resolving it again.
Money on the wire
Section titled “Money on the wire”The same field carries different units depending on which surface you read it from. This is the single most common source of mapping bugs.
| Field | REST /api/v1/markets |
markets / market_updates channels |
|---|---|---|
max_price |
"1.0000", dollar string |
100, cents |
last_traded_price |
"0.5500", dollar string |
55, cents |
price |
"0.6000", dollar string |
60, cents |
bids[].price, offers[].price |
"0.5300", dollar string |
53, cents |
recent_trades[].price |
"0.5500", dollar string |
55, cents |
Reading the same market both ways returns "0.5300" over REST and 53 on the
markets channel for the identical price level.
Two further details on the REST decimal strings:
- Parse them as decimals. Money has at least four decimal places and some fields carry more, so never compare them as strings.
price_change24his a percentage, rounded to a whole number, not a price delta.
One market is one outcome
Section titled “One market is one outcome”Every market is a contract on a single named outcome. It settles at the
market’s max_price if that outcome happens and at 0 if it does not.
Buying means you expect it to happen; selling means you expect it not
to. You always trade against other participants, never against the exchange.
The outcome is named by two fields together: rules says what kind of question
the market asks, and specifier gives the strike. Everything else on the
payload (titles, questions, descriptions) is prose derived from those two.
Which side a market takes
Section titled “Which side a market takes”| Family | Sides on the exchange | One grouping_id covers |
|---|---|---|
| Winner, 2-way | Two markets: home_winner and away_winner |
Both |
| Winner, 3-way | Three markets: home_winner_regulation_3way, away_winner_regulation_3way, draw_regulation_3way |
All three |
| Totals | One market per line, and it is the over. Sell it to be short the over | The whole ladder |
| Handicap | One market per line, always stated from the home team’s side | The whole ladder |
| Team/event stat | One market per stat, and per team where the stat is team-scoped | One stat on one side |
To pair home_winner with away_winner, match on grouping_id: both sides of
a moneyline carry the same value, and so do all three legs of a three-way and
every line of a ladder.
rules: what the market asks
Section titled “rules: what the market asks”These are the values this page covers. Scoped variants are listed separately under Scope.
rules |
Question | Event types |
|---|---|---|
home_winner |
Does the home team win? | baseball, basketball, football, hockey, soccer, cricket |
away_winner |
Does the away team win? | baseball, basketball, football, hockey, soccer, cricket |
home_winner_regulation_3way |
Home win in regulation, draw excluded | soccer, cricket |
away_winner_regulation_3way |
Away win in regulation, draw excluded | soccer, cricket |
draw_regulation_3way |
Does the game end level in regulation? | baseball, soccer, cricket |
home_winner_regulation_2way |
Home win in regulation, 2-way | soccer, cricket |
away_winner_regulation_2way |
Away win in regulation, 2-way | soccer, cricket |
home_winner_regulation |
Home win in regulation | cricket |
away_winner_regulation |
Away win in regulation | cricket |
over_under |
Do both teams combine for more than the line? | baseball, basketball, football, hockey, soccer, cricket, tennis |
spread |
Does the home team beat the handicap? | baseball, basketball, football, hockey, soccer, tennis |
event_stat_line |
A game-level or team-level stat question | baseball, basketball, football, hockey, soccer |
participant_stat_line |
A team total for one named stat | baseball, basketball, football, hockey, soccer |
The event types listed are the ones each rule is defined for. Which of those markets actually open is a per-environment choice, so a sport can support a rule without any live markets in it at a given moment.
specifier: the strike
Section titled “specifier: the strike”rules |
specifier format |
Example |
|---|---|---|
| All winner and draw rules | null |
null |
over_under |
A half number, the line | "54.5" |
spread |
A signed half number, from the home team’s side | "-7.5" |
participant_stat_line |
{home|away}|{STAT}|{line} |
"home|POINTS|57.5" |
event_stat_line |
{home|away|both}|{STAT}|{line} |
"both|HITS|16.5", "home|FIRST_SCORE|NA" |
Lines are always half numbers, so a totals or handicap market cannot end level and there are no pushes on them.
Handicaps are stated from the home side
Section titled “Handicaps are stated from the home side”spread with "-7.5" is home team wins by more than 7.5; with "7.5" it is
home team wins, or loses by less than 7.5. The away team never gets its own
handicap market; to be on the away side of the line, sell the home market.
Every line of the ladder shares one grouping_id.
Stat lines: three fields, always
Section titled “Stat lines: three fields, always”Split a stat-line specifier on |. It always has three fields:
- the side:
home,away, orbothfor a statistic about the game itself - the statistic code
- the line, or
NAwhere the statistic states no threshold
For countable stats the line is a real over/under threshold:
"both|HITS|16.5" Will both teams combine for more than 16.5 hits?"both|ANY_SCORE|39.5" Will either team score 40+ points?"home|POINTS|57.5" Will the home team have more than 57.5 total points?For yes/no stats there is no threshold, and the line field reads NA:
"both|OVERTIME|NA" Will the game go to overtime?"home|FIRST_SCORE|NA" Will the home team score first?"both|FIRST_SCORE_TOUCHDOWN|NA" Will the first score be a touchdown?NA is the signal, so you do not need to know which statistics are countable
to render a market correctly: a numeric third field means an over/under, and
NA means a proposition. Fall back to the market’s question for display.
The set of stat codes in use is a per-environment setting rather than a fixed part of the API, so treat any list you build as the set in use today and handle an unrecognized code by skipping the market rather than failing.
Scope: markets on part of a game
Section titled “Scope: markets on part of a game”A market that settles over part of a game carries a scope suffix on rules.
No suffix means the full game, which is most markets.
| Suffix | Window |
|---|---|
| (none) | Full game |
_f2, _f5 |
The first 2 or 5 periods |
_r1, _r2 |
Period 1, period 2 |
over_under full gameover_under_r1 first period onlyhome_winner_f5 first five periodsspread_f2 first two periodsThe scope is also a segment of grouping_id, so two scopes of one rule never
share a set:
{event}:total:full over_under{event}:total:r1 over_under_r1Two scopes of one rule are two independent markets with separate books.
over_under and over_under_r1 on the same game are unrelated instruments.
Naming the side: which text to trust
Section titled “Naming the side: which text to trust”Every market carries several human-readable strings, and they are not interchangeable.
| Field | For a home_winner market |
|---|---|
title |
"NCAAF - Week 1 TULN @ DUKE" |
short_title |
"TULN @ DUKE" |
group_title |
"Duke" |
position |
"Duke Blue Devils" |
grouping_name |
"Moneyline" |
question |
"Will the Duke Blue Devils defeat the Tulane Green Wave?" |
description |
"Contracts for this market settle into $1 if the Duke Blue Devils beats the Tulane Green Wave and settle into $0 if they do not." |
Handicap and totals markets do include the line in short_title
("TULN @ DUKE -7.5", "BC @ CIN OU 54.5"), which is why the problem is easy
to miss until a moneyline reaches your book. position is also unreliable on
stat-line markets, where it can be the bare stat code ("FIRST_SCORE"); for
those, grouping_name reads as the statistic and its side: "Last Run (BOS)".
participants describes the fixture, not the market
Section titled “participants describes the fixture, not the market”participants is the game’s two teams (role of away and home, away
first), and it is the same on every market of that game. It does not tell
you which side a market settles on. Read that from rules for winner markets,
and from the side prefix in specifier for stat lines.
"participants": [ {"name": "Tulane Green Wave", "role": "away", "short_name": "Green Wave", "abbreviation": "TULN"}, {"name": "Duke Blue Devils", "role": "home", "short_name": "Blue Devils", "abbreviation": "DUKE"}]max_price is a per-market field
Section titled “max_price is a per-market field”max_price is the settlement value of one winning contract, in dollars, and the
ceiling on order prices. An order must price strictly below it.
Read it from each market. It is not a per-region constant: a single environment can carry markets at several different values at the same time, and one competition’s markets can differ from another’s. A client that hardcodes a value will have orders rejected, or will misprice settlement value and sell-side liability.
{"symbol": "STXNCAAF-26SEP051530TULNDUKE-GAMEDUKE", "max_price": "1.0000"}max_price also sets the reference point for sell-order liability; see
Understanding positions.
The order book on a market
Section titled “The order book on a market”bids and offers on a market carry the top seven price levels per side,
each a {price, quantity} object, with quantity aggregated across all resting
orders at that price. They are a summary for display and mapping, not a feed to
trade from: for the full aggregated book on one market, join the
order book channel.
Both arrays are sorted by price descending. For bids that puts the best price first; for offers it puts the best price last.
"bids": [ {"price": "0.4900", "quantity": "120.00"}, {"price": "0.4700", "quantity": "40.00"}, {"price": "0.4500", "quantity": "75.00"}],"offers": [ {"price": "0.5700", "quantity": "60.00"}, {"price": "0.5400", "quantity": "25.00"}, {"price": "0.5100", "quantity": "90.00"}]Best bid is bids[0] at 0.49; best offer is offers[len - 1] at 0.51.
Taking offers[0] gives you the worst offer in the summary, up to seven levels
away from the touch: a mistake that reads as a wide spread rather than as an
error.
Best bid and best offer are enough to compute the implied probability of the
outcome: a market with a best bid of 0.49 and a best offer of 0.51 on a
max_price of "1.0000" is trading around a 50% chance.
Each market in a set has its own book. A set is a set of related contracts, not a combined book.
What this page does not cover
Section titled “What this page does not cover”You will see these markets in the API. Identify them by rules and handle them
deliberately rather than letting them fall into your game-market mapping.
rules |
What it is |
|---|---|
player_stat_line |
Player props. specifier is {player}|{player_id}|{STAT}|{line}: four fields, and the player’s team is not in the payload |
division_winner, conference_winner, superbowl_champion, and similar |
Season futures. specifier is a team abbreviation, and the market is not tied to a single game |
race_winner |
A field of participants rather than a fixture |
ad_hoc_rule |
Hand-created markets with no machine-readable settlement definition; description is the only statement of what settles |
combo_rule |
Combination markets built from other markets’ legs; participants is empty |
The identity rules on this page hold for all of them: market_id is the key,
event_id groups a game, grouping_id groups a set, and rules plus
specifier name the outcome. Only the specifier grammar and the settlement
source differ.
grouping_id matters most on the season futures, which is the one shape where
event_id is not enough. A single NFL season event carries 96 markets in 11
sets (the Super Bowl, both conferences and all eight divisions), and grouping
that event by event_id pools them into one. grouping_name reads as the set:
"AFC South Division", "American League", "World Series".
{ "rules": "division_winner", "specifier": "JAX", "grouping_id": "201e3810-064d-4ff3-8246-5b9be6dfef0f:division_winner:full:AFCSOUTHDIVISION", "grouping_name": "AFC South Division"}
