In the GUI
Where each order-flow study renders — the one footprint substrate the app feeds, the Footprint style, the volume-profile overlay and CVD pane derived from it, the study panes the chart engine carries, and which of those the desktop app has wired.
The order-flow crate computes; vike-chart draws. Its header claims "the
per-bar orderflow substrate rendering (chart::ChartStyle::Footprint + the CVD/volume-profile
overlays, SP2)", and crates/vike-chart/Cargo.toml lists vike-orderflow among the five vike-*
crates it depends on, annotated "per-bar footprint substrate → CVD/profile/footprint render (SP2)".
Everything below follows from one decision stated in crates/vike-chart/src/orderflow.rs's
header: CVD and the visible volume profile are derived from &[FootprintBar] — "the single
orderflow input — so the app feeds one structure and the renderer computes the three views."
One input: the footprint substrate
ChartInputs::footprint (crates/vike-chart/src/chart/mod.rs) is "the per-bar footprint
substrate (SP2), index-aligned to state.bars — the single orderflow input CVD/profile/footprint
are all derived from chart-side", and it is None "when the app hasn't aggregated trades for
this chart (default-off: no orderflow rendering happens without this)".
The app builds it in crates/vike-app-core/src/orderflow.rs's OrderflowAgg, which buckets "the
drained trade tape into the chart's bars (by open-time)" and retains per-bar footprint cells; the
per-frame drain that feeds it is crates/vike-app-core/src/core_sync.rs, which runs every frame
outside the snapshot gate so a workspace with no kline feed no longer stalls. The bucket width
defaults to nice_orderflow_tick: "a 'nice' 1/2/5×10^k bucket tick ~ price × 0.0002 (≈2 bps)" —
about $10 on BTC, $0.5 on ETH, $0.0001 on a $0.5 coin — computed through the libm crate so a
price on a decade boundary cannot bucket ten times finer on one machine than another.
Turning it on
WinState::orderflow_on (crates/vike-app-core/src/workspace/state.rs) is true "when either
toggle is on, OR the chart style is Footprint on a Kline interval"; the window loop uses it "to
lazily subscribe the trade feed and register an orderflow::OrderflowAgg — never on a fresh,
all-default window". The toggles sit behind the chart toolbar's OF button
(crates/vike-desktop/src/chart_window.rs): a popup with CVD and Volume Profile checkboxes and a
tick-size override, Auto or a pinned value; the label tints while either toggle is on. All three
persist per window (crates/vike-app-core/src/workspace/persist.rs's cvd_on, profile_on,
of_tick_size), and a workspace file written before they existed loads them off.
The subscription is the window's own venue tape — "any venue with a subscribe_trades feed
(OKX/Bybit/…) drives CVD/footprint, not just Binance" — and footprint stays None "until this
chart's aggregator exists AND has ingested at least one trade batch (a one-two-frame startup lag
after the toggle flips)". The tick size the chart receives is the aggregator's actual bucketing
width, so what is drawn matches the granularity the data was bucketed at.
The three views
Footprint is a chart style. ChartStyle::Footprint is the "per-bar buy/sell price-cell
grid — the orderflow footprint chart", rendered from the substrate and falling back to plain
candles when it is None. It has its own "Orderflow" section in the style menu because it is not
a reindexing transform like Renko or Kagi but "an alternate per-bar substrate (buy/sell price
cells) for the SAME bar axis". draw_footprint (crates/vike-chart/src/render.rs) paints, per
bar, a column of per-price cells with a faint imbalance tint; the bar's point-of-control cell —
max buy + sell volume within that bar — gets an amber outline; buy/sell numbers appear only when
cell_text_legible passes, a cell at least 11 px tall — "below that the tinted rect alone
carries the imbalance read". It draws the visible bar range only, clamped against both lists: a
substrate shorter than the bar list ("the aggregator hasn't caught up to a just-opened forming bar
yet") draws the bars that have cells and stops at the shorter of the two, and a stale or
out-of-range bound degrades to drawing nothing rather than a panic. The cells themselves are on
the footprint page.
Volume profile is an overlay on the price pane. paint_volume_profile
(crates/vike-chart/src/chart/price_render.rs) draws "a visible-range volume-at-price histogram
- POC + value-area band painted on the price pane, pinned to the RIGHT edge of the visible
x-range". It is windowed to the same visible bar bounds as the footprint, and the bucket width
comes from one derivation,
orderflow_tick_size, "so the two always agree for the same visible range".ChartState::visible_profile_sharedcaches the profile by visible bounds, substrate length and tick size, so an unchanged frame is an O(1) clone rather than aVolumeProfile::from_footprintsrecompute. Its POC line is the palette's amber,PROFILE_POC_COLOR— "same hex asdom.rs's privateLAST, the DOM ladder's last-trade row". POC and value area are on the volume profile page.
CVD is a sub-pane. PaneKey::Cvd is "a singleton pane like Volume, not per-uid like
Study, since there is at most one CVD pane per chart", and draw_cvd_pane plots
cvd_from_footprints over the whole substrate — "the CVD pane plots the full running series, so
it can't be windowed the same way" as the profile. ChartState::cvd_shared caches it on
footprint_gen, a monotonic generation the aggregator bumps on every rebuild. The pane's gate is
cvd_on and footprint data present; the layout tests pin that "cvd_on alone must not
produce a pane without footprint data". Volume, CVD and study panes are peers in the order the
user arranged, sized by the identity-keyed PaneFractions. The fold is on the
CVD page.
The study panes
crates/vike-chart/src/studies.rs is "the tick-driven twin of crate::indicators::Active":
vike-orderflow owns the compute, and this adapter "turns those TICK-driven folds into
BAR-INDEXED oscillator-pane series", reusing the indicator paint state and ParamSpec surface so
"a study pane is indistinguishable from an indicator pane to the renderer and to the settings
dialogs". study_registry() holds three:
| name | label | needs a book | bands |
|---|---|---|---|
vpin | VPIN (order-flow toxicity) | no | 0.2 · 0.5 · 0.8 |
book_imbalance | Book imbalance (depth-N) | yes | −0.5 · 0 · 0.5 |
otr | Order-to-trade ratio | yes | 0 |
The header's load-bearing decision is the tick-to-bar mapping: each ActiveStudy keeps one
incremental instance folded over the live tick stream and samples its committed value when a bar
closes; a quiet bar carries the last value ("a flat segment is the truthful reading"); bars
before the first committed value sample NaN; the forming bar gets a speculative tail from the
study's interim read, dropped at the next sync. A book-dependent study renders nothing until a
book snapshot has been fed — "no fabricated values" — while VPIN needs trades only.
render_study draws through the same oscillator body indicators use, and a study pane shares the
indicator uid space, so "a study and an indicator can even share one pane". Each fold is on its
own page: VPIN, depth imbalance,
OTR.
The desktop app does not build a study yet
crates/vike-desktop/src/app_ui.rs, the one place the app assembles ChartInputs, passes
studies: &[] — "empty until the app grows its own study menu + tick fan-in — an empty slice
renders byte-identically to the pre-studies chart". The three studies above are a chart-engine
capability with tests, not a menu item: no picker in the shipped app adds a VPIN, book-imbalance
or OTR pane. The Footprint style, the volume-profile overlay and the CVD pane are the order-flow
views you can turn on today.
What is not an order-flow study
Two neighbours look like this crate's work and are not. The DOM ladder
and its order-flow heatmap live in vike-panels, whose manifest calls it "chart-engine-free" and
depends on vike-model, vike-ui-theme and egui only — L2Book is what it paints over, and it
never reads vike-orderflow. And tick and volume bars ("<n>t", "<x>v" intervals) are a bar
source for the chart's ordinary sync path, built by crates/vike-app-core/src/tickvol.rs from
the flow bars builders — not a pane; orderflow_on notes they are
"otherwise out of scope for the Footprint STYLE", which applies on kline intervals. The engine as
a whole is on the chart page, and per-window persistence on
workspaces.
Streaming quantiles
The P² single-quantile estimator — five markers, constant space, an O(1) update per observation, exact below five samples, and gated against a full-sort oracle and the published paper example.
Research
The research loop in Vike Trader — a study reads the history store, may fit a model, scores its own numbers and leaves a run behind — and the headless crate that hosts every step of it.