Binary markets and where their prices come from

Managing /admin/finance/binary/market — the EXCHANGE and ECOSYSTEM price feeds, importing pairs, per-market stake limits, and how to retire a market without stranding open orders.

7 min readUpdated 6 August 2026binary, markets, ecosystem, exchange, limits

A binary market is a row that says "customers may buy a contract on BTC/USDT". It is not a price feed, and it does not inherit one. Every row carries a source column that names which of the platform's two entirely separate price series backs it:

source Where the price comes from Set by
EXCHANGE your centralized exchange provider, through ccxt the default for every row
ECOSYSTEM the matching ecosystem market, and the AI Market Maker driving it when one is running choosing Ecosystem Markets in the create wizard

The two are different numbers for the same pair. Entry price, settlement price and — if you run the Binary AI Engine — the price the engine steers are all resolved from this one column, in backend/src/api/exchange/binary/order/util/marketSource.ts. Get it wrong and the price a customer trades against is not the price their money settles on.

When source is ECOSYSTEM and no ecosystem market exists for that pair, order placement fails with Ecosystem market {symbol} not found — it does not quietly read the centralized exchange, even when the same pair is listed there. That refusal is deliberate. A market-maker pair that also trades on the CEX must never settle on the CEX price.

The screen

Admin → Finance → Binary Options → Binary Markets (/admin/finance/binary/market). Opening it needs access.binary.market.

The table shows:

Column Type Notes
id text UUID. Only visible when you expand a row
currency text base symbol — BTC
pair text quote symbol — USDT
minAmount number smallest stake. Expanded rows only
maxAmount number largest stake. Expanded rows only
isTrending boolean merchandising badge
isHot boolean merchandising badge
status boolean inline toggle — writes straight to the API

currency, pair and status are the priority-1 columns; isTrending and isHot are priority 2, which still puts all five on screen at every width. The other three are flagged expanded-only and appear when you expand a row.

The value is stored on every row and returned by GET /api/admin/finance/binary/market/{id}, but there is no column for it in the table and no control for it on the edit screen. The only place a human chooses it is step 1 of the create wizard.

The update route also ignores it: PUT /api/admin/finance/binary/market/{id} reads currency, pair, minAmount, maxAmount, isTrending, isHot and status from the body and nothing else, so sending source changes nothing. Changing a market's feed means deleting the row and creating it again.

To read a market's feed without touching the database, open /api/admin/finance/binary/market/<id> while signed in as an admin — the JSON carries source.

Adding markets

There are two routes in, and they behave differently.

The create wizard — one market, you pick the feed

/admin/finance/binary/market/create, or the Create button on the table. Three steps:

  1. Select Source — two cards, Exchange Markets and Ecosystem Markets, each with a live count of pairs you have not already added. The ecosystem card is the one that carries AI Market Maker support.

  2. Select Market — the pairs available from that source, searchable. Pairs that already exist as binary markets are filtered out, so an empty list means "all of them are already added", not "none found". On the ecosystem tab each card is badged with whether an AI Market Maker and a Binary AI Engine are configured for it.

  3. Configure — the Active status, Trending and Hot switches. Active defaults on, the other two off.

The wizard reads GET /api/admin/finance/binary/market/available (view.binary.market), which returns both lists plus the pairs you already have, and posts to POST /api/admin/finance/binary/market (create.binary.market) with the source you chose.

Two refusals you will meet here:

  • Picking Ecosystem for a pair that has no ecosystem market is rejected with "No ecosystem market exists for X/Y. Create the ecosystem market first, or import this pair from the exchange instead."
  • If the request arrives with no source at all — a script, or an older client — the backend infers one, and it is deliberately conservative: ECOSYSTEM only when the pair exists as an ecosystem market and not on the exchange. Anything else becomes EXCHANGE.

Import from Exchange — everything at once, always EXCHANGE

The Import from Exchange button at the top of the table calls GET /api/admin/finance/binary/market/import (create.binary.market). It reads every exchangeMarket row with status true and creates a binary market for each pair you do not already have.

Four things about it that matter:

  • Every imported row is source: EXCHANGE. The importer only ever reads exchange markets, so it says so explicitly. It cannot produce an ecosystem market.
  • Everything is imported disabled (status: false). Nothing becomes tradable until you turn it on. This is the safe default and it is deliberate — a provider with 1,500 spot pairs would otherwise list 1,500 binary markets in one click.
  • Swap and futures rows are skipped. Any currency or pair containing a colon (USDT:USDT) is filtered out; those are non-spot rows written by an older exchange-market importer.
  • The toast reports imported and skipped, and skipped counts both the pairs you already had and the colon rows.

With no active exchange markets at all the call answers 404, "No active exchange markets found to import" — check Finance → Trading Infrastructure → Exchange Providers first.

Stake limits

minAmount and maxAmount are per market, denominated in the quote currency, and they are the contract for both feeds. Edit them at /admin/finance/binary/market/{id}/edit, which is the only screen that exposes them.

A customer whose stake falls outside them is refused at order placement with "Amount must be between {min} and {max} {pair}".

What applies when a row carries no usable limit:

Situation Effective limit
row created by the wizard or the importer the column defaults — minAmount 1, maxAmount 10000
column is NULL, zero or otherwise not a positive number DEFAULT_MIN_AMOUNT 1 and DEFAULT_MAX_AMOUNT 100000 from marketSource.ts

The fallback accepts a value only when it is finite and greater than zero, so setting maxAmount to 0 does not close the market — it raises the ceiling to 100,000. To stop trading on a market, turn its status off.

The two numbers also disagree by design in one place: rows written by the platform get maxAmount 10000 from the column default, while a row whose maxAmount is NULL (typically one that predates the column) resolves to 100,000. If a market you never edited is accepting far larger stakes than you expected, that is the row to look at.

What ecosystem markets cannot do

Two order types are refused outright on an ECOSYSTEM market, with "{type} orders are not available on ecosystem markets":

  • Touch / No Touch
  • Turbo

Both settle on intra-period highs and lows, which come from exchange OHLCV. Ecosystem markets expose no equivalent, so a position that could not be settled correctly is never opened in the first place. Rise/Fall, Higher/Lower and Call/Put are unaffected.

If you intend to run the Binary AI Engine, note that it works only when Rise/Fall is the only enabled order type — the create wizard prints a warning on step 1 when your saved binary settings enable anything else. Order types are configured on Admin → Finance → Binary Options → Binary Settings.

Enabling, disabling and retiring a market

The status toggle in the table writes to PUT /api/admin/finance/binary/market/{id}/status (edit.binary.market) as you click it. There is no save step.

A market with status false rejects every order attempt with Binary market {symbol} is disabled. That is the safe way to retire a market: the row survives, its history survives, and nothing new can be opened against it.

The settlement path resolves the market before it settles, and a disabled market makes that lookup throw. For an EXCHANGE-backed order the 15-second backstop still settles it from the exchange candle, so the damage is a log line. For an ECOSYSTEM-backed order it is worse: the backstop cannot learn the feed either, falls back to treating the market as exchange-backed, and will settle the position on the centralized exchange price — the exact confusion the source column exists to prevent — or fail outright if the pair is not listed there.

Turn a market off after its last contract has expired, not while positions are live. If you have already done it, turn it back on until the open orders have settled.

Two limits on bulk work on this screen:

  • Bulk status is API-only. PUT /api/admin/finance/binary/market/status takes { "ids": ["…"], "status": true } and updates them all (edit.binary.market), but the table's bulk menu offers delete selected and nothing else — there is no "disable selected" button, and the bulk restore action exists only inside the deleted-rows view, which this table never shows (see the next bullet). Flip markets one at a time from the table, or call the endpoint.
  • Deletion is one row at a time. There is a per-row delete route (DELETE /api/admin/finance/binary/market/{id}, delete.binary.market) and no collection-level one. The table is not paranoid — binary_market has no soft-delete column, so a delete is immediate and permanent. Prefer disabling.

isHot and isTrending are merchandising, not risk

Neither flag restricts anything. They do two things on the trading page:

  • The market selector has All / Trending / Hot / Favorites tabs; Trending filters on isTrending, Hot filters on isHot, and a market that has neither gets a muted badge.
  • GET /api/exchange/binary/market sorts the public list isTrending first, then isHot, then alphabetically by currency — so flagging a market moves it to the top of everyone's list.

If you want to limit exposure on a market, that is minAmount, maxAmount and status. Turning off Hot does nothing but change where it appears.

Permissions

Key What it opens
access.binary.market the screen itself
view.binary.market the list, one market, and the available-markets lookup
create.binary.market creating a market, and Import from Exchange
edit.binary.market editing, the status toggle, and bulk status
delete.binary.market deleting a market

The route itself is gated on access.binary.market alone. That key opens the page but does not load it — the list request behind it is checked against view.binary.market, so a role holding only access. lands on an empty table. Grant both. Binary orders are a separate screen with its own key, access.binary.order — see Roles and permissions.

Endpoint reference

Paginated market list
One market, including its source
Exchange and ecosystem pairs you have not added yet
Create every missing exchange pair, disabled
Create one market — accepts source
Update limits and flags — ignores source
Toggle one market
Toggle many — body takes ids and status
Delete one market, permanently

When a market is configured and orders still are not settling, the next page is Binary orders that did not settle.