stochastic-rs-quant 2.5.0

Quantitative finance: pricing, calibration, vol surfaces, instruments.
Documentation
use super::*;
use crate::traits::Calibrator;

#[test]
fn test_empirical_wasserstein_1_matches_simple_case() {
  let a = vec![1.0, 2.0, 3.0];
  let b = vec![2.0, 3.0, 4.0];
  let w1 = empirical_wasserstein_1(&a, &b);
  assert!((w1 - 1.0).abs() < 1e-12);
}

#[test]
fn test_rbergomi_calibration_reduces_loss_on_synthetic_data() {
  let true_params = RBergomiParams {
    hurst: 0.12,
    rho: -0.72,
    eta: 1.6,
    xi0: RBergomiXi0::Constant(0.04),
  };

  let maturities = [0.25, 0.5, 1.0];
  let mut market_slices = Vec::with_capacity(maturities.len());
  for (i, &t) in maturities.iter().enumerate() {
    let market_samples = simulate_rbergomi_terminal_samples(
      &true_params,
      100.0,
      0.01,
      0.0,
      t,
      512,
      96,
      12,
      7_777 + i as u64,
    );
    market_slices.push(RBergomiMarketSlice {
      maturity: t,
      terminal_samples: market_samples,
    });
  }

  let init_params = RBergomiParams {
    hurst: 0.30,
    rho: -0.10,
    eta: 0.80,
    xi0: RBergomiXi0::Constant(0.02),
  };

  let config = RBergomiCalibrationConfig {
    paths: 512,
    steps_per_year: 96,
    msoe_terms: 12,
    max_iters: 12,
    learning_rate: 0.08,
    finite_diff_eps: 5e-3,
    adam_beta1: 0.9,
    adam_beta2: 0.99,
    adam_eps: 1e-8,
    random_seed: 123_456,
    stop_loss: None,
    improvement_tol: 1e-5,
  };

  let calibrator = RBergomiCalibrator::new(
    100.0,
    0.01,
    init_params.clone(),
    market_slices,
    config,
    true,
  )
  .expect("RBergomi calibrator construction must succeed in test");

  let result = calibrator.calibrate(None).unwrap();

  println!(
    "rBergomi calibration: initial_loss={:.6}, final_loss={:.6}, iterations={}",
    result.initial_loss, result.final_loss, result.iterations
  );
  println!("initial params: {:?}", result.initial_params);
  println!("estimated params: {:?}", result.calibrated_params);
  println!("per-maturity W1: {:?}", result.maturity_losses);

  assert!(result.final_loss <= result.initial_loss);
  assert!(result.calibrated_params.hurst > 0.0 && result.calibrated_params.hurst < 0.5);
  assert!(result.calibrated_params.rho.abs() < 1.0);
  assert!(result.calibrated_params.eta > 0.0);

  if let RBergomiXi0::Constant(xi0_hat) = result.calibrated_params.xi0 {
    assert!(xi0_hat > 0.0);
  } else {
    panic!("Expected constant xi0 in this test");
  }
}