Skip to content

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.


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.


  1. Pull markets for the competitions you follow with GET /api/v1/markets.

    GET /api/v1/markets?competitions=NCAAF&status=open&trading=true

    Results are ordered tradeable-first, and the response is paginated: follow cursor until it comes back null.

  2. Group by event_id. Every market on one game shares it. event_start, event_title and participants on any of them describe the game.

  3. 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_id carries the event id, so grouping a whole dump by it alone will not merge two fixtures’ moneylines.

  4. Within a set, key each market on (rules, specifier) and store its market_id. That pair is the leg: rules for winners and draws, the home/away/both prefix in specifier for stat lines, home-relative for handicaps, over for totals. market_id is what you send back when you trade.

  5. Read max_price per market and convert prices into your own representation, minding the units table below.

  6. Subscribe for changes. GET /api/v1/markets is a snapshot; the markets channel pushes only the fields that change afterwards. New markets on a game you already track arrive as market_created, so a live client can map them without another REST call.


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.


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_change24h is a percentage, rounded to a whole number, not a price delta.

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.

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.


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.


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.

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.

Split a stat-line specifier on |. It always has three fields:

  1. the side: home, away, or both for a statistic about the game itself
  2. the statistic code
  3. the line, or NA where 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.


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 game
over_under_r1 first period only
home_winner_f5 first five periods
spread_f2 first two periods

The 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_r1

Two scopes of one rule are two independent markets with separate books. over_under and over_under_r1 on the same game are unrelated instruments.


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 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.


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.


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"
}
v1.5.9Changelogllms.txtllms-full.txt