use super::LevyCalibrator;
use super::loss::fourier_call_price;
use super::types::LevyCalibrationResult;
use super::types::LevyModel;
use super::types::LevyModelType;
use super::types::MarketSlice;
use crate::CalibrationLossScore;
use crate::LossMetric;
use crate::traits::Calibrator;
const STRIKES: [f64; 9] = [80.0, 85.0, 90.0, 95.0, 100.0, 105.0, 110.0, 115.0, 120.0];
const VG_REF: [f64; 9] = [
25.056158, 20.941767, 17.091301, 13.572373, 10.453503, 7.795810, 5.640544, 3.991334, 2.793823,
];
const MJD_REF: [f64; 9] = [
24.537096, 20.322713, 16.420092, 12.915553, 9.877019, 7.340385, 5.303578, 3.729825, 2.557790,
];
#[test]
fn vg_pricer_matches_reference() {
let params = [0.2, -0.1, 0.5]; for (i, &k) in STRIKES.iter().enumerate() {
let price = fourier_call_price(
LevyModelType::VarianceGamma,
¶ms,
100.0,
k,
0.05,
0.0,
1.0,
);
assert!(
(price - VG_REF[i]).abs() < 0.05,
"Vg K={k}: got {price:.6}, expected {:.6}",
VG_REF[i]
);
}
}
#[test]
fn mjd_pricer_matches_reference() {
let params = [0.15, 1.0, -0.05, 0.1]; for (i, &k) in STRIKES.iter().enumerate() {
let price = fourier_call_price(LevyModelType::MertonJD, ¶ms, 100.0, k, 0.05, 0.0, 1.0);
assert!(
(price - MJD_REF[i]).abs() < 0.05,
"MJD K={k}: got {price:.6}, expected {:.6}",
MJD_REF[i]
);
}
}
#[test]
fn vg_calibrate_recovers_reference_prices() {
let market = MarketSlice {
strikes: STRIKES.to_vec(),
prices: VG_REF.to_vec(),
is_call: vec![true; 9],
t: 1.0,
};
let calibrator =
LevyCalibrator::new(LevyModelType::VarianceGamma, 100.0, 0.05, 0.0, vec![market]);
let result = calibrator.calibrate(None).unwrap();
assert!(
result.loss.get(LossMetric::Rmse) < 0.1,
"Vg RMSE={:.6}",
result.loss.get(LossMetric::Rmse)
);
println!("Vg recovered params: {:?}", result.params);
}
#[test]
fn mjd_calibrate_recovers_reference_prices() {
let market = MarketSlice {
strikes: STRIKES.to_vec(),
prices: MJD_REF.to_vec(),
is_call: vec![true; 9],
t: 1.0,
};
let calibrator = LevyCalibrator::new(LevyModelType::MertonJD, 100.0, 0.05, 0.0, vec![market]);
let result = calibrator.calibrate(None).unwrap();
assert!(
result.loss.get(LossMetric::Rmse) < 0.1,
"MJD RMSE={:.6}",
result.loss.get(LossMetric::Rmse)
);
println!("MJD recovered params: {:?}", result.params);
}
#[test]
fn test_levy_vg_calibrate() {
let market = MarketSlice {
strikes: vec![90.0, 95.0, 100.0, 105.0, 110.0],
prices: vec![12.5, 9.0, 6.2, 4.0, 2.3],
is_call: vec![true, true, true, true, true],
t: 0.5,
};
let calibrator = LevyCalibrator::new(
LevyModelType::VarianceGamma,
100.0,
0.03,
0.01,
vec![market],
);
let result = calibrator.calibrate(None).unwrap();
println!("Vg params: {:?}, loss: {:?}", result.params, result.loss);
}
#[test]
fn nig_to_model_matches_calibrator_pricer() {
let params = vec![10.0, -5.0, 0.5];
let s = 100.0;
let r = 0.05;
let q = 0.0;
let t = 1.0;
let result = LevyCalibrationResult {
params: params.clone(),
model_type: LevyModelType::Nig,
iterations: 0,
converged: true,
loss: CalibrationLossScore::default(),
};
let model = result.to_model(r, q);
for &k in &STRIKES {
let from_calibrator = fourier_call_price(LevyModelType::Nig, ¶ms, s, k, r, q, t);
let from_model = <LevyModel as crate::traits::ModelPricer>::price_call(&model, s, k, r, q, t);
assert!(
(from_model - from_calibrator).abs() < 1e-6,
"NIG to_model mismatch at K={k}: model={from_model:.10} calibrator={from_calibrator:.10}",
);
}
}
#[test]
fn test_levy_merton_calibrate() {
let market = MarketSlice {
strikes: vec![90.0, 95.0, 100.0, 105.0, 110.0],
prices: vec![12.5, 9.0, 6.2, 4.0, 2.3],
is_call: vec![true, true, true, true, true],
t: 0.5,
};
let calibrator = LevyCalibrator::new(LevyModelType::MertonJD, 100.0, 0.03, 0.01, vec![market]);
let result = calibrator.calibrate(None).unwrap();
println!(
"Merton params: {:?}, loss: {:?}",
result.params, result.loss
);
}