Markets
Prices
Two numbers, and the whole product is organised around not confusing them. The reference probability is where the market is. The execution price is what a trade costs.
referenceProbability and executionPrice
| referenceProbability | executionPrice | |
|---|---|---|
| What it is | Parity’s normalised view of where the market sits. Usually the midpoint of the book. | A side of a real book. What the trade actually crosses. |
| Who has offered it | Nobody. A midpoint is a statistic, not an offer. | Somebody. A resting order sits there. |
| Where it appears | probability, yesCents, noCents on the catalogue | executableYesCents / executableNoCents on the catalogue (in cents), execution.buyYes / execution.buyNo on the live price stream (0 to 1), and executionPrice on a quote |
| Use it for | Showing a player “63% chance”. | Charging a player, and computing a payout. |
Swipe the table sideways for more columns.
They differ enough to matter
Measured across 22,586 open contracts in this catalogue, the ask sits a median 1.5¢ above the reference, 31¢ at the 90th percentile and 59¢ at the 99th. Roughly one contract in four differs by more than 10¢. The worst observed case displays 0.5¢ and executes at 99¢.
A ticket that renders the reference shows a number the player will not be charged, and a payout computed from it is wrong in the player's favour every time.
priceBasis
Every quote and every catalogue row says which of the two you are holding. Read it: a reference-priced quote is an estimate wearing a price's clothes, and you are entitled to know which one you have.
| Value | Where | Meaning |
|---|---|---|
book | Quote | A real side of a real order book. A buy crossed the ask. |
reference | Quote | No usable two-way price upstream, so this is the normalised probability. An estimate. |
midpoint | Catalogue | Reference derived from bid and ask. |
last | Catalogue | Reference derived from the last trade. |
source | Catalogue | Reference taken from a probability the source published directly. |
none | Catalogue | No price could be derived at all. |
Swipe the table sideways for more columns.
How a side is priced
- Buying YES crosses the YES ask. Selling YES hits the YES bid.
- Where a source publishes a NO book directly, that book is used. Where it does not, NO is mirrored from YES: a NO ask is
1 − (YES bid), which is arithmetically exact for a binary contract. - A crossed book, bid at or above ask, is discarded on both sides and the quote falls back to the reference. Bad data that looks like a gift is exactly what a router would otherwise pick every time.
- A spread wider than the configured maximum (0.2 by default) is treated as no usable two-way price: an illiquid book's midpoint is noise, not a probability.
- 0 and 1 are settled, not tradeable. Neither is ever published as a price.
When there is no executable price
On a catalogue outcome, executableYesCents and executableNoCents are null whenever the book publishes no usable side, and so are execution.buyYes and execution.buyNo on the same contract in the live price stream. That null is a value, not a gap in the response.
"yesCents": 63.4, // reference: a midpoint
"noCents": 37.2,
"executableYesCents": 65.1, // what a YES buy costs
"executableNoCents": null, // no usable side: show no price
"priceBasis": "midpoint""probability": 0.634,
"execution": { "buyYes": 0.651, "buyNo": null, "basis": "book" },
"tradeable": trueNever fall back to the reference
A null means show no price and disable the control. It does not mean “use yesCents instead”. The reference is a midpoint nobody has offered; substituting it renders a number the player cannot trade at, and the substitution is invisible to them.
One side may be executable while the other is not. Price and enable each side independently: a tradeable YES does not make NO tradeable.
- On the stream,
execution.basisisbookwhen at least one side came off a real two-way price, andreferencewhen neither did. Read the per-side value for what to display, and the basis for how much to trust the row. - A quote is the authority, not the catalogue. The catalogue tells you whether to offer a control; the quote tells you what the trade costs. Never charge from a catalogue price.
- An outcome with no executable side can still be shown as a market with a probability. What it cannot be shown with is a price or a buy button.
Precision, and why sub-cent prices are real
Catalogue prices are decimals between 0 and 1. The cent figures, yesCents and noCents, are rounded to the tenth of a cent, which is the finest tick any upstream actually uses. 63.4 is a legitimate value and so is 0.3.
Do not round to whole cents
Thousands of open contracts in this catalogue trade below one cent. Rounding a price of 0.4¢ to zero turns a long-shot contract into a free one, and a payout computed from a zero price is a division by zero or an infinite position, depending on which line runs first.
- Carry prices at the precision you receive them, all the way to display. Round for the player's eye only, at the last step, and never before a calculation.
- Contract counts are returned rounded to four decimal places for transport. The stored quote keeps full precision and is what the fill is checked against: so do not recompute contracts from a rounded figure and expect a match.
- Money amounts are cents-exact. On a quote the stake is rounded to the cent once, the fee rounds up to the cent and
potentialPayoutrounds down to the cent. The settlement credit is computed separately, from the contracts actually held, and also rounds down to the cent, so it matches the quoted payout for the same contracts. Prices are not money, and rounding them the same way is the error this section exists to prevent. - Never store a price as an integer number of cents. It is the same bug as rounding, held for longer.
Freshness
Prices carry an age, and the age decides what may be traded. Thresholds depend on the feed and on the kind of contract: a streamed price 60 seconds old means the socket broke while a polled price 60 seconds old is simply normal, and a five-minute contract or a fixture being played moves too fast for either.
| Level | Streamed | Polled | Live tier | Five-minute | Effect |
|---|---|---|---|---|---|
| fresh | ≤ 30s | ≤ 90s | ≤ 20s | ≤ 5s | Trades at any size. |
| aging | ≤ 90s | ≤ 300s | ≤ 40s | ≤ 20s | Stake capped ($250 by default). Over the cap is 422 stake_exceeds_stale_limit with maxStake. |
| stale | ≤ 300s | ≤ 900s | ≤ 120s | ≤ 45s | Stake capped ($25 by default). Over the cap is 422 stake_exceeds_stale_limit with maxStake. |
| suspended | > 300s | > 900s | > 120s | > 45s | No quote at any size: 409 price_stale. |
Swipe the table sideways for more columns.
The live tier applies to a fixture that has kicked off and to any market within 15 minutes of its close. The five-minute column applies to the recurring five-minute crypto contracts. Where a price will not be confirmed against a fresh book when the order is submitted, each band takes the cap of the band below it: fresh is capped at the aging figure, and aging and stale at the stale figure.
| Contract | Quote lifetime |
|---|---|
| Ordinary market | 30 seconds by default (configurable per platform, 5 to 300) |
| Live tier (in play, or within 15 minutes of close) | 10 seconds |
| Five-minute contract | 5 seconds |
Swipe the table sideways for more columns.
- A missing timestamp is treated as
suspended, not fresh. An unknown age is precisely the case where the price could be hours old. - The gate runs on the server, at quote time and again at order time. A quote that was fresh when issued can go stale inside its own lifetime.
- Every quote returns its own verdict in
freshness. Render that rather than recomputing your own: the two disagreeing is the bug the field exists to prevent.
"freshness": { "level": "fresh", "ageSeconds": 5, "feed": "websocket" }Note
Most of the catalogue is polled rather than streamed: the live socket budget is finite and goes to the contracts with demand behind them. A cold contract is legitimately minutes behind for most of every cycle, which is why the polled bands are wider rather than the streamed bands being loose.
Live prices in a browser
An embedded surface does not have to poll, and does not have to be given a server to poll from. GET /api/v1/stream/prices?contracts=ctr_…,ctr_… is a Server-Sent Event stream: one connection per page, however many contracts it watches, pushing when a price moves rather than on a timer.
const feed = new EventSource(
'/api/v1/stream/prices?contracts=' + ids.join(','),
);
// Everything you hold. Safe to replace state with.
feed.addEventListener('snapshot', (e) => replace(JSON.parse(e.data).quotes));
// Only what changed. Patch, never replace.
feed.addEventListener('update', (e) => patch(JSON.parse(e.data).quotes));
// The FEED went quiet, as opposed to one price being old.
feed.addEventListener('status', (e) => setFeedStale(JSON.parse(e.data).stale));- The fan-out is Parity's. A thousand browsers watching one contract cost one upstream venue subscription, not a thousand. There is nothing to protect by proxying this behind your own server.
- Reconnection is the browser's.
EventSourcebacks off and replaysLast-Event-IDon its own (the stream asks for a 3-second retry). The first frame on every connection ishello. When the resume can be honoured,hello.gapis false and anupdatefollows with only what changed; when it cannot,hello.gapis true and a fullsnapshotfollows, which replaces your state. - Every frame, and what it carries.
hello: what you are subscribed to, what was refused and why (unknown,limit,capacity,not_offered), the per-connectionlimit(128 contracts by default) andheartbeatMs.snapshotandupdate: quotes.heartbeat(every 15 seconds by default): the feed's age and whether it has gone quiet.status: the feed went stale or recovered.revoked: contracts withdrawn from a live connection.pulse: recent trade activity for animation, sent without anid:and never replayed.error:{ code, message }. - Each quote is
{ contractId, status, probability, yes, no, execution: { buyYes, buyNo, basis }, asOf, ageSeconds, freshness, tradeable, rev }, with prices as fractions from 0 to 1, not cents, andfreshnessone offresh,aging,stale,suspended. - Offering filter needs a header. An embed session applies your offering policy when sent as
Authorization: Embed <token>. A browser's nativeEventSourcecannot set headers, and a token in the query string is refused, so use a fetch-based event-stream reader when you need the filter. With no credential the connection serves the unrestricted public catalogue. - No API key, on purpose. A browser cannot hold one. Every frame is built from opaque
ctr_…ids, normalised prices and an age: no venue is named, no venue id is published, nothing is user-specific. Where an embed session identifies your account, your offering policy still filters what that connection receives. - A price that stops ticking does not stay looking live. Each quote carries
freshnessandtradeable, and a frame arrives when a price crosses a band even though nothing new came in.tradeable: falseis the platform saying it will not quote that contract right now. The price is still sent, because the last known price is real information, but it is sent labelled.
Fees and edge
Two independent levers, deliberately not folded together. An edge is hidden in the price; a fee is an explicit charge a player can see.
| Lever | Default | How it works |
|---|---|---|
| Platform fee | 120 bps (1.2%) | Deducted from the stake before contracts are bought. A $50 stake wagers $49.40. |
| House edge | 0 bps | Applied on top of the reference probability, widening YES and NO. Zero by default: Parity’s model is the transaction layer, not taking the other side. |
Swipe the table sideways for more columns.
stake 50.00
− fee 0.60 (120 bps, rounded up to the cent)
= wagered 49.40
÷ price 0.72 (executionPrice)
= contracts 68.6111
× $1.00 each
= payout 68.61 (quoted: rounded DOWN to the cent)
− stake 50.00
= profit 18.61 (measured against the FULL stake)- The fee rounds up to the cent and the QUOTED payout rounds down, so the figure a customer is shown is never more than they will receive. The SETTLEMENT credit is rounded down to the cent the same way, from the contracts actually held: 14.529411764705882 contracts credit $14.52, which is the payout a quote for those contracts shows. Book what the settlement instructs; do not recompute it.
- Profit is measured against the full stake, because that is what left the player's balance.
- The platform fee and house edge above are defaults. Your own rate is set in your commercial terms, and every quote echoes the rate it was struck at in
platformFeeBpsandcommercialTerms. venueFeeisnull(withvenueFeeKnown: false), not0, when the execution fee is not being priced into your quotes: writing zero would assert a fact Parity does not have. Where it is priced in, the rate is taken per contract (the catalogue publishes it asvenueFeeRate),venueFeecarries the amount andvenueFeeKnownistrue. Either way the quote is the figure to show.

