use crate::curves::Curve;
use crate::error::CurveError;
use crate::error::SurfaceError;
use crate::surfaces::Surface;
use positive::Positive;
#[cfg(test)]
use rust_decimal::MathematicalOps;
pub trait ImpliedVolatilityCurve {
fn iv_curve(&self) -> Result<Curve, CurveError>;
}
pub trait ImpliedVolatilitySurface {
fn iv_surface(&self, days_to_expiry: Vec<Positive>) -> Result<Surface, SurfaceError>;
}
#[cfg(test)]
mod tests_implied_volatility_traits {
use super::*;
use crate::curves::Point2D;
use crate::surfaces::Point3D;
use positive::pos_or_panic;
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use std::collections::BTreeSet;
struct TestIVCurve;
impl ImpliedVolatilityCurve for TestIVCurve {
fn iv_curve(&self) -> Result<Curve, CurveError> {
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.21)));
points.insert(Point2D::new(dec!(110.0), dec!(0.23)));
Ok(Curve::new(points))
}
}
struct TestIVSurface;
impl ImpliedVolatilitySurface for TestIVSurface {
fn iv_surface(&self, days_to_expiry: Vec<Positive>) -> Result<Surface, SurfaceError> {
let mut points = BTreeSet::new();
let strikes = [
dec!(90.0),
dec!(95.0),
dec!(100.0),
dec!(105.0),
dec!(110.0),
];
let base_ivs = [dec!(0.25), dec!(0.22), dec!(0.20), dec!(0.21), dec!(0.23)];
for (strike, base_iv) in strikes.iter().zip(base_ivs.iter()) {
for days in &days_to_expiry {
let time_factor = (days.to_dec() / dec!(365.0)).sqrt().unwrap_or(Decimal::ONE);
let adjusted_iv = *base_iv * time_factor;
points.insert(Point3D::new(*strike, days.to_dec(), adjusted_iv));
}
}
Ok(Surface::new(points))
}
}
#[test]
fn test_iv_curve_implementation() {
let iv = TestIVCurve;
let curve = iv.iv_curve().unwrap();
assert_eq!(curve.points.len(), 5);
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));
}
#[test]
fn test_iv_curve_atm_lowest() {
let iv = TestIVCurve;
let curve = iv.iv_curve().unwrap();
let points: Vec<&Point2D> = curve.points.iter().collect();
let atm_iv = points[2].y;
for point in points.iter() {
assert!(point.y >= atm_iv);
}
}
#[test]
fn test_iv_surface_implementation() {
let iv = TestIVSurface;
let days = vec![
pos_or_panic!(30.0),
pos_or_panic!(60.0),
pos_or_panic!(90.0),
];
let surface = iv.iv_surface(days).unwrap();
assert_eq!(surface.points.len(), 15);
}
#[test]
fn test_iv_surface_time_scaling() {
let iv = TestIVSurface;
let days = vec![pos_or_panic!(30.0), pos_or_panic!(90.0)];
let surface = iv.iv_surface(days).unwrap();
let points: Vec<&Point3D> = surface.points.iter().collect();
let strike_100_points: Vec<&&Point3D> =
points.iter().filter(|p| p.x == dec!(100.0)).collect();
assert_eq!(strike_100_points.len(), 2);
}
#[test]
fn test_iv_surface_empty_days() {
let iv = TestIVSurface;
let days: Vec<Positive> = vec![];
let surface = iv.iv_surface(days).unwrap();
assert!(surface.points.is_empty());
}
}