Module Algostream_advanced_models.Ornstein_uhlenbeck

Ornstein-Uhlenbeck continuous-time mean-reverting process modeling.

SDE: dr = theta * (mu - r) dt + sigma * dW. Calibration uses the exact discrete-time transition at fixed time step dt: r at (t + dt) given r at t is Gaussian with mean mu + (r - mu) * exp(-theta * dt) and variance sigma^2 * (1 - exp(-2 * theta * dt)) / (2 * theta). The continuous-time half-life is ln(2) / theta.

fit returns Error `Non_reverting if the AR(1) slope is outside (0, 1) — i.e., the series shows no mean-reversion at the supplied dt. Use Pairs.Mean_reversion.half_life for the discrete-time alternative on residuals.

type params = {
  1. theta : float;
    (*

    mean-reversion speed

    *)
  2. mu : float;
    (*

    equilibrium level

    *)
  3. sigma : float;
    (*

    instantaneous volatility

    *)
}
type fit_result = {
  1. params : params;
  2. half_life : float;
  3. rss : float;
  4. tss : float;
  5. n : int;
}
type fit_error = [
  1. | `Insufficient_data of int * int
  2. | `Non_reverting
  3. | `Ols of Algostream_pairs.Ols.error
]
val fit : series:float array -> dt:float -> (fit_result, fit_error) Stdlib.result
val expected_value : params -> r0:float -> t:float -> float

E[r_t | r_0] = μ + (r_0 − μ) · e^{−θ t}.

val expected_variance : params -> t:float -> float

Var[r_t | r_0] = σ² (1 − e^{−2θ t}) / (2θ).

val simulate : params -> n:int -> dt:float -> seed:int -> r0:float -> float array

Simulate n steps at uniform dt starting from r0, using the exact Gaussian transition. seed makes the trajectory reproducible.

Trajectories are not comparable across the RNG change. This draws from Algostream_rng; an earlier implementation used Math_utils.FastRandom, whose constructor seeds only one of four state words and leaves the rest at fixed constants — so nearby seeds produced visibly correlated paths, and a uniform draw of exactly 0.0 could turn a sample into nan. It now draws from Algostream_rng.Rng, which has neither defect. Same signature, same statistical contract, different numbers for a given seed. No test pinned the old values (they assert run-twice equality and ±30% parameter recovery), but any external result generated by that earlier implementation will not reproduce bit-for-bit.

val simulate_with : params -> rng:Algostream_rng.Rng.t -> n:int -> dt:float -> r0:float -> float array

As simulate, but driven by a caller-owned generator. Use this inside a Monte Carlo worker so every path in a batch draws from Rng.substream ~root_seed ~index and is therefore reproducible independently of how many Domains run the batch.