Skip to content

Commit fba7709

Browse files
pcaspersjenkins
authored andcommitted
QPR-11644 refactor cube interpretation
1 parent e7f5ba8 commit fba7709

11 files changed

Lines changed: 180 additions & 285 deletions

OREAnalytics/orea/aggregation/dimcalculator.cpp

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -60,15 +60,13 @@ DynamicInitialMarginCalculator::DynamicInitialMarginCalculator(
6060
QL_REQUIRE(cubeInterpretation_, "cube interpretation is null");
6161
QL_REQUIRE(scenarioData_, "aggregation scenario data is null");
6262

63-
boost::shared_ptr<RegularCubeInterpretation> regCubeInt =
64-
boost::dynamic_pointer_cast<RegularCubeInterpretation>(cubeInterpretation_);
65-
cubeIsRegular_ = (regCubeInt != NULL);
63+
cubeIsRegular_ = !cubeInterpretation_->withCloseOutLag();
6664
datesLoopSize_ = cubeIsRegular_ ? cube_->dates().size() - 1 : cube_->dates().size();
6765

6866
Size dates = cube_->dates().size();
6967
Size samples = cube_->samples();
7068

71-
if (!cubeInterpretation_->hasMporFlows(cube_)) {
69+
if (!cubeInterpretation_->storeFlows()) {
7270
WLOG("cube holds no mpor flows, will assume no flows in the dim calculation");
7371
}
7472

@@ -93,7 +91,7 @@ DynamicInitialMarginCalculator::DynamicInitialMarginCalculator(
9391
Real defaultNpv = cubeInterpretation_->getDefaultNpv(cube_, i, j, k);
9492
Real closeOutNpv = cubeInterpretation_->getCloseOutNpv(cube_, i, j, k);
9593
Real mporFlow =
96-
cubeInterpretation_->hasMporFlows(cube_) ? cubeInterpretation_->getMporFlows(cube_, i, j, k) : 0.0;
94+
cubeInterpretation_->storeFlows() ? cubeInterpretation_->getMporFlows(cube_, i, j, k) : 0.0;
9795
nettingSetNPV_[nettingSetId][j][k] += defaultNpv;
9896
nettingSetCloseOutNPV_[nettingSetId][j][k] += closeOutNpv;
9997
nettingSetFLOW_[nettingSetId][j][k] += mporFlow;

OREAnalytics/orea/aggregation/exposurecalculator.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,7 @@ ExposureCalculator::ExposureCalculator(
6565
for (Size i = 0; i < dates_.size(); i++)
6666
times_[i] = dc_.yearFraction(today_, cube_->dates()[i]);
6767

68-
boost::shared_ptr<RegularCubeInterpretation> regularCubeInterpretation =
69-
boost::dynamic_pointer_cast<RegularCubeInterpretation>(cubeInterpretation_);
70-
isRegularCubeStorage_ = (regularCubeInterpretation != NULL);
68+
isRegularCubeStorage_ = !cubeInterpretation_->withCloseOutLag();
7169
}
7270

7371
void ExposureCalculator::build() {

OREAnalytics/orea/aggregation/postprocess.cpp

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,14 +75,8 @@ PostProcess::PostProcess(
7575
kvaCapitalHurdle_(kvaCapitalHurdle), kvaOurPdFloor_(kvaOurPdFloor), kvaTheirPdFloor_(kvaTheirPdFloor),
7676
kvaOurCvaRiskWeight_(kvaOurCvaRiskWeight), kvaTheirCvaRiskWeight_(kvaTheirCvaRiskWeight) {
7777

78-
// set a default value for the cube interpretation object if it is NULL
79-
if (!cubeInterpretation_) {
80-
WLOG("cube interpretation is not set, use regular");
81-
cubeInterpretation_ = boost::make_shared<RegularCubeInterpretation>(scenarioData_);
82-
}
83-
boost::shared_ptr<RegularCubeInterpretation> regularCubeInterpretation =
84-
boost::dynamic_pointer_cast<RegularCubeInterpretation>(cubeInterpretation_);
85-
bool isRegularCubeStorage = (regularCubeInterpretation != NULL);
78+
QL_REQUIRE(cubeInterpretation_ != nullptr, "PostProcess: cubeInterpretation is not given.");
79+
bool isRegularCubeStorage = !cubeInterpretation_->withCloseOutLag();
8680

8781
LOG("cube storage is regular: " << isRegularCubeStorage);
8882
LOG("cube dates: " << cube->dates().size());

OREAnalytics/orea/app/analytic.cpp

Lines changed: 26 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -655,7 +655,6 @@ void XvaAnalyticImpl::buildScenarioGenerator(const bool continueOnCalibrationErr
655655
string config = inputs_->marketConfig("simulation");
656656
scenarioGenerator_ = sgb.build(model_, sf, analytic()->configurations().simMarketParams, inputs_->asof(), analytic()->market(), config);
657657
QL_REQUIRE(scenarioGenerator_, "failed to build the scenario generator");
658-
grid_ = analytic()->configurations().scenarioGeneratorData->getGrid();
659658
samples_ = analytic()->configurations().scenarioGeneratorData->samples();
660659
LOG("simulation grid size " << grid_->size());
661660
LOG("simulation grid valuation dates " << grid_->valuationDates().size());
@@ -683,25 +682,13 @@ void XvaAnalyticImpl::buildCrossAssetModel(const bool continueOnCalibrationError
683682
}
684683

685684
void XvaAnalyticImpl::initCubeDepth() {
686-
687685
if (cubeDepth_ == 0) {
688686
LOG("XVA: Set cube depth");
689-
// Determine the required cube depth:
690-
// - Without close-out grid and without storing flows we have a 3d cube (trades * dates * scenarios),
691-
// otherwise we need a 4d "hypercube" with additonal dimension "depth"
692-
// - If we build an auxiliary close-out grid then we store default values at depth 0 and close-out at depth 1
693-
// - If we want to store cash flows that occur during the mpor, then we store them at depth 2
694-
cubeDepth_ = 1;
695-
if (analytic()->configurations().scenarioGeneratorData->withCloseOutLag())
696-
cubeDepth_++;
697-
if (inputs_->storeFlows())
698-
cubeDepth_++;
699-
687+
cubeDepth_ = cubeInterpreter_->requiredNpvCubeDepth();
700688
LOG("XVA: Cube depth set to: " << cubeDepth_);
701689
}
702690
}
703691

704-
705692
void XvaAnalyticImpl::initCube(boost::shared_ptr<NPVCube>& cube, const std::set<std::string>& ids, Size cubeDepth) {
706693

707694
LOG("Init cube with depth " << cubeDepth);
@@ -725,10 +712,11 @@ void XvaAnalyticImpl::initClassicRun(const boost::shared_ptr<Portfolio>& portfol
725712
initCubeDepth();
726713

727714
// May have been set already
728-
if (!scenarioData_) {
715+
if (scenarioData_.empty()) {
729716
LOG("XVA: Create asd " << grid_->valuationDates().size() << " x " << samples_);
730-
scenarioData_ = boost::make_shared<InMemoryAggregationScenarioData>(grid_->valuationDates().size(), samples_);
731-
simMarket_->aggregationScenarioData() = scenarioData_;
717+
scenarioData_.linkTo(
718+
boost::make_shared<InMemoryAggregationScenarioData>(grid_->valuationDates().size(), samples_));
719+
simMarket_->aggregationScenarioData() = *scenarioData_;
732720
}
733721

734722
// We can skip the cube initialization if the mt val engine is used, since it builds its own cubes
@@ -797,14 +785,15 @@ void XvaAnalyticImpl::buildClassicCube(const boost::shared_ptr<Portfolio>& portf
797785
if (analytic()->configurations().scenarioGeneratorData->withCloseOutLag()) {
798786
boost::shared_ptr<NPVCalculator> npvCalc =
799787
boost::make_shared<NPVCalculator>(inputs_->exposureBaseCurrency());
800-
calculators.push_back(boost::make_shared<MPORCalculator>(npvCalc, 0, 1));
788+
calculators.push_back(boost::make_shared<MPORCalculator>(npvCalc, cubeInterpreter_->defaultDateNpvIndex(),
789+
cubeInterpreter_->closeOutDateNpvIndex()));
801790
} else {
802791
calculators.push_back(boost::make_shared<NPVCalculator>(inputs_->exposureBaseCurrency()));
803792
}
804793
if (inputs_->storeFlows()) {
805794
// cash flow stored at index 1 (no close-out lag) or 2 (have close-out lag)
806-
calculators.push_back(boost::make_shared<CashflowCalculator>(inputs_->exposureBaseCurrency(),
807-
inputs_->asof(), grid_, cubeDepth_ - 1));
795+
calculators.push_back(boost::make_shared<CashflowCalculator>(
796+
inputs_->exposureBaseCurrency(), inputs_->asof(), grid_, cubeInterpreter_->mporFlowsIndex()));
808797
}
809798
return calculators;
810799
};
@@ -820,13 +809,6 @@ void XvaAnalyticImpl::buildClassicCube(const boost::shared_ptr<Portfolio>& portf
820809
return cptyCalculators;
821810
};
822811

823-
// set cube interpretation depending on close-out lag
824-
825-
if (analytic()->configurations().scenarioGeneratorData->withCloseOutLag())
826-
cubeInterpreter_ = boost::make_shared<MporGridCubeInterpretation>(scenarioData_, grid_, inputs_->flipViewXVA());
827-
else
828-
cubeInterpreter_ = boost::make_shared<RegularCubeInterpretation>(scenarioData_, inputs_->flipViewXVA());
829-
830812
// log message
831813

832814
ostringstream o;
@@ -970,11 +952,12 @@ void XvaAnalyticImpl::buildAmcPortfolio() {
970952
void XvaAnalyticImpl::amcRun(bool doClassicRun) {
971953

972954
LOG("XVA: amcRun");
973-
974-
if (!scenarioData_) {
955+
956+
if (scenarioData_.empty()) {
975957
LOG("XVA: Create asd " << grid_->valuationDates().size() << " x " << samples_);
976-
scenarioData_ = boost::make_shared<InMemoryAggregationScenarioData>(grid_->valuationDates().size(), samples_);
977-
simMarket_->aggregationScenarioData() = scenarioData_;
958+
scenarioData_.linkTo(
959+
boost::make_shared<InMemoryAggregationScenarioData>(grid_->valuationDates().size(), samples_));
960+
simMarket_->aggregationScenarioData() = *scenarioData_;
978961
}
979962

980963
initCubeDepth();
@@ -993,7 +976,7 @@ void XvaAnalyticImpl::amcRun(bool doClassicRun) {
993976
amcEngine.registerProgressIndicator(progressLog);
994977
// We only need to generate asd, if this does not happen in the classic run
995978
if (!doClassicRun)
996-
amcEngine.aggregationScenarioData() = scenarioData_;
979+
amcEngine.aggregationScenarioData() = *scenarioData_;
997980
amcEngine.buildCube(amcPortfolio_, amcCube_);
998981
} else {
999982
auto cubeFactory = [this](const QuantLib::Date& asof, const std::set<std::string>& ids,
@@ -1019,7 +1002,7 @@ void XvaAnalyticImpl::amcRun(bool doClassicRun) {
10191002
amcEngine.registerProgressIndicator(progressLog);
10201003
// as for the single-threaded case, we only need to generate asd, if this does not happen in the classic run
10211004
if (!doClassicRun)
1022-
amcEngine.aggregationScenarioData() = scenarioData_;
1005+
amcEngine.aggregationScenarioData() = *scenarioData_;
10231006
amcEngine.buildCube(amcPortfolio_);
10241007
amcCube_ = boost::make_shared<JointNPVCube>(amcEngine.outputCubes());
10251008
}
@@ -1076,22 +1059,10 @@ void XvaAnalyticImpl::runPostProcessor() {
10761059

10771060
checkConfigurations(analytic()->portfolio());
10781061

1079-
if (!cubeInterpreter_) {
1080-
// FIXME: Can we get the grid from the cube instead?
1081-
QL_REQUIRE(analytic()->configurations().scenarioGeneratorData, "scenario generator data not set");
1082-
boost::shared_ptr<ScenarioGeneratorData> sgd = analytic()->configurations().scenarioGeneratorData;
1083-
LOG("withCloseOutLag=" << (sgd->withCloseOutLag() ? "Y" : "N"));
1084-
if (sgd->withCloseOutLag())
1085-
cubeInterpreter_ =
1086-
boost::make_shared<MporGridCubeInterpretation>(scenarioData_, sgd->getGrid(), analytics["flipViewXVA"]);
1087-
else
1088-
cubeInterpreter_ = boost::make_shared<RegularCubeInterpretation>(scenarioData_, analytics["flipViewXVA"]);
1089-
}
1090-
10911062
if (!dimCalculator_ && (analytics["mva"] || analytics["dim"])) {
10921063
ALOG("dim calculator not set, create RegressionDynamicInitialMarginCalculator");
10931064
dimCalculator_ = boost::make_shared<RegressionDynamicInitialMarginCalculator>(
1094-
inputs_, analytic()->portfolio(), cube_, cubeInterpreter_, scenarioData_, dimQuantile, dimHorizonCalendarDays, dimRegressionOrder,
1065+
inputs_, analytic()->portfolio(), cube_, cubeInterpreter_, *scenarioData_, dimQuantile, dimHorizonCalendarDays, dimRegressionOrder,
10951066
dimRegressors, dimLocalRegressionEvaluations, dimLocalRegressionBandwidth);
10961067
}
10971068

@@ -1104,7 +1075,7 @@ void XvaAnalyticImpl::runPostProcessor() {
11041075
LOG("baseCurrency " << baseCurrency);
11051076

11061077
postProcess_ = boost::make_shared<PostProcess>(
1107-
analytic()->portfolio(), netting, analytic()->market(), marketConfiguration, cube_, scenarioData_, analytics,
1078+
analytic()->portfolio(), netting, analytic()->market(), marketConfiguration, cube_, *scenarioData_, analytics,
11081079
baseCurrency,
11091080
allocationMethod, marginalAllocationLimit, quantile, calculationType, dvaName, fvaBorrowingCurve,
11101081
fvaLendingCurve, dimCalculator_, cubeInterpreter_, fullInitialCollateralisation, cvaSensiGrid,
@@ -1132,7 +1103,11 @@ void XvaAnalyticImpl::runAnalytic(const boost::shared_ptr<ore::data::InMemoryLoa
11321103
CONSOLEW("XVA: Build Market");
11331104
analytic()->buildMarket(loader);
11341105
CONSOLE("OK");
1135-
1106+
1107+
cubeInterpreter_ = boost::make_shared<CubeInterpretation>(
1108+
inputs_->storeFlows(), analytic()->configurations().scenarioGeneratorData->withCloseOutLag(), scenarioData_,
1109+
analytic()->configurations().scenarioGeneratorData->getGrid(), inputs_->flipViewXVA());
1110+
11361111
if (runSimulation_) {
11371112

11381113
LOG("XVA: Build simulation market");
@@ -1233,8 +1208,8 @@ void XvaAnalyticImpl::runAnalytic(const boost::shared_ptr<ore::data::InMemoryLoa
12331208
CONSOLEW("XVA: Load Cubes");
12341209
QL_REQUIRE(inputs_->cube(), "XVA without EXPOSURE requires an NPV cube as input");
12351210
cube_= inputs_->cube();
1236-
QL_REQUIRE(inputs_->mktCube(), "XVA without EXPOSURE requires a market cube as input");
1237-
scenarioData_ = inputs_->mktCube();
1211+
QL_REQUIRE(inputs_->mktCube(), "XVA without EXPOSURE requires a market cube as input");
1212+
scenarioData_.linkTo(inputs_->mktCube());
12381213
if (inputs_->nettingSetCube())
12391214
nettingSetCube_= inputs_->nettingSetCube();
12401215
if (inputs_->cptyCube())
@@ -1247,7 +1222,7 @@ void XvaAnalyticImpl::runAnalytic(const boost::shared_ptr<ore::data::InMemoryLoa
12471222
// Return the cubes to serialalize
12481223
if (inputs_->writeCube()) {
12491224
analytic()->npvCubes()["XVA"]["cube"] = cube_;
1250-
analytic()->mktCubes()["XVA"]["scenariodata"] = scenarioData_;
1225+
analytic()->mktCubes()["XVA"]["scenariodata"] = *scenarioData_;
12511226
if (nettingSetCube_) {
12521227
analytic()->npvCubes()["XVA"]["nettingsetcube"] = nettingSetCube_;
12531228
}

OREAnalytics/orea/app/analytic.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -299,7 +299,7 @@ class XvaAnalyticImpl : public Analytic::Impl {
299299
boost::shared_ptr<ScenarioGenerator> scenarioGenerator_;
300300
boost::shared_ptr<Portfolio> amcPortfolio_, classicPortfolio_;
301301
boost::shared_ptr<NPVCube> cube_, nettingSetCube_, cptyCube_, amcCube_;
302-
boost::shared_ptr<AggregationScenarioData> scenarioData_;
302+
QuantLib::RelinkableHandle<AggregationScenarioData> scenarioData_;
303303
boost::shared_ptr<CubeInterpretation> cubeInterpreter_;
304304
boost::shared_ptr<DynamicInitialMarginCalculator> dimCalculator_;
305305
boost::shared_ptr<PostProcess> postProcess_;

OREAnalytics/orea/app/xvarunner.cpp

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -138,9 +138,9 @@ void XvaRunner::buildSimMarket(const boost::shared_ptr<ore::data::Market>& marke
138138

139139
DLOG("build scenario data");
140140

141-
scenarioData_ = boost::make_shared<InMemoryAggregationScenarioData>(
142-
scenarioGeneratorData_->getGrid()->valuationDates().size(), scenarioGeneratorData_->samples());
143-
simMarket_->aggregationScenarioData() = scenarioData_;
141+
scenarioData_.linkTo(boost::make_shared<InMemoryAggregationScenarioData>(
142+
scenarioGeneratorData_->getGrid()->valuationDates().size(), scenarioGeneratorData_->samples()));
143+
simMarket_->aggregationScenarioData() = *scenarioData_;
144144

145145
auto ed = boost::make_shared<EngineData>(*engineData_);
146146
ed->globalParameters()["RunType"] = "Exposure";
@@ -183,14 +183,14 @@ void XvaRunner::buildCube(const boost::optional<std::set<std::string>>& tradeIds
183183

184184
std::vector<boost::shared_ptr<ValuationCalculator>> calculators;
185185
boost::shared_ptr<NPVCalculator> npvCalculator = boost::make_shared<NPVCalculator>(baseCurrency_);
186+
cubeInterpreter_ = boost::make_shared<CubeInterpretation>(storeFlows_, scenarioGeneratorData_->withCloseOutLag(),
187+
scenarioData_, scenarioGeneratorData_->getGrid());
186188
if (scenarioGeneratorData_->withCloseOutLag()) {
187189
// depth 2: NPV and close-out NPV
188190
cube_ = getNpvCube(asof_, portfolio->ids(), scenarioGeneratorData_->getGrid()->valuationDates(),
189191
scenarioGeneratorData_->samples(), 2);
190-
cubeInterpreter_ =
191-
boost::make_shared<MporGridCubeInterpretation>(scenarioData_, scenarioGeneratorData_->getGrid());
192-
// default date value stored at index 0, close-out value at index 1
193-
calculators.push_back(boost::make_shared<MPORCalculator>(npvCalculator, 0, 1));
192+
calculators.push_back(boost::make_shared<MPORCalculator>(npvCalculator, cubeInterpreter_->defaultDateNpvIndex(),
193+
cubeInterpreter_->closeOutDateNpvIndex()));
194194
calculationType_ = "NoLag";
195195
if (calculationType_ != inputCalculationType_) {
196196
ALOG("Forcing calculation type " << calculationType_ << " for simulations with close-out grid");
@@ -200,14 +200,12 @@ void XvaRunner::buildCube(const boost::optional<std::set<std::string>>& tradeIds
200200
// regular, depth 2: NPV and cash flow
201201
cube_ = getNpvCube(asof_, portfolio->ids(), scenarioGeneratorData_->getGrid()->dates(),
202202
scenarioGeneratorData_->samples(), 2);
203-
calculators.push_back(
204-
boost::make_shared<CashflowCalculator>(baseCurrency_, asof_, scenarioGeneratorData_->getGrid(), 1));
203+
calculators.push_back(boost::make_shared<CashflowCalculator>(
204+
baseCurrency_, asof_, scenarioGeneratorData_->getGrid(), cubeInterpreter_->mporFlowsIndex()));
205205
} else
206206
// regular, depth 1
207207
cube_ = getNpvCube(asof_, portfolio->ids(), scenarioGeneratorData_->getGrid()->dates(),
208208
scenarioGeneratorData_->samples(), 1);
209-
210-
cubeInterpreter_ = boost::make_shared<RegularCubeInterpretation>(scenarioData_);
211209
calculators.push_back(npvCalculator);
212210
calculationType_ = inputCalculationType_;
213211
}
@@ -234,7 +232,7 @@ void XvaRunner::generatePostProcessor(const boost::shared_ptr<Market>& market,
234232
QL_REQUIRE(analytics_.size() > 0, "analytics map not set");
235233

236234
boost::shared_ptr<DynamicInitialMarginCalculator> dimCalculator =
237-
getDimCalculator(npvCube, cubeInterpreter_, scenarioData_, model_, nettingCube, currentIM);
235+
getDimCalculator(npvCube, cubeInterpreter_, *scenarioData_, model_, nettingCube, currentIM);
238236

239237
postProcess_ = boost::make_shared<PostProcess>(portfolio_, netting_, market, "", npvCube, scenarioData, analytics_,
240238
baseCurrency_, "None", 1.0, 0.95, calculationType_, dvaName_,
@@ -250,7 +248,7 @@ void XvaRunner::runXva(const boost::shared_ptr<Market>& market, bool continueOnE
250248
buildCamModel(market);
251249
buildSimMarket(market, boost::none, true);
252250
buildCube(boost::none, continueOnErr);
253-
generatePostProcessor(market, npvCube(), nettingCube(), aggregationScenarioData(), continueOnErr, currentIM);
251+
generatePostProcessor(market, npvCube(), nettingCube(), *aggregationScenarioData(), continueOnErr, currentIM);
254252
}
255253

256254
boost::shared_ptr<DynamicInitialMarginCalculator> XvaRunner::getDimCalculator(

OREAnalytics/orea/app/xvarunner.hpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class XvaRunner {
8585
boost::shared_ptr<NPVCube> nettingCube() const { return nettingCube_; }
8686

8787
// get aggregation scenario data from step 5
88-
boost::shared_ptr<AggregationScenarioData> aggregationScenarioData() { return scenarioData_; }
88+
QuantLib::Handle<AggregationScenarioData> aggregationScenarioData() { return scenarioData_; }
8989

9090
// partial step 3: build post processor on given cubes / agg scen data (requires runXva() or buildCamModel() call)
9191
void generatePostProcessor(
@@ -152,7 +152,7 @@ class XvaRunner {
152152
boost::shared_ptr<QuantExt::CrossAssetModel> model_;
153153
boost::shared_ptr<ScenarioSimMarket> simMarket_;
154154
boost::shared_ptr<EngineFactory> simFactory_;
155-
boost::shared_ptr<AggregationScenarioData> scenarioData_;
155+
QuantLib::RelinkableHandle<AggregationScenarioData> scenarioData_;
156156
boost::shared_ptr<NPVCube> cube_, nettingCube_;
157157
boost::shared_ptr<CubeInterpretation> cubeInterpreter_;
158158
std::string calculationType_;

0 commit comments

Comments
 (0)