Skip to content

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.

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

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.

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.

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 cancel is taken.
  • A place or modify is refused with market_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.

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.

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.

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