External (A-book) execution
Hedging client flow at a real broker — the two supported venues, the enablement sequence, routing rules and the dry-run tester, the in-flight table, the alert inbox, orphan handling and how to drain back to a pure B-book.
By default this addon is a B-book. Every client order fills against your balance sheet, you keep the spread, and you carry the market risk. That is profitable until a large client is right about a large move.
The A-book layer lets you mirror selected client orders at a real broker. The client's experience does not change — same terminal, same prices, same statement — but behind it, an equal and opposite position is opened in your account at OANDA or a MetaTrader broker. Your P&L on that flow becomes the spread and commission, and nothing else.
Everything else can be undone with a setting. An order routed to a broker is a real trade in a real account with real money, and the master switch is Super Admin only for that reason. Practise on a DEMO venue until the alert inbox, the reconciler and the drain procedure are boring.
Who carries the risk
| Model | You keep | You risk | When it fits |
|---|---|---|---|
| B-book (default) | Spread, commission, swap, and client losses | Client profits come out of your balance sheet | Small tickets, retail flow, no broker relationship |
| A-book | Spread and commission only | Almost nothing on the hedged flow — but you must fund and maintain the hedge account | Large tickets, professional flow, regulatory pressure |
| Hybrid | Both, split by rule | Whatever you chose not to hedge | Most real desks |
Hybrid is the point of the routing rules: hedge the accounts and sizes that scare you and keep the rest internal.
The two venues
The simpler of the two. Synchronous fill-or-kill market orders with a price bound, identical APIs on practice and live, and a gapless, replayable transaction ledger — which is what the reconciler relies on to prove that what the broker did and what you booked agree.
Needs APP_OANDA_API_KEY in .env and the account ID in the provider row.
A cloud REST bridge onto MetaTrader broker accounts. Use it when your broker is MT-only. It adds a third party between you and the venue, and it needs your broker's written approval for datacenter logins — brokers do disable accounts over this.
Needs APP_METAAPI_TOKEN.
cTrader Open API was the third bridge and it is gone —
backend/seeders/20260804000001-forex-remove-ctrader.js retires it. Where no
position, order or deal ever referenced it the row is deleted; where one did,
deleting it would leave those rows pointing at a venue that does not exist, so
the seeder keeps the row disabled and renames it cTrader Open API
(retired). It cannot come back: hasCredentials and createBridgeAdapter in
utils/engine/bridges/index.ts no longer know the name, so enabling it is
refused and building its adapter is a 400. If you still have open cTrader
hedges, close them at the broker directly.
Credentials are environment-only. They are never writable through the admin API and the value never crosses the wire; the provider list returns a boolean saying whether the variable is present, and nothing more.
Unlike market-data providers, multiple execution providers may be enabled at once. There is no single-active rule here.
Enabling a provider, in order
Admin → Forex Trading → Execution → Execution Desk
(/admin/forex-trading/execution)
-
Put the credentials in
.envand restart. There are two —APP_OANDA_API_KEYandAPP_METAAPI_TOKEN— and both are already listed in.env.example, blank. You only need the one belonging to the venue you are enabling; the other stays empty. -
Set the account reference and environment on the provider row. The account reference is the OANDA account ID or the MetaApi account UUID. Start with
DEMO.Updates account reference, environment, proxy and the execution knobs -
Verify the connection. This builds the bridge adapter, runs a live connection test and persists a snapshot of the hedge account's balance, NAV, margin used and closeout percentage onto the provider row. It does not change the enabled status.
Runs a live venue connection test and snapshots the account -
Import and link venue symbols. The venue spells symbols its own way. This pulls the account's instrument list, matches it against your catalog and writes the venue symbol under this provider's key — merging, so another provider's mapping is never clobbered. Read the unmatched report: an instrument with no venue symbol cannot be hedged and will fall through the fallback ladder.
Imports venue symbols and links them to the catalog -
Tune the knobs if the defaults do not suit your venue. They are described below.
-
Enable the provider.
Enables or disables an execution provider, guarded both ways -
Turn on the master switch.
fxTradingExternalRoutingEnabled, on the core system settings page, Super Admin only. Until this is on, no order is ever routed regardless of what the rules say.
The LIVE enable gate
Enabling a provider whose environment is LIVE runs one extra check that a
DEMO provider skips: a real alert-delivery self-test. It fires a genuine
self-test alert to every Super Admin over both in-app and email, waits for
delivery, and refuses the enable with a 422 if it does not land.
That gate exists because the entire A-book safety model is alert-driven. Orphan detection, hedge-margin warnings, cursor stalls and basis drift are all alert-only — the system will never auto-trade to fix them. An operator who does not receive alerts has no safety model at all, only the appearance of one.
Make sure at least one Super Admin has a working email address before you try.
The execution knobs
The environment field cannot be changed while the provider is enabled, or while
open external positions or in-flight routing orders reference it. Disable, edit,
re-enable — which re-runs verification and, for LIVE, the alert self-test.
Routing rules
Execution → Routing Rules (/admin/forex-trading/execution/rules)
Rules are evaluated in ascending priority, first match wins, and a null
dimension is a wildcard. The dimensions are:
| Dimension | Matches on |
|---|---|
instrumentId |
One specific instrument |
symbolGroupId |
Everything in a symbol group |
assetClass |
FOREX, STOCK, COMMODITY, INDEX, CRYPTO |
accountGroupId |
An account tier |
accountId |
One specific customer account |
side |
BUY or SELL only |
minAmount / maxAmount |
A size band, in base units |
The target is INTERNAL (keep it on the book) or EXTERNAL (hedge it), and an
EXTERNAL rule names the execution provider.
Two properties are worth internalising:
Routing is decided once, at placement. A pending order stamps its routing decision when it is placed and honours it at trigger. Editing, disabling or deleting a rule affects new placements only and never re-routes an open position. That is why deleting a rule is safe — existing rows keep their stamped routing for audit.
Saving warns rather than blocks. A rule pointing at a provider that has no symbol mapping for instruments the rule can match is saved with a warning attached, because those orders will follow the fallback ladder rather than failing outright.
The dry-run tester
Describe an order — account, instrument, side, size, type — and the widget runs the actual resolver: master switch, environment guard, priority-ordered rules, global default, fallback policy. Nothing is journaled, no venue is called, no alert is fired. You get the decision plus an ordered rule-by-rule trace showing which rules were considered and why each did or did not match.
Use it before every rule change. It is much cheaper than discovering the answer with a customer's order.
When nothing matches, and when it goes wrong
The fallback fires when a provider is disabled, degraded, or the master switch went off between a pending order's placement and its trigger.
INTERNAL— fill it on the B-book and alert the desk. Recommended. Customers keep trading; you find out immediately and carry that one position yourself.REJECT— refuse the order with an error. Choose this only if you would rather turn business away than carry unhedged risk, and understand it means a broker outage becomes a customer-visible outage.
An INTERNAL decision reached by fallback still records the rule that matched
and why the fallback fired, so the audit trail does not lose it.
Monitoring the hedge
The Execution Desk shows, per provider: NAV, margin used, margin-use ratio, closeout percentage, sync age, a degraded flag and the unacknowledged alert count. The hedge monitor cron refreshes this every 60 seconds.
The in-flight table is the one to watch during an incident.
It lists three things: orders journaled ROUTING and awaiting venue
confirmation with their reserved margin and external reference; orders
REJECTED at timeout but still flagged unresolved, which stay under a 24-hour
ledger re-check window in case the fill turns up late; and open positions whose
broker-side close has been requested but not confirmed.
A healthy desk has a near-empty in-flight table. Rows that persist are the ones that become orphans.
The revenue report is where you find out whether hedging is actually paying: hedged volume, fill counts, price basis in points per leg, broker financing, dividends and commissions, hedge realised P&L from the replayed broker ledger, the client-leg P&L against it, and the combined net A-book margin. Broker-leg amounts are in the venue's account currency; client-leg amounts are converted to USD.
The alert inbox
Every alert lands here and goes out by in-app notification and email to every Super Admin. Acknowledging is idempotent and records who and when.
What raises one: hedge margin breaching marginAlertRatio, repeated hedge sync
failures, a stale sync cursor (which also auto-suspends new routing), broker
timeouts, financing basis drifting past your threshold, ledger drift on a client
account, unconvertible swap rollovers, and orphans in either direction.
Orphans
An orphan is a disagreement between your books and the broker's. The reconciler finds them by replaying the broker's transaction ledger every 60 seconds, and it never trades to fix one. It alerts, and you decide. Both remedies are confirm-gated and both write an alert recording who did what.
Use flatten when the broker holds a position you have no client position for — a hedge that was opened and whose client leg never booked. It closes that trade at market, either by venue trade id or by submitting an offsetting order for a symbol and net size. It refuses with a 409 if a matching open client position exists, because that hedge is not an orphan.
Use book late in the mirror case, in two forms. Either an order was rejected
on timeout and the fill turned up afterwards — the order is re-journaled and the
recorded venue fill is driven through the normal booking path, so the client
gets their position at platform prices exactly as if it had confirmed in time.
Or an open external position whose hedge the broker closed server-side, which is
booked through the external-close path with the reason ADMIN.
The Execution Desk also serves the operator runbook — the enable checklist, an incident playbook for every failure mode that ends in an operator action, the funding and top-up policy, the drain procedure and the practice-to-live promotion. Read it before you enable a live venue; it ships with the backend and is more detailed than this page.
Draining back to a pure B-book
Turning the master switch off does not unwind anything. Existing external positions stay hedged at the broker and keep closing there normally; only new orders stop being routed. That is the correct behaviour — the alternative would be mass-flattening a live book on a settings change.
-
Turn off
fxTradingExternalRoutingEnabled. New flow now stays internal. -
Watch the live counts on the Execution settings tab and the Execution Desk: open external positions, in-flight routing orders, pending closes. This is the drain-warning data.
-
Wait for them to reach zero. Positions close as clients close them, or you force-close them from the Positions screen.
-
Then disable the providers. Disabling is refused with a 409 while any open external position, in-flight routing order or pending close still references the provider — the guard is there so you cannot orphan a hedge by switching something off.
-
Leave the reconciler running until the counts are genuinely zero. It keeps working through the drain by design.
Next
- Running the desk — the day-to-day operator screens
- Settings reference — routing settings and environment variables
- Troubleshooting — routing that will not engage