@@ -798,7 +798,9 @@ ScenarioSimMarket::ScenarioSimMarket(
798798 }
799799 DLOG (" Initial market " << name << " yield volatility type = " << wrapper->volatilityType ());
800800
801- auto proxy = QuantLib::ext::dynamic_pointer_cast<ProxySwaptionVolatility>(*wrapper);
801+ bool stickySabr = smileDynamics == " StickySABR" ;
802+ auto proxy = stickySabr || !useSpreadedTermStructures_ ?
803+ QuantLib::ext::dynamic_pointer_cast<ProxySwaptionVolatility>(*wrapper) : nullptr ;
802804 if (proxy) {
803805 DLOG (" Detected ProxySwaptionVolatility for " << name);
804806 wrapper.linkTo (*proxy->baseVol ());
@@ -815,7 +817,6 @@ ScenarioSimMarket::ScenarioSimMarket(
815817 if (param.second .first ) {
816818 DLOG (" Simulating yield vols for ccy " << name);
817819 DLOG (" YieldVol simulate atm only : " << (simulateAtmOnly ? " True" : " False" ));
818- bool stickySabr = smileDynamics == " StickySABR" ;
819820 bool stickyStrike = smileDynamics == " StickyStrike" ;
820821
821822 if (simulateAtmOnly) {
@@ -1163,15 +1164,20 @@ ScenarioSimMarket::ScenarioSimMarket(
11631164 try {
11641165 LOG (" building " << name << " cap/floor volatility curve..." );
11651166 RelinkableHandle<OptionletVolatilityStructure> wrapper;
1167+
1168+ bool stickySabr = parameters->capFloorVolSmileDynamics (name) == " StickySABR" ;
11661169 QuantLib::ext::shared_ptr<ProxyOptionletVolatility> proxy;
1167- proxy = QuantLib::ext::dynamic_pointer_cast<ProxyOptionletVolatility>(
1168- *initMarket->capFloorVol (name, configuration));
1170+ proxy = stickySabr || !useSpreadedTermStructures_ ?
1171+ QuantLib::ext::dynamic_pointer_cast<ProxyOptionletVolatility>(
1172+ *initMarket->capFloorVol (name, configuration))
1173+ : nullptr ;
11691174 if (proxy) {
11701175 DLOG (" Detected ProxyOptionletVolatility for " << name);
11711176 wrapper.linkTo (*proxy->baseVol ());
11721177 } else {
11731178 wrapper.linkTo (*initMarket->capFloorVol (name, configuration));
11741179 }
1180+
11751181 auto [iborIndexName, rateComputationPeriod] =
11761182 initMarket->capFloorVolIndexBase (name, configuration);
11771183 QuantLib::ext::shared_ptr<IborIndex> iborIndex =
@@ -1217,7 +1223,6 @@ ScenarioSimMarket::ScenarioSimMarket(
12171223 vector<Period> optionTenors = parameters->capFloorVolExpiries (name);
12181224 vector<Date> optionDates (optionTenors.size ());
12191225
1220- bool stickySabr = parameters->capFloorVolSmileDynamics (name) == " StickySABR" ;
12211226 vector<vector<Real>> strikesSabr;
12221227 vector<vector<Handle<Quote>>> volSpreadsSabr;
12231228
@@ -1255,15 +1260,17 @@ ScenarioSimMarket::ScenarioSimMarket(
12551260 isAtm = true ;
12561261 }
12571262
1263+ vector<vector<Real>> strikesProxyAdjusted (optionTenors.size (), strikes);
12581264 vector<vector<Handle<Quote>>> quotes (
12591265 optionTenors.size (), vector<Handle<Quote>>(strikes.size (), Handle<Quote>()));
12601266
12611267 DLOG (" cap floor use adjusted option pillars = " << std::boolalpha << parameters_->capFloorVolAdjustOptionletPillars ());
12621268 DLOG (" have ibor index = " << std::boolalpha << (iborIndex != nullptr ));
12631269
12641270 vector<Rate> atmStrikes (optionTenors.size (), Null<Rate>());
1271+ auto atmStrikesProxyAdjusted = atmStrikes;
12651272 vector<Rate> atmVols (optionTenors.size (), Null<Rate>());
1266- for (Size i = 0 ; i < optionTenors.size (); ++i) {
1273+ for (Size i = 0 , index = 0 ; i < optionTenors.size (); ++i) {
12671274
12681275 if (parameters_->capFloorVolAdjustOptionletPillars () && iborIndex) {
12691276 // If we ask for cap pillars at tenors t_i for i = 1,...,N, we should attempt to
@@ -1355,9 +1362,33 @@ ScenarioSimMarket::ScenarioSimMarket(
13551362 }
13561363 }
13571364 }
1365+
1366+ Real proxyAdjustment = 0.0 ;
1367+ if (proxy) {
1368+ Real baseAtmLevel = proxy->getAtmLevel (optionDates[i], proxy->baseIndex (),
1369+ proxy->baseRateComputationPeriod ());
1370+ DLOG (" Base ATM level from proxy for option tenor " << optionTenors[i]
1371+ << " is " << baseAtmLevel);
1372+ Real targetAtmLevel = proxy->getAtmLevel (optionDates[i], proxy->targetIndex (),
1373+ proxy->targetRateComputationPeriod ());
1374+ DLOG (" Target ATM level from proxy for option tenor " << optionTenors[i]
1375+ << " is " << targetAtmLevel);
1376+ proxyAdjustment = -(targetAtmLevel - baseAtmLevel);
1377+ DLOG (" Adjusted strikes for option tenor " << optionTenors[i]
1378+ << " by proxy adjustment of "
1379+ << proxyAdjustment);
1380+ }
1381+ for (Size j = 0 ; j < strikesProxyAdjusted[i].size (); ++j) {
1382+ strikesProxyAdjusted[i][j] = strikes[j] + proxyAdjustment;
1383+ if (!close_enough (proxyAdjustment, 0.0 ))
1384+ DLOG (" adjusted strike from " << strikes[j] << " to " << strikesProxyAdjusted[i][j]);
1385+ }
1386+ atmStrikesProxyAdjusted[i] = atmStrikes[i] + proxyAdjustment;
1387+ if (!close_enough (proxyAdjustment, 0.0 ))
1388+ DLOG (" adjusted ATM strike from " << atmStrikes[i] << " to " << atmStrikesProxyAdjusted[i]);
13581389
1359- for (Size j = 0 ; j < strikes .size (); ++j) {
1360- Real strike = isAtm ? atmStrikes [i] : strikes [j];
1390+ for (Size j = 0 ; j < strikesProxyAdjusted[i] .size (); ++j, ++index ) {
1391+ Real strike = isAtm ? atmStrikesProxyAdjusted [i] : strikesProxyAdjusted[i] [j];
13611392 Real vol =
13621393 wrapper->volatility (optionDates[i], strike, true );
13631394 if (isAtm)
@@ -1367,7 +1398,7 @@ ScenarioSimMarket::ScenarioSimMarket(
13671398 << std::setprecision (12 ) << vol);
13681399 QuantLib::ext::shared_ptr<SimpleQuote> q =
13691400 QuantLib::ext::make_shared<SimpleQuote>(useSpreadedTermStructures_ ? 0.0 : vol);
1370- Size index = i * strikes. size () + j;
1401+
13711402 simDataTmp.emplace (std::piecewise_construct,
13721403 std::forward_as_tuple (param.first , name, index),
13731404 std::forward_as_tuple (q));
@@ -1378,7 +1409,7 @@ ScenarioSimMarket::ScenarioSimMarket(
13781409 }
13791410 quotes[i][j] = Handle<Quote>(q);
13801411 }
1381- if (i < strikesSabr.size ()) {
1412+ if (! strikesSabr.empty ()) {
13821413 for (Size j = 0 ; j < strikesSabr[i].size (); ++j) {
13831414 QL_REQUIRE (quotes[i].size () == 1 ,
13841415 " SSM internal error: expected quotes size 1 for stickySabr" );
@@ -1415,14 +1446,31 @@ ScenarioSimMarket::ScenarioSimMarket(
14151446 QuantLib::ext::shared_ptr<QuantLib::StrippedOptionlet> optionlet;
14161447
14171448 if (useSpreadedTermStructures_) {
1418- hCapletVol = Handle<OptionletVolatilityStructure>(
1419- QuantLib::ext::make_shared<QuantExt::SpreadedOptionletVolatility2>(wrapper, optionDates,
1420- strikes, quotes));
1449+
1450+ if (proxy) {
1451+ // Use AtmAdjustedSpreadedOptionletVolatility2 which adjusts strike level in the volSpread matrix
1452+ // according to difference in ATM levels when a smileSection is queried
1453+ hCapletVol = Handle<OptionletVolatilityStructure>(
1454+ QuantLib::ext::make_shared<AtmAdjustedSpreadedOptionletVolatility2>(wrapper,
1455+ optionDates,
1456+ strikes,
1457+ quotes,
1458+ proxy->baseIndex (),
1459+ proxy->targetIndex (),
1460+ proxy->baseRateComputationPeriod (),
1461+ proxy->targetRateComputationPeriod (),
1462+ proxy->scalingFactor ()));
1463+ } else {
1464+ hCapletVol = Handle<OptionletVolatilityStructure>(
1465+ QuantLib::ext::make_shared<QuantExt::SpreadedOptionletVolatility2>(wrapper, optionDates,
1466+ strikes, quotes));
1467+ }
1468+
14211469 if (stickySabr) {
14221470 auto strikeVec = vector<vector<Real>>(optionDates.size ());
14231471 auto optionletQuotes = vector<vector<Handle<Quote>>>(optionDates.size ());
14241472 for (Size i = 0 ; i < optionDates.size (); ++i) {
1425- strikeVec[i].push_back (atmStrikes [i]);
1473+ strikeVec[i].push_back (atmStrikesProxyAdjusted [i]);
14261474 optionletQuotes[i] = vector<Handle<Quote>>(1 );
14271475 optionletQuotes[i][0 ] = Handle<Quote>(ext::make_shared<SimpleQuote>(0.0 ));
14281476 }
@@ -1447,7 +1495,7 @@ ScenarioSimMarket::ScenarioSimMarket(
14471495 }
14481496 optionlet = QuantLib::ext::make_shared<QuantLib::StrippedOptionlet>(
14491497 settleDays, wrapper->calendar (), wrapper->businessDayConvention (), iborIndex,
1450- optionDates, strikes , quotes, dc, wrapper->volatilityType (),
1498+ optionDates, strikesProxyAdjusted , quotes, dc, wrapper->volatilityType (),
14511499 wrapper->displacement ());
14521500 if (!stickySabr) {
14531501 hCapletVol = Handle<OptionletVolatilityStructure>(
0 commit comments