vike
Backtesting

The queue lane

The opt-in queue-position fill lane of the tick replay — the queue-gated twins of the frozen per-tick fill pass, entered only when a queue model is configured, where a price touch no longer fills a resting limit on its own.

The frozen tick-replay fill pass fills a resting limit the instant price touches it. The queue lane is the opt-in alternative: the same pass, with every resting limit — a tagged maker quote included — additionally gated by an estimate of how much size sits ahead of it at its price. crates/vike-backtest/src/engine/queued.rs is the lane; the estimators it consults are queue models, and this page is about the lane rather than the arithmetic.

When it runs

The lane is reached only when EngineParams::queue_model is Some. Its module header states the contract the rest of this page rests on: a default run never enters the module at all, and the frozen fill_pending_tick / fill_tagged lanes it mirrors stay byte-identical. From a run profile that is queue_model = "…" in [engine], which EngineCfg::queue_model's doc marks tick mode only — the lane is consulted exclusively by run_ticks, so a bar-mode profile that sets it is rejected by BacktestProfile::validate rather than silently doing nothing.

The module is a separate file for a reason its header gives: the queued twins used to sit some 700 lines from the code they mirror, in a 2 200-line file — a hand-synced duplication no reader could check without scrolling between two ends of the file.

What is shared and what is genuinely different

The per-order gate sequence is not duplicated here. Session → staleness → emulator stop-release → trigger price lives in one function on the parent module, StrategyEngine::gate_pending, which this lane's taker branch calls like every other lane; the maker-lane session gate lives in StrategyEngine::defer_tagged_session. What the header lists as staying in queued.rs is only what makes the twin a twin: queue-state identity and sync (WorkingOrder::qid), the queue_gate verdict layered on top of the frozen price law, partial-fill remainder bookkeeping, and the maker straddle and min-hold guards. The one gate the lane still runs by hand is StrategyEngine::defer_session, on its price-conditional branch.

fill_pending_tick_queued's doc gives the shape: the same structure as the frozen path — trigger → resolve → dispatch → tagged lane — with resting Limit / LimitClose orders additionally gated by their queue-position estimate. Non-limit kinds (market, market-close, stop, trailing) are takers and pass through unchanged: a taker crosses the book, it never rests in it.

The verdict

queue_gate judges one resting limit against the raw tick, because the derived Bar shape cannot distinguish a trade from a quote. It is computed only after the existing fill model has already said the price condition holds — the gate composes on top of the frozen price logic and never replaces it. Three outcomes, QueueVerdict::{Rest, Fill, Partial}:

  • a strict price cross — traded or quoted through the level — fills in full, because the whole level, queue ahead included, was consumed;
  • a trade at the price consumes the front first, and only its excess beyond the front fills — in full when that excess covers the whole order, partially otherwise, the remainder resting;
  • a quote touch fills only once the front is already cleared — there is no trade proof otherwise.

Book events never reach the price/fill path, so a Tick::Book always rests. Two simplifications are declared on the function rather than hidden: trades are not filtered by aggressor side, and simulated orders never consume each other's queue — each order is gated independently, the standard shadow-order convention.

Identity, not fingerprints

Queue state is keyed by the order's engine-local identity, WorkingOrder::qid, stamped the first time the lane sees an order — never by a (side, price) fingerprint. The doc gives the case that rule exists for: the maker re-quote pattern cancel_all + re-submit at the same price correctly re-seeds at the back of the level instead of inheriting the cancelled order's advanced front. States whose order vanished since the last pass — cancel_all, an OCO sibling-cancel, a fill — are dropped by that pass's rebuild.

The tagged (HFT maker) lane, fill_tagged_queued, is the queue-gated twin of fill_tagged and keys differently, because a tag names a quote slot, not a resting order: its sync keys on the tag plus the slot's current side, price and qid. Its doc lists what forfeits priority — a re-priced tag, a re-submit over a live tag (submit_limit_tagged replaces the resting order), a cancel_tagged + re-submit, and a modify_tagged quantity increase all re-seed at the back of the level, each being a new order on a real venue. An amend-down keeps its place, as venues do. Gone tags drop their state; partial fills shrink the resting tag in place instead of retiring it.

The maker guards

The min-hold guard is EngineParams::queue_min_hold_ms (queue_min_hold_ms in the profile, read only when queue_model is set): on the tagged maker lane, a fill that would reduce the current position — the closing side of a maker round-trip — is deferred until at least that long after the position was opened, so a maker cannot book a physically impossible same-millisecond or sub-second round-trip. 0, the default, disables it, and the untagged queued-limit lane runs no such check at any setting. The field's doc says where to calibrate it from — real market-maker behaviour — and quotes Polymarket BTC-5m market-maker wallets flipping at roughly a 2 s p10 and an 11 s median. The straddle guard is the other guard the header names; its mechanics are documented at its call site in the lane.

The session gate reaches this lane too. EngineParams::session_gate's doc is explicit that a closed market crosses no resting limit and no maker quote, queued or not: resting orders, the tagged maker lane and protective stops are gated on the frozen fill lanes and on their opt-in queue_model tick twins alike.

What to feed it

EngineCfg::queue_model's doc closes with the data requirement: for a real maker backtest, feed the trade tape, and — for true size-ahead seeding — the book. Without a book the seed falls back to the last L1 quote and then to queue_seed_depth; queue models carries that chain. The natural partner is a latency model: the queue removes the "always first in line" optimism, latency removes the "reacts on the same tick" optimism, and crates/vike-backtest/src/latency.rs's header names the two as partners.

On this page