The trade panel
Order entry and sizing from a window — the equity hero, the per-venue accounts table, the trade card with its size modes, leverage and TP/SL bracket, and the one dispatch chokepoint every order crosses.
The Trade window is crates/vike-app-core/src/tool_views/trade.rs: the cross-venue equity hero,
the per-venue Accounts table, the focused trade card — order ticket plus sizing, leverage and TP/SL
— and the working-orders table. Its header notes why it moved out of the GUI shell: the four decision
helpers the render code used to inline are now named, pure functions with unit tests, which is the
point of the move for a crate that has no executed tests of its own. The layout is responsive —
two columns when at least 620 px wide, accounts and orders left and the trade card right, stacked
with the card first when narrower — and the composed width is capped at 960 px so a maximized window
does not stretch the tables across the screen.
What it shows
The hero renders the big cross-venue equity_total, a P&L chip, an unpriced-positions badge, and a
Realized / Fees / Funding strip, plus Margin used when it is non-zero. The numbers come from
crates/vike-app-core/src/equity_panel.rs's EquityPanelModel::from_snapshot — one EquityRow per
venue block in snapshot order, with a short mode tag: Δ for a delta-tracked balance, auth for an
authoritative one. hero_totals sums the rows as plain naive folds in row order, matching every
other display sum in the panel.
The Accounts table lists Venue · mode · Equity · uPnL · Fees · Funding with right-aligned tabular
numbers; a venue with positions that carry no mark source gets a warning badge whose hover text
(unpriced_tooltip) names those symbols. A per-asset balances line follows when the snapshot carries
one. The Orders table shows Side · Qty · Type · Status and an inline ✕ on every order that is still
working; is_terminal_status fixes the set that gets no cancel button — FILLED, CANCELED, REJECTED,
DENIED, EXPIRED, LIQUIDATED.
The trade card
The card header carries the snapshot's symbol, the mark on the right, and the core's fault line if one is latched. The position line reads LONG or SHORT with size, average price and unrealized P&L, and adds the leverage and estimated liquidation price when both are known; otherwise it reads Flat.
The ticket has a margin-mode pill (Isolated / Cross) and a leverage pill that opens a 1–125 slider.
Changing leverage does more than relabel: it emits Command::SetMargin with an initial-margin
requirement of 1 / leverage for the snapshot's venue and symbol, so the gate and the liquidation
estimate update truthfully. Order-type tabs are Market, Limit (with a Price field) and Stop (with a
Trigger field). BUY and SELL are enabled only when the resolved quantity is positive, the type's price
or trigger is present, and the core reports no fault. A Flatten position button appears whenever
a position is open and submits a reduce-only market order for the full size on the closing side.
Sizing
crates/vike-app-core/src/trade_sizing.rs is the pure sizing math, and SizeMode is the segmented
control above the size field: Qty is base units; Amount is the position's quote value, so
qty = input / mark; Cost is the margin the trader commits, so qty = input × leverage / mark.
All three modes are always offered, spot or perp. A derived line under the field shows the conversion
— the notional for Qty, the base quantity and margin for the other two — and the presets 25%, 50%,
75% and Max write pct_to_qty of free buying power into the field and switch it to Qty mode. Free
buying power is the primary venue block's free_bp, which the margin engine computes; the Cost /
Max readout under the buttons prints margin_cost and max_qty for the current inputs.
From a click to a command
A click fills crates/vike-app-core/src/tools.rs's TradeSubmit: side, quantity, reduce-only,
the order kind, an optional limit price or stop trigger, and optional take-profit and stop-loss
prices. Its doc states the bracket rule: when both tp and sl are set on an entry, the app
dispatches an OrderIntent::Bracket — an OTO entry with OCO exits — instead of a plain order.
Every UI order then passes crates/vike-app-core/src/order_dispatch.rs's plan_dispatch, which is
pure: the frame's drained intents, the order limits and the snapshot in; commands and rejects out.
Inside it, Planner::admit is the chokepoint for a single order and admit_bracket for a bracket,
and both always validate. The bracket path validates the three legs the runtime will actually build
(vike_model::build_bracket: OTO entry, OCO stop-loss, OCO take-profit, all at the entry quantity)
and refuses atomically if any leg fails — which is also how a market-entry bracket gets a notional
cap through its priced take-profit leg. The module's header records the hole this closed: of five
submit paths only three validated, and the Trade window — the panel a human types into — had no
local notional cap at all, nor did its bracket sub-path.
The preview cap is a preview, not the risk authority
crates/vike-app-core/src/order_entry.rs's validate_with_multiplier runs local checks only: finite
quantity and prices, a strictly positive quantity within the caps, a price on every limit, and
|qty| × |price| × |multiplier| ≤ max_notional. The multiplier comes from
CoreSnapshot::multiplier_of, so options and inverse perps are measured the way the venue-side
RiskGate measures them — the plain validate assumes 1.0 and under-measures them. Market orders
carry no price, so their notional is not checked here at all; the venue-side gate owns that. See
Risk and arming.
The ceiling itself arrives as a value: OrderLimits::with_max_notional takes
max_notional_per_order from policy.toml, resolved once by the binary, and ignores a non-finite or
non-positive number rather than turning it into a silent halt. Its doc records that the value used
to come from VIKE_MAX_ORDER_NOTIONAL — a ceiling any exported variable could raise — and that a
process which still finds that variable set refuses to start; the key is on
policy.toml.
The trade path routes to the snapshot's own (venue, symbol), never a literal. That is identical
in a local build, whose primary engine is what the snapshot publishes, and it is exactly what an
observe-mode client with control enabled needs — the header describes
the panel showing one market while the BUY button submitted another before this changed. A snapshot
with empty strings, the placeholder before an observer's first frame, is an explicit
NoRoutableMarket reject. A plain ticket the preview refuses still consumes its client-order id,
as the original sites did — a bracket and an unroutable ticket consume none. The id spelling is
ui-<n>-<shot> for this window, though the working-orders table never displays it — the table
reads the id only to address a cancel click. What happens to the order after the command lane is
Order lifecycle.
Workspaces
The multi-window desktop — arrangement, the minimized-window rail, the menu bar, and how a chart layout is persisted to workspace.json and named layouts without ever bricking startup.
The options chain
The Options window is a mirrored calls–strike–puts ladder over Deribit's public book, kept live between polls and painted with the trader's own orders and positions; the Greeks window prices those positions off the same chains.