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
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
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.
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]
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.
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:
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.
There are two ways to represent a product:
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.
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.
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.
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
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:
cargo add quantsupport --features plot
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.
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/.
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.
QuantSupport is released under the MIT License.
For business inquiries, contact jmelo@live.cl.
Rust
83.5%
Jupyter Notebook
16.5%
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
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
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.
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]
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.
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:
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.
There are two ways to represent a product:
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.
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.
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.
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
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:
cargo add quantsupport --features plot
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.
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/.
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.
QuantSupport is released under the MIT License.
For business inquiries, contact jmelo@live.cl.
Rust
83.5%
Jupyter Notebook
16.5%