Skip to content

Fix KVA party mapping under flipViewXVA (#359) - #360

Open
dazivo wants to merge 1 commit into
OpenSourceRisk:masterfrom
dazivo:fix/kva-flipview-party-mapping
Open

dazivo wants to merge 1 commit into
OpenSourceRisk:masterfrom
dazivo:fix/kva-flipview-party-mapping

Conversation

@dazivo

@dazivo dazivo commented Sep 10, 2026

Copy link
Copy Markdown

Fixes #359.

With flipViewXVA=Y, PostProcess::updateNettingSetKVA() swapped the PD/LGD sources of the two KVA branches but left the PD floors and the KVA-CVA risk weights attached to the output column, and it did the swap by overwriting the member dvaName_ with the current counterparty inside the netting-set loop. From the second netting set on, "our" KVA was therefore computed against the previous netting set's counterparty curve, and updateNettingSetCvaSensitivity(), which runs next and reads dvaName_ under flip, inherited the last counterparty's name too.

The fix makes the party identity explicit per netting set: bank = {dvaName, kvaOurPdFloor, kvaOurCvaRiskWeight}, cpty = {counterparty(nettingSet), kvaTheirPdFloor, kvaTheirCvaRiskWeight}, then activeOwnParty = flip ? cpty : bank and activeCounterparty = flip ? bank : cpty. Branch 1 (OurKVA*) takes PD, LGD, floor and risk weight from activeCounterparty, branch 2 (TheirKVA*) from activeOwnParty. dvaName_ is no longer written, which also repairs the CVA sensitivities without touching that function. In normal view the inputs of every arithmetic operation are the same as before, so results there are unchanged. A QL_REQUIRE now names the problem when flipViewXVA is set without dvaName instead of failing on an empty curve lookup. The user guide entry for the kva* parameters states which party Our/Their refer to under flip.

Tests (OREAnalytics/test/kva.cpp, suite KvaTest, no simulation: a hand-filled two-sample cube on uncollateralised netting sets, a three-name flat-hazard market, own/their floors 0.15/0.03 with the own floor binding, risk weights 0.02/0.05):

  • single counterparty: flipped Our* equals normal Their* and vice versa for KVA-CCR and KVA-CVA (exact mirror, 1e-12 relative), plus the normal-view numbers pinned to the values produced before this change;
  • multi-counterparty: a netting set's flipped KVA is the same alone and next to another netting set;
  • ordering: flipped KVA per counterparty is invariant under renaming the netting sets so that they are processed in the other order;
  • CVA sensitivities under flip are identical with and without the kva analytic.

Before the fix these fail with the expected fingerprints (18 failures: CCR mirror off by the floor, CVA mirror off by the risk-weight ratio 2.5, multi-counterparty and ordering cases off through the leaked name, sensitivities off through the leaked name); after the fix all pass and the pinned normal-view values are unchanged.

Verified with the ore binary on the issue's reproduction (shipped flip-view example, kva=Y, uncollateralised, BANK at 3 % CDS, user-guide parameter values): after the fix OurKVACCR/TheirKVACCR/OurKVACVA/TheirKVACVA flipped equal TheirKVACCR/OurKVACCR/TheirKVACVA/OurKVACVA normal to the cent, CPTY_B's flipped KVA is identical alone and together with CPTY_A, and the normal-view xva.csv of both portfolios is byte-identical to the one produced before the fix.

Not changed: ValueAdjustmentCalculator::build() already isolates its loops with an origDvaName snapshot, so CVA/DVA/FVA were not affected and are untouched. No shipped example enables kva, so no expected output changes.

Under flipViewXVA PostProcess::updateNettingSetKVA() swapped the PD/LGD
sources of the two KVA branches by overwriting dvaName_ per netting set,
while the PD floors and the KVA-CVA risk weights stayed attached to the
output column. Make the party identity explicit per netting set, derive
PD, LGD, floor and risk weight of the "our" branch from the active
counterparty and of the "their" branch from the active own party, and
never write dvaName_, which also repairs the flipped CVA sensitivities.
Normal view results are unchanged.

Add KvaTest covering the flip view mirror, multi-counterparty and
netting set order invariance and the CVA sensitivities, and clarify the
kva parameter semantics in the user guide.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q5n2ExoAFe9qtbybzMxnsQ
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

KVA under flipViewXVA: CVA risk weights and PD floors not swapped with the parties, dvaName_ overwritten per netting set

1 participant