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.
crates/vike-orderflow/src/otr.rs is the order-to-trade ratio, "a quoting-intensity study
inferred from consecutive book snapshots". Its header is candid about what it can and cannot see,
and that candour is the contract: without an order-by-order feed, messaging activity is
approximated by diffing the visible top-N level lists of consecutive snapshots.
The proxy
For each side, two consecutive snapshots of the top depth levels are compared price by price:
| a price that is… | counts as |
|---|---|
| present only in the new list | an add |
| present in both with a different quantity | an update |
| present only in the old list | a cancel |
Prices are matched on exact bits, which is safe because "both lists come off the SAME integer-tick
grid (L2Book::top_n prices are tick · tick_size), so equal price ⇒ equal bits". The first push
has nothing to diff against and contributes no messages; it is the baseline.
A level scrolling out of the window is a cancel — by construction
A level pushed off the top-N by a better price "counts as a cancel by construction — this is a visible-window proxy, not exchange message truth; document-level contract, not a bug". The tests pin it: a new best bid arriving on a depth-2 window reads as one add and one cancel. Read the ratio as activity at the visible edge of the book, not as a message count.
The formula
Per fixed event-time window — default 100 ms, configurable:
OTR = (adds + 2·updates + cancels) / max(trades, 1) − 1An update is one cancel-plus-replace, hence two messages. The −1 sets the baseline at parity:
"0 means one message per trade above baseline parity". The max(trades, 1) floor keeps a window
with no prints finite — the tests show five messages over zero trades reporting 4.0.
The tracker
pub struct OtrConfig { pub depth: usize, pub window_ms: i64 } // Default: depth 10, 100 ms
pub struct OtrSample {
pub window_start: i64, pub window_end: i64, // end = start + window_ms
pub adds: u64, pub updates: u64, pub cancels: u64, pub trades: u64,
pub otr: f64,
}
impl OtrTracker {
pub fn new(cfg: OtrConfig) -> Self; // asserts depth >= 1, window_ms >= 1
pub fn push(&mut self, ts: i64, book: &L2Book, trade_count: u32) -> Option<OtrSample>;
pub fn forming(&self) -> Option<OtrSample>;
pub fn last(&self) -> Option<OtrSample>;
}push folds "one observation: ts (event time, ms), the book to snapshot, and how many trades
printed since the previous push". It returns the committed sample only when this push crossed the
window boundary, and then "the push's own diff lands in the NEXT window". forming() is the open
window's counts with an interim ratio, None before any push; last() is the last committed
window. The header says the pair "mirror the crate's forming/committed convention" — the same
reading the flow bars offer.
Windows are event-anchored
Two rules from the header decide where windows begin and end:
- "The first push after a close starts the next window at its own ts (idle gaps don't emit empty
windows)." A push at 730 ms into a 100 ms window closes exactly one window,
[0, 100), and opens the next at 730 — however long the gap. - "A push whose ts reaches
start + window_msFIRST closes the window (its own diff opens the next one)." The boundary is half-open: a push at 99 ms is still in the first window; a push at 100 ms closes it.
The module's scripted window, from its tests: baseline at t=0; at t=50 one bid add and one ask
update with three trades; the push at t=100 closes the window with adds 1, updates 1, cancels 0, trades 3 and OTR = (1 + 2·1 + 0) / 3 − 1 = 0. The t=100 diff — a bid pulled — opens window two
with one cancel.
How it is gated
Like depth imbalance, this study has no trade-slice batch
twin, so the crate header lists it among the studies that are "pure functions/scripted-snapshot
gated": the diff counts (identical lists, from-empty, to-empty), the window arithmetic, the
half-open boundary, the event anchoring and the trade floor are each pinned against hand-computed
values. The book it reads is crates/vike-marketdata/src/orderbook.rs's L2Book, on the
data model page.
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.
VPIN
Volume-synchronized probability of informed trading — fixed-volume buckets, the per-bucket imbalance, the rolling window, and the aggressor-free bulk-volume-classification twin with its side-bias read.