libitofin 0.51.0

A ground-up Rust port of QuantLib: quantitative-finance primitives for pricing, risk, and numerical methods.
Documentation
use super::*;

#[test]
fn independent_quantlib_price_and_greek_fixtures() {
    let cases = [
        (
            OptionType::Call,
            1.0,
            -0.1,
            0.3,
            360,
            [
                14.586956184779012,
                0.6175760317781934,
                0.012393297925041149,
                -8.239240639178727,
                24.78659585008232,
                47.17064699304032,
                -61.75760317781934,
            ],
        ),
        (
            OptionType::Put,
            1.0,
            -0.1,
            0.3,
            360,
            [
                11.690031304174994,
                -0.3626226415285577,
                0.012393297925041173,
                -5.443490863290569,
                24.78659585008237,
                -47.95229545703078,
                36.26226415285577,
            ],
        ),
        (
            OptionType::Call,
            2.5,
            0.2,
            0.3,
            360,
            [
                26.799237829294686,
                0.5881096362288443,
                0.005316638728967413,
                -12.993719078787757,
                10.633277457934824,
                32.01172579358976,
                -58.810963622884444,
            ],
        ),
        (
            OptionType::Put,
            5.0,
            -0.2,
            0.01,
            1800,
            [
                27.894319309407567,
                -0.22001528452903665,
                0.0027058771720590165,
                -0.969877184352032,
                27.05877172059017,
                -249.47923881155612,
                110.0076422645183,
            ],
        ),
        (
            OptionType::Call,
            0.0,
            -0.1,
            0.3,
            360,
            [
                9.227005508154061,
                0.5868511461347649,
                0.01895057875500871,
                -5.089318913998339,
                37.90115751001742,
                49.45810910532239,
                -58.68511461347645,
            ],
        ),
        (
            OptionType::Put,
            0.0,
            -0.1,
            0.3,
            360,
            [
                6.330080627549918,
                -0.39334752717199073,
                0.01895057875500871,
                -2.2935691381082712,
                37.90115751001742,
                -45.664833344749,
                39.33475271719908,
            ],
        ),
        (
            OptionType::Call,
            2.0,
            0.08,
            0.0,
            360,
            [
                10.375222262501174,
                0.5797189785696198,
                0.01656025464287714,
                -5.613586080223574,
                33.12050928575426,
                47.59667559446084,
                -57.97189785696202,
            ],
        ),
        (
            OptionType::Call,
            8.0,
            0.0,
            0.0,
            360,
            [
                9.227005508153308,
                0.586851146134717,
                0.01895057875500715,
                -5.089318913976321,
                37.90115751001434,
                49.458109105318364,
                -58.68511461347167,
            ],
        ),
        (
            OptionType::Call,
            800.0,
            0.0,
            0.02,
            360,
            [
                24.248655648833182,
                0.6241657093580795,
                0.006132118812473648,
                -11.696711504971956,
                12.264237624947306,
                38.167915286974676,
                -62.41657093580786,
            ],
        ),
        (
            OptionType::Put,
            800.0,
            0.0,
            0.02,
            360,
            [
                21.351730768751178,
                -0.35603296377505017,
                0.006132118812473648,
                -8.900961731202003,
                12.264237624947306,
                -56.955027146256214,
                35.60329637750505,
            ],
        ),
    ];
    for (kind, intensity, mean, jump_vol, days, expected) in cases {
        let m = JumpMarket::new(intensity, mean, jump_vol);
        let mut option = m.market.option(kind, 100.0, today() + days);
        option
            .base_mut()
            .set_pricing_engine(shared_mut(
                JumpDiffusionEngine::new(Shared::clone(&m.process), 1e-12, 4096).unwrap(),
            ) as crate::shared::SharedMut<dyn PricingEngine>);
        for (actual, expected) in values(&mut option).into_iter().zip(expected) {
            close(actual, expected, 1e-8);
        }
    }
}

#[test]
fn different_reference_dates_and_day_counters_match_quantlib() {
    use crate::interestrate::Compounding;
    use crate::processes::GeneralizedBlackScholesProcess;
    use crate::termstructures::volatility::BlackConstantVol;
    use crate::termstructures::yields::FlatForward;
    use crate::time::daycounters::{actual360::Actual360, actual365fixed::Actual365Fixed};
    use crate::time::frequency::Frequency;
    let m = JumpMarket::new(1.0, -0.1, 0.3);
    let rate = shared(FlatForward::new(
        today(),
        quote_handle(&m.market.r_rate),
        Actual365Fixed::new(),
        Compounding::Continuous,
        Frequency::Annual,
    ));
    let dividend = shared(FlatForward::new(
        today(),
        quote_handle(&m.market.q_rate),
        Actual365Fixed::new(),
        Compounding::Continuous,
        Frequency::Annual,
    ));
    let vol = shared(BlackConstantVol::with_quote(
        today() - 30,
        None,
        quote_handle(&m.market.vol),
        Actual360::new(),
    ));
    let black = shared(GeneralizedBlackScholesProcess::new(
        quote_handle(&m.market.spot),
        Handle::new(dividend),
        Handle::new(rate),
        Handle::new(vol),
    ));
    let process = shared(
        Merton76Process::from_black_scholes(
            black,
            quote_handle(&m.intensity),
            quote_handle(&m.mean),
            quote_handle(&m.jump_vol),
        )
        .unwrap(),
    );
    let mut option = m.market.option(OptionType::Call, 100.0, today() + 360);
    option.base_mut().set_pricing_engine(shared_mut(
        JumpDiffusionEngine::new(process, 1e-12, 4096).unwrap(),
    ) as crate::shared::SharedMut<dyn PricingEngine>);
    let expected = [
        14.430782838565015,
        0.6108593616328124,
        0.012360216923478787,
        -8.011973418209974,
        24.72043384695758,
        46.65515332471622,
        -60.2491425172089,
    ];
    for (actual, expected) in values(&mut option).into_iter().zip(expected) {
        close(actual, expected, 1e-8);
    }
}

#[test]
fn zero_diffusion_greeks_match_independent_price_derivatives() {
    for (intensity, expected) in [
        (
            1.0,
            [
                11.711311771578648,
                0.727923385342851,
                0.006742450870215559,
                -7.938350512148121,
                0.0,
                61.0810267626638,
                -72.79233853430877,
            ],
        ),
        (
            0.0,
            [
                2.896924880604118,
                0.9801986733075789,
                0.0,
                -2.7957497758990253,
                0.0,
                95.12294245003734,
                -98.01986733063946,
            ],
        ),
    ] {
        let m = JumpMarket::new(intensity, -0.1, 0.3);
        m.market.vol.set_value(0.0);
        for (actual, expected) in values(&mut m.option(OptionType::Call, 100.0, 1e-12, 4096))
            .into_iter()
            .zip(expected)
        {
            close(actual, expected, 1e-6);
        }
    }
}

#[test]
fn engine_retains_market_inputs_after_source_owners_drop() {
    let m = JumpMarket::new(1.0, -0.1, 0.3);
    let mut option = m.option(OptionType::Call, 100.0, 1e-12, 4096);
    let quote = Shared::clone(&m.intensity);
    let initial = option.npv().unwrap();
    drop(m);
    close(option.npv().unwrap(), initial, 0.0);
    quote.set_value(2.0);
    assert!((option.npv().unwrap() - initial).abs() > 1e-3);
}

#[test]
fn rare_large_jumps_do_not_stop_at_zero_early_payoffs() {
    for diffusion in [0.0, 0.001] {
        let m = JumpMarket::new(0.01, 1.0, 0.0);
        m.market.vol.set_value(diffusion);
        let mut option = m.option(OptionType::Call, 1000.0, 1e-12, 1000);
        let expected = [
            0.00016415889568634478,
            0.000003215128392864,
            0.0,
            -0.0004885479016182441,
            0.0,
            0.0001573539436001241,
            -0.0003215128392863766,
        ];
        for (actual, expected) in values(&mut option).into_iter().zip(expected) {
            close(actual, expected, 1e-10);
        }
        assert!(option.npv().unwrap() > 0.0);
    }
}