2222#include < ored/model/crossassetmodelbuilder.hpp>
2323#include < ored/model/eqbsbuilder.hpp>
2424#include < ored/model/fxbsbuilder.hpp>
25+ #include < ored/model/hwbuilder.hpp>
2526#include < ored/model/inflation/infdkbuilder.hpp>
2627#include < ored/model/inflation/infjybuilder.hpp>
2728#include < ored/model/inflation/infjydata.hpp>
28- #include < ored/model/lgmbuilder.hpp>
2929#include < ored/model/irhwmodeldata.hpp>
30- #include < ored/model/hwbuilder .hpp>
30+ #include < ored/model/lgmbuilder .hpp>
3131#include < ored/model/structuredmodelerror.hpp>
3232#include < ored/model/utilities.hpp>
3333#include < ored/utilities/correlationmatrix.hpp>
@@ -150,6 +150,15 @@ void CrossAssetModelBuilder::performCalculations() const {
150150 }
151151}
152152
153+ void CrossAssetModelBuilder::resetModelParams (const AssetType t, const Size param, const Size index, const Size i) const {
154+ auto mp = model_->MoveParameter (t, param, index, i);
155+ for (Size idx = 0 ; idx < mp.size (); ++idx) {
156+ if (!mp[idx]) {
157+ model_->setParam (idx, params_[idx]);
158+ }
159+ }
160+ }
161+
153162void CrossAssetModelBuilder::buildModel () const {
154163
155164 LOG (" Start building CrossAssetModel" );
@@ -222,7 +231,7 @@ void CrossAssetModelBuilder::buildModel() const {
222231 for (Size i = 0 ; i < config_->irConfigs ().size (); i++) {
223232 auto irConfig = config_->irConfigs ()[i];
224233 DLOG (" IR Parametrization " << i << " qualifier " << irConfig->qualifier ());
225-
234+
226235 if (auto ir = boost::dynamic_pointer_cast<IrLgmData>(irConfig)) {
227236 if (!buildersAreInitialized) {
228237 subBuilders_[CrossAssetModel::AssetType::IR][i] = boost::make_shared<LgmBuilder>(
@@ -244,12 +253,11 @@ void CrossAssetModelBuilder::buildModel() const {
244253 irParametrizations.push_back (parametrization);
245254 irDiscountCurves.push_back (builder->discountCurve ());
246255 processInfo[CrossAssetModel::AssetType::IR].emplace_back (ir->ccy (), 1 );
247- }
248- else if (auto ir = boost::dynamic_pointer_cast<HwModelData>(irConfig)) {
256+ } else if (auto ir = boost::dynamic_pointer_cast<HwModelData>(irConfig)) {
249257 bool evaluateBankAccount = true ; // updated in cross asset model for non-base ccys
250258 bool setCalibrationInfo = false ;
251259 HwModel::Discretization discr = HwModel::Discretization::Euler;
252- if (!buildersAreInitialized) {
260+ if (!buildersAreInitialized) {
253261 subBuilders_[CrossAssetModel::AssetType::IR][i] = boost::make_shared<HwBuilder>(
254262 market_, ir, measure, discr, evaluateBankAccount, configurationLgmCalibration_,
255263 config_->bootstrapTolerance (), continueOnError_, referenceCalibrationGrid_, setCalibrationInfo);
@@ -317,9 +325,9 @@ void CrossAssetModelBuilder::buildModel() const {
317325 QuantLib::Currency eqCcy = ore::data::parseCurrency (eq->currency ());
318326 QL_REQUIRE (std::find (currencies.begin (), currencies.end (), eqCcy.code ()) != currencies.end (),
319327 " Currency (" << eqCcy << " ) for equity " << eqName << " not covered by CrossAssetModelData" );
320- if (!buildersAreInitialized) {
328+ if (!buildersAreInitialized) {
321329 subBuilders_[CrossAssetModel::AssetType::EQ][i] = boost::make_shared<EqBsBuilder>(
322- market_, eq, domesticCcy, configurationEqCalibration_, referenceCalibrationGrid_);
330+ market_, eq, domesticCcy, configurationEqCalibration_, referenceCalibrationGrid_);
323331 }
324332 boost::shared_ptr<EqBsBuilder> builder =
325333 boost::dynamic_pointer_cast<EqBsBuilder>(subBuilders_[CrossAssetModel::AssetType::EQ][i]);
@@ -336,9 +344,9 @@ void CrossAssetModelBuilder::buildModel() const {
336344 boost::shared_ptr<InflationModelData> imData = config_->infConfigs ()[i];
337345 DLOG (" Inflation parameterisation (" << i << " ) for index " << imData->index ());
338346 if (auto dkData = boost::dynamic_pointer_cast<InfDkData>(imData)) {
339- if (!buildersAreInitialized) {
347+ if (!buildersAreInitialized) {
340348 subBuilders_[CrossAssetModel::AssetType::INF][i] = boost::make_shared<InfDkBuilder>(
341- market_, dkData, configurationInfCalibration_, referenceCalibrationGrid_, dontCalibrate_);
349+ market_, dkData, configurationInfCalibration_, referenceCalibrationGrid_, dontCalibrate_);
342350 }
343351 boost::shared_ptr<InfDkBuilder> builder =
344352 boost::dynamic_pointer_cast<InfDkBuilder>(subBuilders_[CrossAssetModel::AssetType::INF][i]);
@@ -369,8 +377,9 @@ void CrossAssetModelBuilder::buildModel() const {
369377 LOG (" CR LGM Parametrization " << i);
370378 boost::shared_ptr<CrLgmData> cr = config_->crLgmConfigs ()[i];
371379 string crName = cr->name ();
372- if (!buildersAreInitialized) {
373- subBuilders_[CrossAssetModel::AssetType::CR][i] = boost::make_shared<CrLgmBuilder>(market_, cr, configurationCrCalibration_);
380+ if (!buildersAreInitialized) {
381+ subBuilders_[CrossAssetModel::AssetType::CR][i] =
382+ boost::make_shared<CrLgmBuilder>(market_, cr, configurationCrCalibration_);
374383 }
375384 auto builder = boost::dynamic_pointer_cast<CrLgmBuilder>(subBuilders_[CrossAssetModel::AssetType::CR][i]);
376385 boost::shared_ptr<QuantExt::CrLgm1fParametrization> parametrization = builder->parametrization ();
@@ -472,6 +481,14 @@ void CrossAssetModelBuilder::buildModel() const {
472481 model_.linkTo (boost::make_shared<QuantExt::CrossAssetModel>(parametrizations, corrMatrix, salvaging_, measure,
473482 config_->discretization ()));
474483
484+ /* Store initial params to ensure identical start values when recalibrating a component.
485+ This is only used for fx, eq, inf, cr, com, for ir this is handled in LgmBuilder directly.
486+ Therefore it does not matter that the IR parameters are calibrated at this point already. */
487+
488+ if (!buildersAreInitialized) {
489+ params_ = model_->params ();
490+ }
491+
475492 /* ************************
476493 * Calibrate IR components
477494 */
@@ -521,6 +538,9 @@ void CrossAssetModelBuilder::buildModel() const {
521538
522539 if (!dontCalibrate_) {
523540
541+ // reset to initial params to ensure identical calibration outcomes for identical baskets
542+ resetModelParams (CrossAssetModel::AssetType::FX, 0 , i, Null<Size>());
543+
524544 if (fx->calibrationType () == CalibrationType::Bootstrap && fx->sigmaParamType () == ParamType::Piecewise)
525545 model_->calibrateBsVolatilitiesIterative (CrossAssetModel::AssetType::FX, i, fxOptionBaskets_[i],
526546 *optimizationMethod_, endCriteria_);
@@ -584,6 +604,9 @@ void CrossAssetModelBuilder::buildModel() const {
584604
585605 if (!dontCalibrate_) {
586606
607+ // reset to initial params to ensure identical calibration outcomes for identical baskets
608+ resetModelParams (CrossAssetModel::AssetType::EQ, 0 , i, Null<Size>());
609+
587610 if (eq->calibrationType () == CalibrationType::Bootstrap && eq->sigmaParamType () == ParamType::Piecewise)
588611 model_->calibrateBsVolatilitiesIterative (CrossAssetModel::AssetType::EQ, i, eqOptionBaskets_[i],
589612 *optimizationMethod_, endCriteria_);
@@ -623,7 +646,7 @@ void CrossAssetModelBuilder::buildModel() const {
623646 DLOG (" COM Calibration " << i);
624647 comOptionCalibrationErrors_[i] = csBuilder[i]->error ();
625648 }
626-
649+
627650 /* ************************
628651 * Relink LGM discount curves to curves used for INF calibration
629652 */
@@ -700,12 +723,16 @@ void CrossAssetModelBuilder::calibrateInflation(const InfDkData& data, Size mode
700723 return ;
701724
702725 if (data.volatility ().calibrate () && !data.reversion ().calibrate ()) {
726+ // reset to initial params to ensure identical calibration outcomes for identical baskets
727+ resetModelParams (CrossAssetModel::AssetType::INF, 0 , modelIdx, Null<Size>());
703728 if (data.calibrationType () == CalibrationType::Bootstrap && data.volatility ().type () == ParamType::Piecewise) {
704729 model_->calibrateInfDkVolatilitiesIterative (modelIdx, cb, *optimizationMethod_, endCriteria_);
705730 } else {
706731 model_->calibrateInfDkVolatilitiesGlobal (modelIdx, cb, *optimizationMethod_, endCriteria_);
707732 }
708733 } else if (!data.volatility ().calibrate () && data.reversion ().calibrate ()) {
734+ // reset to initial params to ensure identical calibration outcomes for identical baskets
735+ resetModelParams (CrossAssetModel::AssetType::INF, 1 , modelIdx, Null<Size>());
709736 if (data.calibrationType () == CalibrationType::Bootstrap && data.reversion ().type () == ParamType::Piecewise) {
710737 model_->calibrateInfDkReversionsIterative (modelIdx, cb, *optimizationMethod_, endCriteria_);
711738 } else {
@@ -804,6 +831,8 @@ void CrossAssetModelBuilder::calibrateInflation(const InfJyData& data, Size mode
804831 DLOG (" Bootstrap calibration of JY index volatility for index " << data.index () << " ." );
805832 QL_REQUIRE (idxVol.type () == ParamType::Piecewise, " Index volatility parameter should be Piecewise for "
806833 << " a Bootstrap calibration." );
834+ // reset to initial params to ensure identical calibration outcomes for identical baskets
835+ resetModelParams (CrossAssetModel::AssetType::INF, 2 , modelIdx, Null<Size>());
807836 model_->calibrateInfJyIterative (modelIdx, 2 , idxBasket, *optimizationMethod_, endCriteria_);
808837
809838 } else if (rrVol.calibrate () && !idxVol.calibrate ()) {
@@ -812,6 +841,8 @@ void CrossAssetModelBuilder::calibrateInflation(const InfJyData& data, Size mode
812841 DLOG (" Bootstrap calibration of JY real rate volatility for index " << data.index () << " ." );
813842 QL_REQUIRE (rrVol.type () == ParamType::Piecewise, " Real rate volatility parameter should be "
814843 << " Piecewise for a Bootstrap calibration." );
844+ // reset to initial params to ensure identical calibration outcomes for identical baskets
845+ resetModelParams (CrossAssetModel::AssetType::INF, 0 , modelIdx, Null<Size>());
815846 model_->calibrateInfJyIterative (modelIdx, 0 , rrBasket, *optimizationMethod_, endCriteria_);
816847
817848 } else if (rrRev.calibrate () && !idxVol.calibrate ()) {
@@ -820,6 +851,8 @@ void CrossAssetModelBuilder::calibrateInflation(const InfJyData& data, Size mode
820851 DLOG (" Bootstrap calibration of JY real rate reversion for index " << data.index () << " ." );
821852 QL_REQUIRE (rrRev.type () == ParamType::Piecewise, " Real rate reversion parameter should be "
822853 << " Piecewise for a Bootstrap calibration." );
854+ // reset to initial params to ensure identical calibration outcomes for identical baskets
855+ resetModelParams (CrossAssetModel::AssetType::INF, 1 , modelIdx, Null<Size>());
823856 model_->calibrateInfJyIterative (modelIdx, 1 , rrBasket, *optimizationMethod_, endCriteria_);
824857
825858 } else if ((rrVol.calibrate () && idxVol.calibrate ()) || (rrRev.calibrate () && idxVol.calibrate ())) {
@@ -837,6 +870,10 @@ void CrossAssetModelBuilder::calibrateInflation(const InfJyData& data, Size mode
837870 Size numIts = 0 ;
838871 inflationCalibrationErrors_[modelIdx] = getCalibrationError (allHelpers);
839872
873+ // reset to initial params to ensure identical calibration outcomes for identical baskets
874+ resetModelParams (CrossAssetModel::AssetType::INF, 2 , modelIdx, Null<Size>());
875+ resetModelParams (CrossAssetModel::AssetType::INF, 2 , rrIdx, Null<Size>());
876+
840877 while (inflationCalibrationErrors_[modelIdx] > cc.rmseTolerance () && numIts < cc.maxIterations ()) {
841878 model_->calibrateInfJyIterative (modelIdx, 2 , idxBasket, *optimizationMethod_, endCriteria_);
842879 model_->calibrateInfJyIterative (modelIdx, rrIdx, rrBasket, *optimizationMethod_, endCriteria_);
@@ -880,8 +917,9 @@ void CrossAssetModelBuilder::calibrateInflation(const InfJyData& data, Size mode
880917 LOG (" Finished calibrating JY inflation model for inflation index " << data.index ());
881918}
882919
883- void CrossAssetModelBuilder::setJyPricingEngine (
884- Size modelIdx, const vector<boost::shared_ptr<CalibrationHelper>>& calibrationBasket, bool indexIsInterpolated) const {
920+ void CrossAssetModelBuilder::setJyPricingEngine (Size modelIdx,
921+ const vector<boost::shared_ptr<CalibrationHelper>>& calibrationBasket,
922+ bool indexIsInterpolated) const {
885923
886924 DLOG (" Start setting pricing engines on JY calibration instruments." );
887925
@@ -904,7 +942,8 @@ void CrossAssetModelBuilder::setJyPricingEngine(
904942
905943 if (boost::shared_ptr<YoYCapFloorHelper> h = boost::dynamic_pointer_cast<YoYCapFloorHelper>(ci)) {
906944 if (!yoyCapFloorEngine) {
907- yoyCapFloorEngine = boost::make_shared<AnalyticJyYoYCapFloorEngine>(*model_, modelIdx, indexIsInterpolated);
945+ yoyCapFloorEngine =
946+ boost::make_shared<AnalyticJyYoYCapFloorEngine>(*model_, modelIdx, indexIsInterpolated);
908947 }
909948 h->setPricingEngine (yoyCapFloorEngine);
910949 continue ;
0 commit comments