QuantSupport (QS) is a quantitative finance library written in Rust, with Python and Excel bindings. It supports valuation across several asset classes, market sensitivities using automatic differentiation, interest-rate and credit curve construction, volatility markets, Monte Carlo simulation and XVA.
Observable quotes and fixings are stored separately from constructed market objects, such as bootstrapped curves and implied-volatility surfaces. Instruments define contractual terms, and trades add the position held. Pricers combine those trades with the market data needed for valuation.
Add the Rust crate to your project with:
cargo add quantsupport- Python: typed instruments, market data, pricing, and risk analytics. See the Python README for installation, build instructions, examples, and the API reference.
- Excel: a native XLL for 64-bit Windows Excel. See the Excel README for downloads, installation, build instructions, and the worksheet API reference.
| Reading purpose | Entry point |
|---|---|
| Financial concepts, architecture, conventions, and all three interfaces | QuantSupport Book |
| Exact native Rust signatures, errors, and numerical contracts | Rust API reference |
| Independently usable binding guides and declarations | Python documentation and Excel documentation |
| Executable quantitative-finance studies | Notebook collection |
Consider a five-year USD swap that receives a fixed coupon of 3% and pays
a floating coupon every six months on USD 10 million. We construct it with
MakeSwap, create a trade and supply a flat curve for projection and
discounting. The pricer returns NPV, par coupon, cashflows and sensitivities.
The fixed coupon uses simple ACT/360 accrual, while the 3% curve is continuously
compounded. The floating leg uses one SOFR-indexed observation per coupon
period. For daily compounded overnight coupons, use MakeOvernightIndexSwap.
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(0),
Request::Cashflows,
Request::Sensitivities,
];
let results = pricer.evaluate(&trade, &requests, &context)?;
let npv = results.price()
.ok_or_else(|| QSError::ValueNotSetErr("swap NPV".into()))?;
let par_rate = results.fair_rate()
.ok_or_else(|| QSError::ValueNotSetErr("swap par rate".into()))?;
println!("NPV (USD): {npv:.2}");
println!("Par coupon (decimal): {par_rate:.6}");
if let Some(risk) = results.sensitivities() {
for (pillar, exposure) in risk.instrument_keys().iter().zip(risk.exposure()) {
println!("dPV/dPillar {pillar}: {exposure:.4}");
println!("Approximate USD P&L for +1 bp: {:.2}", exposure * 1e-4);
}
}
if let Some(cashflows) = results.cashflows() {
println!("Cashflows: {}", cashflows.payment_dates().len());
}
Ok(())
}A related program with a detailed cashflow report is available in
examples/valuation/swaps.
The NPV is approximately -10,723.59 USD and the flat-rate derivative is -47,405,484.05 USD per unit decimal rate, corresponding to about -4,740.55 USD for a one-basis-point increase. The continuous curve implies simple floating coupons slightly above the agreed fixed coupon, giving the receive-fixed position its small negative value. Sensitivity is measured against the flat rate supplied to the curve. The cashflow table contains unsigned, undiscounted amounts; apply direction and discounting to reconcile it to NPV.
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, 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 affected market objects from changed inputs, preserving their construction dependencies.
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:
- discount and forwarding curves, including multi-curve and collateralized FX dependencies;
- credit curves bootstrapped from CDS quotes;
- volatility surfaces and cubes;
- 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. The market lifecycle
chapter develops the distinction between observations, construction, and
valuation dependencies.
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()?;A scenario borrows the context and builds an independent market view. Here SOFR quote inputs rise by one basis point. Pricing requests construct the required curves and their dependencies; the base market stays available:
let scenario = Scenario::new(vec![
Shock::new("SOFR", 0.0001, ScenarioType::Absolute),
]);
let shocked_market = scenario.apply(&context)?;
let stressed = pricer.evaluate(&trade, &[Request::Value], &shocked_market)?;
let base = pricer.evaluate(&trade, &[Request::Value], &context)?;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 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 language and model-backed runtime chapters explain this route.
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#"
note = 0;
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"))?;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. A successful
valuation returns an EvaluationResults object. Its optional fields contain
the measures requested and supported by that engine; an absent measure is not
a zero value.
Automatic differentiation runs through market construction and valuation. Market pillars become labelled leaves on the AAD tape, and calibrated curves reconnect first-order risk through numerical calibration Jacobians. Reported derivatives apply to the inputs actually differentiated by each engine; stored numerical simulations hold path-generation inputs fixed and therefore omit their model and volatility dependencies. 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.
Supported trades and scripted products can be converted into contingent claims, each representing a payment. The exposure engine evaluates them across simulated paths and aggregates them by netting set and CSA. The high-level XVA engine produces pre-margin NPV cubes, pointwise exposure profiles, and CVA/FVA from net-of-margin exposures when variation margin is enabled. DVA is available through the lower-level aggregator interface. FVA uses signed exposure; ENE profiles are likewise signed, not absolute loss magnitudes.
Add the latest Rust crate release:
cargo add quantsupportTo 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 quantsupportAll examples below are workspace packages and use local JSON market data where appropriate.
| Example | Demonstrates | Run |
|---|---|---|
valuation/swaps |
Flat-curve swap NPV, cashflows, and AAD sensitivity | cargo run -p valuation-swaps |
valuation/bonds |
Callable mixed stepped-coupon bond priced by tree and backward Fourier evolution | cargo run -p valuation-bonds |
valuation/caps |
Black caplet and cap-strip valuation with curve and volatility quote risk | cargo run -p valuation-caps |
valuation/loans |
Fixed contractual principal, reset requests, prepayment functions, fair-rate modes, credit and quote risk | cargo run -p valuation-loans |
bootstrap |
JSON quote loading, USD/CLP multi-curve bootstrapping, and a corporate bond curve | 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 |
scripting |
Native callable stepped-coupon bond checked against an equivalent deterministic script | cargo run -p scripting-examples --bin stepped-coupon-bond |
The plot Cargo feature enables the library's plotting helpers:
cargo add quantsupport --features plotContributions 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.