stochastic-rs
Concepts

Traits overview

The trait surface that organises the library — RealExt, FloatExt, ProcessExt, DistributionExt, ModelPricer, Calibrator, and friends.

Traits overview

The library's organising principle is a small set of traits. Each trait captures one abstraction, with concrete types implementing the trait generically over the float type via FloatExt.

The trait map

TraitCratePurpose
RealExtstochastic-rs-distributionsScalar real bound — open to custom scalars (AAD)
FloatExtstochastic-rs-distributionsFull simulation-grade float bound (f32 / f64 only)
SimdFloatExtstochastic-rs-distributions8-lane SIMD surface over RealExt
ProcessExt<T>stochastic-rs-stochasticStochastic process simulation (sample, sample_map, sample_par)
DistributionExtstochastic-rs-distributionsClosed-form pdf / cdf / cf / moments (f64-only)
BivariateExt<T>stochastic-rs-copulasBivariate copulas
MultivariateExt<T>stochastic-rs-copulasMultivariate copulas (Gaussian, t, vines, NAC)
ModelPricerstochastic-rs-quantConcrete-typed strike/tau-grid pricer (no &dyn)
ShortRatePricerstochastic-rs-quantShort-rate bond pricing (Vasicek, Cir, HullWhite)
ToModel, ToShortRateModelstochastic-rs-quantCalibrator → Concrete pricer bridge
FourierModelExt<T>stochastic-rs-quantCharacteristic function models
Calibratorstochastic-rs-quantResult-based calibration (type Params, type Error)
Instrument, PricingEnginestochastic-rs-quantCross-engine comparison harness (not in the prelude)
GreeksExtstochastic-rs-quantNo-argument Greeks for the MC Malliavin estimators
CalendarExtstochastic-rs-quantPluggable holiday calendars
TimeExtstochastic-rs-quantDay-count-aware maturity, on instruments (tau_with_dcc)

RealExt, SimdFloatExt, FloatExt

The numeric bound is a three-layer hierarchy. RealExt is the scalar floor — arithmetic, conversions and constants, with every supertrait pulled in for a concrete reason (Float for transcendentals, FloatConst for PI/TAU, ScalarOperand so ndarray ops work elementwise, AddAssign/SubAssign for in-place accumulation, …):

pub trait RealExt:
  num_traits::Float
  + num_traits::FromPrimitive
  + num_traits::Signed
  + num_traits::FloatConst
  + std::iter::Sum
  + num_traits::Zero
  + Default
  + std::fmt::Debug
  + Send
  + Sync
  + ndarray::ScalarOperand
  + std::ops::AddAssign
  + std::ops::SubAssign
  + 'static
{
  // from_usize_, from_f64_fast, pi, two_pi, min_positive_val
}

pub trait SimdFloatExt: RealExt {
  type Simd; // f32x8 / f64x8
  // splat, simd_ln/exp/sqrt/…, fill_uniform, sample_uniform
}

pub trait FloatExt: RealExt + SimdFloatExt {
  // fill_standard_normal_slice, with_fgn_complex_scratch, normal_array
}

f32 and f64 implement all three. The split is what the layers demand: RealExt asks for nothing beyond scalar math, so a custom scalar — an AAD dual number, a higher-precision float — can implement it and flow through everything bounded on it. FloatExt additionally demands the 8-lane SIMD surface and RNG-backed fills, so f32/f64 are its only possible implementors — the right bound for code that simulates, and a closed door by construction. Analytic pricing code is bounded on RealExt; path generation and Monte Carlo on FloatExt.

ProcessExt<T>

Every stochastic process implements ProcessExt<T>:

pub trait ProcessExt<T: FloatExt>: Send + Sync {
  type Output: Send; // Array1<T>, [Array1<T>; N], Array2<T>, …
  fn sample(&self) -> Self::Output;
  fn sample_map<R: Send>(&self, m: usize, f: impl Fn(&Self::Output) -> R + Sync) -> Vec<R>;
  fn sample_par(&self, m: usize) -> Vec<Self::Output>;
}

sample() produces a single path; sample_map(m, f) folds f over m independent paths in parallel (reusing one buffer per worker — the fast Monte-Carlo path); sample_par(m) keeps every path. See ProcessExt for the full contract and when to use which.

DistributionExt

Closed-form moments / pdf / cdf / characteristic function — implemented by 18/19 distributions in the library (5 named no-closed-form unimplemented!() cases on specific moments per the project status memo). The trait is f64-only (not generic over T), and every method defaults to a type_name-annotated unimplemented!():

// Signature sketch. Every method below is a *provided* method whose default
// body is a `type_name`-annotated `unimplemented!()`; the bodies are elided
// here.
pub trait DistributionExt {
  fn characteristic_function(&self, t: f64) -> num_complex::Complex64;
  fn pdf(&self, x: f64) -> f64;
  fn cdf(&self, x: f64) -> f64;
  fn inv_cdf(&self, p: f64) -> f64;
  fn mean(&self) -> f64;
  fn median(&self) -> f64;
  fn mode(&self) -> f64;
  fn variance(&self) -> f64;
  fn skewness(&self) -> f64;
  fn kurtosis(&self) -> f64;
  fn entropy(&self) -> f64;
  fn moment_generating_function(&self, t: f64) -> f64;
}

Default behaviour: unimplemented moments panic with a helpful message — never silently return zero. This is a hard project rule. See DistributionExt for a compiled, runnable proof.

Calibrator and CalibrationResult

Calibration is Result-based with associated types for the initial guess, the parameter struct, the output, and the error — market data (prices, strikes, spots, …) is passed to the calibrator's own constructor, not to calibrate itself:

pub trait Calibrator {
  type InitialGuess;
  type Params: Clone;
  type Output: CalibrationResult<Params = Self::Params>;
  type Error;
  fn calibrate(&self, initial: Option<Self::InitialGuess>) -> Result<Self::Output, Self::Error>;
}

pub trait CalibrationResult {
  type Params: Clone;
  fn rmse(&self) -> f64;
  fn converged(&self) -> bool;
  fn params(&self) -> Self::Params;
  fn loss_score(&self) -> Option<&CalibrationLossScore> {
    None
  }
  fn iterations(&self) -> Option<usize> {
    None
  }
  fn message(&self) -> Option<&str> {
    None
  }
  fn max_error(&self) -> f64 {
    f64::NAN
  }
}

calibrate(None) lets the calibrator infer its own initial guess. CalibrationResult carries the calibrated Params (via .params()), the RMSE, and a convergence flag. iterations(), message() and max_error() are optional — they default to None / None / f64::NAN, and only the calibrators that record them override. See the Quant catalog for the calibrators that ship today.

GreeksExt and the Greeks aggregator

Greeks is the aggregate: a plain f64 struct with nine named fields, not generic over T. It is what a pricer's inherent greeks(s, k, r, q, tau, option_type) returns — BSMPricer, HestonPricer, Merton1976Pricer, CashOrNothingPricer and AssetOrNothingPricer all share that one signature, because they hold model state only and price a grid from a single instance.

GreeksExt is a separate, narrower trait: one required accessor (delta), NaN-defaulted accessors for the rest, and an aggregating greeks() — all taking &self and nothing else. Only the Monte Carlo Malliavin estimators (GbmMalliavinGreeks, HestonMalliavinGreeks) implement it, and for them the bundled query is the point: their greeks() override runs one simulation so the nine numbers come from the same paths. The analytic pricers above cannot implement it and deliberately do not, so GreeksExt is not the crate's Greeks interface and is not in the prelude — reach it via stochastic_rs::traits::GreeksExt.

tests/doctest_concepts_greeks_shape.rs
// docs: concepts/traits#greeksext-and-the-greeks-aggregator
//! Backs the `Greeks` field list on the traits-overview concept page.
//! `Greeks` is a plain `f64` struct (not generic over `T`), verified here
//! by constructing the real type with exactly the 9 named fields shown.

use stochastic_rs::traits::Greeks;

#[test]
fn greeks_has_the_nine_documented_fields() {
  let g = Greeks {
    delta: 0.5,
    gamma: 0.1,
    vega: 0.2,
    theta: -0.05,
    rho: 0.3,
    vanna: 0.4,
    charm: 0.05,
    volga: 0.6,
    veta: -0.02,
  };
  assert_eq!(g.as_array().len(), 9);
}

See Quant for the pricers that expose Greeks.

On this page