jmelo11/quantsupport

Quantitative finance library in Rust for derivatives pricing, curve bootstrapping, risk factor simulations, and XVA, all with AD support.

24

stars

624

commits

Rust

primary language

Sep 15, 2026

updated

jmelo11.github.io/quantsupport/
automatic-differentiation
credit-risk
derivatives-pricing
equity
fx
interest-rates
quantitative-finance
risk-management
xva

README

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.

A crate is available for usage in your project:

cargo add quantsupport

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.

Architecture and components

QuantSupport separates market observables, calibrated market objects, product definitions, and valuation engines. This keeps the same market construction and risk machinery reusable across deterministic pricing, Monte Carlo, scripting, and XVA.

flowchart LR
    A[Quotes, fixings, and FX] --> B[PricingContext]
    C[Curve, volatility, and model configuration] --> B
    B --> D[Curves, surfaces, cubes, and simulations]
    E[Native trade or scripted payoff] --> F[Pricing engine]
    D --> F
    F --> G[NPV, cashflows, fair values, and sensitivities]
    E --> H[Exposure and XVA engine]
    D --> H
    H --> I[EPE, ENE, CVA, DVA, and FVA]

Market inputs and conventions

QuoteStore, FixingStore, and FxStore contain observable data: instrument quotes, historical index fixings, and spot FX rates. Dates, calendars, schedules, day-count rules, compounding, currencies, and indices provide the conventions used to interpret those observations and build product cashflows.

Scenarios also operate at this input layer. A shocked valuation rebuilds the dependent market rather than modifying an already-built curve, so curves, volatility objects, and simulations remain consistent with one another.

Market construction

PricingContext is the boundary between raw inputs and objects that can be used for valuation. It combines the stores with serializable configuration and builds the dependency graph in order:

  1. discount and forwarding curves, including multi-curve and collateralized FX dependencies;
  2. credit curves bootstrapped from CDS quotes;
  3. volatility surfaces and cubes;
  4. calibrated model configurations and Monte Carlo simulations.

The resulting ConstructedElementStore is shared by all downstream engines. Pricers request only the curves, fixings, FX rates, volatility objects, or paths needed by a particular trade. See the architecture and pricing context chapters for the detailed object model.

For example, a production context can be assembled from JSON-backed stores and configuration, then initialized once before pricing a portfolio:

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_volatility_surface_configurations(surface_configs)
    .with_simulation_configurations(simulation_configs);

context.initialize()?;

Attaching a scenario uses the same construction path. Here every quote whose identifier contains the SOFR segment is shifted up by one basis point before the dependent market is rebuilt:

let mut shocked_context = PricingContext::new()
    .with_quote_store(quotes)
    .with_fixing_store(fixings)
    .with_curve_configurations(curve_configs)
    .with_scenarios(vec![Scenario::new(
        "SOFR",
        0.0001,
        ScenarioType::Absolute,
    )]);

shocked_context.initialize()?;

See examples/bootstrap for configuration loading and multi-curve construction, and examples/sensitivity for quote-level risk across dependent curves.

Products: native instruments and scripts

There are two ways to represent a product:

  • Native instruments model standard products such as bonds, swaps, caps, swaptions, equity and FX options, cross-currency swaps, futures, and CDSs. An instrument defines the economics and cashflows; a trade adds ownership information such as notional, side, and trade date.
  • Scripted products describe bespoke payoffs as dated events that observe rates, discount factors, FX, or equity spots and emit payments. Scripts use the same market models and automatic-differentiation tape as native products, so they produce NPV, expected cashflows, and quote-level sensitivities without requiring a new Rust instrument or pricer.

Native products are the preferred path when a standard cashflow or closed-form model exists. Scripting is intended for structured coupons, digitals, range accruals, autocallables, and products whose terms change more quickly than the library API. The scripting guide documents the language and runtime.

A scripted product is a dated financial event stream. For example, the following event observes SOFR for one accrual period and adds the discounted coupon to the note variable, which would represent the value of the product:

let events = vec![CodedEvent::new(
    Date::new(2026, 1, 2),
    r#"
        accrual = cvg("2026-01-02", "2026-04-02", "Actual360");
        coupon = RateIndex("SOFR", "2026-01-02", "2026-04-02");
        note pays 1000000 * coupon * accrual on "2026-04-02" in "USD";
    "#
    .to_string(),
)];

let stream = EventStream::try_from(events)?;
let engine = ScriptEngine::new(
    stream,
    reference_date,
    Currency::USD,
    MarketIndex::SOFR,
)?;
let (values, cashflows) =
    engine.evaluate_with_cashflows(&mut market_model, Some("note"))?;

Interoperability between engines is possible under QuantSupport. In this example, the same EventStream can be wrapped in ScriptedProduct and converted to contingent claims for XVA. examples/scripting compares this path with a native swap for NPV, pillar sensitivities, and exposure.

Pricing and risk

Pricers combine a trade with the market objects supplied by PricingContext. Cashflow products use generic discounting, while options and optional rates products can use Black, Hull-White, LGM, or Monte Carlo engines. Every call to a pricer returns an EvaluationResults object containing the outputs requested by the caller, such as NPV, fair rate, cashflows, or sensitivities.

Automatic differentiation runs through market construction and valuation. Quotes become labelled leaves on the AAD tape, so a reverse sweep maps a result back to the curve or volatility quotes that produced it. This is the common risk mechanism for native pricers, scripted payoffs, and XVA. Full revaluation under quote scenarios complements AAD for stress tests and non-linear moves.

Exposure and XVA

Trades and scripted products can both be converted into contingent claims. A contigent claim in QuantSupport represents a single cashflow inside a product. The exposure engine evaluates those claims across simulated paths and aggregates them by netting set and CSA. The same workflow produces NPV cubes and exposure profiles, then CVA, DVA, and FVA with sensitivities to the original market quotes.

Installation

Add the latest Rust crate release:

cargo add quantsupport

To work from this checkout instead:

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

The main Rust workflows are re-exported through the prelude:

use quantsupport::prelude::*;

Build and test the library with:

cargo build -p quantsupport
cargo test -p quantsupport

Runnable Rust examples

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

ExampleDemonstratesRun
valuationFlat-curve swap NPV, cashflows, and AAD sensitivitycargo run -p valuation
bootstrapJSON quote loading and dependent USD/CLP multi-curve bootstrappingcargo run -p bootstrap
sensitivityMulti-curve pricing of SOFR, Term SOFR, ICP, and cross-currency swaps with pillar DV01cargo run -p sensitivity
volatilitysurfaceBuilding and querying an interpolated SOFR caplet Black-volatility surfacecargo run -p volatilitysurface
hullwhiteCurve construction, caplet-vol calibration, Hull-White pricing, simulation, and plotscargo run -p hullwhite
pfeMulti-currency LGM exposure simulation for swaps, FX products, and cross-currency swapscargo run -p pfe
cvaHigh-level netting-set XVA with CSA, credit/funding inputs, CVA/FVA values, exposure profiles, and AAD sensitivitiescargo run -p cva
scriptingScripted swap vs native swap: NPV and pillar sensitivities through ScriptEnginecargo run -p scripting-examples --bin valuation
scriptingScripted product as XVA contingent claims: EPE and CVA/FVA sensitivities vs native swapcargo run -p scripting-examples --bin xva

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

cargo add quantsupport --features plot

Python bindings

The Python are under development, but a 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/.

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.

Contributors

jmelo11

476 commits

Poss9368

122 commits

Copilot

13 commits

oopscompiled

6 commits

jmelo11/quantsupport

Quantitative finance library in Rust for derivatives pricing, curve bootstrapping, risk factor simulations, and XVA, all with AD support.

24

stars

624

commits

Rust

primary language

Sep 15, 2026

updated

jmelo11.github.io/quantsupport/
automatic-differentiation
credit-risk
derivatives-pricing
equity
fx
interest-rates
quantitative-finance
risk-management
xva

README

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.

A crate is available for usage in your project:

cargo add quantsupport

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.

Architecture and components

QuantSupport separates market observables, calibrated market objects, product definitions, and valuation engines. This keeps the same market construction and risk machinery reusable across deterministic pricing, Monte Carlo, scripting, and XVA.

flowchart LR
    A[Quotes, fixings, and FX] --> B[PricingContext]
    C[Curve, volatility, and model configuration] --> B
    B --> D[Curves, surfaces, cubes, and simulations]
    E[Native trade or scripted payoff] --> F[Pricing engine]
    D --> F
    F --> G[NPV, cashflows, fair values, and sensitivities]
    E --> H[Exposure and XVA engine]
    D --> H
    H --> I[EPE, ENE, CVA, DVA, and FVA]

Market inputs and conventions

QuoteStore, FixingStore, and FxStore contain observable data: instrument quotes, historical index fixings, and spot FX rates. Dates, calendars, schedules, day-count rules, compounding, currencies, and indices provide the conventions used to interpret those observations and build product cashflows.

Scenarios also operate at this input layer. A shocked valuation rebuilds the dependent market rather than modifying an already-built curve, so curves, volatility objects, and simulations remain consistent with one another.

Market construction

PricingContext is the boundary between raw inputs and objects that can be used for valuation. It combines the stores with serializable configuration and builds the dependency graph in order:

  1. discount and forwarding curves, including multi-curve and collateralized FX dependencies;
  2. credit curves bootstrapped from CDS quotes;
  3. volatility surfaces and cubes;
  4. calibrated model configurations and Monte Carlo simulations.

The resulting ConstructedElementStore is shared by all downstream engines. Pricers request only the curves, fixings, FX rates, volatility objects, or paths needed by a particular trade. See the architecture and pricing context chapters for the detailed object model.

For example, a production context can be assembled from JSON-backed stores and configuration, then initialized once before pricing a portfolio:

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_volatility_surface_configurations(surface_configs)
    .with_simulation_configurations(simulation_configs);

context.initialize()?;

Attaching a scenario uses the same construction path. Here every quote whose identifier contains the SOFR segment is shifted up by one basis point before the dependent market is rebuilt:

let mut shocked_context = PricingContext::new()
    .with_quote_store(quotes)
    .with_fixing_store(fixings)
    .with_curve_configurations(curve_configs)
    .with_scenarios(vec![Scenario::new(
        "SOFR",
        0.0001,
        ScenarioType::Absolute,
    )]);

shocked_context.initialize()?;

See examples/bootstrap for configuration loading and multi-curve construction, and examples/sensitivity for quote-level risk across dependent curves.

Products: native instruments and scripts

There are two ways to represent a product:

  • Native instruments model standard products such as bonds, swaps, caps, swaptions, equity and FX options, cross-currency swaps, futures, and CDSs. An instrument defines the economics and cashflows; a trade adds ownership information such as notional, side, and trade date.
  • Scripted products describe bespoke payoffs as dated events that observe rates, discount factors, FX, or equity spots and emit payments. Scripts use the same market models and automatic-differentiation tape as native products, so they produce NPV, expected cashflows, and quote-level sensitivities without requiring a new Rust instrument or pricer.

Native products are the preferred path when a standard cashflow or closed-form model exists. Scripting is intended for structured coupons, digitals, range accruals, autocallables, and products whose terms change more quickly than the library API. The scripting guide documents the language and runtime.

A scripted product is a dated financial event stream. For example, the following event observes SOFR for one accrual period and adds the discounted coupon to the note variable, which would represent the value of the product:

let events = vec![CodedEvent::new(
    Date::new(2026, 1, 2),
    r#"
        accrual = cvg("2026-01-02", "2026-04-02", "Actual360");
        coupon = RateIndex("SOFR", "2026-01-02", "2026-04-02");
        note pays 1000000 * coupon * accrual on "2026-04-02" in "USD";
    "#
    .to_string(),
)];

let stream = EventStream::try_from(events)?;
let engine = ScriptEngine::new(
    stream,
    reference_date,
    Currency::USD,
    MarketIndex::SOFR,
)?;
let (values, cashflows) =
    engine.evaluate_with_cashflows(&mut market_model, Some("note"))?;

Interoperability between engines is possible under QuantSupport. In this example, the same EventStream can be wrapped in ScriptedProduct and converted to contingent claims for XVA. examples/scripting compares this path with a native swap for NPV, pillar sensitivities, and exposure.

Pricing and risk

Pricers combine a trade with the market objects supplied by PricingContext. Cashflow products use generic discounting, while options and optional rates products can use Black, Hull-White, LGM, or Monte Carlo engines. Every call to a pricer returns an EvaluationResults object containing the outputs requested by the caller, such as NPV, fair rate, cashflows, or sensitivities.

Automatic differentiation runs through market construction and valuation. Quotes become labelled leaves on the AAD tape, so a reverse sweep maps a result back to the curve or volatility quotes that produced it. This is the common risk mechanism for native pricers, scripted payoffs, and XVA. Full revaluation under quote scenarios complements AAD for stress tests and non-linear moves.

Exposure and XVA

Trades and scripted products can both be converted into contingent claims. A contigent claim in QuantSupport represents a single cashflow inside a product. The exposure engine evaluates those claims across simulated paths and aggregates them by netting set and CSA. The same workflow produces NPV cubes and exposure profiles, then CVA, DVA, and FVA with sensitivities to the original market quotes.

Installation

Add the latest Rust crate release:

cargo add quantsupport

To work from this checkout instead:

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

The main Rust workflows are re-exported through the prelude:

use quantsupport::prelude::*;

Build and test the library with:

cargo build -p quantsupport
cargo test -p quantsupport

Runnable Rust examples

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

ExampleDemonstratesRun
valuationFlat-curve swap NPV, cashflows, and AAD sensitivitycargo run -p valuation
bootstrapJSON quote loading and dependent USD/CLP multi-curve bootstrappingcargo run -p bootstrap
sensitivityMulti-curve pricing of SOFR, Term SOFR, ICP, and cross-currency swaps with pillar DV01cargo run -p sensitivity
volatilitysurfaceBuilding and querying an interpolated SOFR caplet Black-volatility surfacecargo run -p volatilitysurface
hullwhiteCurve construction, caplet-vol calibration, Hull-White pricing, simulation, and plotscargo run -p hullwhite
pfeMulti-currency LGM exposure simulation for swaps, FX products, and cross-currency swapscargo run -p pfe
cvaHigh-level netting-set XVA with CSA, credit/funding inputs, CVA/FVA values, exposure profiles, and AAD sensitivitiescargo run -p cva
scriptingScripted swap vs native swap: NPV and pillar sensitivities through ScriptEnginecargo run -p scripting-examples --bin valuation
scriptingScripted product as XVA contingent claims: EPE and CVA/FVA sensitivities vs native swapcargo run -p scripting-examples --bin xva

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

cargo add quantsupport --features plot

Python bindings

The Python are under development, but a 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/.

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.

Contributors

jmelo11

476 commits

Poss9368

122 commits

Copilot

13 commits

oopscompiled

6 commits

Languages

Rust

83.5%

Jupyter Notebook

16.5%