regit-svi 2.0.0

Arbitrage-free SVI volatility surfaces in pure Rust. Raw, Jump-Wings and SSVI parametrisations, calibration, and static-arbitrage checks. Zero dependencies.
Documentation
// Copyright 2026 Regit.io — Nicolas Koenig
// SPDX-License-Identifier: Apache-2.0

//! Market-coordinate conversion conventions.

/// Converts implied volatility to total implied variance, `vol² × t`.
///
/// This infallible kernel assumes finite, non-negative `vol` and finite,
/// non-negative `t`; it propagates IEEE non-finite results outside that domain
/// or the representable range.
///
/// # Examples
///
/// ```
/// use regit_svi::total_variance_from_vol;
///
/// assert!((total_variance_from_vol(0.20, 2.0) - 0.08).abs() < 1e-15);
/// ```
#[must_use]
#[inline]
pub fn total_variance_from_vol(vol: f64, t: f64) -> f64 {
    vol * vol * t
}

/// Converts strike and forward to log-moneyness, `ln(strike / forward)`.
///
/// The logarithms are taken before subtraction so a representable strike and
/// forward do not spuriously overflow in their ratio.
/// Both inputs must be finite and strictly positive; IEEE `NaN` or infinity
/// propagates when those preconditions fail or the result is out of range.
///
/// # Examples
///
/// ```
/// use regit_svi::log_moneyness;
///
/// assert!(log_moneyness(100.0, 100.0).abs() < 1e-15);
/// assert!(log_moneyness(1e308, 1e-308).is_finite());
/// ```
#[must_use]
#[inline]
pub fn log_moneyness(strike: f64, forward: f64) -> f64 {
    strike.ln() - forward.ln()
}

#[cfg(test)]
#[allow(clippy::expect_used)] // Validated fixtures use contextual expectations.
mod tests {
    use super::*;

    #[test]
    fn total_variance_conversion() {
        assert!((total_variance_from_vol(0.2, 2.0) - 0.08).abs() < 1e-15);
    }

    #[test]
    fn at_the_money_log_moneyness_is_zero() {
        assert!(log_moneyness(100.0, 100.0).abs() < 1e-15);
    }

    #[test]
    fn log_moneyness_avoids_ratio_overflow() {
        let value = log_moneyness(1e308, 1e-308);
        assert!(value.is_finite());
        assert!((value - 1_418.392_417_284_332_2).abs() < 1e-12);
    }
}