The binary order desk
Reading and reconstructing a binary contract from Admin → Order Management → Binary Orders — the statuses, the columns the screen does not show, the demo flag, and why hand-editing a status races settlement.
This is the screen you open when a customer says a contract settled wrongly. It is a reading desk: it lists every binary contract ever placed and lets you open one, and it is deliberately configured with no create, no edit and no delete row action.
The routes behind it are not so restrained. An admin holding
edit.binary.order can rewrite an order's status through the API, and the bulk
delete action is reachable from this very screen. Both of those touch a row that
a settlement engine may be about to write. This page is what each control does
and when it is safe.
Admin → Finance → Order Management → Binary Orders, at
/admin/finance/order/binary. Opening the page needs access.binary.order.
The routes, and who can call them
| Action | Route | Permission |
|---|---|---|
| List orders | GET /api/admin/finance/order/binary |
view.binary.order |
| Read one | GET /api/admin/finance/order/binary/{id} |
view.binary.order |
| Rewrite a whole order | PUT /api/admin/finance/order/binary/{id} |
edit.binary.order |
| Change one order's status | PUT /api/admin/finance/order/binary/{id}/status |
edit.binary.order |
| Change many statuses | PUT /api/admin/finance/order/binary/status |
edit.binary.order |
| Delete one | DELETE /api/admin/finance/order/binary/{id} |
delete.binary.order |
| Delete many | DELETE /api/admin/finance/order/binary |
delete.binary.order |
Every mutating route writes to the admin audit trail under the ADMIN_FIN
module — see The admin panel. The list route masks
user.email when the platform is in demo mode.
The table is configured canCreate: false, canEdit: false, canDelete: false,
so no row carries an edit or delete action. The toolbar is a different code
path: selecting rows reveals an Actions menu, and because this table
declares no custom bulk actions it still offers the built-in Delete selected
to anyone holding delete.binary.order.
binaryOrder is a paranoid model, so that is a soft delete —
deletedAt is stamped and the rows disappear from every list. The table is
configured isParanoid: false, so this screen has no deleted view and no
restore button: from the admin panel the rows are simply gone.
They are recoverable through the API, which still honours the flags:
# see what was deleted
GET /api/admin/finance/order/binary?showDeleted=true
# put them back
DELETE /api/admin/finance/order/binary?restore=true {"ids": ["…"]}Deleting binary orders is almost never the right move regardless. The rows are the evidence for every revenue figure and every dispute, and the wallet movements they caused are not deleted with them.
The status enum, and what each one meant for the balance
binaryOrder.status is an enum of exactly six values.
| Status | What happened | The customer's wallet |
|---|---|---|
PENDING |
Placed, not yet expired or not yet settled | The stake is held — moved from balance into inOrder on a live order |
WIN |
Settled in the customer's favour | Stake released back to balance, plus profit credited |
LOSS |
Settled against the customer | The held stake is consumed. A Turbo partial loss can release part of it back |
DRAW |
Settled with no winner — the close price equalled the entry | Stake released, nothing credited |
CANCELED |
Closed early by the customer | Stake released less the penalty; profit is set to 0 and metadata records refundedAmount and cancelPenalty |
ERROR |
Reserved on the model as "data fetch failed, needs manual review" | Nothing. No code path in the settlement engine writes this status today |
Two consequences of that last row: a row in ERROR on your install came from
somewhere other than settlement, and the desk's Settlement errors backlog
analytics card — which counts exactly this status — will read zero however badly
settlement is going. The card to watch instead is Unsettled past expiry.
Both status routes declare an enum of PENDING, WIN, LOSS, DRAW, and the
request body is validated against it, so a request carrying CANCELED or
ERROR is refused with "Status must be one of the following: PENDING, WIN,
LOSS, DRAW."
There is therefore no supported way to mark an order cancelled from the admin panel. Cancellation is a customer action with a refund attached — see Early cancellation and risk limits.
The columns
The desk shows eleven fields. The binaryOrder row carries more than that,
and the ones it does not show are the ones you need to reconstruct a barrier
trade.
On the screen:
| Column | Is | Where |
|---|---|---|
symbol |
The market, BTC/USDT. Free text on the row — there is no currency column |
list |
price |
The entry price, captured at placement | list |
amount |
The stake | list |
profit |
The payout amount on a win, or the loss figure. Not a percentage | list |
side |
The direction — see the warning below | list |
status |
The enum above | list |
closePrice |
The settlement price. Empty on a PENDING row, where the view dialog prints "Still open" |
expanded / view |
createdAt |
When the contract was placed | list |
closedAt |
The expiry instant, not when it settled | list |
isDemo |
Practice or real money | expanded / view |
id · user |
The row id and the trader (avatar, name, email) | expanded / view |
On the row but not on the screen — read these from an export, from
GET /api/admin/finance/order/binary/{id}, or from the database:
| Field | Why you want it |
|---|---|
type |
Which of the five order types this was. Nothing on the desk shows it |
durationType |
TIME or TICKS. TICKS is Turbo-only |
barrier |
The barrier price for Higher/Lower, Touch/No Touch and Turbo |
strikePrice |
The strike for Call/Put |
payoutPerPoint |
The per-point rate on Call/Put and Turbo |
profitPercentage |
The payout rate frozen onto the order at placement. This is the number that decides the payout, and the only proof of what a customer was actually sold |
metadata |
Idempotency key, the copy-trading wallet type, and on a cancelled order refundedAmount and cancelPenalty |
The side column on the model has ten values — RISE, FALL, HIGHER,
LOWER, TOUCH, NO_TOUCH, CALL, PUT, UP, DOWN — but the desk's filter
offers only RISE and FALL, and the view dialog's badge is a single test: it
paints red Fall when side === "FALL" and green Rise for everything
else.
So a LOWER, NO_TOUCH, PUT or DOWN contract — every downside barrier
trade in the product — appears on this screen as an upward bet. Combined with
type not being shown at all, a Touch contract and a Rise/Fall contract are
indistinguishable on the desk.
If you are reconstructing anything other than a Rise/Fall trade, read the row,
not the screen. Open GET /api/admin/finance/order/binary/{id} or query the
binary_order table directly. This matters most in exactly the situation the
desk exists for: a customer disputing a barrier settlement.
isDemo and why it must come out of every revenue figure
Practice contracts never touch a wallet. Every wallet operation in the binary
order service — the hold at placement, the release and credit at settlement, the
refund on cancel — sits behind if (!isDemo). A demo order still creates a row,
still gets a status, still carries a profit, and still appears on this desk
beside real ones.
Summing profit or amount without filtering isDemo = 0 therefore produces a
figure that is not money. The analytics on this page already do it correctly:
every P&L card filters on isDemo: false, and demo volume is carried as its own
separate card precisely so the two can be compared without ever being added
together.
A binary contract is staked in the quote half of symbol — the wallet
debited is the one whose currency is the pair — and binaryOrder has no column
saying which asset that was. symbol is free text and the model's only
association is to user.
So a book carrying 500 USDT of stakes and 0.01 BTC of stakes has no single sum, and the analytics deliberately render plain numbers rather than a dollar figure they have not earned. Do not read the KPI cards as dollars unless every one of your binary markets quotes the same asset.
The analytics tabs
Two sections of KPI cards sit above the table.
Section one — the book. Net house P&L (stakes won minus payouts paid), stakes
won by the house (sum of amount where status = LOSS), payouts paid (sum of
profit where status = WIN), live staked volume, demo staked volume, and
outstanding payout liability — the sum of profit across live PENDING
contracts, which is what you owe if every open position wins. There is a
stacked-bar chart of house take against payouts over time and a ranking of the
top five symbols by payouts paid.
Section two — is settlement working.
| Card | Counts | Read it as |
|---|---|---|
| Settlement errors backlog | status = ERROR |
always zero today; see the status table above |
| Unsettled past expiry | PENDING with closedAt more than 5 minutes ago |
the real watchdog. Anything above zero is a contract whose expiry has passed and which neither settlement path has closed |
| Worst settlement lag (min) | the maximum closedAt → updatedAt gap on live settled contracts |
a proxy — there is no settledAt column, so a later admin edit inflates that row's figure permanently |
| Win / Settled / Win rate | live settled contracts only | the customers' win rate. Compare it against the break-even rate your payouts imply |
Anything on the Unsettled past expiry card sends you to Binary orders that did not settle, which is the diagnostic path — a disabled market, a deleted market, or a market maker that has stopped.
Editing an order by hand
There are two edit routes and they do different amounts of damage.
PUT /{id}/status writes the status column and nothing else. It does not
compute a profit, does not credit a wallet, does not release a held stake and
does not write a transaction row. Flipping a stuck PENDING contract to WIN
marks it won and pays the customer nothing — turning a settlement delay into a
support case about missing money.
PUT /{id} rewrites symbol, price, amount, profit, side, type,
status, isDemo and closePrice in one call, with the same absence of any
wallet effect. Its request schema is also narrower than the model:
sideis validated againstRISEandFALLonly;typeagainstRISE_FALLonly;statusagainstPENDING,WIN,LOSS,DRAW;symbol,price,amount,side,statusandclosePriceare all required.
So this route cannot be used on a barrier order at all — a HIGHER_LOWER
contract fails the request schema before anything is written, with both
violations joined into one message: "Side must be one of the following: RISE,
FALL.; Type must be one of the following: RISE_FALL." — and it cannot be used
on a PENDING contract without inventing a close price.
A live binary contract is watched by two things at once:
- an in-process timer armed when the order was placed, which fires at expiry and settles it immediately; and
processPendingOrders, a cron job that runs every 15 seconds and settles anything the timers missed, taking a Redis lock per order.
Settlement claims the row with SELECT … FOR UPDATE inside a transaction and a
status = PENDING predicate, so it will not overwrite a terminal order. Your
edit has no such protection. A row can move from PENDING to WIN between the
moment the page loaded and the moment you press save, and the admin write lands
on top of a settled contract whose wallet movements have already happened.
The safe move on a stuck contract is to fix the cause and let the engine settle it — 15 seconds is the worst case once the price feed answers. If a contract has genuinely settled wrongly, correct the customer's balance with a wallet adjustment, which is auditable and reversible, and leave the order row telling the truth about what the engine did. See Wallet administration.
Reconstructing a disputed contract
-
Find the row. Search the desk by user email or symbol, or filter on
statusandcreatedAt. Open the row to get itsid. -
Read the full record, not the dialog:
GET /api/admin/finance/order/binary/{id}. You needtype,side,barrierorstrikePrice, andprofitPercentage— none of which the screen shows. -
Confirm whether it was real money.
isDemo: trueends the conversation: no wallet was touched, in either direction. -
Check the contract's own arithmetic.
priceis the entry,closePriceis the settlement price,closedAtis the expiry instant. For a Rise/Fall contract that is the whole story; for a barrier type compareclosePriceandbarrier, remembering that Touch/No Touch is decided by whether the barrier was ever reached during the contract, not by where the price finished. -
Check what they were sold.
profitPercentageis the rate frozen at placement. If it disagrees with what the customer says the trading page offered, the payout configuration changed while their page was open — see Order types, durations and payouts. -
Check the settlement timing.
closedAtversusupdatedAtis how late the settlement was. A large gap points at the backstop rather than the timer, which is a settlement problem, not a pricing one. -
If the customer is owed money, adjust the wallet. Do not edit the order row: the row is your record of what happened, and rewriting it destroys the evidence while paying nobody.
Related
- Binary orders that did not settle —
the diagnostic path for a contract stuck in
PENDING. - Binary markets — the price source each contract settles against.
- Binary options: switching it on — the switches, the per-user limits and the settings console.
- Barriers, strikes and the payout optimizer
— where
profitPercentagecomes from. - What to monitor and alert on — the scheduler heartbeat that
keeps
processPendingOrdersrunning at all.