Depth imbalance
Book pressure over the L2 book — the depth-N size imbalance, its inverse-distance-weighted twin, and the tracker that keeps the last readable value through a one-sided book.
crates/vike-orderflow/src/imbalance.rs holds the depth-imbalance studies over
vike_model::L2Book — a book-pressure mechanism from the standard literature, implemented
independently. Unlike the trade-fed builders it reads no TradeTick; its input is the reducer
described on the data model page.
Two pure functions
depth_imbalance(book, n) is (Σ bidQ − Σ askQ) / (Σ bidQ + Σ askQ) over the top n levels of
each side, in [−1, 1]. The header positions it precisely: "the depth-N generalization of
L2Book::imbalance (which is the n=1 top-of-book special case, kept where it is)". The sign is
bid pressure — +1 all bid, −1 all ask. It returns None if either side is empty or all sizes
are zero. An n deeper than the book uses what exists.
weighted_depth_imbalance(book, n) is the same ratio "with every level's size damped by
inverse distance to mid": each level contributes w·q with
w = 1 / (1 + d)², d = |price − mid| / mid"so touch depth counts (almost) fully and far depth fades quadratically". It returns None if
either side is empty, if the mid is unavailable or non-positive — "Polymarket-style books can sit
at 0" — or if all weighted sizes are zero.
The book side of both comes from crates/vike-marketdata/src/orderbook.rs's L2Book::top_n, "the N
best (price, qty) on each side (bids high→low, asks low→high)", and L2Book::mid, which is None
unless both a best bid and a best ask exist. The module fixes the arithmetic order: "top_n
yields bids high→low and asks low→high, and both sums are naive += folds in that order" — so
the number is a deterministic function of the book, not of iteration luck.
The tests carry the reference values. Bids (99, 10), (98, 5); asks (101, 10), (102, 15):
n = 2 gives (15 − 25) / 40 = −0.25, n = 1 gives 0 and equals L2Book::imbalance. A
mirrored book flips the sign exactly, asserted on the bit pattern. And a book whose bid mass sits
at the touch while an equal ask mass sits far away reads 0 unweighted and tips positive
weighted — the weighting seeing what the plain count cannot.
The tracker
pub struct ImbalanceTracker { /* n, weighted, last */ }
impl ImbalanceTracker {
pub fn new(n: usize, weighted: bool) -> Self; // asserts n >= 1
pub fn update(&mut self, book: &L2Book) -> Option<f64>;
pub fn last(&self) -> Option<f64>;
}ImbalanceTracker is "a small streaming wrapper that recomputes on every book update and caches
the last GOOD value across momentarily unreadable books (one side empty mid-resync)". update
recomputes the chosen variant; a Some result replaces the cache and is returned. A None — the
resync flicker where one side is briefly empty — is returned as None but "does NOT wipe
last()".
update and last answer different questions
update tells you whether this book was readable; last tells you the most recent reading that
was. A consumer that displays last() during a resync is showing a value from before the
flicker, by design — the alternative was a gauge that blanks every time a venue re-snapshots.
How it is gated
The crate header (crates/vike-orderflow/src/lib.rs) notes that the book-fed studies have no
trade-slice batch twin, so instead of the streaming ↔ batch parity the trade builders carry,
imbalance is "pure functions/scripted-snapshot gated" — hand-built books with hand-computed
answers, including the bounds, the exact sign flip under mirroring, and the cache surviving a
one-sided update. The order-to-trade ratio is the other study on the
same footing.
Volume profile
Volume at price on the tick grid, the point of control, and the 70% value area — with the tie rules that make the same trades produce the same profile every time.
Order-to-trade ratio
Quoting intensity inferred from consecutive book snapshots — the add/update/cancel proxy over the visible top-N, the per-window formula, and the event-anchored windows it is reported in.