Flow bars
Tick, volume and dollar bars with a buy/sell/delta split, how each one treats the trade that crosses its boundary, and the opt-in imbalance and runs bars that sample by information rather than by count.
crates/vike-orderflow/src/bars.rs is "tick-, volume- and dollar-interval bar aggregation with a
buy/sell/delta split, plus the opt-in López-de-Prado information-driven bars". All five builders
reuse the one classification semantic and emit the same bar.
The bar
pub struct OrderflowBar {
pub o: f64, pub h: f64, pub l: f64, pub c: f64,
pub buy_vol: f64, pub sell_vol: f64, pub delta: f64,
pub trade_count: u32,
pub first_ts: i64, pub last_ts: i64,
}Behind every builder sits one private accumulator, BarAcc, which "accumulates the running fields
of one open bar": OHLC, the buy and sell sums, a trade count, and a running notional Σ price·size
that "is only consulted by DollarBarBuilder; the tick/volume builders carry it inertly". At close
delta is buy − sell, and first_ts/last_ts are the first and last contributing trade's
ts.
The five builders
| builder | closes when | the boundary trade | push returns |
|---|---|---|---|
TickBarBuilder::new(n) | the count reaches n | whole — a trade is one tick | Option<OrderflowBar> |
VolumeBarBuilder::new(threshold) | buy + sell reaches threshold | split by volume; the filling slice closes the bar | Vec<OrderflowBar> |
DollarBarBuilder::new(threshold) | Σ price·size reaches threshold | split by the size that fills the remaining dollar room | Vec<OrderflowBar> |
ImbalanceBarBuilder::new(unit, threshold) | |Σ ±unit| reaches threshold | included whole — no split | Option<OrderflowBar> |
RunsBarBuilder::new(unit, threshold) | max(buy-run, sell-run) reaches threshold | included whole — no split | Option<OrderflowBar> |
Every constructor asserts its parameter (n >= 1, threshold > 0). Each builder has a forming()
read — "Snapshot of the OPEN (unclosed) bar — the chart's forming bar. Pure read." — and a batch
from_trades over a slice, which returns closed bars only; the tick and imbalance forms say so in
place ("trailing partial bar dropped"). Zero- and negative-size trades never open or advance a bar
on any path.
Why volume and dollar bars return a Vec
vol_fold closes bars "whenever acc.buy+acc.sell reaches threshold" and "splits the trade
across boundaries by volume": the trade is cut into a synthetic sub-trade of the size that fills
the open bar's room, at the trade's own price, side and timestamp, and the remainder starts the
next bar. One large print can therefore close several bars in a single push, which is why the
return type is a list.
dollar_fold is "the dollar twin of vol_fold": it splits by room / price, the size that fills
the remaining dollar room. It carries a guard the volume form does not need. If the computed take
is non-positive, or too small to advance the remainder at all (sub-ulp), "the whole remainder is
absorbed into the open bar instead of looping". That is also where a degenerate price <= 0 trade
lands: it contributes volume but no notional, "so it can never close a bar on its own".
The two boundary conventions are deliberate and different
Volume and dollar bars split the crossing trade so a bar's volume is exactly its threshold. The imbalance and runs bars include the crossing trade whole, because — in the module's words — "LdP samples AT the crossing tick". Do not expect an imbalance bar's total volume to be a round number.
Information-driven bars
The two remaining builders are the López de Prado imbalance and runs bars (AFML §2.3.2), in their "fixed-threshold deterministic form". Both are opt-in: "a caller that never constructs this leaves every existing bar path byte-identical", and the header restates that a default path which builds none leaves the tick/volume/dollar behavior unchanged.
FlowUnit is the per-trade quantity they weight each signed trade by — Tick weights every trade
as 1, Volume as its size, Dollar as price·size. It is fixed at construction and affects
only when a bar closes; "the ordinary buy/sell/delta volume split on the emitted bar is
unaffected by the chosen unit".
Imbalance bars advance a running signed sum θ by +weight for a buy aggressor and −weight for a sell, and close when |θ| reaches the threshold.
Runs bars grow two cumulative per-side sums and close when the larger reaches the threshold. The doc comment is emphatic about what this is: "LdP's cumulative-side statistic, NOT the longest CONSECUTIVE same-signed streak."
The EWMA-adaptive thresholds from the same chapter — E[T]·|2P(buy)−1| for imbalance,
E[T]·max{P, 1−P} for runs — are named as the documented future extension; what ships is the
fixed-threshold variant.
On the chart
crates/vike-app-core/src/tickvol.rs is where these bars become chart intervals. Its BarKind
classifies an interval string: the fixed kline set passes through unchanged, while "<n>t" and
"<x>v" select the tick and volume aggregators. TickVolAgg wraps one builder — the header names
TickBarBuilder or VolumeBarBuilder — and converts each closed OrderflowBar to a
vike_model::Bar, with funding, bid, ask and symbol left None the way the kline path
leaves its own not-sourced fields. Dollar, imbalance and runs bars are not named on that interval
path.
Buy/sell classification
The one load-bearing semantic of the order-flow crate — how a trade's aggressor side is read off the maker flag, and how every bar, delta, footprint and profile inherits it unchanged.
CVD
Cumulative volume delta — the running sum of buy minus sell volume over the trade stream, the accumulator that computes it, and the per-bar form the chart derives from footprints instead.