XVA Overview
XvaEngine (src/xva/engine.rs) turns a PricingContext plus a set of NettingSets into exposure cubes, CVA/FVA values and AD sensitivities in one run.
Pipeline
flowchart LR
T[Trades] -->|IntoContingentClaims| C[ContingentClaims]
C --> N[NettingSet + CsaTerms]
N --> E[XvaEngine::run]
E --> P[FixingPreprocessor]
P --> S[LgmMarketModel paths]
S --> Q[NpvCube per trade]
Q --> A[CVA / FVA aggregators]
A --> R[ExposureResult]
run performs, in order:
- Preprocess claims (
FixingPreprocessorfills realized fixings) and collect theSimulationRequests each claim needs. - Validate that every discount index selected by a netting set’s discount policy has an
LgmModelConfig. - Build the evaluation grid with
MakeSchedule::new(reference_date, max_payment_date).with_frequency(frequency). - Compute system discount factors \(P(0,t_k)\) from the domestic curve; XVA values are reported in present value on that curve (deterministic, no rate sensitivity through this term).
- For each netting set build a CVA aggregator (
CreditCurveCvaFactoryifcredit_indexis set, else flatCvaFactoryfromcredit_spread/recovery) and an FVA aggregator (FundingCurveFvaFactoryfromfunding_indexorfunding_spread_curve, else flatFvaFactoryfromfunding_spread). - Build the LGM market model from the configs (calibrating sigma schedules if
volatilityisCalibrated), simulate, evaluate claims intoNpvCubes, aggregate, and back-propagate adjoints to the labelled leaves.
Configuration
pub struct XvaEngineConfig {
model_configs: Vec<LgmModelConfig>, // one per simulated curve
fx_configs: Vec<FxModelConfig>, // one per foreign currency
n_paths: usize,
seed: u64,
frequency: Frequency,
}
pub struct LgmModelConfig { market_index, lambda: Option<f64>, sigma: Option<f64>,
volatility: Option<VolatilitySourceConfiguration>, driver: Option<MarketIndex> }
pub struct FxModelConfig { foreign_currency: Currency, fx_vol: f64, rho: f64 }
examples/cva/data/xva_config.json:
{
"model_configs": [
{
"market_index": "SOFR",
"lambda": 0.05,
"volatility": {
"Calibrated": {
"source": { "Surface": { "market_index": "SOFR" } },
"quote_ids": [
"CapletFloorlet_USD_SOFR_3M_1Y_Absolute_0.045_Straddle_Black"
],
"strike": "Atm",
"alpha": 0.05
}
}
},
{
"market_index": "ICP",
"lambda": 0.05,
"volatility": {
"Calibrated": {
"source": { "Cube": { "market_index": "ICP" } },
"quote_ids": ["Swaption_CLP_ICP_1Y_2Y_Absolute_0.045_Black"],
"alpha": 0.05
}
}
}
],
"fx_configs": [{ "foreign_currency": "CLP", "fx_vol": 0.12, "rho": 0.0 }],
"n_paths": 2000,
"seed": 42,
"frequency": "Monthly"
}
Running
let config: XvaEngineConfig = serde_json::from_str(&fs::read_to_string("data/xva_config.json")?)?;
let mut engine = XvaEngine::new(&ctx, config)?;
let csa: CsaTerms = serde_json::from_str(&fs::read_to_string("data/csa_terms.json")?)?;
let mut sets = HashMap::new();
sets.insert("CLIENT_A".to_string(),
NettingSet::with_csa_terms(vec![swap.into_claims()?, xccy.into_claims()?].concat(), csa));
let result = engine.run(&mut sets)?;
for cube in &result.cubes { println!("{} EPE(1Y) = {:.0}", cube.trade_id, cube.epe()[12]); }
for v in result.xva_values.unwrap_or_default() { println!("{} {} {:.2}", v.netting_set, v.measure, v.value); }
for (label, dv) in result.sensitivities.unwrap_or_default() { println!("{label:40} {dv:12.4}"); }
ExposureResult { cubes: Vec<NpvCube>, xva_values: Option<Vec<XvaValue { netting_set, measure, value }>>, sensitivities: Option<Vec<(String, f64)>> }. measure is "CVA" or "FVA" (a DvaAggregator exists for own-credit calculations built manually).
cargo run -p cva runs this on a 5Y USD SOFR swap (10M, receive 3.78%) and a 5Y USD/CLP float-float cross-currency swap and prints netting set, measure and value.
Chapters: Netting Sets and CSA, CVA, DVA and FVA, XVA Sensitivities.