pub mod least_squares;
pub mod quasi_explicit;
pub mod ssvi;
use crate::arbitrage::butterfly_scan;
use crate::errors::CalibrationError;
use crate::raw::RawSvi;
use crate::types::Quote;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct CalibrationResult {
pub slice: RawSvi,
pub rmse: f64,
pub butterfly_free: bool,
}
impl CalibrationResult {
#[must_use]
pub fn new(slice: RawSvi, rmse: f64) -> Self {
let butterfly_free = butterfly_scan(&slice, -1.0, 1.0).is_free;
Self {
slice,
rmse,
butterfly_free,
}
}
}
pub fn calibrate_slice(quotes: &[Quote]) -> Result<CalibrationResult, CalibrationError> {
let qe = quasi_explicit::calibrate(quotes)?;
match least_squares::refine(quotes, &qe.slice) {
Ok(lm) if lm.rmse < qe.rmse => Ok(lm),
_ => Ok(qe),
}
}
#[cfg(test)]
mod tests {
use super::*;
fn synthetic(svi: &RawSvi, ks: &[f64]) -> Vec<Quote> {
ks.iter()
.map(|&k| Quote::new(k, svi.total_variance(k), 1.0).unwrap())
.collect()
}
#[test]
fn calibration_result_runs_butterfly_scan() {
let benign = RawSvi::new(0.04, 0.1, -0.2, 0.0, 0.3).unwrap();
assert!(CalibrationResult::new(benign, 1e-6).butterfly_free);
let vogt = RawSvi::new(-0.0410, 0.1331, 0.3060, 0.3586, 0.4153).unwrap();
assert!(!CalibrationResult::new(vogt, 1e-6).butterfly_free);
}
#[test]
fn calibrate_slice_recovers_synthetic() {
let truth = RawSvi::new(0.04, 0.4, -0.3, 0.05, 0.15).unwrap();
let ks = [-0.4, -0.25, -0.1, 0.0, 0.1, 0.25, 0.4];
let quotes = synthetic(&truth, &ks);
let fit = calibrate_slice("es).unwrap();
assert!(fit.rmse < 1e-5, "rmse = {}", fit.rmse);
for &k in &[-0.5, 0.0, 0.5] {
let err = (fit.slice.total_variance(k) - truth.total_variance(k)).abs();
assert!(err < 1e-4, "k = {k}, err = {err}");
}
}
#[test]
fn calibrate_slice_polish_never_worsens() {
let truth = RawSvi::new(0.03, 0.3, 0.0, 0.0, 0.2).unwrap();
let ks = [-0.5, -0.3, -0.1, 0.0, 0.1, 0.3, 0.5];
let quotes = synthetic(&truth, &ks);
let qe = quasi_explicit::calibrate("es).unwrap();
let pipeline = calibrate_slice("es).unwrap();
assert!(pipeline.rmse <= qe.rmse + 1e-12);
}
#[test]
fn calibrate_slice_propagates_errors() {
assert!(matches!(
calibrate_slice(&[]),
Err(CalibrationError::EmptyQuotes)
));
}
}