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
| Trait | Crate | Purpose |
|---|---|---|
RealExt | stochastic-rs-distributions | Scalar real bound — open to custom scalars (AAD) |
FloatExt | stochastic-rs-distributions | Full simulation-grade float bound (f32 / f64 only) |
SimdFloatExt | stochastic-rs-distributions | 8-lane SIMD surface over RealExt |
ProcessExt<T> | stochastic-rs-stochastic | Stochastic process simulation (sample, sample_map, sample_par) |
DistributionExt | stochastic-rs-distributions | Closed-form pdf / cdf / cf / moments (f64-only) |
BivariateExt<T> | stochastic-rs-copulas | Bivariate copulas |
MultivariateExt<T> | stochastic-rs-copulas | Multivariate copulas (Gaussian, t, vines, NAC) |
ModelPricer | stochastic-rs-quant | Concrete-typed strike/tau-grid pricer (no &dyn) |
ShortRatePricer | stochastic-rs-quant | Short-rate bond pricing (Vasicek, Cir, HullWhite) |
ToModel, ToShortRateModel | stochastic-rs-quant | Calibrator → Concrete pricer bridge |
FourierModelExt<T> | stochastic-rs-quant | Characteristic function models |
Calibrator | stochastic-rs-quant | Result-based calibration (type Params, type Error) |
Instrument, PricingEngine | stochastic-rs-quant | Cross-engine comparison harness (not in the prelude) |
GreeksExt | stochastic-rs-quant | No-argument Greeks for the MC Malliavin estimators |
CalendarExt | stochastic-rs-quant | Pluggable holiday calendars |
TimeExt | stochastic-rs-quant | Day-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.
// 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.
Workspace layout
How the stochastic-rs Cargo workspace is organised — sub-crates, dependency topology, and how the umbrella re-exports preserve v1.x import paths.
Prelude
stochastic_rs::prelude — 25 items in six groups that cover ~95% of day-to-day usage. What is in the prelude and what is intentionally kept out.