use crate::error::VolatilityError;
use optionstratlib_core::model::Positive;
use optionstratlib_math::curves::Curve;
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)
}
}
#[cfg(test)]
mod tests_volatility_traits {
use super::*;
use optionstratlib_math::curves::{Curve, Point2D};
use optionstratlib_core::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(VolatilityError::AtmIvUnavailable {
source: Box::new(VolatilityError::IvNotFound),
})
}
}
let provider = ErrorIvProvider;
let result = provider.atm_iv();
assert!(result.is_err());
let error = result.unwrap_err();
assert!(
error
.to_string()
.contains("ATM implied volatility is 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_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));
}
}