The trading terminal
What your customers see and do — demo and live accounts, funding from the platform wallet, the terminal layout, placing and managing orders, margin in plain words, and every reason an order gets refused.
The customer-facing product is one page: /forex-trading/trade. There is no
separate accounts area — accounts, funding and statements open as views over the
chart from the terminal header, because a trader should never have to leave the
market to move money.
Trading accounts
A customer can hold up to three DEMO and three LIVE accounts. Each one is bound to their user record at creation; there is no pool to claim from.
DEMO is the funnel and it works the moment the extension is active. On the first visit to the terminal one demo account is provisioned automatically, in the first configured account currency, seeded with 10,000 units of paper money. Deposits are refused on demo accounts — that seed is the whole balance. There is no KYC gate.
LIVE requires three things to be true: fxTradingEnabled is on, the
customer's country is not on the geo-block list, and — when the platform's KYC
system is enabled — they hold an approved KYC level carrying the trade_forex
feature. Platforms with KYC switched off skip that last check entirely.
The account currency is fixed at creation and chosen from the operator's list. It never changes afterwards, because the deals ledger is denominated in it and re-denominating a ledger is not a thing you can do safely.
Each account carries its own leverage knob, defaulting to 100. Effective leverage on any trade is the minimum of that, the symbol group's leverage and the account tier's cap — so a customer setting 500 on a group capped at 50 gets 50.
Funding a live account
The transfer is instant and atomic: the platform wallet is debited and the
trading account credited inside one transaction, with idempotency on both
ledgers. The source wallet must be FIAT, SPOT or ECO in the same
currency as the trading account — there is no conversion step. A customer with
a USD trading account and only a EUR wallet gets a clear error rather than a
surprise exchange rate.
A FxTradingDepositConfirmation email and an in-app notification follow.
Withdrawals are two-phase and gated on free margin. The withdrawable amount is
max(0, min(balance, equity − usedMargin)), computed inside the same row lock
as the debit, with margin reserved by any in-flight external orders added to
usedMargin. On top of that:
- Blocked entirely while the account's margin level is below its margin-call level and positions are open.
- Blocked while pricing for any open position is stale, because a wrong equity number here pays out money that is not there.
- Subject to rolling daily and monthly limits stored per account (defaults 5,000 and 50,000).
The trading account is debited immediately and a PENDING
FX_TRADING_WITHDRAW wallet transaction is queued. The platform wallet is only
credited when an operator approves it — see
Withdrawal approvals.
The risk disclosure
When fxTradingRiskWarningEnabled is on, the first entry to the terminal shows
a blocking leveraged-trading risk disclosure, and the order ticket carries a
persistent risk notice thereafter.
The acknowledgment is stored server-side per account, so it survives a new device or a cleared browser. It is idempotent — a repeat call returns the original timestamp.
Country restrictions
The geo-block list is enforced on the way in, never on the way out. Blocked countries are refused (403) at account creation, order placement, pending-order modification and deposits. Closing a position, cancelling an order and withdrawing are deliberately never blocked: a policy change must not trap someone's money.
The customer's country is resolved from approved KYC data first, then the
self-declared profile country, then the CDN geo header (cf-ipcountry). A match
on any signal blocks, so neither a VPN nor an edited profile bypasses a
verified identity. A user with no resolvable country is allowed — the platform
has no mandatory country capture before KYC, and blocking unknowns would lock
out every pre-KYC demo visitor.
The terminal at a glance
Every visible instrument with its live bid, ask and day change, grouped by asset class, searchable and filterable. A symbol that is not currently quoting shows its state rather than a stale price.
Candles for the selected symbol with the usual timeframes, back-filled from the provider's history and extended live from the tick stream. Closed-session ranges are never fabricated — a gap in the chart is a real gap in the market.
Side, order type, size in lots, optional stop loss, take profit and trailing distance, plus a live readout of the margin the order will consume and the commission it will cost.
Three tabs — Positions, Orders and History — with the account's totals in the header row.
Sizes in the ticket are entered in lots and converted to base units against the instrument's contract size before they reach the API. The minimum, maximum and step come from the instrument's limits, so a stock with a one-share minimum and an FX pair with a 0.01-lot minimum both behave correctly without the customer knowing why.
Client-side validation in the ticket is a hint layer only. Session state, delayed-data policy, size limits, stops distance and margin are all enforced by the backend, and its message is surfaced verbatim — so when a customer quotes an error at you, it is the server's words.
The gear icon opens terminal settings: a font-scale control and an order confirmation toggle, which is off by default. Both persist in the browser.
Placing orders
Four order types are accepted.
| Type | Behaviour |
|---|---|
MARKET |
Fills instantly at the marked-up ask (buy) or bid (sell) |
LIMIT |
Rests below the market for a buy, above for a sell |
STOP |
Rests above the market for a buy, below for a sell |
STOP_LIMIT |
A stop trigger that then places a limit |
Pending orders take a time in force:
GTC— good till cancelled.GTD— good till a date you supply, which must be in the future.DAY— expires at the session close. Only placeable while the session is open, because otherwise there is no close to expire against.
Pending orders reserve no margin. The hedge-aware free-margin check runs at trigger time instead, and an account that can no longer afford the position gets the order rejected rather than filled. That is a deliberate trade-off: reserving margin on resting orders would let a customer lock up their whole balance with orders that may never fill.
A pending order can be placed while the market is closed — it parks and triggers on reopen ticks.
Placement rules
Both paths enforce the same gates, because a pending order is not a harmless row: when it triggers it opens a real position with the levels it was placed with.
- Size must sit within the instrument's minimum, maximum and step.
- A pending order's price must not be instantly triggerable, and must sit at
least
stopsLevelpoints away from the current marked-up price. - Attached stop loss and take profit are validated against the price the order will actually enter at, not the current market — so a pending buy whose stop loss sits above its own entry is refused rather than opening a position that closes itself on the next tick.
- A
requestNoncemakes the placement idempotent. A retry after a dropped response returns the original order rather than opening a second position.
Managing what is open
Trailing stops are server-side. The distance is set in points and the engine moves the stop on every favourable tick, whether or not the customer's browser is open.
Partial closes book a PARTIAL_CLOSE deal and leave the remainder open with its
levels intact.
The Orders tab also shows recently rejected orders with their rejection reason until the customer dismisses them, so a pending order that failed its margin check at trigger time does not just silently disappear.
What a trade costs
Three separate charges, and it is worth being able to explain all three:
- The spread. The difference between what the provider quotes and what the
customer gets, set by the symbol group's
spreadMarkupPips. Paid implicitly at entry. - Commission.
commissionPerLotin the account currency, charged in full at open and priced as a round turn, so there is no second charge at close. Zero on every seeded group except US Stocks. - Swap. Overnight financing at
17:00New York, triple on the group's triple-swap day. Can be a credit rather than a charge, depending on the instrument's swap points.
Margin, in plain words
Used margin is what the open positions require: roughly
position value ÷ effective leverage, per symbol, converted into the account
currency.
Equity is balance + floating profit and loss.
Free margin is equity − used margin — how much more the account could
commit.
Margin level is equity ÷ used margin × 100. This is the number that
matters:
| Margin level | What happens |
|---|---|
| Comfortably above the margin-call level | Normal trading |
| Below the margin-call level (default 100%) | Email and in-app warning; margin-increasing orders refused; withdrawals blocked |
| Below the stop-out level (default 50%) | Positions force-closed, largest loser first, until the level recovers |
| Balance still negative after everything closed | Zeroed by negative-balance protection, if the account tier has it on |
Hedging is cheap by default. With hedgedMarginRate at 0, margin per symbol is
charged on the larger side only, so a fully hedged pair costs what one leg
costs.
Why an order gets refused
Every one of these is a real, distinct message the API returns:
The symbol is not in the QUOTING state — either the feed has gone stale past
the configured threshold, or the provider's WebSocket is not connected at all.
This is the most common refusal on a misconfigured install and it is almost
always the provider, not the order. See
Troubleshooting.
The instrument's session calendar says so. The terminal shows the next open time. Pending orders can still be placed and will trigger on reopen.
The instrument is flagged delayed and fxTradingDelayedStocksTradable is off.
Seeded stocks and index ETFs carry that flag. Turning the setting on when your
provider plan does not actually stream real-time equities hands customers
guaranteed latency arbitrage against your book.
The message states exactly how much was required and how much was available, in the account currency. It folds in commission and any margin reserved by in-flight external orders.
Only risk-reducing orders are accepted at this point. Closing is still allowed.
An operator has flipped the per-account kill-switch. Closes are still permitted; only new orders are blocked.
fxTradingEnabled is off. Demo accounts are unaffected.
The market is being wound down. Existing positions can still be closed.
The USD hub pair needed to convert this instrument's quote currency into the account currency is not listed and quoting. List it, or restrict account currencies to ones your catalog can convert.
The statement
Every balance-affecting event, newest first, each row carrying the balance it left behind: fills, commission, swaps, deposits, withdrawals and operator adjustments. The sum of the ledger equals the account balance — a reconciler verifies exactly that every five minutes and alerts if it ever stops being true.
This is what a customer should be pointed at in a dispute, and it is the same data the operator sees in the admin Deals Ledger.
Next
- The trading desk — the operator side of everything above
- Instruments, groups and sessions — where the costs come from
- Troubleshooting — when the terminal will not quote