quantsupport 0.1.6

Rust quantitative finance library for derivatives pricing, yield-curve bootstrapping, AAD risk, Monte Carlo exposure, and XVA.
Documentation

QuantSupport

QuantSupport is a quantitative-finance library written in Rust, with Python bindings provided in the same repository. It combines instrument construction, market-data bootstrapping, pricing, automatic differentiation, payoff scripting, Monte Carlo exposure simulation, and XVA in one toolkit.

Quick start: price and risk a swap

This complete example values a five-year receive-fixed USD swap against a flat SOFR curve and asks for NPV, par rate, cashflows, and curve sensitivity.

use std::{cell::RefCell, rc::Rc};

use quantsupport::prelude::*;

fn main() -> Result<()> {
    let valuation_date = Date::new(2024, 1, 15);
    let maturity_date = Date::new(2029, 1, 15);
    let notional = 10_000_000.0;

    let swap = MakeSwap::<DualFwd>::default()
        .with_identifier("USD_IRS_5Y".to_string())
        .with_start_date(valuation_date)
        .with_maturity_date(maturity_date)
        .with_fixed_rate(0.03)
        .with_notional(notional)
        .with_rate_definition(RateDefinition::new(
            DayCounter::Actual360,
            Compounding::Simple,
            Frequency::Semiannual,
        ))
        .with_currency(Currency::USD)
        .with_market_index(MarketIndex::SOFR)
        .with_side(Side::LongReceive)
        .with_fixed_leg_frequency(Frequency::Semiannual)
        .with_floating_leg_frequency(Frequency::Semiannual)
        .build()?;
    let trade = SwapTrade::new(swap, valuation_date, notional, Side::LongReceive);

    let curve = FlatForwardTermStructure::new(
        valuation_date,
        DualFwd::from(0.03),
        RateDefinition::new(
            DayCounter::Actual360,
            Compounding::Continuous,
            Frequency::Annual,
        ),
    )
    .with_pillar_label("SOFR_flat".to_string());

    let mut elements = ConstructedElementStore::default();
    elements.discount_curves_mut().insert(
        MarketIndex::SOFR,
        DiscountCurveElement::new(MarketIndex::SOFR, Rc::new(RefCell::new(curve))),
    );

    let context = PricingContext::new()
        .with_quote_store(QuoteStore::new(valuation_date))
        .with_fixing_store(FixingStore::default())
        .with_base_currency(Currency::USD)
        .with_constructed_elements(elements);

    let pricer = DiscountedCashflowPricer::<Swap<DualFwd>, SwapTrade<DualFwd>>::new();
    let requests = [
        Request::Value,
        Request::FairRate,
        Request::Cashflows,
        Request::Sensitivities,
    ];
    let results = pricer.evaluate(&trade, &requests, &context)?;

    println!("NPV: {:.2}", results.price().unwrap_or_default());
    println!(
        "Par rate: {:.6}",
        results.fair_rate().unwrap_or_default()
    );

    if let Some(risk) = results.sensitivities() {
        for (pillar, exposure) in risk.instrument_keys().iter().zip(risk.exposure()) {
            println!("dPV/dQuote {pillar}: {exposure:.4}");
        }
    }

    if let Some(cashflows) = results.cashflows() {
        println!("Cashflows: {}", cashflows.payment_dates().len());
    }

    Ok(())
}

The same program, with a more detailed cashflow report, is available in examples/valuation.

Capabilities

Area Current support
Instruments Fixed-rate deposits and bonds, floating-rate notes, rate futures, swaps, basis swaps, caps/floors, caplets/floorlets, European swaptions, fixed/float and float/float cross-currency swaps, equity forwards and European options, FX forwards and options, futures, and credit default swaps
Pricing Generic discounted-cashflow pricing; Black equity, FX, caplet, and cap/floor pricing; Monte Carlo equity option pricing; Hull-White caplet, cap/floor, and European swaption pricing; rate-futures and CDS pricing
Results and risk NPV, fair rate, cashflow tables, and quote-pillar sensitivities through automatic differentiation; type-erased pricer dispatch through Evaluator
Curves Flat and interpolated term structures, multi-curve bootstrapping, cross-curve dependencies, FX-implied collateral curves, and CDS-based survival-curve bootstrapping
Volatility Interpolated volatility surfaces and cubes, Black and normal volatility conventions, FX surface orientation, and constant, surface-, cube-, or calibration-driven volatility sources
Models and simulation Brownian motion, Hull-White, and LGM models; Hull-White/LGM volatility calibration; seeded Monte Carlo path generation from serializable configurations
Exposure and XVA Contingent-claim decomposition, fixing preprocessing, claim compression, netting sets, CSA terms, NPV cubes, EPE/ENE/EE, CVA, DVA, FVA, and parallel AAD sensitivities
Scripting Payoff scripting language (assignments, if/else, for, pays, RateIndex, Df, Spot, cvg, fif, arrays); dated event streams; single-tape and Rayon-parallel Monte Carlo evaluation with AAD sensitivities and expected cashflows; smoothed conditionals for digital payoffs; scripted products as XVA contingent claims
Market data Quote, fixing, and FX stores; bid/mid/ask selection; absolute and relative quote scenarios that rebuild dependent curves, volatility objects, and simulations
Conventions and numerics Dates, periods, schedules, IMM dates, calendars, business-day conventions, day counts, compounding, interpolation, root solvers, FFT, and probability utilities
Languages Native Rust API and PyO3-based Python bindings with pandas result tables

The Rust prelude re-exports the types used by the main workflows:

use quantsupport::prelude::*;

Installation

Add the Rust crate to Cargo.toml:

[dependencies]
quantsupport = "0.1.6"

To work from this checkout instead:

[dependencies]
quantsupport = { path = "../quantsupport" }

Build and test the Rust library with:

cargo build -p quantsupport
cargo test -p quantsupport

Configuration-driven market setup

PricingContext::initialize builds the requested market objects in dependency order: scenario-shocked quotes, discount curves, credit curves, volatility surfaces, volatility cubes, then model-driven simulations. All configuration types support Serde, so production inputs can live in JSON rather than application code.

// `quotes`, `fixings`, `fx`, and the configuration vectors can be
// deserialized from the JSON schemas used under examples/*/data/.
let mut context = PricingContext::new()
    .with_quote_store(quotes)
    .with_fixing_store(fixings)
    .with_fx_store(fx)
    .with_base_currency(Currency::USD)
    .with_base_index(MarketIndex::SOFR)
    .with_curve_configurations(curve_configs)
    .with_credit_curve_configurations(credit_curve_configs)
    .with_volatility_surface_configurations(surface_configs)
    .with_volatility_cube_configurations(cube_configs)
    .with_simulation_configurations(simulation_configs);

context.initialize()?;

let market = context.constructed_elements();
let sofr_curve = market
    .discount_curve(&MarketIndex::SOFR)
    .expect("SOFR was configured");
let five_year_df = sofr_curve
    .curve()
    .discount_factor(context.evaluation_date() + Period::from_str("5Y")?)?;
println!("SOFR 5Y discount factor: {:.8}", five_year_df.value());

For a complete configuration-loading implementation, see examples/bootstrap.

Scenario analysis

A scenario can target one exact quote identifier or match identifier segments such as SOFR, OIS_USD_SOFR, or Swaption_USD. Absolute shocks are added to quote values; relative shocks multiply them by 1 + shock.

use std::str::FromStr;

use quantsupport::prelude::*;

fn main() -> Result<()> {
    let mut quotes = QuoteStore::new(Date::new(2025, 11, 11));
    let details = QuoteDetails::from_str("OIS_USD_SOFR_1Y")?;
    quotes.add_quote(Quote::new(details, QuoteLevels::with_mid(0.04)));

    // Add 100 basis points to every quote with a SOFR identifier segment.
    let scenario = Scenario::new("SOFR", 0.01, ScenarioType::Absolute);
    let shocked_quotes = scenario.apply(&mut quotes)?;

    let shocked_mid = quotes
        .quote("OIS_USD_SOFR_1Y")
        .and_then(|quote| quote.levels().mid())
        .unwrap_or_default();
    println!(
        "Shocked {shocked_quotes} quote(s); new 1Y OIS rate: {:.2}%",
        shocked_mid * 100.0
    );

    Ok(())
}

Attach scenarios with .with_scenarios(...) before PricingContext::initialize() to rebuild the full market consistently from shocked inputs.

Scripting

Bespoke payoffs can be described as dated scripts instead of new Rust instruments. A script is a list of CodedEvents (date + source); the ScriptEngine parses and indexes them once, derives the discount factors, forward rates, FX rates, and spots it needs from the market model, and evaluates every Monte Carlo path in DualFwd, so NPV, pillar sensitivities, and expected cashflows come out of the same run.

The language supports =/+=/-=/*=//=, arithmetic (+ - * / **), comparisons combined with and/or/not, if { } else { }, for x in range(a, b) { }, arrays ([..], .append, .mean, .std, indexing), exp, ln, pow, min, max, cvg(start, end, day_counter), the smoothed indicator fif(x, a, b, eps), market observations RateIndex("SOFR", start, end), Df(date[, curve]), Spot("AAPL") / Spot("USD", "CLP"), and payments acc pays amount on "date" in "CCY";. Conditionals are evaluated with scale-aware smoothing so digital payoffs keep finite AAD sensitivities.

use quantsupport::prelude::*;

// One event per accrual period: observe SOFR on the start date, pay the net coupon at the end.
let events: Vec<CodedEvent> = periods
    .iter()
    .enumerate()
    .map(|(i, (start, end))| {
        let init = if i == 0 { "swap = 0; fixed_rate = 0.035;" } else { "" };
        CodedEvent::new(*start, format!(r#"
            {init}
            accrual = cvg("{start}", "{end}", "Actual360");
            floating_rate = RateIndex("SOFR", "{start}", "{end}");
            swap pays 10000000 * (fixed_rate - floating_rate) * accrual on "{end}";
        "#))
    })
    .collect();

let engine = ScriptEngine::new(EventStream::try_from(events)?, ref_date, Currency::USD, MarketIndex::SOFR)?;

// Any MarketModel<DualFwd> works; here an LGM model whose curve pillars are on the AD tape.
let (values, cashflows) = engine.evaluate_with_cashflows(&mut lgm_model, Some("swap"))?;
println!("NPV = {}", values["swap"]);          // pillar.adjoint() now holds dNPV/dPillar
for cf in cashflows {
    println!("{} {} amount={:.2} pv={:.2}", cf.date, cf.currency, cf.amount, cf.present_value);
}

// Multi-threaded evaluation rebuilds the model per Rayon worker through `ScriptModelSetup`
// and returns values, labelled sensitivities, and cashflows.
let parallel: ParallelScriptEvaluation = engine.evaluate_parallel(&setup, Some("swap"))?;

// The same script enters the XVA engine as ordinary contingent claims.
let claims = ScriptedProduct::new("note", EventStream::try_from(events)?, ref_date, Currency::USD, MarketIndex::SOFR)?
    .contingent_claims()?;

examples/scripting prices a swap both natively and as a script and checks that NPV, pillar sensitivities, EPE, and CVA/FVA sensitivities agree. The Scripting part of the book documents the full language and runtime.

Runnable Rust examples

All examples below are workspace packages and use local JSON market data where appropriate.

Example Demonstrates Run
valuation Flat-curve swap NPV, cashflows, and AAD sensitivity cargo run -p valuation
bootstrap JSON quote loading and dependent USD/CLP multi-curve bootstrapping cargo run -p bootstrap
sensitivity Multi-curve pricing of SOFR, Term SOFR, ICP, and cross-currency swaps with pillar DV01 cargo run -p sensitivity
volatilitysurface Building and querying an interpolated SOFR caplet Black-volatility surface cargo run -p volatilitysurface
hullwhite Curve construction, caplet-vol calibration, Hull-White pricing, simulation, and plots cargo run -p hullwhite
pfe Multi-currency LGM exposure simulation for swaps, FX products, and cross-currency swaps cargo run -p pfe
cva High-level netting-set XVA with CSA, credit/funding inputs, CVA/FVA values, exposure profiles, and AAD sensitivities cargo run -p cva
scripting Scripted swap vs native swap: NPV and pillar sensitivities through ScriptEngine cargo run -p scripting-examples --bin valuation
scripting Scripted product as XVA contingent claims: EPE and CVA/FVA sensitivities vs native swap cargo run -p scripting-examples --bin xva

The plot Cargo feature enables the library's plotting helpers:

quantsupport = { version = "0.1.4", features = ["plot"] }

Python bindings

The Python package exposes typed dates and enums, market-data/configuration objects, curve/volatility/simulation exploration, the supported trade specifications, pricing results as pandas tables, quote scenarios, and the high-level XVA workflow.

Build it into the active virtual environment from the repository root:

python -m pip install maturin
maturin develop -m bindings/python/Cargo.toml --release

Minimal usage:

import quantsupport as qs

quotes = qs.QuoteStore.from_json("quotes.json")
curves = qs.CurveConfiguration.from_json("curve_specs.json")
discounting = qs.DiscountingConfig(
    currency=qs.Currency.USD,
    index=qs.MarketIndex.SOFR,
)

with qs.PricingContext(
    quotes=quotes,
    curves=curves,
    discounting=discounting,
) as context:
    sofr = context.curve(qs.MarketIndex.SOFR)
    print(sofr.nodes())
    print(sofr.discount_factor(quotes.reference_date + "5Y"))

See the Python README and guided notebook for pricing and XVA examples.

Book

The QuantSupport Book covers installation, market construction, pricing, risk, scripting, simulation, and XVA. It is published to GitHub Pages on every push to main; the sources live under book/src. To build it locally, install mdBook, then from the repository root:

mdbook build
mdbook serve --open

Generated HTML is written to book/html/.

Current limitations

  • The project is still in alpha and does not promise API or serialized-configuration stability yet.
  • The high-level XVA FX model currently accepts constant FX volatility; sourcing FX volatility directly from a constructed surface remains on the roadmap.
  • Instrument definitions and standalone pricing coverage are not yet one-to-one; check the pricing capability list above for the currently available public pricers.

Contributing

Contributions are welcome. For small fixes, feel free to open a pull request directly. For larger changes or design discussions, please open an issue first.

License

QuantSupport is released under the MIT License.

Contact

For business inquiries, contact jmelo@live.cl.