use crate::error::SurfaceError;
use crate::surfaces::Surface;
use positive::Positive;
pub trait VannaVolgaSurface {
fn vanna_volga_surface(
&self,
price_range: (Positive, Positive),
vol_range: (Positive, Positive),
price_steps: usize,
vol_steps: usize,
) -> Result<Surface, SurfaceError>;
}
#[cfg(test)]
mod tests_vanna_volga {
use super::*;
use crate::surfaces::Point3D;
use positive::pos_or_panic;
use rust_decimal_macros::dec;
use std::collections::BTreeSet;
struct TestVannaVolgaSurface;
impl VannaVolgaSurface for TestVannaVolgaSurface {
fn vanna_volga_surface(
&self,
price_range: (Positive, Positive),
vol_range: (Positive, Positive),
price_steps: usize,
vol_steps: usize,
) -> Result<Surface, SurfaceError> {
let mut points = BTreeSet::new();
let price_step = if price_steps > 0 {
(price_range.1 - price_range.0).to_dec() / rust_decimal::Decimal::from(price_steps)
} else {
rust_decimal::Decimal::ZERO
};
let vol_step = if vol_steps > 0 {
(vol_range.1 - vol_range.0).to_dec() / rust_decimal::Decimal::from(vol_steps)
} else {
rust_decimal::Decimal::ZERO
};
for p in 0..=price_steps {
let price = price_range.0.to_dec() + price_step * rust_decimal::Decimal::from(p);
for v in 0..=vol_steps {
let vol = vol_range.0.to_dec() + vol_step * rust_decimal::Decimal::from(v);
let atm_price = dec!(450.0);
let moneyness = (price - atm_price).abs() / atm_price;
let vv_cost = moneyness * vol * dec!(100.0);
points.insert(Point3D::new(price, vol, vv_cost));
}
}
Ok(Surface::new(points))
}
}
#[test]
fn test_vanna_volga_surface_creation() {
let vv = TestVannaVolgaSurface;
let price_range = (pos_or_panic!(400.0), pos_or_panic!(500.0));
let vol_range = (pos_or_panic!(0.10), pos_or_panic!(0.40));
let surface = vv.vanna_volga_surface(price_range, vol_range, 10, 10);
assert!(surface.is_ok());
let surface = surface.unwrap();
assert_eq!(surface.points.len(), 121);
}
#[test]
fn test_vanna_volga_surface_cost_increases_with_moneyness() {
let vv = TestVannaVolgaSurface;
let price_range = (pos_or_panic!(400.0), pos_or_panic!(500.0));
let vol_range = (pos_or_panic!(0.20), pos_or_panic!(0.20));
let surface = vv
.vanna_volga_surface(price_range, vol_range, 10, 0)
.unwrap();
let points: Vec<&Point3D> = surface.points.iter().collect();
let atm_point = points.iter().min_by(|a, b| {
let a_dist = (a.x - dec!(450.0)).abs();
let b_dist = (b.x - dec!(450.0)).abs();
a_dist.partial_cmp(&b_dist).unwrap()
});
if let Some(atm) = atm_point {
for point in points.iter() {
assert!(point.z >= atm.z || (point.z - atm.z).abs() < dec!(0.01));
}
}
}
#[test]
fn test_vanna_volga_surface_cost_increases_with_volatility() {
let vv = TestVannaVolgaSurface;
let price_range = (pos_or_panic!(400.0), pos_or_panic!(400.0)); let vol_range = (pos_or_panic!(0.10), pos_or_panic!(0.40));
let surface = vv
.vanna_volga_surface(price_range, vol_range, 0, 10)
.unwrap();
let points: Vec<&Point3D> = surface.points.iter().collect();
for i in 1..points.len() {
assert!(points[i].z >= points[i - 1].z);
}
}
}