libitofin 0.45.0

A ground-up Rust port of QuantLib: quantitative-finance primitives for pricing, risk, and numerical methods.
Documentation
use super::*;
use crate::handle::Handle;
use crate::instrument::Instrument;
use crate::instruments::VarianceSwap;
use crate::pricingengines::forward::test_market::{Market, close, curve, quote_handle, today};
use crate::shared::shared_mut;
use crate::termstructures::volatility::{BlackVarianceCurve, BlackVolTermStructure};
use crate::time::daycounters::actual365fixed::Actual365Fixed;

#[allow(clippy::too_many_arguments)]
fn swap(
    market: &Market,
    process: Shared<GeneralizedBlackScholesProcess>,
    position: Position,
    steps: Option<usize>,
    steps_per_year: Option<usize>,
    samples: Option<usize>,
    tolerance: Option<f64>,
    max_samples: Option<usize>,
    seed: u64,
) -> VarianceSwap {
    let mut result = VarianceSwap::new(
        position,
        0.04,
        50_000.0,
        today(),
        today() + 90,
        market.settings.clone(),
    )
    .unwrap();
    result.base_mut().set_pricing_engine(shared_mut(
        MCVarianceSwapEngine::new(
            process,
            steps,
            steps_per_year,
            samples,
            tolerance,
            max_samples,
            seed,
        )
        .unwrap(),
    ));
    result
}

#[test]
fn native_time_dependent_variance_curve_case() {
    let market = Market::new();
    let volatility = BlackVarianceCurve::new(
        today(),
        &[today() + 36, today() + 90],
        &[0.1, 0.2],
        Actual365Fixed::new(),
        true,
    )
    .unwrap();
    let process = shared(GeneralizedBlackScholesProcess::new(
        quote_handle(&market.spot),
        Handle::new(curve(today(), &market.dividend, Actual365Fixed::new())),
        Handle::new(curve(today(), &market.rate, Actual365Fixed::new())),
        Handle::new(shared(volatility) as Shared<dyn BlackVolTermStructure>),
    ));
    let mut result = swap(
        &market,
        process,
        Position::Long,
        None,
        Some(250),
        Some(1023),
        None,
        None,
        42,
    );
    close(result.variance().unwrap(), 0.04, 3e-4);
    assert_eq!(result.samples().unwrap(), 1023);
    assert!(result.variance_error().unwrap() >= 0.0);
    assert!(result.option_weights().unwrap().is_empty());
}

#[test]
fn flat_zero_short_and_recalculation() {
    let market = Market::new();
    let mut long = swap(
        &market,
        market.process.clone(),
        Position::Long,
        Some(10),
        None,
        Some(1023),
        None,
        None,
        42,
    );
    let mut short = swap(
        &market,
        market.process.clone(),
        Position::Short,
        Some(10),
        None,
        Some(1023),
        None,
        None,
        42,
    );
    close(long.variance().unwrap(), 0.04, 2e-14);
    assert_eq!(
        long.variance().unwrap().to_bits(),
        short.variance().unwrap().to_bits()
    );
    assert_eq!(long.npv().unwrap(), -short.npv().unwrap());
    assert_eq!(
        long.error_estimate().unwrap(),
        -short.error_estimate().unwrap()
    );
    let first = long.variance().unwrap().to_bits();
    long.recalculate().unwrap();
    assert_eq!(long.variance().unwrap().to_bits(), first);
    market.vol.set_value(0.3);
    close(long.variance().unwrap(), 0.09, 2e-14);
    market.vol.set_value(0.0);
    assert_eq!(long.variance().unwrap(), 0.0);
    assert_eq!(long.variance_error().unwrap(), 0.0);
    assert!(long.npv().unwrap() < 0.0);
}

#[test]
fn tolerance_mode_reports_actual_initial_batch() {
    let market = Market::new();
    let mut result = swap(
        &market,
        market.process.clone(),
        Position::Long,
        Some(10),
        None,
        None,
        Some(1e-12),
        Some(1023),
        42,
    );
    close(result.variance().unwrap(), 0.04, 2e-14);
    assert!(result.variance_error().unwrap() <= 1e-12);
    assert_eq!(result.samples().unwrap(), 1023);
}

#[test]
fn invalid_modes_limits_and_seeds_are_rejected() {
    let market = Market::new();
    for (steps, per_year, samples, tolerance, maximum, seed) in [
        (None, None, Some(2), None, None, 42),
        (Some(1), Some(1), Some(2), None, None, 42),
        (Some(0), None, Some(2), None, None, 42),
        (Some(100_001), None, Some(2), None, None, 42),
        (Some(1), None, None, None, None, 42),
        (Some(1), None, Some(2), Some(0.1), None, 42),
        (Some(1), None, Some(1), None, None, 42),
        (Some(1), None, Some(1_000_001), None, None, 42),
        (Some(1), None, Some(3), None, Some(2), 42),
        (Some(1), None, None, Some(0.0), None, 42),
        (Some(1), None, None, Some(f64::NAN), None, 42),
        (Some(1), None, None, Some(0.1), Some(1022), 42),
        (Some(100_000), None, Some(1023), None, None, 42),
        (Some(1), None, Some(2), None, None, u32::MAX as u64 + 1),
    ] {
        assert!(
            MCVarianceSwapEngine::new(
                market.process.clone(),
                steps,
                per_year,
                samples,
                tolerance,
                maximum,
                seed
            )
            .is_err()
        );
    }
}

#[test]
fn failures_do_not_return_old_results_and_expiry_clears_sampling() {
    let market = Market::new();
    let mut result = swap(
        &market,
        market.process.clone(),
        Position::Long,
        Some(10),
        None,
        Some(2),
        None,
        None,
        42,
    );
    assert!(result.variance().is_ok());
    market.spot.set_value(-1.0);
    assert!(result.npv().is_err());
    assert!(result.variance_error().is_err());
    assert!(result.samples().is_err());
    market.spot.set_value(100.0);
    close(result.variance().unwrap(), 0.04, 2e-14);
    market.settings.set_evaluation_date(today() + 1);
    assert!(result.npv().is_err());
    market.settings.set_evaluation_date(today() + 90);
    assert_eq!(result.npv().unwrap(), 0.0);
    assert_eq!(result.error_estimate().unwrap(), 0.0);
    assert!(result.samples().is_err());
    assert!(result.variance_error().is_err());
    assert!(result.option_weights().is_err());
}

#[test]
fn replacement_with_replication_clears_sampling_fields() {
    use crate::pricingengines::ReplicatingVarianceSwapEngine;
    use crate::pricingengines::forward::test_market::{CALLS, PUTS};
    let market = Market::new();
    let mut result = swap(
        &market,
        market.process.clone(),
        Position::Long,
        Some(10),
        None,
        Some(2),
        None,
        None,
        42,
    );
    assert_eq!(result.samples().unwrap(), 2);
    assert!(result.error_estimate().unwrap().is_finite());
    result.base_mut().set_pricing_engine(shared_mut(
        ReplicatingVarianceSwapEngine::new(market.process.clone(), 5.0, CALLS, PUTS).unwrap(),
    ));
    assert!(result.variance().unwrap().is_finite());
    assert!(result.samples().is_err());
    assert!(result.variance_error().is_err());
    assert!(result.error_estimate().is_err());
    assert!(!result.option_weights().unwrap().is_empty());
}

#[test]
fn per_year_grid_truncates_with_minimum_one_and_checks_actual_work() {
    let market = Market::new();
    let mut result = swap(
        &market,
        market.process.clone(),
        Position::Long,
        None,
        Some(1),
        Some(2),
        None,
        None,
        42,
    );
    close(result.variance().unwrap(), 0.04, 2e-14);
    let mut over_work = swap(
        &market,
        market.process.clone(),
        Position::Long,
        None,
        Some(100_000),
        Some(2048),
        None,
        None,
        42,
    );
    assert!(over_work.variance().is_err());
    let mut longer = VarianceSwap::new(
        Position::Long,
        0.04,
        50_000.0,
        today(),
        today() + 730,
        market.settings.clone(),
    )
    .unwrap();
    longer.base_mut().set_pricing_engine(shared_mut(
        MCVarianceSwapEngine::new(
            market.process.clone(),
            None,
            Some(100_000),
            Some(1023),
            None,
            None,
            42,
        )
        .unwrap(),
    ));
    assert!(longer.variance().is_err());
}