vike
Concepts

The data model

The value types every crate shares — bars and ticks, the L2 book and its one delta-apply law, the per-symbol instrument grid, the fill primitive, the twenty-variant Event enum the core consumes — and the interning rule that decides which fields may be a pointer.

vike-model is rank 10 in the layering: the vocabulary every other crate names and depends on. Almost all of it is pure value types; the exceptions touch disk — crates/vike-model/src/state_path.rs, store_path.rs and tick_store_path.rs probe the filesystem to answer where a project, its settings and its store root are, and change_journal.rs and scratch.rs write under the answer. The crate header carries the parity rules every port in it obeys — f64 end to end, expressions in the same order as the source they were ported from, no fused multiply-add, exact-zero flat checks kept as == 0.0, timestamps as epoch-millisecond i64. This page is the handful of its types a reader meets on every other page.

Bars and ticks

crates/vike-marketdata/src/bar.rs's Bar is one OHLCV candle, resolution-agnostic: a timestamped price event with ts, open/high/low/close, volume, and four optionals — funding (a perpetual's rate at this bar, if available), bid and ask (the opening best quote of the bar's window, tick-derived and None for plain OHLCV bars), and symbol, the fully-qualified SYMBOL.VENUE id the engine dispatch attaches. QuoteTick and TradeTick are the two tick shapes, and both carry local_ts beside ts: the machine receive time, captured so feed latency can later be replayed rather than modelled — it cannot be captured retroactively, and 0 means the row was never stamped (a fixture, a backfill).

Ticks become bars in crates/vike-marketdata/src/consolidator.rs: a quote bar takes OHLC from the mid, volume as the tick count, and the bucket's opening best bid and ask — what a next-open market order fills against — while a trade bar takes OHLC from price, volume as summed size, and no quote. Two shapes live there: the batch consolidate_* functions, hex-pinned against frozen fixture bytes by the parity gate, and BarConsolidator, the streaming twin for live folds where ticks arrive one at a time.

The L2 book, and its one delta-apply law

crates/vike-marketdata/src/orderbook.rs's L2Book is the in-core reducer: venue tasks decode depth deltas, the book folds them, and a strategy reads the result through on_order_book. Prices are quantized to integer ticks — price / tick, rounded half to even — so levels live in a BTreeMap<i64, f64> keyed by tick: exact equality with no float-key hazard, ordered for O(log n) best bid and ask. A delta whose quantity is zero removes the level, by venue convention. All its arithmetic is add, subtract, multiply, divide and compare, so it is bit-parity eligible.

Whether an incoming delta folds, is dropped as stale, or means frames were lost is one decision, L2Book::delta_decision, with two policies, so that live consumers and replay can never diverge:

SeqPolicyapplies whenstale whengap when
Monotonicseq > last_seqseq <= last_seq (except the "no seq supplied" sentinel 0, which always applies)never — a jump is normal on these streams, and gap detection is the bridge's own upstream rule
Strictseq == last_seq + 1seq == last_seqanything else: a forward jump (dropped frames) or a regression (a venue restart) — the consumer must resync from a fresh snapshot

Strict is the rule for streams whose sequence increments by exactly one per applied event, and for the recorded BookUpdate chain every feed emits — which is what gives tick replay its integrity rule.

The walk-the-book helpers are upper bounds, not execution guarantees

avg_px_for_quantity, quantity_for_price, simulate_fill, can_fill and fill_ratio are pure, read-only estimates against displayed liquidity. Hidden flow, latency and queue dynamics only make the real fill worse. Their intended consumers are the risk gate's opt-in impact veto and the DOM ladder's cost-to-fill display, and degenerate inputs — a NaN, a crossed book — degrade rather than panic.

SymbolProperties — the per-symbol placement grid

crates/vike-model/src/instrument.rs's SymbolProperties holds the venue-neutral order-placement bounds every venue's REST parser fills from its own instrument endpoint: tick_size, step_size, min_qty, max_qty, min_notional, and the later arrivals contract_size (how much underlying one unit of quantity controls — a Deribit BTC option is one BTC per contract) and an optional tiered tick_scheme. It lives in vike-model rather than in the bridge crates because it is pure domain data consumed on the venue-neutral side of the seam; the per-venue parse functions stay with their venues. An absent property is 0.0 — "the venue did not constrain this" — and the risk gate's limits are built from this grid at mount. The struct grows, and its doc says to construct it with functional-update syntax for that reason: one added field once broke construction across fourteen crates. Its own page in the instruments tree goes deeper.

The fill primitive

crates/vike-model/src/fill.rs's compute_fill is the one cost-basis primitive: how a fill transitions a position — open, add, reduce, close, or flip — returning a FillOutcome with the new size and average price, the closing quantity, the realized PnL on it and the leftover opened on the far side of a flip. Callers keep their own cash, fee and trade-record bookkeeping and delegate only this shared arithmetic, which is what stops the backtest engines and the live read-model drifting from each other. Its branch math mirrors a frozen fixture bit for bit, and its own documentation says not to reorder an operation, introduce a fused multiply-add, or "simplify" an expression.

The Event enum

crates/vike-model/src/events.rs defines the immutable order, fill, position and account events. Every one is a plain value struct — Clone, no interior references — so events are copy-safe across the channel boundary between a venue thread and the core. Event is the tagged union the core loop consumes, serialized with a "type" tag, and it has twenty variants:

AccountState · Fill · Funding · OrderAccepted · OrderCancelRejected · OrderCanceled · OrderDenied · OrderExpired · OrderFilled · OrderLiquidated · OrderModified · OrderModifyRejected · OrderPartiallyFilled · OrderRejected · OrderSubmitted · OrderTriggered · PositionChanged · PositionClosed · PositionLiquidated · PositionOpened

Each has its own page under events, generated from the enum so the list cannot drift. The order lifecycle page is where the order events meet the state machine; OrderRequest, in the same crate, is the submission intent itself — what a strategy, a manual ticket or a bracket builder constructs, with every field's Rust Default pinned equal to its #[serde(default)].

The interning rule

venue and symbol on every event are interned strings — 8-byte Copy, pointer-equality, serde-transparent. The interner never frees an entry, so interning is only sound for a field whose set of distinct values over a process lifetime is bounded. That holds for a venue (about ten values ever) and a symbol (bounded by the distinct instruments subscribed per session — even a prediction-market token id is minted per market and reused on every tick of it, so the interned set is hundreds to low thousands of markets, not the trade count).

trade_id is deliberately NOT interned, and cannot be empty

It is minted per fill, unbounded, so an interned trade id would leak without bound. It is a TradeId newtype over a compact stack string instead. The newtype also closes a measured defect: trade_id is the dedup key for the whole money lane, and an empty id did not dedup badly — it skipped dedup entirely, so a re-applied fill double-booked commission and realized PnL on a live daemon. TradeId implements no Default, so the unwrap_or_default() that produced those empties is a compile error; TradeId::new refuses "" and hands the venue mapper the decision. The rule generalises: only bounded, repeated label fields are interned; high-cardinality per-event ids are not; and secrets are never interned, because an interned string cannot be zeroized.

On this page