vike
Order flow

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.

crates/vike-orderflow/src/classify.rs opens by calling itself "the one load-bearing semantic of the whole crate", and the rest of the crate agrees: every study that splits volume into buy and sell reaches this module rather than deciding for itself. It is three items long.

pub enum Side { Buy, Sell }

pub fn classify(t: &TradeTick) -> Side          // is_buyer_maker → Sell, else Buy
pub fn signed(t: &TradeTick) -> (f64, f64)      // (buy_add, sell_add) for the full size

The convention

TradeTick::is_buyer_maker is the Binance-aggTrades convention, and the header spells out what it means: the buyer was the resting maker — a bid — so the aggressor was the seller. classify therefore maps is_buyer_maker = true to Side::Sell and false to Side::Buy. The module's own tests put it in market terms: an ask lifted is a buy aggressor; a bid hit is a sell aggressor.

signed is the form the builders actually consume. Its doc comment: "(buy_add, sell_add) for the full size of t, routed to the aggressor side" — a buy contributes (size, 0.0), a sell (0.0, size). A trade is never split between sides; it goes whole to the side that crossed the spread.

The module ports no Python twin — it is new native code — so there is no frozen fixture behind it, only the hand-computed tests beside it (the index explains why that is the crate's oracle).

Everything downstream inherits it

The crate header (crates/vike-orderflow/src/lib.rs) states the consequence: delta = buy_vol − sell_vol everywhere below. Concretely:

studyhow it uses the semantic
flow barsthe header of bars.rs: all builders "reuse the one crate::classify::signed buy/sell semantic"; OrderflowBar::delta is buy − sell at close. The information-driven bars call classify directly to sign each trade's weight.
CVDCvdAccumulator::push adds b − s from signed
footprintFootprintBuilder::push adds signed's pair to the trade's price bucket
volume profileVolumeProfileBuilder::push does the same into its own grid

Because there is exactly one classification site, a feed whose maker flag is inverted would invert every delta, footprint cell and profile bin at once — there is no second reading anywhere to disagree with.

A missing flag reads as a buy

On crates/vike-marketdata/src/bar.rs's TradeTick the field is #[serde(default)], and the struct's own description says the aggressor flag is present "where available". A trade record that carries no flag deserializes with is_buyer_maker = false, which classify reads as a buy aggressor. On a tape that never supplies the flag, every study on this page's list would report all volume as buy volume and a delta equal to total volume — a number that looks like a signal and is not.

The aggressor-free alternatives

Not every venue publishes an aggressor flag, and crates/vike-orderflow/src/vpin.rs answers that in two places. Vpin itself has a flagless mode: "feeds without a real aggressor flag construct with has_aggressor = false and pass the prevailing mid to push: price >= mid → Buy, else Sell (the quote-midpoint test)", and a missing mid falls back to the flag so no trade is dropped; the chart's study registry exposes the same switch as its aggressor flag parameter set to 0. The crate header names the other: BvcVpin, "the aggressor-free" VPIN of Easley, López de Prado and O'Hara (2012), which classifies volume in bulk by the price change of each bucket, Φ(ΔP/σ), with a tick-rule option. It does not go through classify at all — that is the point of it. Both are described on the VPIN page.

On this page