Module Algostream_montecarlo.Engine

Monte Carlo over a strategy.

Two modes, because "10,000 runs" and "path-dependent scenarios" cost differently

Both ship. The guide publishes both numbers rather than quoting the cheap one and implying the expensive one.

Reproducibility

Run i draws from exactly two substreams of root_seed: index 2i for data and 2i+1 for execution. Neither depends on n_domains, on scheduling, or on how many runs preceded it. Therefore run with n_domains = 1 and with n_domains = 16 produce bit-identical summaries, which test/montecarlo/test_engine.ml asserts.

Memory

10,000 runs × a full equity curve is gigabytes, so metrics are computed inside the worker and only a ~27-float vector crosses back. keep_first_n_results retains whole Result.t values for the first few runs, for debugging. This is a hard constraint on the API, not an optimization.

module Rng = Algostream_rng.Rng
module Backtest_engine = Algostream_backtest.Engine
type config = {
  1. n_runs : int;
  2. root_seed : int64;
  3. n_domains : int;
    (*

    <= 1 runs inline; see Pool.recommended_domains

    *)
  4. generator : Generator.t;
  5. n_steps : int;
    (*

    path length per run

    *)
  6. backtest : Backtest_engine.config;
  7. keep_first_n_results : int;
    (*

    default 3

    *)
}
val default_config : n_runs:int -> root_seed:int64 -> generator:Generator.t -> backtest:Backtest_engine.config -> config
type summary = {
  1. n_runs : int;
  2. n_failed : int;
  3. root_seed : int64;
  4. generator : string;
  5. per_metric : (string * Quantile.summary) array;
    (*

    one distribution per Metrics field, in Metrics.to_assoc order

    *)
  6. failures : (int * string) array;
    (*

    run index and message, in index order

    *)
  7. retained : Result.t array;
}
val run : (module Algostream_strategy.Strategy.S with type params = 'p) -> params:'p -> config:config -> summary

Engine-level Monte Carlo: n_runs full backtests over synthetic markets.

val run_paths : returns:float array -> n_runs:int -> root_seed:int64 -> n_domains:int -> periods_per_year:float -> ?block_len:int -> ?n_domains_hint:int -> unit -> summary

Path-level Monte Carlo: resample a return series n_runs times and recompute metrics. No strategy, no fill model — you are asking about the distribution of the equity curve you already have, not about how it would have been executed.

type 'p arm = {
  1. params : 'p;
  2. backtest : Backtest_engine.config option;
    (*

    None uses the shared config.backtest

    *)
}

One side of a paired comparison.

backtest overrides config.backtest for this arm only. It exists because the interesting comparisons are not all parameter changes: risk limits, cost model, slippage and latency live in the backtest config, so a params-only comparative cannot express "same strategy, same market, risk limits on versus off".

val arm : 'p -> 'p arm

An arm that changes only parameters.

val arm_with : 'p -> Backtest_engine.config -> 'p arm

An arm that changes the backtest configuration.

type comparison = {
  1. n_runs : int;
  2. n_failed : int;
    (*

    Runs where either arm raised. Reported rather than dropped: a comparison computed from the subset that happened to survive is a biased sample, and silently returning it as though it were the whole batch is the failure mode worth guarding.

    *)
  3. failures : (int * string) array;
  4. per_metric : (string * Quantile.summary) array;
    (*

    Distribution of B − A per Metrics field, in Metrics.to_assoc order.

    *)
}
val run_comparative : (module Algostream_strategy.Strategy.S with type params = 'p) -> a:'p arm -> b:'p arm -> config:config -> comparison

Paired comparison under common random numbers: run i of A and run i of B share substream 2i, so the two see the same market and differ only in whatever the arms differ in. Reports the distribution of the difference, which has far lower variance than differencing two independent batches — and is the statistically correct way to ask "is A better than B".

val metric : summary -> string -> Quantile.summary option

Look up one metric's distribution by name.

val summary_to_string : summary -> string