use crate::errors::QlResult;
use crate::fail;
use crate::patterns::observable::AsObservable;
use crate::time::date::Date;
use crate::types::{Real, Time};
pub trait StochasticProcess1D: AsObservable {
fn x0(&self) -> QlResult<Real>;
fn drift(&self, t: Time, x: Real) -> QlResult<Real>;
fn diffusion(&self, t: Time, x: Real) -> QlResult<Real>;
fn expectation(&self, t0: Time, x0: Real, dt: Time) -> QlResult<Real> {
Ok(self.apply(x0, self.drift(t0, x0)? * dt))
}
fn std_deviation(&self, t0: Time, x0: Real, dt: Time) -> QlResult<Real> {
Ok(self.diffusion(t0, x0)? * dt.sqrt())
}
fn variance(&self, t0: Time, x0: Real, dt: Time) -> QlResult<Real> {
let sigma = self.diffusion(t0, x0)?;
Ok(sigma * sigma * dt)
}
fn evolve(&self, t0: Time, x0: Real, dt: Time, dw: Real) -> QlResult<Real> {
Ok(self.apply(
self.expectation(t0, x0, dt)?,
self.std_deviation(t0, x0, dt)? * dw,
))
}
fn apply(&self, x0: Real, dx: Real) -> Real {
x0 + dx
}
fn time(&self, _date: &Date) -> QlResult<Time> {
fail!("date/time conversion not supported");
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::patterns::observable::{Observable, Observer, ResetThenNotify};
use crate::quotes::{Quote, SimpleQuote};
use crate::shared::{Shared, SharedMut, shared};
use crate::test_support::{Flag, as_observer};
use crate::time::date::Month;
struct ConstantProcess {
mu: Real,
sigma: Real,
quote: Shared<SimpleQuote>,
observable: Shared<Observable>,
_listener: SharedMut<ResetThenNotify>,
}
impl ConstantProcess {
fn new(initial: Real, mu: Real, sigma: Real) -> Self {
let quote = shared(SimpleQuote::new(initial));
let observable = shared(Observable::new());
let listener = ResetThenNotify::forwarding(Shared::clone(&observable));
quote
.observable()
.register_observer(&(listener.clone() as SharedMut<dyn Observer>));
ConstantProcess {
mu,
sigma,
quote,
observable,
_listener: listener,
}
}
}
impl AsObservable for ConstantProcess {
fn observable(&self) -> &Observable {
&self.observable
}
}
impl StochasticProcess1D for ConstantProcess {
fn x0(&self) -> QlResult<Real> {
self.quote.value()
}
fn drift(&self, _t: Time, _x: Real) -> QlResult<Real> {
Ok(self.mu)
}
fn diffusion(&self, _t: Time, _x: Real) -> QlResult<Real> {
Ok(self.sigma)
}
}
struct LogProcess {
inner: ConstantProcess,
}
impl AsObservable for LogProcess {
fn observable(&self) -> &Observable {
self.inner.observable()
}
}
impl StochasticProcess1D for LogProcess {
fn x0(&self) -> QlResult<Real> {
self.inner.x0()
}
fn drift(&self, t: Time, x: Real) -> QlResult<Real> {
self.inner.drift(t, x)
}
fn diffusion(&self, t: Time, x: Real) -> QlResult<Real> {
self.inner.diffusion(t, x)
}
fn apply(&self, x0: Real, dx: Real) -> Real {
x0 * dx.exp()
}
}
#[test]
fn defaults_follow_the_euler_discretization() {
let process = ConstantProcess::new(100.0, 0.05, 0.20);
let (t0, x0, dt, dw): (Time, Real, Time, Real) = (1.0, 100.0, 0.25, 0.5);
assert_eq!(process.x0().unwrap(), 100.0);
assert_eq!(process.expectation(t0, x0, dt).unwrap(), x0 + 0.05 * dt);
assert_eq!(process.std_deviation(t0, x0, dt).unwrap(), 0.20 * dt.sqrt());
assert_eq!(process.variance(t0, x0, dt).unwrap(), 0.20 * 0.20 * dt);
assert_eq!(
process.evolve(t0, x0, dt, dw).unwrap(),
(x0 + 0.05 * dt) + 0.20 * dt.sqrt() * dw
);
assert_eq!(process.apply(x0, 2.5), x0 + 2.5);
}
#[test]
fn expectation_and_evolve_route_through_apply() {
let process = LogProcess {
inner: ConstantProcess::new(100.0, 0.05, 0.20),
};
let (t0, x0, dt, dw): (Time, Real, Time, Real) = (0.0, 100.0, 0.25, -1.0);
let expectation = x0 * (0.05 * dt).exp();
assert_eq!(process.expectation(t0, x0, dt).unwrap(), expectation);
assert_eq!(
process.evolve(t0, x0, dt, dw).unwrap(),
expectation * (0.20 * dt.sqrt() * dw).exp()
);
}
#[test]
fn time_defaults_to_an_error() {
let process = ConstantProcess::new(100.0, 0.05, 0.20);
let date = Date::new(15, Month::May, 2026);
let err = process.time(&date).unwrap_err();
assert_eq!(err.message(), "date/time conversion not supported");
}
#[test]
fn input_notifications_are_forwarded_to_process_observers() {
let process = ConstantProcess::new(100.0, 0.05, 0.20);
let flag = Flag::new();
process.observable().register_observer(&as_observer(&flag));
process.quote.set_value(101.0);
assert!(
Flag::is_up(&flag),
"process observers were not notified of an input change"
);
}
#[test]
fn trait_is_object_safe() {
let process = ConstantProcess::new(100.0, 0.05, 0.20);
let dynamic: &dyn StochasticProcess1D = &process;
assert_eq!(dynamic.x0().unwrap(), 100.0);
}
}