Skip to content

Commit f8ea030

Browse files
committed
Merge branch 'feature/QPR-12825' into 'master'
QPR-12825 Add sticky SABR sensitivity calculation Closes QPR-12825 See merge request qs/oreplus!3018
2 parents 876fc81 + 920f0a2 commit f8ea030

20 files changed

Lines changed: 882 additions & 79 deletions

Docs/UserGuide/parameterisation/simulation.tex

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -665,15 +665,22 @@ \subsubsection{Market}\label{sec:sim_market}
665665
\item Extrapolation: This can be FlatFwd or FlatZero. If not given, the value defaults to FlatFwd.
666666
\end{itemize}
667667
668-
For swap, yield, interest cap-floor, yoy inflation cap-floor, zc inflation cap-floor, cds, fx, equity, commodity
669-
volatilities the smile dynamics can be specified as shown in listing \ref{lst:smile_dynamics_configuration} for swap
668+
For swaption, yield, interest rate cap-floor, yoy inflation cap-floor, zc inflation cap-floor, cds, fx, equity, commodity
669+
volatilities the smile dynamics can be specified as shown in listing \ref{lst:smile_dynamics_configuration} for swaption
670670
vols. The empty key serves as a default configuration for all keys for which no own smile dynamics node is present. The
671-
allowed smile dynamics values are StickyStrike and StickyMoneyness. If not given, the smile dynamics defaults to
672-
StickyStrike.
671+
allowed smile dynamics values are StickyStrike, StickyMoneyness and StickySABR.
672+
If not given, the smile dynamics defaults to StickyStrike.
673+
674+
Note that StickySABR is only available for swaption volatilities, yield volatilities and interest rate cap-floor volatilities,
675+
and can only be used if the corresponding T0 surface has been calibrated using a SABR model.
676+
The SABR volatility surface in the simulation market is recalibrated to the expiry/term/strike grid specified in the simulation
677+
configuration, using the initial SABR configuration from the T0 surface. Any subsequent recalibration will keep all SABR parameters
678+
except for $\alpha$ fixed.
673679
\begin{listing}
674680
\begin{minted}[fontsize=\footnotesize]{xml}
675681
<SmileDynamics key="">StickyStrike</SmileDynamics>
676682
<SmileDynamics key="EUR-ESTER">StickyMoneyness</SmileDynamics>
683+
<SmileDynamics key="USD-SOFR-3M">StickySABR</SmileDynamics>
677684
\end{minted}
678685
\caption{Smile Configuration Node}
679686
\label{lst:smile_dynamics_configuration}

OREAnalytics/orea/engine/parsensitivityinstrumentbuilder.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,7 @@ void ParSensitivityInstrumentBuilder::createParInstruments(
434434
Size n_expiries = data.shiftExpiries.size();
435435

436436
// Determine if the cap floor is ATM
437-
bool isAtm = data.shiftStrikes.size() == 1 && data.shiftStrikes[0] == 0.0 && data.isRelative;
437+
bool isAtm = data.shiftStrikes.size() == 1 && data.shiftStrikes[0] == 0.0;
438438

439439
for (Size j = 0; j < n_strikes; ++j) {
440440
Real strike = data.shiftStrikes[j];

OREAnalytics/orea/scenario/scenariosimmarket.cpp

Lines changed: 350 additions & 26 deletions
Large diffs are not rendered by default.

OREAnalytics/orea/scenario/sensitivityscenariogenerator.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1361,7 +1361,7 @@ void SensitivityScenarioGenerator::generateCapFloorVolScenarios(bool up) {
13611361
vector<Real> shiftStrikes = data.shiftStrikes;
13621362
// Has an ATM shift been configured?
13631363
bool sensiIsAtm = false;
1364-
if (shiftStrikes.size() == 1 && shiftStrikes[0] == 0.0 && data.isRelative) {
1364+
if (shiftStrikes.size() == 1 && shiftStrikes[0] == 0.0) {
13651365
sensiIsAtm = true;
13661366
}
13671367

QuantExt/qle/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,7 @@ termstructures/spreadedsmilesection2.cpp
423423
termstructures/spreadedsurvivalprobabilitytermstructure.cpp
424424
termstructures/spreadedswaptionvolatility.cpp
425425
termstructures/spreadedyoyvolsurface.cpp
426+
termstructures/strippedoptionlet.cpp
426427
termstructures/strippedoptionletadapter2.cpp
427428
termstructures/strippedyoyinflationoptionletvol.cpp
428429
termstructures/subperiodsswaphelper.cpp
@@ -1041,6 +1042,7 @@ termstructures/spreadedswaptionvolatility.hpp
10411042
termstructures/spreadedyoyvolsurface.hpp
10421043
termstructures/staticallycorrectedyieldtermstructure.hpp
10431044
termstructures/strippedcpivolatilitystructure.hpp
1045+
termstructures/strippedoptionlet.hpp
10441046
termstructures/strippedoptionletadapter.hpp
10451047
termstructures/strippedoptionletadapter2.hpp
10461048
termstructures/strippedyoyinflationoptionletvol.hpp

QuantExt/qle/quantext.hpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -598,6 +598,7 @@
598598
#include <qle/termstructures/spreadedyoyvolsurface.hpp>
599599
#include <qle/termstructures/staticallycorrectedyieldtermstructure.hpp>
600600
#include <qle/termstructures/strippedcpivolatilitystructure.hpp>
601+
#include <qle/termstructures/strippedoptionlet.hpp>
601602
#include <qle/termstructures/strippedoptionletadapter.hpp>
602603
#include <qle/termstructures/strippedoptionletadapter2.hpp>
603604
#include <qle/termstructures/strippedyoyinflationoptionletvol.hpp>

QuantExt/qle/termstructures/proxyoptionletvolatility.cpp

Lines changed: 16 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -78,27 +78,8 @@ QuantLib::ext::shared_ptr<SmileSection> ProxyOptionletVolatility::smileSectionIm
7878

7979
// compute the base and target forward rate levels
8080

81-
Real baseAtmLevel;
82-
if (isOis(baseIndex_))
83-
baseAtmLevel = getOisAtmLevel(QuantLib::ext::dynamic_pointer_cast<OvernightIndex>(baseIndex_),
84-
baseIndex_->fixingCalendar().adjust(fixingDate), baseRateComputationPeriod_);
85-
else if (isBMA(baseIndex_))
86-
baseAtmLevel = getBMAAtmLevel(QuantLib::ext::dynamic_pointer_cast<BMAIndexWrapper>(baseIndex_)->bma(),
87-
baseIndex_->fixingCalendar().adjust(fixingDate), baseRateComputationPeriod_);
88-
else
89-
baseAtmLevel = baseIndex_->fixing(baseIndex_->fixingCalendar().adjust(fixingDate));
90-
91-
Real targetAtmLevel;
92-
if (isOis(targetIndex_))
93-
targetAtmLevel =
94-
getOisAtmLevel(QuantLib::ext::dynamic_pointer_cast<OvernightIndex>(targetIndex_),
95-
targetIndex_->fixingCalendar().adjust(fixingDate), targetRateComputationPeriod_);
96-
else if (isBMA(targetIndex_))
97-
targetAtmLevel =
98-
getBMAAtmLevel(QuantLib::ext::dynamic_pointer_cast<BMAIndexWrapper>(targetIndex_)->bma(),
99-
targetIndex_->fixingCalendar().adjust(fixingDate), targetRateComputationPeriod_);
100-
else
101-
targetAtmLevel = targetIndex_->fixing(targetIndex_->fixingCalendar().adjust(fixingDate));
81+
Real baseAtmLevel = ProxyOptionletVolatility::getAtmLevel(fixingDate, baseIndex_, baseRateComputationPeriod_);
82+
Real targetAtmLevel = ProxyOptionletVolatility::getAtmLevel(fixingDate, targetIndex_, targetRateComputationPeriod_);
10283

10384
// build the atm-adjusted smile section and return it
10485

@@ -111,4 +92,18 @@ Volatility ProxyOptionletVolatility::volatilityImpl(Time optionTime, Rate strike
11192
return smileSection(optionTime)->volatility(strike) * scalingFactor_;
11293
}
11394

95+
Real ProxyOptionletVolatility::getAtmLevel(
96+
const QuantLib::Date& fixingDate,
97+
const QuantLib::ext::shared_ptr<QuantLib::IborIndex>& index,
98+
const QuantLib::Period& rateComputationPeriod) {
99+
if (isOis(index))
100+
return getOisAtmLevel(QuantLib::ext::dynamic_pointer_cast<OvernightIndex>(index),
101+
index->fixingCalendar().adjust(fixingDate), rateComputationPeriod);
102+
else if (isBMA(index))
103+
return getBMAAtmLevel(QuantLib::ext::dynamic_pointer_cast<BMAIndexWrapper>(index)->bma(),
104+
index->fixingCalendar().adjust(fixingDate), rateComputationPeriod);
105+
else
106+
return index->fixing(index->fixingCalendar().adjust(fixingDate));
107+
}
108+
114109
} // namespace QuantExt

QuantExt/qle/termstructures/proxyoptionletvolatility.hpp

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
#pragma once
2525

26+
#include <ql/indexes/iborindex.hpp>
2627
#include <ql/termstructures/volatility/optionlet/optionletvolatilitystructure.hpp>
2728

2829
namespace QuantExt {
@@ -40,9 +41,20 @@ class ProxyOptionletVolatility : public QuantLib::OptionletVolatilityStructure {
4041
QuantLib::Rate maxStrike() const override { return baseVol_->maxStrike(); }
4142
QuantLib::Date maxDate() const override { return baseVol_->maxDate(); }
4243
const QuantLib::Date& referenceDate() const override { return baseVol_->referenceDate(); }
43-
VolatilityType volatilityType() const override { return baseVol_->volatilityType(); }
44-
Real displacement() const override { return baseVol_->displacement(); }
45-
Calendar calendar() const override { return baseVol_->calendar(); }
44+
QuantLib::VolatilityType volatilityType() const override { return baseVol_->volatilityType(); }
45+
QuantLib::Real displacement() const override { return baseVol_->displacement(); }
46+
QuantLib::Calendar calendar() const override { return baseVol_->calendar(); }
47+
48+
const QuantLib::Handle<QuantLib::OptionletVolatilityStructure>& baseVol() const { return baseVol_; }
49+
const QuantLib::ext::shared_ptr<QuantLib::IborIndex>& baseIndex() const { return baseIndex_; }
50+
const QuantLib::ext::shared_ptr<QuantLib::IborIndex>& targetIndex() const { return targetIndex_; }
51+
const QuantLib::Period& baseRateComputationPeriod() const { return baseRateComputationPeriod_; }
52+
const QuantLib::Period& targetRateComputationPeriod() const { return targetRateComputationPeriod_; }
53+
double scalingFactor() const { return scalingFactor_; }
54+
55+
static QuantLib::Real getAtmLevel(const QuantLib::Date& fixingDate,
56+
const QuantLib::ext::shared_ptr<QuantLib::IborIndex>& index,
57+
const QuantLib::Period& rateComputationPeriod);
4658

4759
private:
4860
QuantLib::ext::shared_ptr<QuantLib::SmileSection> smileSectionImpl(const QuantLib::Date& optionDate) const override;

QuantExt/qle/termstructures/proxyswaptionvolatility.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ ProxySwaptionVolatility::ProxySwaptionVolatility(const QuantLib::Handle<Swaption
3232
: SwaptionVolatilityStructure(baseVol->businessDayConvention(), baseVol->dayCounter()), baseVol_(baseVol),
3333
baseSwapIndexBase_(baseSwapIndexBase), baseShortSwapIndexBase_(baseShortSwapIndexBase),
3434
targetSwapIndexBase_(targetSwapIndexBase), targetShortSwapIndexBase_(targetShortSwapIndexBase) {
35+
registerWith(baseVol_);
36+
registerWith(baseSwapIndexBase_);
37+
registerWith(baseShortSwapIndexBase_);
38+
registerWith(targetSwapIndexBase_);
39+
registerWith(targetShortSwapIndexBase_);
3540
enableExtrapolation(baseVol->allowsExtrapolation());
3641
}
3742

QuantExt/qle/termstructures/proxyswaptionvolatility.hpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,10 @@ class ProxySwaptionVolatility : public QuantLib::SwaptionVolatilityStructure {
4545

4646
const QuantLib::Period& maxSwapTenor() const override { return baseVol_->maxSwapTenor(); }
4747

48+
QuantLib::Handle<QuantLib::SwaptionVolatilityStructure> baseVol() const { return baseVol_; }
49+
QuantLib::ext::shared_ptr<QuantLib::SwapIndex> baseSwapIndexBase() const { return baseSwapIndexBase_; }
50+
QuantLib::ext::shared_ptr<QuantLib::SwapIndex> baseShortSwapIndexBase() const { return baseShortSwapIndexBase_; }
51+
4852
private:
4953
QuantLib::ext::shared_ptr<QuantLib::SmileSection> smileSectionImpl(const QuantLib::Date& optionDate,
5054
const QuantLib::Period& swapTenor) const override;

0 commit comments

Comments
 (0)