Skip to content
Sarthak Bagaria
All model code

quant/src/markovfunctional.rs

The Markov functional construction: one driver, and a functional form chosen by matching quantiles rather than by writing down an SDE for the rate itself.

//! The Markov functional construction: one driver, and a functional form//! chosen by matching quantiles rather than by writing down an SDE for the//! rate itself.//!//! The fitting-and-testing chapter's table lists Markov functional models//! alongside short rate and market models without ever building one. This is//! that model, in the simplest case that still shows the whole mechanism: a//! single driftless Brownian motion under the terminal measure, and a rate at//! each fixing date defined as the monotone function of the driver that sends//! the driver's own quantile to the market's. use crate::black::norm_cdf;use crate::special::norm_inv; /// The Markov functional form for a rate whose market-implied quantile/// function (inverse CDF, under the measure the driver lives in) is `target`.////// `x` is the driver's value at the fixing date `t`, driftless Brownian so/// `x ~ N(0, t)`; its own CDF is `norm_cdf(x / sqrt(t))`. Composing that with/// the market's quantile function is the whole construction: send the/// driver's quantile to the rate's. There is no freedom left once `target` is/// fixed --- the map is the unique monotone one reproducing that marginal,/// which is what "impose one dimension and choose the functional form" means/// once it is written down.pub fn functional_form(target_quantile: impl Fn(f64) -> f64, t: f64, x: f64) -> f64 {    target_quantile(norm_cdf(x / t.sqrt()))} /// The quantile function of a lognormal with the given forward and/// volatility, `F exp(sigma sqrt(t) z - sigma^2 t / 2)` at the normal quantile/// `z = norm_inv(p)`.pub fn lognormal_quantile(forward: f64, sigma: f64, t: f64, p: f64) -> f64 {    let z = norm_inv(p);    forward * (sigma * t.sqrt() * z - 0.5 * sigma * sigma * t).exp()} /// The closed-form driftless lognormal transform of the driver,/// `F exp(sigma x - sigma^2 t / 2)`.////// When the target marginal is itself lognormal, [`functional_form`] reduces/// to exactly this: the flat-smile special case of the general construction,/// checked in the tests below.pub fn lognormal_functional_form(forward: f64, sigma: f64, t: f64, x: f64) -> f64 {    forward * (sigma * x - 0.5 * sigma * sigma * t).exp()} #[cfg(test)]mod tests {    use super::*;     #[test]    fn quantile_matching_a_lognormal_target_reproduces_the_lognormal_transform() {        // Two routes to the same number: compose norm_cdf with the lognormal        // quantile (the general construction), against the closed form the        // composition is supposed to collapse to. They share only the claim        // that they must agree, nothing about how either is computed.        let (forward, sigma, t) = (100.0f64, 0.3f64, 2.0f64);        for x in [-2.5f64, -1.0, -0.1, 0.0, 0.4, 1.3, 2.8] {            let general = functional_form(|p| lognormal_quantile(forward, sigma, t, p), t, x);            let closed = lognormal_functional_form(forward, sigma, t, x);            assert!(                (general / closed - 1.0).abs() < 1e-9,                "x={x}: general form {general} against closed form {closed}"            );        }    }     #[test]    fn the_functional_form_is_monotone_in_the_driver() {        // Reproducing a marginal exactly forces the map to be monotone in the        // driver, for any target: two drivers in the same order must give        // rates in the same order, or the map could not be a quantile        // function composed with another quantile function.        let (forward, sigma, t) = (0.03f64, 0.6f64, 5.0f64);        let xs = [-3.0f64, -1.5, -0.2, 0.5, 1.7, 3.2];        let mut previous = f64::NEG_INFINITY;        for &x in &xs {            let rate = functional_form(|p| lognormal_quantile(forward, sigma, t, p), t, x);            assert!(rate > previous, "not increasing at x={x}");            previous = rate;        }    }}