vike
Desktop app

The DOM ladder

The depth-of-market order-entry ladder — side-bound click-to-trade over a live L2 book, the Elite liquidity heatmap, the cost-to-fill readout, and the PAPER/LIVE badge that says where a click goes.

The DOM lives in crates/vike-panels/src/dom.rs, whose header calls it the "Pro" click-trade ladder plus the "Elite" order-flow surface — a time×price liquidity heatmap strip — switchable in the window header. It is Rust-native (the Python app it descends from had no DOM), and it is pure egui painting over vike_model::L2Book with no dependency on the execution core: draw is a stateless render over caller-owned DomState and borrowed DomInputs, and every trader intent leaves as a neutral DomAction the app maps to a vike_exec::Command. The same data-in, actions-out seam the chart uses.

The interaction grammar

The header states it: click-to-trade is side-bound. The column decides the side — Buy/Bid on the left, Sell/Ask on the right — and the row decides the price. Limit versus stop resolves by side of market: a buy above the ask or a sell below the bid would cross, so it becomes a stop, and holding Shift forces a stop regardless (resolve_is_stop). Clicking your own resting order cancels it; dragging it reprices it and emits DomAction::Modify. The footer carries Close and Reverse plus cancel-side buttons. DomAction is the complete vocabulary: Place, Modify, Cancel, CancelSide, CancelAll, ClosePosition and Reverse.

The toolbar exposes quantity presets (QTY_PRESETS: 0.001, 0.005, 0.01, 0.05, 0.1 — BTC-scale defaults), a price-grouping selector in ticks (GROUP_STEPS: 1, 2, 5, 10, 25, 50), recenter, and a reduce-only arm. DomState::default starts in Pro mode on Binance at qty 0.01, grouping 1.

What the ladder is showing

DomVenue cycles Binance → Bybit → OKX → Aster → Hyperliquid in the header. The glue in crates/vike-app-core/src/tool_views/dom.rs renders the live L2 book for the selected venue, folded in data_sink::BookStore, and paints a synthetic stand-in (crates/vike-app-core/src/dom_math.rs's synth_book, seeded by default_price and tick_for) only until the first real snapshot lands. Working orders and the position are projected per venue from the published snapshot — orders filtered to the window's venue and instrument, the position from that venue's block — and signed_position_size resolves direction the venue-correct way: hedge-mode perps carry it in the LONG/SHORT string, one-way and paper positions in the sign of the size. Reading the string alone once made every short render as a long with inverted P/L and made Close/Reverse pick the wrong side.

The ladder centres on the live book's mid rather than on the last trade, so real depth stays in view even when the last-trade mark lags a few ticks; scrolling latches a manual centre. Depth bars scale against the peak grouped quantity over the visible rows, the last-trade row is amber, and the position's average price gets a marker.

The header badge is where a click's order actually goes

header paints PAPER in amber or ● LIVE in red — in the source's own words, "where a click's order actually goes." The glue sets paper to the inverse of the window's live flag, which is true only when the selected venue has a credential-gated live execution client. A ● STALE badge appears and the ladder dims when no book update has arrived inside the freshness window, so a frozen book never reads as live.

Capabilities, not post-hoc rejects

DomInputs::caps carries the selected venue's declared adapter capabilities, filled by the app from vike_model::caps_for. The widget consults caps.allows_modify() at drag-start, at the drop and at the marker affordance, so a venue with no native modify never offers drag-to-reprice instead of letting it become a reject after the fact; an unknown venue resolves to VenueCaps::UNSUPPORTED, which offers nothing. The per-venue table this reads is described on Venues.

Elite: the liquidity heatmap

In Elite mode the region splits — the heatmap strip takes the left 42% and the ladder the rest, sharing row geometry. DomHeatmap is a fixed-capacity ring buffer of per-row intensity columns (180 columns by default), pushed on a throttled cadence — every sixth repaint — and normalised against a running peak. The header describes it as the modest, egui-painter form of the surface: O(rows) per push, rendered as rectangles, with a GPU or texture upgrade left as a later, feature-gated change that would read the same ring buffer.

Cost to fill

DomState::cost_to_fill is an opt-in footer readout, off by default so the render path is unchanged: for a hypothetical market order of cost_qty (which follows the toolbar quantity unless set) it walks both sides of the displayed book and shows the projected average fill and slippage versus mid. cost_to_fill is L2Book::simulate_fill verbatim — the same walk vike_exec::impact_veto performs — so the basis points a trader reads here are the ones a max_slippage_bps gate compares to its budget. cost_label states the two known divergences: an incomplete walk renders ∞ where the slippage would go (the displayed book cannot cover the size, and the gate rejects exactly that case as not fillable), and the gate rounds size to the venue lot grid while the readout walks the size as typed. A one-sided book shows no slippage number rather than a fabricated zero.

What happens after the click

Every DomAction drains through crates/vike-app-core/src/order_dispatch.rs, the one place a UI intent becomes a command. A Place is SubmitSource::Dom and a Close or Reverse is SubmitSource::DomExit; both go through Planner::admit, the one chokepoint where the local preview runs before anything reaches the command lane. What that preview can measure differs by shape: a priced Place is capped on notional, computed with the instrument's contract multiplier so the UI measures the same quantity RiskGate does; a Close or Reverse is a market leg carrying no price, so no notional is measured for it at all and only the qty bounds apply. OrderIntent::Modify — the drag-to-reprice — is a declared residual: it carries only the order id and the new price, so it is not capped, and the exemption is named in an exhaustive classification test rather than left implicit. The venue-side RiskGate remains the authority either way; see Risk and arming and The trade panel for the rest of that chokepoint.

On this page