use crate::error::SimulationError;
use crate::simulation::path_buffer;
use optionstratlib_core::model::Positive;
use optionstratlib_core::model::decimal::{d_add, d_mul, decimal_normal_sample_with, p_sqrt};
use optionstratlib_core::model::utils::sub_floor_zero;
use rand::Rng;
use rust_decimal::Decimal;
pub fn generate_ou_process(
x0: Positive,
mu: Positive,
theta: Positive,
volatility: Positive,
dt: Positive,
steps: usize,
) -> Result<Vec<Positive>, SimulationError> {
ou_path(x0, mu, theta, volatility, dt, steps, &mut rand::rng())
}
pub(crate) fn ou_path<R: Rng + ?Sized>(
x0: Positive,
mu: Positive,
theta: Positive,
volatility: Positive,
dt: Positive,
steps: usize,
rng: &mut R,
) -> Result<Vec<Positive>, SimulationError> {
let sqrt_dt = p_sqrt(&dt, "volatility::utils::generate_ou_process")?;
let mut x = x0.to_dec();
let mut result = path_buffer(steps, 0)?;
result.push(Positive::new_decimal(x).unwrap_or(Positive::ZERO));
for _ in 1..steps {
let dw = d_mul(
decimal_normal_sample_with(rng),
sqrt_dt.to_dec(),
"volatility::ou::dw",
)?;
let drift = theta
.checked_mul(&sub_floor_zero(mu, &x))?
.checked_mul(&dt)?
.to_dec();
let diffusion = d_mul(dw, volatility.to_dec(), "volatility::ou::diffusion")?;
x = d_add(
x,
d_add(drift, diffusion, "volatility::ou::step")?,
"volatility::ou::step",
)?;
x = x.max(Decimal::ZERO); result.push(Positive::new_decimal(x).unwrap_or(Positive::ZERO));
}
Ok(result)
}
#[cfg(test)]
mod tests_generate_ou_process {
use super::*;
use optionstratlib_core::pos_or_panic;
use optionstratlib_core::utils::deterministic_rng;
use rust_decimal_macros::dec;
const SEED: u64 = 685;
#[test]
fn test_process_length() {
let steps = 500;
let process = generate_ou_process(
Positive::ONE,
pos_or_panic!(1.5),
pos_or_panic!(0.1),
pos_or_panic!(0.2),
pos_or_panic!(0.01),
steps,
)
.unwrap();
assert_eq!(process.len(), steps);
}
#[test]
fn test_all_values_positive() {
let process = ou_path(
Positive::ONE,
pos_or_panic!(1.5),
pos_or_panic!(0.2),
pos_or_panic!(0.3),
pos_or_panic!(0.01),
1000,
&mut deterministic_rng(SEED),
)
.unwrap();
for value in process {
assert!(
value >= Positive::ZERO,
"Found non-positive value: {value:?}"
);
}
}
#[test]
fn test_mean_reversion_tendency() {
let process = ou_path(
pos_or_panic!(0.1),
Positive::ONE,
Positive::ONE, pos_or_panic!(0.01), pos_or_panic!(0.01),
1000,
&mut deterministic_rng(SEED),
)
.unwrap();
let last = process.last().unwrap().to_dec();
let diff = (last - dec!(1.0)).abs();
assert!(diff < dec!(0.1), "Final value too far from mean: {last}");
}
}