vike
Desktop app

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.

On this page