Module Algostream_backtest.Latency

Execution latency simulation.

Venue.base_latency_us is read by nothing else. This module is its first consumer. Without it a backtest fills against the very tick that triggered the decision, which is the single most flattering and least realistic assumption a simulator can make.

Four delays, all in event time:

Jitter is drawn from the caller's Rng.t. The engine holds a separate substream for execution noise from the one driving the price path, so changing the latency model does not shift the data a Monte Carlo comparison runs on — common random numbers.

module Rng = Algostream_rng.Rng
type t = {
  1. decision_to_venue_ns : int64;
  2. venue_match_ns : int64;
  3. fill_to_strategy_ns : int64;
  4. cancel_to_venue_ns : int64;
  5. jitter_ns : int64;
    (*

    symmetric: the actual delay is uniform on ±jitter_ns around the base

    *)
}
val zero : t

All delays zero. For unit tests that want to isolate the fill logic, and for the "perfect execution" upper bound a strategy's real result should be compared against.

val of_venue : Venue.t -> ?jitter_ns:int64 -> unit -> t

Derive from a venue's published latency. decision_to_venue_ns seeds from venue.base_latency_us × 1000; the match and inbound legs default to a quarter and a half of that respectively, which is the usual shape (matching is fast, market data dissemination is not). Override any of them explicitly.

val outbound : t -> rng:Rng.t -> int64

Outbound delay for a new order: decision_to_venue_ns + venue_match_ns, jittered.

val inbound : t -> rng:Rng.t -> int64

Inbound delay for a fill report: fill_to_strategy_ns, jittered.

val cancel : t -> rng:Rng.t -> int64

Outbound delay for a cancel: cancel_to_venue_ns, jittered.

val to_string : t -> string