Markets
Each market is one window on one asset: Bitcoin from 14:50 to 14:55 UTC, say. It asks whether the price at the end is at or above the price at the start. You trade two outcomes, Up and Down, and one of them wins. If no price can decide the market, it is void and neither outcome wins. See How a market resolves.
Windows are one or five minutes long and run back to back. Each asset has its own trading hours.
The market record
Section titled “The market record”/api/markets/market, /api/markets/markets and /api/markets/schedule all
return markets with these fields:
| Field | |
|---|---|
market_id |
The number you use to trade it. See Market ids. |
slug |
The window’s name, e.g. btc-updown-5m-20260929145000. |
asset, category, duration |
What it is on, and how long. |
series |
The line of windows it belongs to: the slug without its time, like btc-updown-5m. |
open_ms, expiry_ms |
When the window starts and ends. |
status |
Where it is in its life. See below. |
strike |
The asset’s price at the start, in USD, once known. |
settlement |
The price at the end, once known. It can be set before the market settles. |
winning_outcome |
up, down or void. null until the vault resolves the market. See How a market resolves. |
venue, symbol, candle |
Where strike and settlement come from. |
taker_fee_rate_bps, maker_fee_rate_bps, maker_rebate_share_bps |
The market’s fee rates. See Fees. |
The asset is btc, wtioil or nvda. The duration is 1m or 5m.
The two list routes take these as filters. /api/markets/schedule takes asset
and duration, alone or together. /api/markets/markets takes series,
status, asset and duration. It needs at least one of series, status or
asset, so duration alone is 400. If series conflicts with asset or
duration, the page is empty. A value the route does not list is 400. The
status filter takes open, live, halted, closed or settled. See the
HTTP reference.
/api/markets/markets and /api/markets/schedule return one page of markets
and a cursor for the next page. /api/markets/schedule lists the windows that
have not ended, in order of open_ms, then market_id. It lists open and
live windows, and a window that an operator closed early. A before or
after that names no market is 400. To read the pages, see the
market data guide.
/api/markets/markets lists the newest listed market first: the order is the
time each market was listed, then market_id.
Market ids
Section titled “Market ids”The markets service builds the market_id from the market’s series and end
time. Every id is a whole number from 8,589,934,592 to 9,007,199,254,740,991
(2^53 − 1). An id is never reused, not even after a restart.
Ids do not rise in the order markets are listed. Do not use an id to find the
time of a market. Read open_ms or expiry_ms.
Life of a market
Section titled “Life of a market”A market goes from open to live to closed to settled.
status |
Means |
|---|---|
open |
Listed before its window starts, and taking orders. strike is not set yet. |
live |
The window has started and strike is set. The market takes orders until expiry_ms. |
halted |
Trading is halted. It is shown instead of open or live. See Halted trading. |
closed |
Trading has ended, at expiry_ms or because an operator closed the market. winning_outcome is null. |
settled |
The outcome is recorded and positions pay out. winning_outcome is set, to void if no price decided the market. |
Trading stops at expiry_ms, by the exchange’s own clock. From that time, a
place or modify is refused with market_not_open. A cancel still works
until the market closes.
A moment later the market closes: its status becomes closed, and the
trading socket sends market_closed to everyone. Your resting orders in it are
cancelled. Your positions stay open.
The market settles after the vault resolves it. When a price decides the
market, this is usually within a few minutes after expiry_ms. Until then winning_outcome
is null. When the market settles, the trading socket sends market_settled
with the outcome to everyone. Your positions pay out: each winning lot pays
10,000 units. If the market is void, each lot pays 5,000 units, on either side.
The market data socket reports the same changes as a status message to the
clients that subscribed to status. It uses its own status words. See the
market data guide.
Halted trading
Section titled “Halted trading”The exchange halts trading in every market at once. While the halt lasts, each
market that is open or live has the status halted. When the halt
ends, the markets show open or live again.
In a halted market:
- A
cancelis taken. - A
placeormodifyis refused withmarket_halted. See Errors. - The market closes and settles on time, as it does without a halt.
Trading halts in these cases:
- The vault has stopped taking batches of fills.
- An operator halts trading by hand.
A fill has a fee. The takers pay it. The makers pay nothing at launch, and get back a share of the taker’s fee on the same fill.
Each market has three rates, fixed when it is listed. They never change for that market.
| Field | Means |
|---|---|
taker_fee_rate_bps |
The taker’s rate on the fee curve. |
maker_fee_rate_bps |
The maker’s rate on the same curve. It is 0 at launch. |
maker_rebate_share_bps |
The part of the taker’s fee that the maker gets back, in basis points of that fee. |
The fee is on a curve that is highest at a price of 50¢. It is smaller as the
price moves toward 0¢ or 100¢. For one fill, let c be the cost of one lot of
the side in units: the price in basis points for a bid, and 10,000 minus the
price for an ask. Both sides of a fill have the same fee on the curve.
fee = floor(lots × rate × c × (10,000 − c) / 10^8)The fee is in units (1,000,000 to the dollar). It is rounded down once for each
fill. The maker’s fee is the same formula at maker_fee_rate_bps, less
floor(taker fee × maker_rebate_share_bps / 10,000). A negative maker fee is a
rebate paid to the maker.
For example, a market has taker_fee_rate_bps 350 and maker_rebate_share_bps
2,000, which are the Bitcoin rates at launch. A taker buys 100 shares of Up at
50¢ from a resting ask. That is 10,000 lots, and c is 5,000.
- The taker’s fee is floor(10,000 × 350 × 5,000 × 5,000 / 10^8), which is 875,000 units ($0.875).
- The maker’s fee is 0 less floor(875,000 × 2,000 / 10,000), which is −175,000 units. The maker gets $0.175.
At 30¢, the same fill costs the taker 735,000 units. At 10¢ or 90¢, it costs 315,000 units.
The fee comes out of the account’s cash when the fill happens. Each fill
message carries the fee and the liquidity (taker or maker) of your side.
A new order holds the most taker fee its fills can pay, so a taker fee can
make an order fail the collateral check.
Your account’s /fills and /portfolio routes show the same fee and
liquidity for each fill. fee is positive when you paid it and negative for a
rebate. /portfolio gives fees for each position, the sum of the fee of the
fills listed under it. /pnl gives fees for all time and for a range. Each
fees is the fees paid less rebates, in units.
realized_pnl, pnl and cost_basis leave fees out. On /pnl, subtract
fees from realized_pnl to get your result after fees.
An order’s max_fee_bps is the most fee it accepts, in basis points of the
fill’s cost. At a price near 0¢ the taker fee is nearly taker_fee_rate_bps of
the cost, and at every other price it is less. So the gateway rejects a place
with a max_fee_bps below taker_fee_rate_bps, with
max_fee_below_market_rate. Sign max_fee_bps at least that. See
Signing orders.
Trading hours
Section titled “Trading hours”Bitcoin trades all the time. Nvidia trades only while NASDAQ is open. Crude oil
(WTI) trades only while the CL contract trades on CME Globex. The hours come
from each exchange’s calendar, with its weekends, holidays and early closes.
A window is listed only if it starts inside one session of its home exchange, and its end plus 10 seconds comes before that session closes. The 10 seconds are the longest wait for the price at the end. See How a market resolves. So the last window of each Nvidia and crude oil session is not listed.
If the vault prices an asset from Pyth, the window must also fall inside the trading hours of that Pyth feed. The testnet vault prices no asset from Pyth, so only the exchange calendars apply there. A local test stack does not check Pyth’s hours.
Outside a series’ hours it has no windows. At the weekend, for example,
/api/markets/schedule can list no Nvidia window.
To find out when a series opens next, read /api/markets/series. It takes no
parameters and returns one row for each series, in a fixed order. This example
shows two of the rows:
{"series": [ {"series": "btc-updown-5m", "asset": "btc", "duration": "5m", "category": "crypto", "next_open_ms": null}, {"series": "nvda-updown-5m", "asset": "nvda", "duration": "5m", "category": "tradfi", "next_open_ms": 1791811800000}]}next_open_ms is the start of the series’ next window that will trade, in epoch
milliseconds. It is null while the current window trades, and always null for
Bitcoin. If the market calendar can’t be read, the route returns 503 with
{"detail": "market calendar unavailable"}. Ask again later.
How a market resolves
Section titled “How a market resolves”The vault decides the outcome. Up wins if the price at the end is at or above the price at the start. Otherwise Down wins.
The vault takes each price from a price feed: the first price published at or
after the boundary, if it comes within 10 seconds of it. The boundary is
open_ms for the start and expiry_ms for the end. The end of one window is
also the start of the next, so they share one price. The feed is Pyth, or on
testnet our own price reporter. See Who decides on testnet.
The markets service reads the outcome from the vault and settles the market
with it. This usually takes a few minutes after expiry_ms. For that time the market
is closed and winning_outcome is null.
The markets service also records the prices itself. They are the strike and
settlement of the market, and you can read them while the market is
closed. If they differ from the vault’s, the vault’s outcome stands.
The venue, symbol and candle fields name the source of the prices. On
testnet they are:
| Asset | venue |
symbol |
candle |
|---|---|---|---|
| Bitcoin | binance |
BTCUSDT |
1s |
| Crude oil (WTI) | hyperliquid |
xyz:CL |
1m |
| Nvidia | hyperliquid |
xyz:NVDA |
1m |
A price is the open of the candle that starts at the boundary. So strike is
the open at open_ms, and settlement is the open at expiry_ms.
If the vault prices an asset from Pyth, venue is pyth, symbol is the Pyth
feed and candle is null. The feeds are Crypto.BTC/USD for Bitcoin,
Commodities.Index.WTI1M/USD for crude oil and Equity.US.NVDA/USD for
Nvidia. We don’t redistribute Pyth prices. Read them from Pyth if your strategy
needs them.
A local test stack with no chain shows the same values. It decides the outcome from those prices, and it has no void.
Who decides on testnet
Section titled “Who decides on testnet”Until we pay for Pyth, one key of ours decides every testnet market. The exchange uses that key to post the price for each boundary. The vault accepts the price with no independent check, unlike Pyth.
If the key leaks, or we or the markets service make an error, the winners can be wrong. Money moves only between traders in the same market. The key cannot create cash, take deposits or change roles.
Every price is public on chain. Anyone can check it against Binance and Hyperliquid afterwards. If the key goes silent, anyone can void a market 24 hours after it ends, and each lot then pays half. See A void market.
A void market
Section titled “A void market”A market is void when the vault has no price for its start or its end, from
any of the price sources it may use. The vault then sets the outcome to void,
and the market settles with winning_outcome void.
Each lot you hold pays 5,000 units ($0.005), whether you hold Up or Down. A share of 100 lots pays $0.50. Your fills stand: the vault does not undo a trade.
A void comes later than a normal resolution. The vault’s admin can void a market after a wait that the vault sets for the series. Anyone can void it, but not before 24 hours after its end. If the series has a fallback price source, nobody can void the market while that source may still decide it.
Until the vault voids the market, it stays closed and winning_outcome is
null.