use crate::curves::Curve;
use crate::error::CurveError;
use crate::error::SurfaceError;
use crate::surfaces::Surface;
use positive::Positive;
pub trait VolumeProfileCurve {
fn volume_profile_curve(&self) -> Result<Curve, CurveError>;
}
pub trait VolumeProfileSurface {
fn volume_profile_surface(&self, days: Vec<Positive>) -> Result<Surface, SurfaceError>;
}
#[cfg(test)]
mod tests_volume_profile {
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 TestVolumeProfile;
impl VolumeProfileCurve for TestVolumeProfile {
fn volume_profile_curve(&self) -> Result<Curve, CurveError> {
let mut points = BTreeSet::new();
let strikes = [
(dec!(380.0), dec!(500.0)),
(dec!(400.0), dec!(1200.0)),
(dec!(420.0), dec!(2500.0)),
(dec!(440.0), dec!(4000.0)),
(dec!(450.0), dec!(5000.0)), (dec!(460.0), dec!(4200.0)),
(dec!(480.0), dec!(2800.0)),
(dec!(500.0), dec!(1500.0)),
(dec!(520.0), dec!(600.0)),
];
for (strike, volume) in strikes {
points.insert(Point2D::new(strike, volume));
}
Ok(Curve::new(points))
}
}
impl VolumeProfileSurface for TestVolumeProfile {
fn volume_profile_surface(&self, days: Vec<Positive>) -> Result<Surface, SurfaceError> {
let mut points = BTreeSet::new();
let strikes = [
dec!(400.0),
dec!(420.0),
dec!(440.0),
dec!(450.0),
dec!(460.0),
dec!(480.0),
dec!(500.0),
];
let base_volumes = [
dec!(1200.0),
dec!(2500.0),
dec!(4000.0),
dec!(5000.0),
dec!(4200.0),
dec!(2800.0),
dec!(1500.0),
];
for day in &days {
let time_factor = Decimal::ONE + (dec!(30.0) - day.to_dec()) / dec!(30.0);
for (strike, base_vol) in strikes.iter().zip(base_volumes.iter()) {
let volume = *base_vol * time_factor;
points.insert(Point3D::new(*strike, day.to_dec(), volume));
}
}
Ok(Surface::new(points))
}
}
#[test]
fn test_volume_profile_curve_creation() {
let volume = TestVolumeProfile;
let curve = volume.volume_profile_curve();
assert!(curve.is_ok());
let curve = curve.unwrap();
assert_eq!(curve.points.len(), 9);
}
#[test]
fn test_volume_profile_atm_highest() {
let volume = TestVolumeProfile;
let curve = volume.volume_profile_curve().unwrap();
let points: Vec<&Point2D> = curve.points.iter().collect();
let max_vol = points.iter().max_by(|a, b| a.y.partial_cmp(&b.y).unwrap());
if let Some(max) = max_vol {
assert_eq!(max.x, dec!(450.0));
assert_eq!(max.y, dec!(5000.0));
}
}
#[test]
fn test_volume_profile_surface_creation() {
let volume = TestVolumeProfile;
let days = vec![pos_or_panic!(5.0), pos_or_panic!(10.0), pos_or_panic!(20.0)];
let surface = volume.volume_profile_surface(days);
assert!(surface.is_ok());
let surface = surface.unwrap();
assert_eq!(surface.points.len(), 21);
}
#[test]
fn test_volume_profile_surface_time_effect() {
let volume = TestVolumeProfile;
let days = vec![pos_or_panic!(5.0), pos_or_panic!(25.0)];
let surface = volume.volume_profile_surface(days).unwrap();
let points: Vec<&Point3D> = surface.points.iter().collect();
let vol_5d = points
.iter()
.find(|p| p.x == dec!(450.0) && p.y == dec!(5.0))
.map(|p| p.z);
let vol_25d = points
.iter()
.find(|p| p.x == dec!(450.0) && p.y == dec!(25.0))
.map(|p| p.z);
if let (Some(v5), Some(v25)) = (vol_5d, vol_25d) {
assert!(v5 > v25);
}
}
#[test]
fn test_volume_profile_surface_empty_days() {
let volume = TestVolumeProfile;
let days: Vec<Positive> = vec![];
let surface = volume.volume_profile_surface(days).unwrap();
assert!(surface.points.is_empty());
}
#[test]
fn test_volume_profile_positive_values() {
let volume = TestVolumeProfile;
let curve = volume.volume_profile_curve().unwrap();
for point in curve.points.iter() {
assert!(point.y > Decimal::ZERO);
}
}
}