Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Sensitivities

Requesting

let results = pricer.evaluate(&trade, &[Request::Value, Request::Sensitivities], &ctx)?;
let sens = results.sensitivities().ok_or(/* ... */)?;
for (key, dv) in sens.instrument_keys().iter().zip(sens.exposure()) {
    println!("{key:40} {dv:12.2}");
}

Sensitivities are only available when the context and trade use DualFwd. Values are \(\partial \text{NPV} / \partial q\) for each quote \(q\) in its own units (rate quotes in absolute rate: multiply by 1e-4 for a DV01 per basis point).

SensitivityMap

pub struct SensitivityMap { instrument_key: Vec<String>, exposure: Vec<f64> }
impl SensitivityMap {
    pub fn instrument_keys(&self) -> &[String];
    pub fn exposure(&self) -> &[f64];
    pub fn with_instrument_keys(self, keys: &[String]) -> Self;
    pub fn with_exposure(self, exposure: &[f64]) -> Self;
    pub fn aggregate(self) -> Self;   // sums duplicate keys, keeps first-occurrence order
}

aggregate() is applied by the pricers: when a child curve (a basis or collateral curve) depends on a parent curve, the IFT produces contributions to the parent quotes from both curves; they are summed under one label.

What appears in the table

Market elementLabels
Discount/projection curvesthe quotes in the CurveConfiguration (OIS_USD_SOFR_1Y, Swap_CLP_ICP_5Y, Deposit_USD_SOFR_1W, BasisSwap_*, FloatFloatCrossCurrencySwap_*)
Credit curvesCds_* quotes
Volatility surfaces/cubesCapletFloorlet_*, Swaption_*, FxCall_* quotes (vega ladder)
FX spotonly if the spot was added to the FxStore as DualFwd::new
Model parametersHull-White/LGM sigma pillars when built with HullWhiteTimeDependentVolatility::with_pillar_labels().with_ift_sensitivities()

Quotes that do not influence the price are omitted (zero adjoint).

Portfolio aggregation

Sum maps across trades keyed by label:

let mut total: BTreeMap<String, f64> = BTreeMap::new();
for r in results {
    if let Some(s) = r.sensitivities() {
        for (k, v) in s.instrument_keys().iter().zip(s.exposure()) {
            *total.entry(k.clone()).or_default() += v;
        }
    }
}

Because all trades share the same quote leaves, the summed ladder is the exact portfolio sensitivity.

Example

cargo run -p sensitivity prices SOFR, Term SOFR, ICP and USD/CLP cross-currency swaps and prints, per trade, the NPV followed by a table of quote identifier and exposure. The Term SOFR swap shows both BasisSwap_USD_SOFR_TermSOFR3m_* and OIS_USD_SOFR_* rows; the cross-currency swap adds OIS_CLP_ICP_* and FloatFloatCrossCurrencySwap_USD_SOFR_ICP_CLP_*.

Verifying against bumps

For a check, shock a quote with a Scenario and reprice:

let base = ctx.evaluate(&trade, &[Request::Value])?.price();
let mut bumped = base_ctx.with_scenarios(vec![Scenario::new("OIS_USD_SOFR_5Y", 1e-4, ScenarioType::Absolute)]);
bumped.initialize()?;
let fd = (bumped.evaluate(&trade, &[Request::Value])?.price() - base) / 1e-4;

fd should match the OIS_USD_SOFR_5Y entry to first order.