vike
Order flow

Order flow

The pure trade-aggregation core behind every order-flow study — one buy/sell semantic, flow bars, CVD, footprint and volume profile folded from the trade tape, the book-fed microstructure studies beside them, and the parity discipline each builder is gated by.

vike-orderflow is the crate every order-flow study in Vike Trader is computed by. Its own header, crates/vike-orderflow/src/lib.rs, states the contract in one line: a pure trade-aggregation core that folds vike_model::TradeTick streams into orderflow primitives — no I/O, no rendering, no threading. The manifest (crates/vike-orderflow/Cargo.toml) declares it at layer 20, tier domain, "one hop over the vocabulary: a single-purpose pure core", and its only workspace dependency is vike-model. The one crate beyond that is libm, pulled in for the standard-normal CDF that VPIN's bulk volume classification needs — a pure-Rust FDLIBM erf, deterministic across platforms, and already a workspace-pinned dependency rather than a new supply-chain entry.

The input

Six of the nine modules read one type: crates/vike-marketdata/src/bar.rs's TradeTick, "one executed trade: price + size, with the aggressor flag where available". Its fields are ts (epoch milliseconds, UTC), local_ts (the machine receive time — 0 means not stamped, which is what fixtures and backfill carry), price, size, is_buyer_maker and symbol. Two more, imbalance and otr, read a vike_model::L2Book instead — the same reducer the data model page describes. The ninth, p2, reads neither: its push takes a bare f64, which is why the streaming quantiles page calls it the one module that folds nothing from the tape.

One semantic, nine modules

modulesymbolswhat it ispage
classifySide, classify, signedthe load-bearing semantic: is_buyer_maker=false → Buy, true → Sell; delta = buy_vol − sell_vol everywhere belowclassification
barsOrderflowBar, TickBarBuilder, VolumeBarBuilder, DollarBarBuilder, ImbalanceBarBuilder, RunsBarBuilder, FlowUnittick, volume and dollar bars with a buy/sell/delta split; the opt-in information-driven barsflow bars
cvdCvdAccumulatorrunning Σ(buy − sell)CVD
footprintFootprintBuilder, FootprintBara per-bar PriceBin gridfootprint
profileVolumeProfileBuilder, VolumeProfile, PriceBinprice-bucketed bins, POC, 70% value areavolume profile
imbalancedepth_imbalance, weighted_depth_imbalance, ImbalanceTrackerdepth-N and inverse-distance-weighted book pressuredepth imbalance
otrOtrTracker, OtrConfig, OtrSampleorder-to-trade ratio from consecutive top-N snapshot diffs per event-time windoworder-to-trade ratio
vpinVpin, BvcVpin, VolumeClassifierfixed-volume-bucket order-flow toxicity; the aggressor-free variant with bulk volume classification and a tick-rule optionVPIN
p2P2Quantilethe Jain–Chlamtac P² single-quantile estimator in O(1) space, for future adaptive thresholdsstreaming quantiles

The header splits them into two families. The first five are "builders, one file each"; the last four are microstructure studies — mechanisms from the standard literature, implemented independently.

Two conventions every builder shares

Forming versus committed. Every bar builder carries a forming() read, documented identically on each: "Snapshot of the OPEN (unclosed) bar — the chart's forming bar. Pure read." The closed bar is what push returns; the open one is what the chart draws while it is still filling. OtrTracker's forming() and last() "mirror the crate's forming/committed convention", so a book-fed study reads the same way.

Zero-size trades are skipped. TickBarBuilder::push returns None for size <= 0, CvdAccumulator::push, FootprintBuilder::push and VolumeProfileBuilder::push return without touching state, and every batch from_trades filters on size > 0 before folding. ImbalanceBarBuilder::push says it outright: "Zero/negative-size trades are skipped entirely (never open or advance a bar)."

The gate discipline

The header states what every builder is tested against, and it is not the parity-fixture machinery the rest of the platform uses:

  • hand-computed expected-value tests — "the real oracle — no CPython twin here";
  • invariant and zero-skip tests;
  • streaming ↔ batch bit-parity on f64::to_bits, where "batch is a genuinely separate slice computation, not push in disguise".

The book-fed studies, imbalance and otr, have no trade-slice batch twin, so they are gated as pure functions over scripted snapshots instead. p2 is gated against an exact full-sort quantile oracle plus the published paper example.

No Python twin, so no frozen fixture

crates/vike-orderflow/src/classify.rs says it "ports no Python twin (new native)". The parity gate's frozen fixtures therefore do not reach this crate; its oracle is hand computation checked into the tests beside each builder, and its parity claim is the streaming-equals-batch one above.

Where it is consumed

The header closes with "SP2 renders this, SP3 backfills it — both out of scope here." The consumers name themselves in their own headers:

  • crates/vike-app-core/src/tickvol.rs — interval strings of the form "<n>t" and "<x>v" select the tick and volume aggregators; TickVolAgg wraps one builder and converts each closed OrderflowBar to a vike_model::Bar for the chart's normal sync path, while the fixed kline set passes through unchanged.
  • crates/vike-app-core/src/orderflow.rs's OrderflowAgg buckets the drained trade tape into the chart's bars by open time and retains per-bar footprint cells — "the substrate SP2 renders from".
  • crates/vike-chart/src/orderflow.rs derives CVD from &[FootprintBar], "the single orderflow input — so the app feeds one structure and the renderer computes the three views"; the visible volume profile is this crate's own VolumeProfile::from_footprints, which the chart reaches through ChartState::visible_profile_shared.

Which pane draws which study is the in the GUI page; the chart engine itself is under the chart.

On this page