Differentiation and market sensitivities
Automatic differentiation computes a valuation’s local response to its inputs. QuantSupport registers those inputs under market pillar labels, so a risk report can identify the deposit, swap, volatility quote or other quantity whose change affects the trade.
For a calibrated curve, the derivative includes the connection between market quotes and solved curve nodes. The label and units of each input determine how to translate the derivative into a monetary change.
For a decimal rate quote \(q\), the reported row is \(\partial V/\partial q\). Its first-order +1 bp change is \(10^{-4}\partial V/\partial q\). A volatility quote, FX spot or spread anchor has different units. Apply the scaling appropriate to each input when reporting DV01, vega or other market-move estimates.
Forward and reverse representations
Fwd<T> stores a value and a first-order seed of the inner scalar type.
Nesting supplies higher orders: Fwd1 = Fwd<f64> and
Fwd2 = Fwd<Fwd<f64>>. Dual<T> records reverse operations;
DualFwd = Dual<Fwd2> is the standard pricing scalar.
#![allow(unused)]
fn main() {
extern crate quantsupport;
use quantsupport::prelude::*;
let x = Fwd2::var(1.5);
let y = x * x;
assert!((y.value() - 2.25).abs() < 1e-12);
assert!((y.first_derivative() - 3.0).abs() < 1e-12);
assert!((y.second_derivative() - 2.0).abs() < 1e-12);
}
Reverse mode starts from a selected output and accumulates adjoints toward
registered independent inputs. In the example below, x is a tracked
variable and the exponential’s derivative is propagated back to it.
#![allow(unused)]
fn main() {
extern crate quantsupport;
use quantsupport::ad::expr::FloatExt;
use quantsupport::prelude::*;
Tape::start_recording_fwd();
let x = DualFwd::new(0.04);
let y: DualFwd = (x * DualFwd::scalar(2.0)).exp().into();
y.backward()?;
assert!((x.adjoint()?.value() - 2.0 * 0.08_f64.exp()).abs() < 1e-12);
Tape::stop_recording_fwd();
Ok::<(), QSError>(())
}
The tape is thread-local. Marks separate persistent market dependencies from temporary trade/path nodes. Pricers register required pillars, calculate values and propagate adjoints before reporting labelled derivatives. Script and XVA services rewind path-local nodes and use worker-local markets. These valuation services manage recording separately from context initialization.
Calibration dependencies
For calibrated rate curves, the nonlinear solve is numerical f64 work.
Finite-difference residual Jacobians and the implicit-function theorem reconnect
the converged discount factors to market quotes. The resulting connection
approximates the local first-order calibration response; higher-order
calibration effects require separate validation.
The resulting ladder can include parent curves. A Term SOFR swap discounted on SOFR may expose basis quotes and OIS quotes because both define the market used by valuation. Model calibration has its own omissions: current Gaussian sigma Jacobians estimate independent-fit volatility response by diagonal forward differences, not joint schedule-calibration risk or curve/strike-induced recalibration. Materialized simulation paths omit generation dependencies entirely.
SensitivityMap stores parallel labels and values. Aggregation sums repeated
labels reached through several paths. Reporting is pricer-specific: do not
assume every implementation removes zero rows or includes every conceivable
model parameter. Bond Z-spread/OAS anchors, for example, appear as explicitly
independent spread factors rather than being re-solved under every curve move.
Changing the curve risk basis
PricingContext provides two transformations that return a RiskContext
implementing MarketDataProvider. Pass the resulting view to the same pricer
and request Request::Sensitivities as usual. Both transformations preserve the
original curve values and interpolation, including prices between nodes.
The complete runnable example is
risk-reprojection.rs.
Run cargo run -p sensitivity --bin risk-reprojection from the repository root
to compare deposit, OIS and post-bootstrap zero-rate sensitivities for the same
swap. The example verifies that all three NPVs are identical.
This fragment reports sensitivities to zero rates implied from bootstrapped discount factors:
let convention = RateDefinition::new(
DayCounter::Actual365,
Compounding::Continuous,
Frequency::Annual,
);
let zero_context = context.with_zero_rate_risk(&[
ZeroRateRisk::new(MarketIndex::SOFR, convention),
ZeroRateRisk::new(MarketIndex::TermSOFR3m, convention),
])?;
let result = pricer.evaluate(
&trade,
&[Request::Value, Request::Sensitivities],
&zero_context,
)?;
Each grid defaults to that curve’s future nodes. Add
.with_dates(vec![date_1, date_2, ...]) to select another grid; curves without
nodes, such as composites, require explicit dates. Labels include the curve,
date and rate convention. Exposures are derivatives per unit of annual decimal
zero rate; multiply by 1e-4 for a first-order +1 bp change. Selected curves’
parent quote dependencies enter the transformation jointly. Other curve quotes
retain their original coordinates.
This fragment reports curve risk against another set of calibration instruments:
let instruments: Vec<QuoteDetails> = [
"OIS_USD_SOFR_1Y",
"OIS_USD_SOFR_2Y",
"Swap_USD_TermSOFR3m_1Y",
"Swap_USD_TermSOFR3m_2Y",
].iter().map(|label| label.parse()).collect::<Result<_>>()?;
let projected_context = context.reproject_risk(&instruments)?;
let result = pricer.evaluate(&trade, &[Request::Sensitivities], &projected_context)?;
The target contracts need not have observations in the quote store. Their
implied quotes are solved against the original curves. The view exposes
target_labels() and target_values() for inspection. The target basket must
span all native discount/forward curve pillars in the context; the example
assumes two pillars on each of its two curves. Instrument reprojection requires
configuration-driven curves. Supplied runtime curves support zero-rate risk.
For target quotes x and original curve quotes q, reprojection computes
J = dx/dq and reports dV/dx = dV/dq * J⁺. Zero-rate Jacobians use AD;
instrument Jacobians use central differences of reconstructed source curves.
Insufficient or singular bases return an error. With more target factors than
source factors, the pseudoinverse chooses minimum Euclidean-norm exposures in
the supplied quote units. These transformations are local and first order;
construct a fresh view when market inputs change. FX spots, volatility and
other independent factors are held fixed in the curve basis calculation and
keep their native reported risk. This does not add model recalibration or
simulation-path dependencies omitted by the original pricer.
Validation and numerical accuracy
AD differentiates the active implemented program. Branches use their selected local behavior; hard digitals have zero pathwise derivative almost everywhere. Scripting can smooth comparisons, introducing a deliberate approximation. Numerical integration, finite-difference calibration and time/state grids retain their errors.
Reverse work often avoids a separate pricing run for every market input, but there is no universal fixed runtime multiplier. Memory, calibration graph, trade count and path count matter. Validation should compare selected labeled derivatives against same-input, same-seed full revaluation, with a bump size small enough to test local behavior and large enough to avoid numerical cancellation.