Skip to content

Commit ef1fd75

Browse files
pcaspersjenkins
authored andcommitted
QPR-12491 allow for sabr parameter term structures in curve config
1 parent 421d91a commit ef1fd75

7 files changed

Lines changed: 110 additions & 58 deletions

File tree

OREData/ored/configuration/parametricsmileconfiguration.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ namespace data {
2727
void ParametricSmileConfiguration::Parameter::fromXML(XMLNode* node) {
2828
XMLUtils::checkNode(node, "Parameter");
2929
name = XMLUtils::getChildValue(node, "Name", true);
30-
initialValue = parseReal(XMLUtils::getChildValue(node, "InitialValue", true));
30+
initialValue = parseListOfValues<Real>(XMLUtils::getChildValue(node, "InitialValue", true), parseReal);
3131
isFixed = parseBool(XMLUtils::getChildValue(node, "IsFixed", true));
3232
}
3333

OREData/ored/configuration/parametricsmileconfiguration.hpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ class ParametricSmileConfiguration : public XMLSerializable {
4040
ore::data::XMLNode* toXML(ore::data::XMLDocument& doc) const override;
4141

4242
std::string name;
43-
double initialValue = 0.0;
43+
std::vector<double> initialValue = {0.0};
4444
bool isFixed = false;
4545
};
4646

OREData/ored/marketdata/capfloorvolcurve.cpp

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -329,7 +329,7 @@ void CapFloorVolCurve::termOptSurface(const Date& asof, CapFloorVolatilityCurveC
329329
Size dontThrowSteps = config.bootstrapConfig().dontThrowSteps();
330330

331331
// Get configuration values for parametric smile
332-
std::vector<std::pair<Real,bool>> initialModelParameters;
332+
std::vector<std::vector<std::pair<Real, bool>>> initialModelParameters;
333333
Size maxCalibrationAttempts = 10;
334334
Real exitEarlyErrorThreshold = 0.005;
335335
Real maxAcceptableError = 0.05;
@@ -338,10 +338,19 @@ void CapFloorVolCurve::termOptSurface(const Date& asof, CapFloorVolatilityCurveC
338338
auto beta = config.parametricSmileConfiguration()->parameter("beta");
339339
auto nu = config.parametricSmileConfiguration()->parameter("nu");
340340
auto rho = config.parametricSmileConfiguration()->parameter("rho");
341-
initialModelParameters.push_back(std::make_pair(alpha.initialValue, alpha.isFixed));
342-
initialModelParameters.push_back(std::make_pair(beta.initialValue, beta.isFixed));
343-
initialModelParameters.push_back(std::make_pair(nu.initialValue, nu.isFixed));
344-
initialModelParameters.push_back(std::make_pair(rho.initialValue, rho.isFixed));
341+
QL_REQUIRE(alpha.initialValue.size() == beta.initialValue.size() &&
342+
alpha.initialValue.size() == nu.initialValue.size() &&
343+
alpha.initialValue.size() == rho.initialValue.size(),
344+
"CapFloorVolCurve: parametric smile config: alpha size ("
345+
<< alpha.initialValue.size() << ") beta size (" << beta.initialValue.size() << ") nu size ("
346+
<< nu.initialValue.size() << ") rho size (" << rho.initialValue.size() << ") must match");
347+
for (Size i = 0; i < alpha.initialValue.size(); ++i) {
348+
initialModelParameters.push_back(std::vector<std::pair<Real, bool>>());
349+
initialModelParameters.back().push_back(std::make_pair(alpha.initialValue[i], alpha.isFixed));
350+
initialModelParameters.back().push_back(std::make_pair(beta.initialValue[i], beta.isFixed));
351+
initialModelParameters.back().push_back(std::make_pair(nu.initialValue[i], nu.isFixed));
352+
initialModelParameters.back().push_back(std::make_pair(rho.initialValue[i], rho.isFixed));
353+
}
345354
maxCalibrationAttempts = config.parametricSmileConfiguration()->calibration().maxCalibrationAttempts;
346355
exitEarlyErrorThreshold = config.parametricSmileConfiguration()->calibration().exitEarlyErrorThreshold;
347356
maxAcceptableError = config.parametricSmileConfiguration()->calibration().maxAcceptableError;
@@ -795,7 +804,7 @@ void CapFloorVolCurve::optOptSurface(const QuantLib::Date& asof, CapFloorVolatil
795804
QL_REQUIRE(config.optionalQuotes() == false, "Optional quotes for optionlet volatilities are not supported.");
796805

797806
// Get configuration values for parametric smile
798-
std::vector<std::pair<Real,bool>> initialModelParameters;
807+
std::vector<std::vector<std::pair<Real,bool>>> initialModelParameters;
799808
Size maxCalibrationAttempts = 10;
800809
Real exitEarlyErrorThreshold = 0.005;
801810
Real maxAcceptableError = 0.05;
@@ -804,10 +813,19 @@ void CapFloorVolCurve::optOptSurface(const QuantLib::Date& asof, CapFloorVolatil
804813
auto beta = config.parametricSmileConfiguration()->parameter("beta");
805814
auto nu = config.parametricSmileConfiguration()->parameter("nu");
806815
auto rho = config.parametricSmileConfiguration()->parameter("rho");
807-
initialModelParameters.push_back(std::make_pair(alpha.initialValue, alpha.isFixed));
808-
initialModelParameters.push_back(std::make_pair(beta.initialValue, beta.isFixed));
809-
initialModelParameters.push_back(std::make_pair(nu.initialValue, nu.isFixed));
810-
initialModelParameters.push_back(std::make_pair(rho.initialValue, rho.isFixed));
816+
QL_REQUIRE(alpha.initialValue.size() == beta.initialValue.size() &&
817+
alpha.initialValue.size() == nu.initialValue.size() &&
818+
alpha.initialValue.size() == rho.initialValue.size(),
819+
"CapFloorVolCurve: parametric smile config: alpha size ("
820+
<< alpha.initialValue.size() << ") beta size (" << beta.initialValue.size() << ") nu size ("
821+
<< nu.initialValue.size() << ") rho size (" << rho.initialValue.size() << ") must match");
822+
for (Size i = 0; i < alpha.initialValue.size(); ++i) {
823+
initialModelParameters.push_back(std::vector<std::pair<Real, bool>>());
824+
initialModelParameters.back().push_back(std::make_pair(alpha.initialValue[i], alpha.isFixed));
825+
initialModelParameters.back().push_back(std::make_pair(beta.initialValue[i], beta.isFixed));
826+
initialModelParameters.back().push_back(std::make_pair(nu.initialValue[i], nu.isFixed));
827+
initialModelParameters.back().push_back(std::make_pair(rho.initialValue[i], rho.isFixed));
828+
}
811829
maxCalibrationAttempts = config.parametricSmileConfiguration()->calibration().maxCalibrationAttempts;
812830
exitEarlyErrorThreshold = config.parametricSmileConfiguration()->calibration().exitEarlyErrorThreshold;
813831
maxAcceptableError = config.parametricSmileConfiguration()->calibration().maxAcceptableError;
@@ -1657,21 +1675,21 @@ void CapFloorVolCurve::buildCalibrationInfo(const Date& asof, const CurveConfigu
16571675

16581676
if (p) {
16591677
DLOG("SABR parameters:");
1660-
DLOG("alpha:");
1678+
DLOG("alpha (pls ignore second row, this is present for technical reasons):");
16611679
DLOGGERSTREAM(p->alpha());
1662-
DLOG("beta:");
1680+
DLOG("beta (pls ignore second row, this is present for technical reasons):");
16631681
DLOGGERSTREAM(p->beta());
1664-
DLOG("nu:");
1682+
DLOG("nu (pls ignore second row, this is present for technical reasons):");
16651683
DLOGGERSTREAM(p->nu());
1666-
DLOG("rho:");
1684+
DLOG("rho (pls ignore second row, this is present for technical reasons):");
16671685
DLOGGERSTREAM(p->rho());
1668-
DLOG("lognormal shift:");
1686+
DLOG("lognormal shift (pls ignore second row, this is present for technical reasons):");
16691687
DLOGGERSTREAM(p->lognormalShift());
16701688
DLOG("calibration attempts:");
16711689
DLOGGERSTREAM(p->numberOfCalibrationAttempts());
16721690
DLOG("calibration error:");
16731691
DLOGGERSTREAM(p->calibrationError());
1674-
DLOG("isInterpolated:");
1692+
DLOG("isInterpolated (1 means calibration failed and point is interpolated):");
16751693
DLOGGERSTREAM(p->isInterpolated());
16761694
}
16771695

OREData/ored/marketdata/genericyieldvolcurve.cpp

Lines changed: 37 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -359,7 +359,8 @@ GenericYieldVolCurve::GenericYieldVolCurve(
359359
config->extrapolation() == GenericYieldVolatilityCurveConfig::Extrapolation::Flat);
360360
cube->enableExtrapolation();
361361
} else {
362-
std::vector<std::pair<Real,bool>> initialModelParameters;
362+
std::map<std::pair<QuantLib::Period, QuantLib::Period>, std::vector<std::pair<Real, bool>>>
363+
initialModelParameters;
363364
Size maxCalibrationAttempts = 10;
364365
Real exitEarlyErrorThreshold = 0.005;
365366
Real maxAcceptableError = 0.05;
@@ -368,10 +369,30 @@ GenericYieldVolCurve::GenericYieldVolCurve(
368369
auto beta = config->parametricSmileConfiguration()->parameter("beta");
369370
auto nu = config->parametricSmileConfiguration()->parameter("nu");
370371
auto rho = config->parametricSmileConfiguration()->parameter("rho");
371-
initialModelParameters.push_back(std::make_pair(alpha.initialValue, alpha.isFixed));
372-
initialModelParameters.push_back(std::make_pair(beta.initialValue, beta.isFixed));
373-
initialModelParameters.push_back(std::make_pair(nu.initialValue, nu.isFixed));
374-
initialModelParameters.push_back(std::make_pair(rho.initialValue, rho.isFixed));
372+
QL_REQUIRE(alpha.initialValue.size() == beta.initialValue.size() &&
373+
alpha.initialValue.size() == nu.initialValue.size() &&
374+
alpha.initialValue.size() == rho.initialValue.size(),
375+
"GenericYieldVolCurve: parametric smile config: alpha size ("
376+
<< alpha.initialValue.size() << ") beta size (" << beta.initialValue.size()
377+
<< ") nu size (" << nu.initialValue.size() << ") rho size ("
378+
<< rho.initialValue.size() << ") must match");
379+
QL_REQUIRE(alpha.initialValue.size() == 1 ||
380+
alpha.initialValue.size() == optionTenors.size() * underlyingTenors.size(),
381+
"GenericYieldVolCurve: parametric smile config: alpha, beta, nu, rho size ("
382+
<< alpha.initialValue.size() << ") must match product of option tenors ("
383+
<< optionTenors.size() << ") and swap tenors (" << underlyingTenors.size()
384+
<< ") = " << optionTenors.size() * underlyingTenors.size() << ")");
385+
for (Size i = 0; i < optionTenors.size(); ++i) {
386+
for (Size j = 0; j < underlyingTenors.size(); ++j) {
387+
std::vector<std::pair<Real, bool>> tmp;
388+
Size idx = alpha.initialValue.size() == 1 ? 0 : i;
389+
tmp.push_back(std::make_pair(alpha.initialValue[idx], alpha.isFixed));
390+
tmp.push_back(std::make_pair(beta.initialValue[idx], beta.isFixed));
391+
tmp.push_back(std::make_pair(nu.initialValue[idx], nu.isFixed));
392+
tmp.push_back(std::make_pair(rho.initialValue[idx], rho.isFixed));
393+
initialModelParameters[std::make_pair(optionTenors[i], underlyingTenors[j])] = tmp;
394+
}
395+
}
375396
maxCalibrationAttempts =
376397
config->parametricSmileConfiguration()->calibration().maxCalibrationAttempts;
377398
exitEarlyErrorThreshold =
@@ -604,21 +625,24 @@ GenericYieldVolCurve::GenericYieldVolCurve(
604625
if (auto p = QuantLib::ext::dynamic_pointer_cast<QuantExt::SabrParametricVolatility>(
605626
sabr->parametricVolatility())) {
606627
DLOG("SABR parameters:");
607-
DLOG("alpha:");
628+
DLOG("alpha (rows = underlying lengths, cols = option tenors):");
608629
DLOGGERSTREAM(p->alpha());
609-
DLOG("beta:");
630+
DLOG("beta (rows = underlying lengths, cols = option tenors):");
610631
DLOGGERSTREAM(p->beta());
611-
DLOG("nu:");
632+
DLOG("nu (rows = underlying lengths, cols = option tenors):");
612633
DLOGGERSTREAM(p->nu());
613-
DLOG("rho:");
634+
DLOG("rho (rows = underlying lengths, cols = option tenors):");
614635
DLOGGERSTREAM(p->rho());
615-
DLOG("lognormal shift:");
636+
DLOG("lognormal shift (rows = underlying lengths, cols = option tenors):");
616637
DLOGGERSTREAM(p->lognormalShift());
617-
DLOG("calibration attempts:");
638+
DLOG("calibration attempts (rows = underlying lengths, cols = option tenors):");
618639
DLOGGERSTREAM(p->numberOfCalibrationAttempts());
619-
DLOG("calibration error:");
640+
DLOG("calibration error (rows = underlying lengths, cols = option tenors, rmse of relative "
641+
"errors w.r.t. max of sabr variant's preferred quotation type, i.e. nvol, slnvol, "
642+
"premium:");
620643
DLOGGERSTREAM(p->calibrationError());
621-
DLOG("isInterpolated:");
644+
DLOG("isInterpolated (rows = underlying lengths, cols = option tenors, 1 means calibration "
645+
"failed and point is interpolated):");
622646
DLOGGERSTREAM(p->isInterpolated());
623647
}
624648
}

QuantExt/qle/termstructures/sabrstrippedoptionletadapter.hpp

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ class SabrStrippedOptionletAdapter : public QuantLib::OptionletVolatilityStructu
5050
const QuantExt::SabrParametricVolatility::ModelVariant modelVariant,
5151
const TimeInterpolator& ti = TimeInterpolator(),
5252
const boost::optional<QuantLib::VolatilityType> outputVolatilityType = boost::none,
53-
const std::vector<std::pair<Real, bool>>& initialModelParameters = {},
53+
const std::vector<std::vector<std::pair<Real, bool>>>& initialModelParameters = {},
5454
const QuantLib::Size maxCalibrationAttempts = 10,
5555
const QuantLib::Real exitEarlyErrorThreshold = 0.005,
5656
const QuantLib::Real maxAcceptableError = 0.05);
@@ -62,7 +62,7 @@ class SabrStrippedOptionletAdapter : public QuantLib::OptionletVolatilityStructu
6262
const QuantExt::SabrParametricVolatility::ModelVariant modelVariant,
6363
const TimeInterpolator& ti = TimeInterpolator(),
6464
const boost::optional<QuantLib::VolatilityType> outputVolatilityType = boost::none,
65-
const std::vector<std::pair<Real, bool>>& initialModelParameters = {},
65+
const std::vector<std::vector<std::pair<Real, bool>>>& initialModelParameters = {},
6666
const QuantLib::Size maxCalibrationAttempts = 10,
6767
const QuantLib::Real exitEarlyErrorThreshold = 0.005,
6868
const QuantLib::Real maxAcceptableError = 0.05);
@@ -120,7 +120,7 @@ class SabrStrippedOptionletAdapter : public QuantLib::OptionletVolatilityStructu
120120
//! SABR specific inputs
121121
QuantExt::SabrParametricVolatility::ModelVariant modelVariant_;
122122
boost::optional<QuantLib::VolatilityType> outputVolatilityType_;
123-
std::vector<std::pair<Real, bool>> initialModelParameters_;
123+
std::vector<std::vector<std::pair<Real, bool>>> initialModelParameters_;
124124
QuantLib::Size maxCalibrationAttempts_;
125125
QuantLib::Real exitEarlyErrorThreshold_;
126126
QuantLib::Real maxAcceptableError_;
@@ -136,8 +136,9 @@ SabrStrippedOptionletAdapter<TimeInterpolator>::SabrStrippedOptionletAdapter(
136136
const QuantLib::ext::shared_ptr<QuantLib::StrippedOptionletBase>& sob,
137137
const QuantExt::SabrParametricVolatility::ModelVariant modelVariant, const TimeInterpolator& ti,
138138
const boost::optional<QuantLib::VolatilityType> outputVolatilityType,
139-
const std::vector<std::pair<Real, bool>>& initialModelParameters, const QuantLib::Size maxCalibrationAttempts,
140-
const QuantLib::Real exitEarlyErrorThreshold, const QuantLib::Real maxAcceptableError)
139+
const std::vector<std::vector<std::pair<Real, bool>>>& initialModelParameters,
140+
const QuantLib::Size maxCalibrationAttempts, const QuantLib::Real exitEarlyErrorThreshold,
141+
const QuantLib::Real maxAcceptableError)
141142
: OptionletVolatilityStructure(sob->settlementDays(), sob->calendar(), sob->businessDayConvention(),
142143
sob->dayCounter()),
143144
optionletBase_(sob), ti_(ti), modelVariant_(modelVariant), outputVolatilityType_(outputVolatilityType),
@@ -151,8 +152,9 @@ SabrStrippedOptionletAdapter<TimeInterpolator>::SabrStrippedOptionletAdapter(
151152
const QuantLib::Date& referenceDate, const QuantLib::ext::shared_ptr<QuantLib::StrippedOptionletBase>& sob,
152153
const QuantExt::SabrParametricVolatility::ModelVariant modelVariant, const TimeInterpolator& ti,
153154
const boost::optional<QuantLib::VolatilityType> outputVolatilityType,
154-
const std::vector<std::pair<Real, bool>>& initialModelParameters, const QuantLib::Size maxCalibrationAttempts,
155-
const QuantLib::Real exitEarlyErrorThreshold, const QuantLib::Real maxAcceptableError)
155+
const std::vector<std::vector<std::pair<Real, bool>>>& initialModelParameters,
156+
const QuantLib::Size maxCalibrationAttempts, const QuantLib::Real exitEarlyErrorThreshold,
157+
const QuantLib::Real maxAcceptableError)
156158
: OptionletVolatilityStructure(referenceDate, sob->calendar(), sob->businessDayConvention(), sob->dayCounter()),
157159
optionletBase_(sob), ti_(ti), modelVariant_(modelVariant), outputVolatilityType_(outputVolatilityType),
158160
initialModelParameters_(initialModelParameters), maxCalibrationAttempts_(maxCalibrationAttempts),
@@ -203,6 +205,11 @@ inline void SabrStrippedOptionletAdapter<TimeInterpolator>::performCalculations(
203205

204206
std::vector<ParametricVolatility::MarketSmile> marketSmiles;
205207
std::map<std::pair<QuantLib::Real, QuantLib::Real>, std::vector<std::pair<Real, bool>>> modelParameters;
208+
QL_REQUIRE(initialModelParameters_.empty() ||
209+
initialModelParameters_.size() == this->optionletBase()->optionletFixingTimes().size(),
210+
"SabrStrippedOptionletAdapter: initial model parameters must be empty or their size ("
211+
<< initialModelParameters_.size() << " must match the number of optionlet fixing times ("
212+
<< this->optionletBase()->optionletFixingTimes().size() << ")");
206213
for (Size i = 0; i < this->optionletBase()->optionletFixingTimes().size(); ++i) {
207214
Real forward = atmInterpolation_->operator()(this->optionletBase()->optionletFixingTimes()[i]);
208215
marketSmiles.push_back(ParametricVolatility::MarketSmile{this->optionletBase()->optionletFixingTimes()[i],
@@ -212,9 +219,10 @@ inline void SabrStrippedOptionletAdapter<TimeInterpolator>::performCalculations(
212219
{},
213220
this->optionletBase()->optionletStrikes(i),
214221
this->optionletBase()->optionletVolatilities(i)});
215-
if (!initialModelParameters_.empty())
222+
if (!initialModelParameters_.empty()) {
216223
modelParameters[std::make_pair(this->optionletBase()->optionletFixingTimes()[i], Null<Real>())] =
217-
initialModelParameters_;
224+
initialModelParameters_.size() == 1 ? initialModelParameters_.front() : initialModelParameters_[i];
225+
}
218226
}
219227

220228
parametricVolatility_ = QuantLib::ext::make_shared<SabrParametricVolatility>(

0 commit comments

Comments
 (0)