use crate::chains::chain::OptionChain;
use crate::curves::Curve;
use crate::error::VolatilityError;
use positive::Positive;
pub trait VolatilitySmile {
fn smile(&self) -> Curve;
}
pub trait AtmIvProvider {
fn atm_iv(&self) -> Result<&Positive, VolatilityError>;
}
impl AtmIvProvider for Positive {
fn atm_iv(&self) -> Result<&Positive, VolatilityError> {
Ok(self)
}
}
impl AtmIvProvider for OptionChain {
fn atm_iv(&self) -> Result<&Positive, VolatilityError> {
match self.get_atm_implied_volatility() {
Ok(iv) => Ok(iv),
Err(e) => Err(format!("ATM IV not available: {e}").into()),
}
}
}
#[cfg(test)]
mod tests_volatility_traits {
use super::*;
use crate::curves::{Curve, Point2D};
use crate::error::CurveError;
use crate::metrics::VolatilitySkewCurve;
use positive::pos_or_panic;
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use std::collections::BTreeSet;
struct TestSmile;
impl VolatilitySmile for TestSmile {
fn smile(&self) -> Curve {
create_sample_curve()
}
}
struct TestIvProvider {
iv: Positive,
}
impl AtmIvProvider for TestIvProvider {
fn atm_iv(&self) -> Result<&Positive, VolatilityError> {
Ok(&self.iv)
}
}
fn create_sample_curve() -> Curve {
let mut points = BTreeSet::new();
points.insert(Point2D::new(dec!(90.0), dec!(0.25)));
points.insert(Point2D::new(dec!(95.0), dec!(0.22)));
points.insert(Point2D::new(dec!(100.0), dec!(0.20)));
points.insert(Point2D::new(dec!(105.0), dec!(0.22)));
points.insert(Point2D::new(dec!(110.0), dec!(0.25)));
Curve {
points,
x_range: (dec!(90.0), dec!(110.0)),
}
}
#[test]
fn test_volatility_smile_implementation() {
let smile = TestSmile;
let curve = smile.smile();
assert_eq!(curve.points.len(), 5);
assert_eq!(curve.x_range, (dec!(90.0), dec!(110.0)));
let points: Vec<&Point2D> = curve.points.iter().collect();
assert_eq!(points[0].x, dec!(90.0));
assert_eq!(points[0].y, dec!(0.25));
assert_eq!(points[2].x, dec!(100.0));
assert_eq!(points[2].y, dec!(0.20));
assert_eq!(points[4].x, dec!(110.0));
assert_eq!(points[4].y, dec!(0.25));
}
#[test]
fn test_volatility_smile() {
let smile = TestSmile;
let curve = smile.smile();
assert_eq!(curve.points.len(), 5);
assert_eq!(curve.x_range, (dec!(90.0), dec!(110.0)));
let points: Vec<&Point2D> = curve.points.iter().collect();
assert_eq!(points[0].x, dec!(90.0));
assert_eq!(points[0].y, dec!(0.25));
assert_eq!(points[2].x, dec!(100.0));
assert_eq!(points[2].y, dec!(0.20));
assert_eq!(points[4].x, dec!(110.0));
assert_eq!(points[4].y, dec!(0.25));
}
#[test]
fn test_volatility_smile_with_empty_curve() {
struct EmptySmile;
impl VolatilitySmile for EmptySmile {
fn smile(&self) -> Curve {
Curve {
points: BTreeSet::new(),
x_range: (Decimal::ZERO, Decimal::ZERO),
}
}
}
let smile = EmptySmile;
let curve = smile.smile();
assert!(curve.points.is_empty());
assert_eq!(curve.x_range, (Decimal::ZERO, Decimal::ZERO));
}
#[test]
fn test_atm_iv_provider_for_positive() {
let value = pos_or_panic!(0.2);
let result = value.atm_iv();
assert!(result.is_ok());
}
#[test]
fn test_atm_iv_provider_some() {
let provider = TestIvProvider {
iv: pos_or_panic!(0.25),
};
let result = provider.atm_iv();
assert!(result.is_ok());
}
#[test]
fn test_atm_iv_provider_error() {
struct ErrorIvProvider;
impl AtmIvProvider for ErrorIvProvider {
fn atm_iv(&self) -> Result<&Positive, VolatilityError> {
Err("ATM IV not available: test error".into())
}
}
let provider = ErrorIvProvider;
let result = provider.atm_iv();
assert!(result.is_err());
let error = result.unwrap_err();
assert!(error.to_string().contains("ATM IV not available"));
}
#[test]
fn test_combined_traits_usage() {
struct CombinedProvider {
iv_value: Positive,
}
impl VolatilitySmile for CombinedProvider {
fn smile(&self) -> Curve {
create_sample_curve()
}
}
impl AtmIvProvider for CombinedProvider {
fn atm_iv(&self) -> Result<&Positive, VolatilityError> {
Ok(&self.iv_value)
}
}
let provider_with_iv = CombinedProvider {
iv_value: pos_or_panic!(0.2),
};
let curve = provider_with_iv.smile();
assert_eq!(curve.points.len(), 5);
let iv_result = provider_with_iv.atm_iv();
assert!(iv_result.is_ok());
}
#[test]
fn test_atm_iv_provider_for_option_chain_success() {
use crate::chains::chain::OptionChain;
let mut chain = OptionChain::new(
"TEST",
Positive::HUNDRED,
"2025-12-31".to_string(),
Some(dec!(0.05)),
None,
);
chain.add_option(
pos_or_panic!(95.0),
Some(pos_or_panic!(6.0)),
Some(pos_or_panic!(6.5)),
Some(Positive::ONE),
Some(pos_or_panic!(1.5)),
pos_or_panic!(0.25),
Some(dec!(0.7)),
Some(dec!(-0.3)),
Some(dec!(0.02)),
None,
None,
None,
);
chain.add_option(
Positive::HUNDRED,
Some(pos_or_panic!(3.0)),
Some(pos_or_panic!(3.5)),
Some(pos_or_panic!(3.0)),
Some(pos_or_panic!(3.5)),
pos_or_panic!(0.20),
Some(dec!(0.5)),
Some(dec!(-0.5)),
Some(dec!(0.025)),
None,
None,
None,
);
chain.add_option(
pos_or_panic!(105.0),
Some(Positive::ONE),
Some(pos_or_panic!(1.5)),
Some(pos_or_panic!(6.0)),
Some(pos_or_panic!(6.5)),
pos_or_panic!(0.22),
Some(dec!(0.3)),
Some(dec!(-0.7)),
Some(dec!(0.02)),
None,
None,
None,
);
let result = chain.atm_iv();
assert!(result.is_ok());
let iv = result.unwrap();
assert!(*iv > Positive::ZERO);
}
#[test]
fn test_atm_iv_provider_for_option_chain_empty() {
use crate::chains::chain::OptionChain;
let chain = OptionChain::new(
"TEST",
Positive::HUNDRED,
"2025-12-31".to_string(),
None,
None,
);
let result = chain.atm_iv();
assert!(result.is_err());
let error = result.unwrap_err();
assert!(error.to_string().contains("ATM IV not available"));
}
#[test]
fn test_volatility_smile_symmetry() {
struct SymmetricSmile;
impl VolatilitySmile for SymmetricSmile {
fn smile(&self) -> Curve {
let mut points = BTreeSet::new();
points.insert(Point2D::new(dec!(80.0), dec!(0.30)));
points.insert(Point2D::new(dec!(90.0), dec!(0.22)));
points.insert(Point2D::new(dec!(100.0), dec!(0.18)));
points.insert(Point2D::new(dec!(110.0), dec!(0.22)));
points.insert(Point2D::new(dec!(120.0), dec!(0.30)));
Curve {
points,
x_range: (dec!(80.0), dec!(120.0)),
}
}
}
let smile = SymmetricSmile;
let curve = smile.smile();
let points: Vec<&Point2D> = curve.points.iter().collect();
assert_eq!(points[0].y, points[4].y); assert_eq!(points[1].y, points[3].y); assert!(points[2].y < points[1].y);
}
#[test]
fn test_atm_iv_provider_positive_value() {
let value = pos_or_panic!(0.35);
let result = value.atm_iv();
assert!(result.is_ok());
let iv = result.unwrap();
assert_eq!(*iv, pos_or_panic!(0.35));
}
#[test]
fn test_atm_iv_provider_small_value() {
let value = pos_or_panic!(0.01);
let result = value.atm_iv();
assert!(result.is_ok());
let iv = result.unwrap();
assert_eq!(*iv, pos_or_panic!(0.01));
}
#[test]
fn test_volatility_smile_single_point() {
struct SinglePointSmile;
impl VolatilitySmile for SinglePointSmile {
fn smile(&self) -> Curve {
let mut points = BTreeSet::new();
points.insert(Point2D::new(dec!(100.0), dec!(0.20)));
Curve {
points,
x_range: (dec!(100.0), dec!(100.0)),
}
}
}
let smile = SinglePointSmile;
let curve = smile.smile();
assert_eq!(curve.points.len(), 1);
let point = curve.points.iter().next().unwrap();
assert_eq!(point.x, dec!(100.0));
assert_eq!(point.y, dec!(0.20));
}
#[test]
fn test_combined_smile_and_skew_traits() {
struct CombinedVolatility;
impl VolatilitySmile for CombinedVolatility {
fn smile(&self) -> Curve {
create_sample_curve()
}
}
impl VolatilitySkewCurve for CombinedVolatility {
fn volatility_skew(&self) -> Result<Curve, CurveError> {
let mut points = BTreeSet::new();
points.insert(Point2D::new(dec!(-10.0), dec!(0.25)));
points.insert(Point2D::new(dec!(0.0), dec!(0.20)));
points.insert(Point2D::new(dec!(10.0), dec!(0.25)));
let curve = Curve {
points,
x_range: (dec!(-10.0), dec!(10.0)),
};
Ok(curve)
}
}
let vol = CombinedVolatility;
let smile_curve = vol.smile();
assert_eq!(smile_curve.points.len(), 5);
let skew_curve = vol.volatility_skew().unwrap();
assert_eq!(skew_curve.points.len(), 3);
}
}