Skip to content

Commit 42cc3af

Browse files
committed
Merge branch 'feature/QPR-13719' into 'master'
Resolve QPR-13719 add exposure cube npv overlay Closes QPR-13719 See merge request qs/oreplus!3184
2 parents a6fd293 + 1ac7623 commit 42cc3af

11 files changed

Lines changed: 192 additions & 4 deletions

File tree

Docs/UserGuide/parameterisation/ore.tex

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -348,7 +348,7 @@ \subsubsection{Simulation and Model Calibration}
348348
<Parameter name="cptyCubeFile">cptyCube.csv.gz</Parameter>
349349
<Parameter name="storeSurvivalProbabilities">Y</Parameter>
350350
<Parameter name="storeCreditStateNPVs">8</Parameter>
351-
<Parameter name="salvageCorrelationMatrix">true</Parameter>
351+
<Parameter name="cubeNpvOverlay">true</Parameter>
352352
</Analytic>
353353
</Analytics>
354354
\end{minted}
@@ -369,6 +369,8 @@ \subsubsection{Simulation and Model Calibration}
369369
cube for post processing in the context of some Dynamic Initial Margin and Variation Margin models. And finally, the
370370
key `store survival probabilities' (Y or N) controls whether survival probabilities on simulation dates are stored in the
371371
cube for post processing in the context of Dynamic Credit XVA calculation.
372+
Key 'cubeNpvOverlay' is optional and defaults to false. If true, all raw npv cube entries are corrected by the
373+
difference of the T0 npv from the pricing analytic and the T0 npv from the simulation npv.
372374

373375
\medskip
374376
To use AMC simulation the simulation setup needs the additional elements shown in \ref{lst:ore_amc_simulation}

Docs/UserGuide/parameterisation/simulation.tex

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,7 @@ \subsubsection{Model}\label{sec:sim_model}
121121
<BootstrapTolerance>0.0001</BootstrapTolerance>
122122
<Measure>LGM</Measure><!-- Choices: LGM, BA -->
123123
<Discretization>Exact</Discretization>
124+
<SalvagingAlgorithm>Spectral</SalvagingAlgorithm>
124125
<!-- ... -->
125126
</CrossAssetModel>
126127
\end{minted}
@@ -139,6 +140,8 @@ \subsubsection{Model}\label{sec:sim_model}
139140
testing purposes or if more sophisticated component models are used). If {\em Euler} is used, you should consider
140141
setting TimeStepsPerYear (see above) to a large enough value.
141142

143+
The SalvagingAlgorithm tag specifies the preprocessing of the input correlation matrix (None, Spectral, Hypersphere, LowerDiagonal, Higham). Optional, defaults to None.
144+
142145
\medskip
143146

144147
Each interest rate model is specified by a block as follows

OREAnalytics/orea/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ cube/inmemorycubeopt.cpp
7171
cube/jaggedcube.cpp
7272
cube/jointnpvcube.cpp
7373
cube/jointnpvsensicube.cpp
74+
cube/overlaynpvcube.cpp
7475
cube/sensicube.cpp
7576
cube/sensitivitycube.cpp
7677
cube/sparsenpvcube.cpp
@@ -292,6 +293,7 @@ cube/jointnpvcube.hpp
292293
cube/jointnpvsensicube.hpp
293294
cube/npvcube.hpp
294295
cube/npvsensicube.hpp
296+
cube/overlaynpvcube.hpp
295297
cube/sensicube.hpp
296298
cube/sensitivitycube.hpp
297299
cube/sparsenpvcube.hpp

OREAnalytics/orea/app/analytics/pricinganalytic.cpp

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ namespace analytics {
3333
* PRICING Analytic: NPV, CASHFLOW, CASHFLOWNPV, SENSITIVITY, STRESS
3434
*******************************************************************/
3535

36+
void PricingAnalyticImpl::overwriteResultCurrency(const std::string& ccy) { overwriteResultCurrency_ = ccy; }
37+
3638
void PricingAnalyticImpl::setUpConfigurations() {
3739
if (find(begin(analytic()->analyticTypes()), end(analytic()->analyticTypes()), "SENSITIVITY") !=
3840
end(analytic()->analyticTypes())) {
@@ -83,8 +85,12 @@ void PricingAnalyticImpl::runAnalytic(
8385
if (runTypes.find(type) == runTypes.end())
8486
continue;
8587

86-
std::string effectiveResultCurrency =
87-
inputs_->resultCurrency().empty() ? inputs_->baseCurrency() : inputs_->resultCurrency();
88+
std::string effectiveResultCurrency = inputs_->baseCurrency();
89+
if (!inputs_->resultCurrency().empty())
90+
effectiveResultCurrency = inputs_->resultCurrency();
91+
if (overwriteResultCurrency_) {
92+
effectiveResultCurrency = *overwriteResultCurrency_;
93+
}
8894
auto marketConfig = inputs_->marketConfig("pricing");
8995
if (type == "NPV") {
9096
CONSOLEW("Pricing: NPV Report");

OREAnalytics/orea/app/analytics/pricinganalytic.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,16 @@ class PricingAnalyticImpl : public Analytic::Impl {
5252
offsetSimMarketParams_ = offsetSimMarketParams;
5353
}
5454

55+
void overwriteResultCurrency(const std::string& ccy);
56+
5557
private:
5658
QuantLib::ext::shared_ptr<SensitivityAnalysis> sensiAnalysis_;
5759
QuantLib::ext::shared_ptr<ParSensitivityAnalysis> parAnalysis_;
5860

5961
protected:
6062
QuantLib::ext::shared_ptr<Scenario> offsetScenario_;
6163
QuantLib::ext::shared_ptr<ScenarioSimMarketParameters> offsetSimMarketParams_;
64+
std::optional<std::string> overwriteResultCurrency_;
6265
};
6366

6467
static const std::set<std::string> pricingAnalyticSubAnalytics {"NPV", "CASHFLOW", "CASHFLOWNPV", "SENSITIVITY"};

OREAnalytics/orea/app/analytics/xvaanalytic.cpp

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include <orea/app/reportwriter.hpp>
2828
#include <orea/app/structuredanalyticserror.hpp>
2929
#include <orea/app/structuredanalyticswarning.hpp>
30+
#include <orea/cube/overlaynpvcube.hpp>
3031
#include <orea/cube/jointnpvcube.hpp>
3132
#include <orea/cube/npvcube.hpp>
3233
#include <orea/cube/sparsenpvcube.hpp>
@@ -93,6 +94,13 @@ void XvaAnalyticImpl::buildDependencies() {
9394
if (correlationAnalytic.second)
9495
addDependentAnalytic(corrLookupKey, correlationAnalytic.second);
9596
}
97+
if (inputs_->cubeNpvOverlay()) {
98+
if (auto pricingAnalytic =
99+
AnalyticFactory::instance().build("PRICING", inputs_, analytic()->analyticsManager(), false);
100+
pricingAnalytic.second) {
101+
addDependentAnalytic("PRICING", pricingAnalytic.second);
102+
}
103+
}
96104
}
97105

98106
void XvaAnalyticImpl::feedCorrelationToCAM(const std::map<std::pair<RiskFactorKey, RiskFactorKey>, Real>& corrData){
@@ -969,6 +977,21 @@ void XvaAnalyticImpl::runAnalytic(const QuantLib::ext::shared_ptr<ore::data::InM
969977
Settings::instance().includeReferenceDateEvents() = localIncRefDateEvents;
970978
LOG("Simulation IncludeReferenceDateEvents is set to " << (localIncRefDateEvents ? "true" : "false"));
971979

980+
std::map<std::string, double> cubeNpvOverlay;
981+
if (inputs_->cubeNpvOverlay()) {
982+
auto pricingAnalytic = dependentAnalytic("PRICING");
983+
static_cast<PricingAnalyticImpl*>(pricingAnalytic->impl().get())
984+
->overwriteResultCurrency(analytic()->configurations().simMarketParams->baseCcy());
985+
pricingAnalytic->runAnalytic(loader,{"NPV"});
986+
auto npvReport = pricingAnalytic->reports().at("NPV").at("npv");
987+
std::size_t colTradeId = npvReport->columnPosition("TradeId");
988+
std::size_t colNpvBase = npvReport->columnPosition("NPV(Base)");
989+
for (Size r = 0; r < npvReport->rows(); ++r) {
990+
cubeNpvOverlay[boost::get<std::string>(npvReport->data(colTradeId, r))] =
991+
boost::get<double>(npvReport->data(colNpvBase, r));
992+
}
993+
}
994+
972995
if(inputs_->generateCorrelations()){
973996
auto corrAnalytic = dependentAnalytic(corrLookupKey);
974997
corrAnalytic->runAnalytic(loader,{"CORRELATION"});
@@ -1108,11 +1131,19 @@ void XvaAnalyticImpl::runAnalytic(const QuantLib::ext::shared_ptr<ore::data::InM
11081131
LOG("We have generated an AMC cube only");
11091132
cube_ = amcCube_;
11101133
} else {
1111-
WLOG("We have generated a classic cube only");
1134+
LOG("We have generated a classic cube only");
11121135
}
11131136

11141137
LOG("NPV cube generation completed");
11151138

1139+
/************************************************************
1140+
* Apply correction pricing t0 npv - sim t0 npv if requested
1141+
************************************************************/
1142+
1143+
if (!cubeNpvOverlay.empty()) {
1144+
cube_ = QuantLib::ext::make_shared<OverlayNPVCube>(cube_, cubeNpvOverlay);
1145+
}
1146+
11161147
/***********************************************************************
11171148
* We may have two non-empty portfolios to be merged for post processing
11181149
***********************************************************************/

OREAnalytics/orea/app/inputparameters.hpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -332,6 +332,7 @@ class InputParameters {
332332
void setStoreSurvivalProbabilities(bool b) { storeSurvivalProbabilities_ = b; }
333333
void setWriteCube(bool b) { writeCube_ = b; }
334334
void setWriteScenarios(bool b) { writeScenarios_ = b; }
335+
void setCubeNpvOverlay(bool b) { cubeNpvOverlay_ = b; }
335336
void setExposureSimMarketParams(const std::string& xml);
336337
void setExposureSimMarketParamsFromFile(const std::string& fileName);
337338
void setScenarioGeneratorData(const std::string& xml);
@@ -823,6 +824,7 @@ class InputParameters {
823824
bool writeCube() const { return writeCube_; }
824825
bool writeScenarios() const { return writeScenarios_; }
825826
bool generateCorrelations() const {return generateCorrelations_;}
827+
bool cubeNpvOverlay() const { return cubeNpvOverlay_; }
826828
const QuantLib::ext::shared_ptr<ore::analytics::ScenarioSimMarketParameters>& exposureSimMarketParams() const { return exposureSimMarketParams_; }
827829
const QuantLib::ext::shared_ptr<ScenarioGeneratorData> scenarioGeneratorData() const { return scenarioGeneratorData_; }
828830
const QuantLib::ext::shared_ptr<CrossAssetModelData>& crossAssetModelData() const { return crossAssetModelData_; }
@@ -1316,6 +1318,7 @@ class InputParameters {
13161318
bool writeCube_ = false;
13171319
bool writeScenarios_ = false;
13181320
bool generateCorrelations_ = false;
1321+
bool cubeNpvOverlay_ = false;
13191322
QuantLib::ext::shared_ptr<ore::analytics::ScenarioSimMarketParameters> exposureSimMarketParams_;
13201323
QuantLib::ext::shared_ptr<ScenarioGeneratorData> scenarioGeneratorData_;
13211324
QuantLib::ext::shared_ptr<CrossAssetModelData> crossAssetModelData_;

OREAnalytics/orea/app/oreapp.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2103,6 +2103,10 @@ void OREAppInputParameters::loadParameters() {
21032103
if (tmp != "")
21042104
setWriteCube(true);
21052105

2106+
tmp = params_->get("simulation", "cubeNpvOverlay", false);
2107+
if (tmp != "")
2108+
setCubeNpvOverlay(parseBool(tmp));
2109+
21062110
tmp = params_->get("simulation", "scenariodump", false);
21072111
if (tmp != "")
21082112
setWriteScenarios(true);
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
Copyright (C) 2026 Quaternion Risk Management Ltd
3+
All rights reserved.
4+
5+
This file is part of ORE, a free-software/open-source library
6+
for transparent pricing and risk analysis - http://opensourcerisk.org
7+
8+
ORE is free software: you can redistribute it and/or modify it
9+
under the terms of the Modified BSD License. You should have received a
10+
copy of the license along with this program.
11+
The license is also available online at <http://opensourcerisk.org>
12+
13+
This program is distributed on the basis that it will form a useful
14+
contribution to risk analytics and model standardisation, but WITHOUT
15+
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16+
FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17+
*/
18+
19+
/*! \file orea/cube/overlaynpvcube.hpp
20+
\brief npv cube corrected by difference pricing t0 npv and sim t0 npv
21+
\ingroup cube
22+
*/
23+
24+
#include <orea/cube/overlaynpvcube.hpp>
25+
26+
namespace ore {
27+
namespace analytics {
28+
29+
OverlayNPVCube::OverlayNPVCube(const QuantLib::ext::shared_ptr<NPVCube>& cube,
30+
const std::map<std::string, double>& pricingNpvs)
31+
: cube_(cube) {
32+
pricingNpvs_.resize(cube_->numIds());
33+
for (auto const& [id, index] : cube_->idsAndIndexes()) {
34+
QL_REQUIRE(index < pricingNpvs_.size(), "OverlayNPVCube(): numIds (" << cube_->numIds()
35+
<< ") does not cover index (" << index
36+
<< ") for id " << id);
37+
if (auto p = pricingNpvs.find(id); p != pricingNpvs.end())
38+
pricingNpvs_[index] = p->second;
39+
else {
40+
QL_FAIL("OverlayNPVCube(): no pricingNpv given for id " << id);
41+
}
42+
}
43+
}
44+
45+
Size OverlayNPVCube::numIds() const { return cube_->numIds(); }
46+
47+
Size OverlayNPVCube::numDates() const { return cube_->numDates(); }
48+
49+
Size OverlayNPVCube::samples() const { return cube_->samples(); }
50+
51+
Size OverlayNPVCube::depth() const { return cube_->depth(); }
52+
53+
const std::map<std::string, Size>& OverlayNPVCube::idsAndIndexes() const { return cube_->idsAndIndexes(); }
54+
55+
const std::vector<QuantLib::Date>& OverlayNPVCube::dates() const { return cube_->dates(); }
56+
57+
QuantLib::Date OverlayNPVCube::asof() const { return cube_->asof(); }
58+
59+
Real OverlayNPVCube::getT0(Size id, Size depth) const { return cube_->getT0(id, depth) + correction(id, depth); }
60+
61+
void OverlayNPVCube::setT0(Real value, Size id, Size depth) { cube_->setT0(value - correction(id, depth), id, depth); }
62+
63+
Real OverlayNPVCube::get(Size id, Size date, Size sample, Size depth) const {
64+
return cube_->get(id, date, sample, depth) + correction(id, depth);
65+
}
66+
67+
void OverlayNPVCube::set(Real value, Size id, Size date, Size sample, Size depth) {
68+
cube_->set(value - correction(id, depth), id, date, sample, depth);
69+
}
70+
71+
bool OverlayNPVCube::usesDoublePrecision() const { return cube_->usesDoublePrecision(); }
72+
73+
double OverlayNPVCube::correction(Size id, Size depth) const {
74+
return depth == 0 ? pricingNpvs_[id] - cube_->getT0(id, depth) : 0.0;
75+
}
76+
77+
} // namespace analytics
78+
} // namespace ore
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
/*
2+
Copyright (C) 2026 Quaternion Risk Management Ltd
3+
All rights reserved.
4+
5+
This file is part of ORE, a free-software/open-source library
6+
for transparent pricing and risk analysis - http://opensourcerisk.org
7+
8+
ORE is free software: you can redistribute it and/or modify it
9+
under the terms of the Modified BSD License. You should have received a
10+
copy of the license along with this program.
11+
The license is also available online at <http://opensourcerisk.org>
12+
13+
This program is distributed on the basis that it will form a useful
14+
contribution to risk analytics and model standardisation, but WITHOUT
15+
ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16+
FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17+
*/
18+
19+
/*! \file orea/cube/overlaynpvcube.hpp
20+
\brief npv cube corrected by difference pricing t0 npv and sim t0 npv
21+
\ingroup cube
22+
*/
23+
24+
#pragma once
25+
26+
#include <orea/cube/npvcube.hpp>
27+
28+
namespace ore {
29+
namespace analytics {
30+
31+
class OverlayNPVCube : public NPVCube {
32+
public:
33+
OverlayNPVCube(const QuantLib::ext::shared_ptr<NPVCube>& cube, const std::map<std::string, double>& pricingNpvs);
34+
Size numIds() const override;
35+
Size numDates() const override;
36+
Size samples() const override;
37+
Size depth() const override;
38+
const std::map<std::string, Size>& idsAndIndexes() const override;
39+
const std::vector<QuantLib::Date>& dates() const override;
40+
QuantLib::Date asof() const override;
41+
Real getT0(Size id, Size depth = 0) const override;
42+
void setT0(Real value, Size id, Size depth = 0) override;
43+
Real get(Size id, Size date, Size sample, Size depth = 0) const override;
44+
void set(Real value, Size id, Size date, Size sample, Size depth = 0) override;
45+
bool usesDoublePrecision() const override;
46+
47+
private:
48+
double correction(Size id, Size depth) const;
49+
50+
QuantLib::ext::shared_ptr<NPVCube> cube_;
51+
std::vector<double> pricingNpvs_;
52+
};
53+
54+
} // namespace analytics
55+
} // namespace ore

0 commit comments

Comments
 (0)