use super::functions::*;
pub struct EulerMaruyama {
pub drift: fn(f64) -> f64,
pub diffusion: fn(f64) -> f64,
}
impl EulerMaruyama {
pub fn new(drift: fn(f64) -> f64, diffusion: fn(f64) -> f64) -> Self {
EulerMaruyama { drift, diffusion }
}
pub fn simulate(&self, x0: f64, t_end: f64, n_steps: u32, seed: u64) -> Vec<(f64, f64)> {
let mut lcg = Lcg::new(seed);
let dt = t_end / n_steps as f64;
let sqrt_dt = dt.sqrt();
let mut path = Vec::with_capacity(n_steps as usize + 1);
let mut t = 0.0f64;
let mut x = x0;
path.push((t, x));
for _ in 0..n_steps {
let dw = lcg.next_normal() * sqrt_dt;
x += (self.drift)(x) * dt + (self.diffusion)(x) * dw;
t += dt;
path.push((t, x));
}
path
}
}
#[allow(dead_code)]
pub struct LocalTimeEstimator {
pub path: Vec<(f64, f64)>,
}
impl LocalTimeEstimator {
pub fn new(path: Vec<(f64, f64)>) -> Self {
Self { path }
}
pub fn estimate_local_time(&self, a: f64, epsilon: f64) -> f64 {
if self.path.len() < 2 || epsilon <= 0.0 {
return 0.0;
}
let mut total = 0.0f64;
for k in 1..self.path.len() {
let (t_prev, _) = self.path[k - 1];
let (t_curr, x_curr) = self.path[k];
let dt = t_curr - t_prev;
if (x_curr - a).abs() < epsilon {
total += dt;
}
}
total / epsilon
}
pub fn occupation_time(&self, lo: f64, hi: f64) -> f64 {
if self.path.len() < 2 {
return 0.0;
}
let mut total = 0.0f64;
for k in 1..self.path.len() {
let (t_prev, _) = self.path[k - 1];
let (t_curr, x_curr) = self.path[k];
let dt = t_curr - t_prev;
if x_curr >= lo && x_curr < hi {
total += dt;
}
}
total
}
pub fn quadratic_variation(&self) -> f64 {
self.path
.windows(2)
.map(|w| {
let diff = w[1].1 - w[0].1;
diff * diff
})
.sum()
}
}
#[derive(Debug, Clone)]
pub struct CtMarkovChain {
pub states: Vec<String>,
pub rate_matrix: Vec<Vec<f64>>,
}
impl CtMarkovChain {
pub fn new(states: Vec<String>, rate_matrix: Vec<Vec<f64>>) -> Self {
CtMarkovChain {
states,
rate_matrix,
}
}
pub fn stationary_distribution(&self) -> Option<Vec<f64>> {
let n = self.states.len();
if n == 0 {
return None;
}
let q_max = self
.rate_matrix
.iter()
.enumerate()
.map(|(i, row)| row.get(i).map(|v| v.abs()).unwrap_or(0.0))
.fold(0.0f64, f64::max);
if q_max < 1e-15 {
return Some(vec![1.0 / n as f64; n]);
}
let mut p = vec![vec![0.0f64; n]; n];
for i in 0..n {
for j in 0..n {
let q_ij = self.rate_matrix[i].get(j).copied().unwrap_or(0.0);
p[i][j] = if i == j {
1.0 + q_ij / q_max
} else {
q_ij / q_max
};
}
}
let mut pi = vec![1.0 / n as f64; n];
for _ in 0..10_000 {
let mut pi_new = vec![0.0f64; n];
for j in 0..n {
for i in 0..n {
pi_new[j] += pi[i] * p[i][j];
}
}
let s: f64 = pi_new.iter().sum();
if s < 1e-15 {
return None;
}
for v in &mut pi_new {
*v /= s;
}
pi = pi_new;
}
Some(pi)
}
pub fn expected_hitting_time(&self, from: usize, to: usize) -> f64 {
if from == to {
return 0.0;
}
let rate = self
.rate_matrix
.get(from)
.and_then(|row| row.get(to))
.copied()
.unwrap_or(0.0);
if rate > 1e-15 {
1.0 / rate
} else {
f64::INFINITY
}
}
pub fn is_irreducible(&self) -> bool {
let n = self.states.len();
for i in 0..n {
for j in 0..n {
if i == j {
continue;
}
let q = self
.rate_matrix
.get(i)
.and_then(|r| r.get(j))
.copied()
.unwrap_or(0.0);
if q <= 0.0 {
return false;
}
}
}
true
}
}
#[derive(Debug, Clone)]
pub struct PoissonProcessSimulator {
pub rate: f64,
}
impl PoissonProcessSimulator {
pub fn new(rate: f64) -> Self {
PoissonProcessSimulator { rate }
}
pub fn arrival_times(&self, t_end: f64, seed: u64) -> Vec<f64> {
let mut lcg = Lcg::new(seed);
let mut times = Vec::new();
let mut t = 0.0f64;
loop {
let u = lcg.next_f64().max(1e-15);
t += (-u.ln()) / self.rate;
if t > t_end {
break;
}
times.push(t);
}
times
}
pub fn counting_process(&self, t_end: f64, n_steps: u32, seed: u64) -> Vec<(f64, u64)> {
let arrivals = self.arrival_times(t_end, seed);
let dt = t_end / n_steps as f64;
let mut path = Vec::with_capacity(n_steps as usize + 1);
let mut count = 0u64;
let mut arrival_idx = 0usize;
for k in 0..=n_steps {
let t = k as f64 * dt;
while arrival_idx < arrivals.len() && arrivals[arrival_idx] <= t {
count += 1;
arrival_idx += 1;
}
path.push((t, count));
}
path
}
pub fn expected_count(&self, t: f64) -> f64 {
self.rate * t
}
}
#[derive(Debug, Clone)]
pub struct CompoundPoissonProcess {
pub rate: f64,
pub jump_mean: f64,
pub jump_std: f64,
}
impl CompoundPoissonProcess {
pub fn new(rate: f64, jump_mean: f64, jump_std: f64) -> Self {
CompoundPoissonProcess {
rate,
jump_mean,
jump_std,
}
}
pub fn simulate(&self, t_end: f64, n_steps: u32, seed: u64) -> Vec<(f64, f64)> {
let mut lcg = Lcg::new(seed);
let poisson = PoissonProcessSimulator::new(self.rate);
let arrivals = poisson.arrival_times(t_end, seed.wrapping_add(99_999));
let jumps: Vec<f64> = arrivals
.iter()
.map(|_| self.jump_mean + self.jump_std * lcg.next_normal())
.collect();
let dt = t_end / n_steps as f64;
let mut path = Vec::with_capacity(n_steps as usize + 1);
let mut cumulative = 0.0f64;
let mut arrival_idx = 0usize;
for k in 0..=n_steps {
let t = k as f64 * dt;
while arrival_idx < arrivals.len() && arrivals[arrival_idx] <= t {
cumulative += jumps[arrival_idx];
arrival_idx += 1;
}
path.push((t, cumulative));
}
path
}
pub fn expected_value(&self, t: f64) -> f64 {
self.rate * t * self.jump_mean
}
pub fn variance(&self, t: f64) -> f64 {
self.rate * t * (self.jump_std * self.jump_std + self.jump_mean * self.jump_mean)
}
}
#[allow(dead_code)]
pub struct MilsteinScheme {
pub drift: fn(f64) -> f64,
pub diffusion: fn(f64) -> f64,
pub diffusion_deriv: fn(f64) -> f64,
}
impl MilsteinScheme {
pub fn new(
drift: fn(f64) -> f64,
diffusion: fn(f64) -> f64,
diffusion_deriv: fn(f64) -> f64,
) -> Self {
MilsteinScheme {
drift,
diffusion,
diffusion_deriv,
}
}
pub fn simulate(&self, x0: f64, t_end: f64, n_steps: u32, seed: u64) -> Vec<(f64, f64)> {
let mut lcg = Lcg::new(seed);
let dt = t_end / n_steps as f64;
let sqrt_dt = dt.sqrt();
let mut path = Vec::with_capacity(n_steps as usize + 1);
let mut t = 0.0f64;
let mut x = x0;
path.push((t, x));
for _ in 0..n_steps {
let dw = lcg.next_normal() * sqrt_dt;
let sig = (self.diffusion)(x);
let sig_p = (self.diffusion_deriv)(x);
x += (self.drift)(x) * dt + sig * dw + 0.5 * sig * sig_p * (dw * dw - dt);
t += dt;
path.push((t, x));
}
path
}
pub fn monte_carlo_mean(
&self,
x0: f64,
t_end: f64,
n_steps: u32,
n_paths: u32,
seed: u64,
) -> f64 {
if n_paths == 0 {
return 0.0;
}
let total: f64 = (0..n_paths)
.map(|i| {
let path = self.simulate(x0, t_end, n_steps, seed.wrapping_add(i as u64));
path.last().map(|&(_, x)| x).unwrap_or(x0)
})
.sum();
total / n_paths as f64
}
}
#[allow(dead_code)]
pub struct HestonModel {
pub mu: f64,
pub kappa: f64,
pub theta: f64,
pub xi: f64,
pub rho: f64,
}
impl HestonModel {
pub fn new(mu: f64, kappa: f64, theta: f64, xi: f64, rho: f64) -> Self {
HestonModel {
mu,
kappa,
theta,
xi,
rho,
}
}
pub fn simulate(
&self,
s0: f64,
v0: f64,
t_end: f64,
n_steps: u32,
seed: u64,
) -> Vec<(f64, f64, f64)> {
let mut lcg = Lcg::new(seed);
let dt = t_end / n_steps as f64;
let sqrt_dt = dt.sqrt();
let mut path = Vec::with_capacity(n_steps as usize + 1);
let mut t = 0.0f64;
let mut s = s0;
let mut v = v0.max(0.0);
path.push((t, s, v));
for _ in 0..n_steps {
let z1 = lcg.next_normal();
let z2 = lcg.next_normal();
let w1 = z1;
let w2 = self.rho * z1 + (1.0 - self.rho * self.rho).max(0.0).sqrt() * z2;
let sqrt_v = v.max(0.0).sqrt();
s *= 1.0 + self.mu * dt + sqrt_v * w1 * sqrt_dt;
v = (v + self.kappa * (self.theta - v) * dt + self.xi * sqrt_v * w2 * sqrt_dt).max(0.0);
t += dt;
path.push((t, s, v));
}
path
}
pub fn feller_condition_satisfied(&self) -> bool {
2.0 * self.kappa * self.theta > self.xi * self.xi
}
pub fn variance_long_run_mean(&self) -> f64 {
self.theta
}
}
#[derive(Debug, Clone)]
pub struct GeometricBrownianMotionProcess {
pub mu: f64,
pub sigma: f64,
}
impl GeometricBrownianMotionProcess {
pub fn new(mu: f64, sigma: f64) -> Self {
GeometricBrownianMotionProcess { mu, sigma }
}
pub fn simulate(&self, s0: f64, t_end: f64, n_steps: u32, seed: u64) -> Vec<(f64, f64)> {
geometric_brownian_motion(s0, self.mu, self.sigma, t_end, n_steps, seed)
}
pub fn expected_value(&self, s0: f64, t: f64) -> f64 {
s0 * (self.mu * t).exp()
}
pub fn variance(&self, s0: f64, t: f64) -> f64 {
let s0sq = s0 * s0;
s0sq * (2.0 * self.mu * t).exp() * ((self.sigma * self.sigma * t).exp() - 1.0)
}
}
#[derive(Debug, Clone)]
pub struct BlackScholesPricer {
pub spot: f64,
pub strike: f64,
pub time_to_expiry: f64,
pub rate: f64,
pub volatility: f64,
}
impl BlackScholesPricer {
pub fn new(spot: f64, strike: f64, time_to_expiry: f64, rate: f64, volatility: f64) -> Self {
BlackScholesPricer {
spot,
strike,
time_to_expiry,
rate,
volatility,
}
}
fn d1(&self) -> f64 {
let sqrt_t = self.time_to_expiry.sqrt();
((self.spot / self.strike).ln()
+ (self.rate + 0.5 * self.volatility * self.volatility) * self.time_to_expiry)
/ (self.volatility * sqrt_t)
}
fn d2(&self) -> f64 {
self.d1() - self.volatility * self.time_to_expiry.sqrt()
}
pub fn call_price(&self) -> f64 {
black_scholes_call(
self.spot,
self.strike,
self.time_to_expiry,
self.rate,
self.volatility,
)
}
pub fn put_price(&self) -> f64 {
black_scholes_put(
self.spot,
self.strike,
self.time_to_expiry,
self.rate,
self.volatility,
)
}
pub fn call_delta(&self) -> f64 {
if self.time_to_expiry <= 0.0 {
return if self.spot > self.strike { 1.0 } else { 0.0 };
}
standard_normal_cdf(self.d1())
}
pub fn put_delta(&self) -> f64 {
self.call_delta() - 1.0
}
pub fn gamma(&self) -> f64 {
if self.time_to_expiry <= 0.0 {
return 0.0;
}
let d1 = self.d1();
let phi = (-0.5 * d1 * d1).exp() / (2.0 * std::f64::consts::PI).sqrt();
phi / (self.spot * self.volatility * self.time_to_expiry.sqrt())
}
pub fn vega(&self) -> f64 {
if self.time_to_expiry <= 0.0 {
return 0.0;
}
let d1 = self.d1();
let phi = (-0.5 * d1 * d1).exp() / (2.0 * std::f64::consts::PI).sqrt();
self.spot * phi * self.time_to_expiry.sqrt()
}
pub fn call_rho(&self) -> f64 {
if self.time_to_expiry <= 0.0 {
return 0.0;
}
self.strike
* self.time_to_expiry
* (-self.rate * self.time_to_expiry).exp()
* standard_normal_cdf(self.d2())
}
pub fn put_rho(&self) -> f64 {
if self.time_to_expiry <= 0.0 {
return 0.0;
}
-self.strike
* self.time_to_expiry
* (-self.rate * self.time_to_expiry).exp()
* standard_normal_cdf(-self.d2())
}
pub fn implied_volatility_call(&self, market_price: f64) -> Option<f64> {
let intrinsic =
(self.spot - self.strike * (-self.rate * self.time_to_expiry).exp()).max(0.0);
if market_price < intrinsic {
return None;
}
let mut lo = 1e-6f64;
let mut hi = 10.0f64;
for _ in 0..200 {
let mid = 0.5 * (lo + hi);
let pricer = BlackScholesPricer {
volatility: mid,
..*self
};
let price = pricer.call_price();
if (price - market_price).abs() < 1e-8 {
return Some(mid);
}
if price < market_price {
lo = mid;
} else {
hi = mid;
}
}
Some(0.5 * (lo + hi))
}
}
#[allow(dead_code)]
pub struct VarianceGammaProcess {
pub mu: f64,
pub sigma: f64,
pub nu: f64,
}
impl VarianceGammaProcess {
pub fn new(mu: f64, sigma: f64, nu: f64) -> Self {
VarianceGammaProcess { mu, sigma, nu }
}
pub fn simulate(&self, x0: f64, t_end: f64, n_steps: u32, seed: u64) -> Vec<(f64, f64)> {
let mut lcg = Lcg::new(seed);
let dt = t_end / n_steps as f64;
let mut path = Vec::with_capacity(n_steps as usize + 1);
let mut t = 0.0f64;
let mut x = x0;
path.push((t, x));
for _ in 0..n_steps {
let dg = sp_ext_gamma_sample(dt / self.nu, 1.0 / self.nu, &mut lcg);
let z = lcg.next_normal();
x += self.mu * dg + self.sigma * dg.sqrt() * z;
t += dt;
path.push((t, x));
}
path
}
pub fn theoretical_mean(&self, x0: f64, t: f64) -> f64 {
x0 + self.mu * t
}
pub fn theoretical_variance(&self, t: f64) -> f64 {
self.sigma * self.sigma * t + self.mu * self.mu * self.nu * t
}
pub fn characteristic_exponent(&self, u: f64) -> f64 {
let a = 1.0 - self.mu * self.nu * u * u + 0.5 * self.sigma * self.sigma * self.nu * u * u;
let b = self.mu * self.nu * u;
let modulus_sq = a * a + b * b;
if modulus_sq <= 0.0 {
return 0.0;
}
-0.5 * modulus_sq.ln() / self.nu
}
}
#[derive(Debug, Clone)]
pub struct OrnsteinUhlenbeckProcess {
pub theta: f64,
pub mean: f64,
pub sigma: f64,
}
impl OrnsteinUhlenbeckProcess {
pub fn new(theta: f64, mean: f64, sigma: f64) -> Self {
OrnsteinUhlenbeckProcess { theta, mean, sigma }
}
pub fn simulate(&self, x0: f64, t_end: f64, n_steps: u32, seed: u64) -> Vec<(f64, f64)> {
ornstein_uhlenbeck(x0, self.theta, self.mean, self.sigma, t_end, n_steps, seed)
}
pub fn stationary_variance(&self) -> f64 {
self.sigma * self.sigma / (2.0 * self.theta)
}
pub fn stationary_std(&self) -> f64 {
self.stationary_variance().sqrt()
}
}
#[allow(dead_code)]
pub struct SubordinatedProcess {
pub base_drift: f64,
pub base_sigma: f64,
pub gamma_a: f64,
pub gamma_b: f64,
}
impl SubordinatedProcess {
pub fn new(base_drift: f64, base_sigma: f64, gamma_a: f64, gamma_b: f64) -> Self {
SubordinatedProcess {
base_drift,
base_sigma,
gamma_a,
gamma_b,
}
}
pub fn simulate(&self, x0: f64, t_end: f64, n_steps: u32, seed: u64) -> Vec<(f64, f64)> {
let mut lcg = Lcg::new(seed);
let dt = t_end / n_steps as f64;
let mut path = Vec::with_capacity(n_steps as usize + 1);
let mut t = 0.0f64;
let mut x = x0;
path.push((t, x));
for _ in 0..n_steps {
let d_sub = sp_ext_gamma_sample(self.gamma_a * dt, self.gamma_b, &mut lcg);
let dw = lcg.next_normal() * d_sub.sqrt();
x += self.base_drift * d_sub + self.base_sigma * dw;
t += dt;
path.push((t, x));
}
path
}
pub fn theoretical_mean(&self, x0: f64, t: f64) -> f64 {
x0 + self.base_drift * (self.gamma_a / self.gamma_b) * t
}
pub fn theoretical_variance(&self, t: f64) -> f64 {
let e_sub = self.gamma_a / self.gamma_b * t;
let var_sub = self.gamma_a / (self.gamma_b * self.gamma_b) * t;
self.base_sigma * self.base_sigma * e_sub + self.base_drift * self.base_drift * var_sub
}
}
pub(super) struct Lcg {
state: u64,
}
impl Lcg {
pub(super) fn new(seed: u64) -> Self {
Lcg {
state: seed.wrapping_add(1),
}
}
pub(super) fn next_u32(&mut self) -> u32 {
self.state = self
.state
.wrapping_mul(6364136223846793005)
.wrapping_add(1442695040888963407);
(self.state >> 33) as u32
}
pub(super) fn next_f64(&mut self) -> f64 {
self.next_u32() as f64 / (u32::MAX as f64 + 1.0)
}
pub(super) fn next_step(&mut self) -> f64 {
if self.next_u32() & 1 == 0 {
1.0
} else {
-1.0
}
}
pub(super) fn next_normal(&mut self) -> f64 {
let u1 = self.next_f64().max(1e-15);
let u2 = self.next_f64();
(-2.0 * u1.ln()).sqrt() * (2.0 * std::f64::consts::PI * u2).cos()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct State(pub usize);
#[derive(Debug, Clone)]
pub struct TransitionMatrix {
pub data: Vec<Vec<f64>>,
pub size: usize,
}
impl TransitionMatrix {
pub fn new(data: Vec<Vec<f64>>) -> Self {
let size = data.len();
TransitionMatrix { data, size }
}
pub fn get(&self, i: usize, j: usize) -> f64 {
self.data
.get(i)
.and_then(|row| row.get(j))
.copied()
.unwrap_or(0.0)
}
pub fn set(&mut self, i: usize, j: usize, val: f64) {
if let Some(row) = self.data.get_mut(i) {
if let Some(cell) = row.get_mut(j) {
*cell = val;
}
}
}
pub fn identity(n: usize) -> Self {
let data = (0..n)
.map(|i| (0..n).map(|j| if i == j { 1.0 } else { 0.0 }).collect())
.collect();
TransitionMatrix { data, size: n }
}
}
#[derive(Debug, Clone)]
pub struct MarkovChain {
pub transition: TransitionMatrix,
pub initial: Vec<f64>,
pub state_names: Vec<String>,
}
impl MarkovChain {
pub fn new(transition: TransitionMatrix, initial: Vec<f64>, state_names: Vec<String>) -> Self {
MarkovChain {
transition,
initial,
state_names,
}
}
pub fn with_uniform_start(transition: TransitionMatrix, state_names: Vec<String>) -> Self {
let n = transition.size;
let initial = vec![1.0 / n as f64; n];
MarkovChain {
transition,
initial,
state_names,
}
}
}
#[derive(Debug, Clone)]
pub struct StationaryDistribution {
pub probs: Vec<f64>,
}
impl StationaryDistribution {
pub fn new(probs: Vec<f64>) -> Self {
StationaryDistribution { probs }
}
pub fn tv_distance(&self, other: &[f64]) -> f64 {
0.5 * self
.probs
.iter()
.zip(other.iter())
.map(|(a, b)| (a - b).abs())
.sum::<f64>()
}
}
#[derive(Debug, Clone)]
pub struct AbsorptionData {
pub absorbing_states: Vec<State>,
pub transient_states: Vec<State>,
pub absorption_probs: Vec<Vec<f64>>,
pub expected_steps: Vec<f64>,
}
#[derive(Debug, Clone)]
pub struct HittingTime {
pub from: State,
pub to: State,
pub expected_steps: f64,
pub variance: Option<f64>,
}
#[derive(Debug, Clone)]
pub enum WalkDistribution {
Simple,
Lazy { stay_prob: f64 },
Biased { bias: Vec<f64> },
}
#[derive(Debug, Clone)]
pub struct RandomWalk {
pub dimension: usize,
pub steps: Vec<Vec<i64>>,
pub step_distribution: WalkDistribution,
}
impl RandomWalk {
pub fn new(dimension: usize, step_distribution: WalkDistribution) -> Self {
let initial = vec![0i64; dimension];
RandomWalk {
dimension,
steps: vec![initial],
step_distribution,
}
}
}
#[derive(Debug, Clone)]
pub struct PoissonProcess {
pub rate: f64,
pub arrivals: Vec<f64>,
}
impl PoissonProcess {
pub fn new(rate: f64, arrivals: Vec<f64>) -> Self {
PoissonProcess { rate, arrivals }
}
pub fn count_by(&self, t: f64) -> usize {
self.arrivals.iter().filter(|&&s| s <= t).count()
}
}
#[derive(Debug, Clone)]
pub struct BrownianMotion {
pub dt: f64,
pub path: Vec<f64>,
}
impl BrownianMotion {
pub fn new(dt: f64, path: Vec<f64>) -> Self {
BrownianMotion { dt, path }
}
pub fn quadratic_variation(&self) -> f64 {
self.path.windows(2).map(|w| (w[1] - w[0]).powi(2)).sum()
}
pub fn terminal_time(&self) -> f64 {
(self.path.len().saturating_sub(1)) as f64 * self.dt
}
}