use crate::curves::Curve;
use crate::error::CurveError;
use crate::model::OptionStyle;
pub trait DollarGammaCurve {
fn dollar_gamma_curve(&self, option_style: &OptionStyle) -> Result<Curve, CurveError>;
}
#[cfg(test)]
mod tests_dollar_gamma_traits {
use super::*;
use crate::curves::Point2D;
use rust_decimal::Decimal;
use rust_decimal_macros::dec;
use std::collections::BTreeSet;
struct TestDollarGamma {
spot: Decimal,
}
impl DollarGammaCurve for TestDollarGamma {
fn dollar_gamma_curve(&self, _option_style: &OptionStyle) -> Result<Curve, CurveError> {
let spot_squared = self.spot * self.spot;
let mut points = BTreeSet::new();
let gammas = vec![
(dec!(90.0), dec!(0.01)),
(dec!(95.0), dec!(0.03)),
(dec!(100.0), dec!(0.05)), (dec!(105.0), dec!(0.03)),
(dec!(110.0), dec!(0.01)),
];
for (strike, gamma) in gammas {
let dollar_gamma = gamma * spot_squared * dec!(0.01);
points.insert(Point2D::new(strike, dollar_gamma));
}
Ok(Curve::new(points))
}
}
struct TestEmptyDollarGamma;
impl DollarGammaCurve for TestEmptyDollarGamma {
fn dollar_gamma_curve(&self, _option_style: &OptionStyle) -> Result<Curve, CurveError> {
Err(CurveError::ConstructionError(
"No valid gamma values computed".to_string(),
))
}
}
#[test]
fn test_dollar_gamma_implementation() {
let dg = TestDollarGamma { spot: dec!(100.0) };
let curve = dg.dollar_gamma_curve(&OptionStyle::Call).unwrap();
assert_eq!(curve.points.len(), 5);
let points: Vec<&Point2D> = curve.points.iter().collect();
assert_eq!(points[0].x, dec!(90.0));
}
#[test]
fn test_dollar_gamma_atm_highest() {
let dg = TestDollarGamma { spot: dec!(100.0) };
let curve = dg.dollar_gamma_curve(&OptionStyle::Call).unwrap();
let points: Vec<&Point2D> = curve.points.iter().collect();
let atm_dg = points.iter().find(|p| p.x == dec!(100.0)).unwrap().y;
for point in points.iter() {
assert!(point.y <= atm_dg);
}
}
#[test]
fn test_dollar_gamma_formula() {
let spot = dec!(100.0);
let dg = TestDollarGamma { spot };
let curve = dg.dollar_gamma_curve(&OptionStyle::Call).unwrap();
let points: Vec<&Point2D> = curve.points.iter().collect();
let atm_point = points.iter().find(|p| p.x == dec!(100.0)).unwrap();
assert_eq!(atm_point.y, dec!(5.0));
}
#[test]
fn test_dollar_gamma_spot_sensitivity() {
let dg_low = TestDollarGamma { spot: dec!(50.0) };
let dg_high = TestDollarGamma { spot: dec!(200.0) };
let curve_low = dg_low.dollar_gamma_curve(&OptionStyle::Call).unwrap();
let curve_high = dg_high.dollar_gamma_curve(&OptionStyle::Call).unwrap();
let points_low: Vec<&Point2D> = curve_low.points.iter().collect();
let points_high: Vec<&Point2D> = curve_high.points.iter().collect();
let atm_low = points_low.iter().find(|p| p.x == dec!(100.0)).unwrap().y;
let atm_high = points_high.iter().find(|p| p.x == dec!(100.0)).unwrap().y;
assert!(atm_high > atm_low);
assert_eq!(atm_high / atm_low, dec!(16));
}
#[test]
fn test_dollar_gamma_empty_error() {
let dg = TestEmptyDollarGamma;
let result = dg.dollar_gamma_curve(&OptionStyle::Call);
assert!(result.is_err());
match result {
Err(CurveError::ConstructionError(msg)) => {
assert!(msg.contains("No valid gamma"));
}
_ => panic!("Expected ConstructionError"),
}
}
#[test]
fn test_dollar_gamma_symmetric() {
let dg = TestDollarGamma { spot: dec!(100.0) };
let curve = dg.dollar_gamma_curve(&OptionStyle::Call).unwrap();
let points: Vec<&Point2D> = curve.points.iter().collect();
let dg_90 = points.iter().find(|p| p.x == dec!(90.0)).unwrap().y;
let dg_110 = points.iter().find(|p| p.x == dec!(110.0)).unwrap().y;
assert_eq!(dg_90, dg_110);
}
#[test]
fn test_dollar_gamma_call_vs_put() {
let dg = TestDollarGamma { spot: dec!(100.0) };
let call_curve = dg.dollar_gamma_curve(&OptionStyle::Call).unwrap();
let put_curve = dg.dollar_gamma_curve(&OptionStyle::Put).unwrap();
let call_points: Vec<&Point2D> = call_curve.points.iter().collect();
let put_points: Vec<&Point2D> = put_curve.points.iter().collect();
for (call_p, put_p) in call_points.iter().zip(put_points.iter()) {
assert_eq!(call_p.y, put_p.y);
}
}
}