The engine dashboard and the drift verdict
The console at /admin/ai/binary-engine — what every figure counts and over what window, how the drift verdict is decided, why a new engine reads "too few trades", and how to read the settlement verdict breakdown.
The dashboard at /admin/ai/binary-engine is the addon's landing screen and the one every other page here sends you back to. It answers three questions and nothing else: is any running engine outside the band you configured, are settlements actually being steered, and did anyone pull the kill switch.
It is a single GET /api/admin/ai/binary-engine/dashboard call, gated on
access.ai.binary_engine. Without that permission the menu entry does not render
and the endpoint returns 403. The whole console also sits behind the
binary_ai_engine licence gate, so an unlicensed install shows the navigation and
a licence message rather than figures.
The page polls every 30 seconds, and only while the tab is visible. Only the first load draws skeletons — a background refresh replaces the figures in place, so a screen left open all day never collapses. The live dot at the top left says which state you are in: Live, Updating…, or Figures are stale when the last poll failed.
The page header carries three controls and no others: Refresh, Manage
Engines (which opens /admin/ai/binary-engine/engine), and Emergency Stop.
Everything else on the screen is a figure or a link into the rows behind it.
Everything here is live money only
Every count, rate and profit figure on this screen is filtered to isDemo: false.
Practice and demo trades are deliberately excluded from the day totals, from the
settlement histogram, and from the per-engine period counters the drift verdict
reads. A busy demo cohort moves nothing on this page.
The panels are also measured over different windows, which is the single most common misreading of the screen — two figures that look like they should agree usually do not cover the same period:
| Panel | Window |
|---|---|
| Engine counts (active / paused / stopped) | Right now |
| Today's numbers and the four KPI tiles | Since the backend server's local midnight |
| Per-engine win rate, W/L and profit — and the drift verdict | Since that engine's last period reset |
| Settlement decisions | Rolling last 24 hours |
| Recent engine events | Rolling last 24 hours, newest 20 |
Server midnight is the backend process's local midnight, not UTC and not your browser's. (The hourly buckets under Analytics → Time are UTC — a different convention on a different screen.)
The profit unit is derived, not assumed
platformProfit is a bare decimal on the position row; nothing in the model
records a currency. The unit is each market's quote asset, so the console reads
the quote off every engine's symbol and prints it only when the whole fleet agrees
on one. With BTC/USDT and ETH/BTC both open there is no single unit, so the profit
figures are printed as bare numbers with no ticker and no $. That is correct, not
a formatting bug.
The summary block
Four counts over every engine row, unbounded — a true total, not a page.
| Field | What it counts |
|---|---|
totalEngines |
Every row in binary_ai_engine |
activeEngines |
status = ACTIVE |
pausedEngines |
status = PAUSED |
stoppedEngines |
status = STOPPED |
They appear as three badges on the Engines card, and activeEngines is what the
kill-switch line in the masthead counts — "Emergency stop armed — would halt N
running engine(s)", or "Nothing is running to stop" when none are ACTIVE.
"Armed" is not a flag the engine exposes; it is simply whether the control still
has anything to act on.
Today's numbers
Counted from binary_ai_engine_position rows with settledAt at or after server
midnight and an outcome other than PENDING.
| Field | What it counts |
|---|---|
totalOrders |
Every settled live position today |
totalWins |
Of those, outcome = WIN |
totalLosses |
Of those, outcome = LOSS |
overallWinRate |
totalWins / totalOrders |
totalPlatformProfit |
The sum of platformProfit across all of them |
totalOrders is every non-pending settlement, which includes DRAW outcomes.
Wins and losses will not always add up to it, and overallWinRate is wins over
all settlements — so on a market that produces draws it reads slightly lower than
wins over wins-plus-losses. Compare like with like when you check it against an
engine's own statistics.
The four KPI tiles are these figures plus one derived comparison:
| Tile | Shows | Opens |
|---|---|---|
| Win rate today | overallWinRate, described against the unweighted mean target of the running engines |
Engines |
| Settled today | totalOrders, with the win/loss split beneath it |
Analytics |
| Platform profit today | totalPlatformProfit, signed — a positive figure is money the desk kept, a negative one is money it paid out |
Analytics |
| Engines off target | Running engines whose drift verdict is above or below |
Engines |
When no engine is ACTIVE the win-rate tile says so instead of comparing against a target nobody is currently aiming at.
The drift verdict
Every engine on the payload carries a driftVerdict. It is computed on the
server, by classifyDrift in utils/assessment.ts — the same function the engine
record page opens with, so the console and the drill-down cannot disagree about
which engines are drifting. The browser keeps an identical fallback copy of the
rule for a payload from an older backend; when the server field is present, it wins.
The inputs are the engine's own live period counters and its own tolerance:
settled = currentPeriodWins + currentPeriodLosses
band = winRateVariance (or 0.05 when the column is zero or missing)
delta = currentWinRate - targetUserWinRate| Verdict | Rule | What it means |
|---|---|---|
insufficient |
settled < 20 |
Not enough has settled this period to make any claim |
above |
delta > band |
Traders are winning more often than configured — the desk is bleeding |
below |
delta < -band |
Traders are winning less often than configured — a fairness and complaint risk |
on_target |
otherwise | Inside the engine's own band; the engine is not correcting |
The band is the engine's own winRateVariance, never a fixed 5%. The
DEFAULT_VARIANCE of 0.05 in the same module is only a fallback for an engine
whose column reads zero — a zero band would put every engine permanently off
target. If you tuned an engine's variance, the verdict respects your number.
DRIFT_MIN_SETTLED = 20
Below twenty settled trades in the current period, the verdict is insufficient
and the realised win rate on the card means nothing. This is a display floor and
it is ours: the engine has no minimum trade-count parameter to borrow
(minPositionForOptimization is a position size in quote units, not a count), so
rather than call an engine "above target" on its first winning trade the console
declares the floor and says so.
A fresh engine has settled nothing, so the console card draws its win-rate bar at 0.0% with a too few trades badge, and the engine record page shows the target as the current rate along with how many more settlements it needs. Both mean the same thing — nothing has settled yet — and neither is a fault. Nothing on this screen is a claim about that engine until twenty live trades have settled in its current period.
The same applies after every period reset. Rolling a period zeroes
currentPeriodWins, currentPeriodLosses and currentPeriodPlatformProfit, so a
fleet you have just reset drops back to insufficient across the board until each
engine settles twenty more trades. A reset immediately after a target change is
usually the right move — see Engines — but expect the drift
meter to go grey for a while afterwards.
The drift meter
The band across the masthead splits the running engines by verdict — a paused
engine's win rate is a frozen number, not a drift, so it is not counted. Segments
are drawn in a declared order, trouble first: above, below, on_target,
insufficient. A single-engine segment is widened to a visible minimum rather than
rounding away, and the width is taken back from the widest band.
Which engines you see first
The grid previews six engine cards and then offers View all N engines. Which six is a decision, not insertion order: cards are sorted by how much they need an operator, and ties break on the size of the miss, largest first.
| Rank | Card |
|---|---|
| 1 | ACTIVE, above target |
| 2 | ACTIVE, below target |
| 3 | ACTIVE, insufficient |
| 4 | ACTIVE, on_target |
| 5 | Anything not ACTIVE |
Note that on_target sorts after insufficient, not before it: a measured,
healthy engine needs less attention than one you cannot yet judge.
The same ladder is declared server-side as severityRank in
utils/assessment.ts, next to the verdict it depends on. That version carries one
extra rung above everything else — an engine that is ACTIVE but blocked from
steering by a gate the server can check from the database — and it sorts PAUSED
ahead of STOPPED at the bottom rather than lumping them together. The two agree on
the ordering of the four drift verdicts, which is the part you see on this screen.
What a card shows
| Element | Source |
|---|---|
| Symbol | The attached market maker's ecosystem market, currency/pair; Unknown when no market maker is attached |
| Status badge | ACTIVE / PAUSED / STOPPED |
| Practice badge | Shown when practiceMode is anything other than DISABLED |
| Whale detection / User tiers chips | The engine's enableWhaleDetection and enableUserTiers switches |
| Drift badge | The verdict, shown on running engines whenever it is not on_target |
| The bar | Fill is the realised period win rate; the tick is the target. The gap between them is the drift |
| Period W/L, trades | currentPeriodWins / currentPeriodLosses since the last reset |
| Platform profit this period | currentPeriodPlatformProfit, signed |
The card body opens the engine record page. Beneath it sits one control on its own rail — Pause, Resume or Start, depending on the engine's status.
It changes what every trader on that market settles against from the next
settlement onward. The control opens a confirmation naming the exact market, takes
an optional reason, and issues PUT /api/admin/ai/binary-engine/engine/{id}/status.
The reason is written to the engine action log.
A STOPPED engine's control says Start, not Resume, because that transition runs through the activation path: it re-checks that the attached AI Market Maker is ACTIVE and can legitimately refuse. The server's message is shown as-is when it does — it names a precondition you can go and fix.
The settlement verdict breakdown
The settlementDecisions panel is the one this addon's other pages keep sending
you to. It counts every live settlement of the last 24 hours by the reason recorded
on the position row (manipulationDetails.settlementDecision), sorted by frequency,
with each row's share of the total.
This is a full population, not a sample — every non-demo settlement in the
window is counted. The (+Nm) lateness suffix on FAIR:EXPIRY_MINUTE_CLOSED is
stripped so the counter groups; the exact lateness stays readable per position on
the engine's positions screen.
Reading it takes one rule:
STEERED— the close was nudged inside the band and published.STEERED:BUCKET_PIN— the close already published for that expiry minute was reused, so every order in the bucket settled on one number.FAIR:<reason>— the engine refused to steer and settled on the honest close. Every one of these is a deliberate, recorded refusal, not an error.
An ACTIVE engine whose entire 24 hours reads FAIR:* is steering nothing, and the
reason is named right there. Each code is explained in
Pricing and settlement, and the diagnostic
path for each one — which switch, which screen, which fix — is
Troubleshooting.
It is the only row this panel flags as a warning, and the only verdict that means something is wrong with the platform rather than with your configuration. It means settlement ran after the candle covering expiry had already sealed, so the steered close could no longer be published into the series clients see. The refusal is correct. A rising share of it means settlements are running late — look at cron health and database latency, not at the engine.
When nothing has settled in the window the panel says so rather than drawing an empty chart.
Recent engine events, and the emergency stop
The events column lists the newest engine actions from the last 24 hours, filtered
to EMERGENCY_STOP, CORRELATION_ALERT, WHALE_DETECTED, WHALE_ORDER_DETECTED
and RISK_ALERT.
It is a sample, not a population. Never count it, and never conclude from a type being absent that it did not happen — twenty whale rows will push everything else off the list. The caption on the panel states the cap. Anything the console asserts as a fact gets its own query instead.
One of those five is a historical spelling and one is knowingly inert.
WHALE_DETECTED is what the audit logger actually writes; WHALE_ORDER_DETECTED
is kept only so an older install still surfaces. RISK_ALERT is never written as an
action type at all — the risk manager records it as a CONFIG_CHANGE row with
details.alertType = "RISK_ALERT" — so it matches nothing. Do not read its absence
as "no risk alerts fired".
Each row shows the event, the time on your own wall clock, the symbol, and the
readable part of its details — the reason for an emergency stop, the deviation
percent for a correlation break. Rows link to the engine they belong to.
"Did the kill switch fire?" is answered separately. lastEmergencyStop is its
own server-side lookup over the same 24-hour window, precisely because it cannot be
answered from a list capped at 20. When it returns a row, a destructive banner sits
above the fold with the time, the reason and who triggered it, and the masthead
repeats the time on the kill-switch line.
The Emergency Stop button in the page header stops every ACTIVE engine at once. It requires a typed reason, which is recorded on every affected engine. Recovery is not a single click — each engine has to be reactivated individually, and each reactivation re-runs the market maker check. Reach for Global Pause on the Settings page first if you want something instantly reversible; the full comparison is in Risk controls.
From the verdict to the engine
A drift badge on the console is where the investigation starts; the engine record page is where it continues. Opening a card gives you the same verdict from the same server function, plus the gates that engine is failing, its period clock and its loss budget.
Two constants govern the period meter there, both declared alongside the drift
logic in utils/assessment.ts:
PERIOD_NEARLY_DUE = 0.9— once a period is more than 90% spent, the drift alert on the record page offers a Reset period button beside Adjust targeting. Below that threshold retargeting alone is the suggestion, because resetting a period that has most of its window left throws away a measurement you are about to need.- The period is overdue when its window is fully spent and the reset has not run. The sweep only visits ACTIVE engines once a minute, so a few seconds of this is normal; a persistent overdue state means the counters the whole win-rate controller reads are stale, and the record page raises its own alert for it.
When the dashboard itself fails
A failed poll never wipes figures that are already on screen. The page keeps the last good values, adds a Refresh failed notice, and the live dot changes to Figures are stale — so a green pulse never sits above a clock that stopped.
A failed first load replaces the body entirely with a Dashboard unavailable message, Try again, and a link to the engine registry, which does not depend on this aggregate. It deliberately does not render a console full of zeros: every zero would be a claim the page cannot support. The header stays mounted on that branch, so Emergency Stop is still reachable when the aggregate is down.
If the screen loads but every figure is zero on a platform you know is trading,
that is a real answer and not a failure — check that engines exist, that they are
ACTIVE, and that the settlement panel is not reporting FAIR:NO_ENGINE_LOADED,
which means the cron is not running.
Related
- Engines — the record page each card opens, and what every field changes
- Pricing and settlement — what each
FAIR:andSTEEREDcode means - Troubleshooting — the diagnostic path for an ACTIVE engine that steers nothing
- Global settings — the master switches that suppress steering fleet-wide
- Risk controls — emergency stop, global pause and the loss budget
- Snapshots and audit — the full action log behind the events panel
- API reference — the endpoint, its payload and its permission