Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
infdkbuilder.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2016 Quaternion Risk Management Ltd
3 All rights reserved.
4
5 This file is part of ORE, a free-software/open-source library
6 for transparent pricing and risk analysis - http://opensourcerisk.org
7
8 ORE is free software: you can redistribute it and/or modify it
9 under the terms of the Modified BSD License. You should have received a
10 copy of the license along with this program.
11 The license is also available online at <http://opensourcerisk.org>
12
13 This program is distributed on the basis that it will form a useful
14 contribution to risk analytics and model standardisation, but WITHOUT
15 ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
16 FITNESS FOR A PARTICULAR PURPOSE. See the license for more details.
17*/
18
19#include <ql/math/optimization/levenbergmarquardt.hpp>
20#include <ql/quotes/simplequote.hpp>
21
27
35
36
37using namespace QuantLib;
38using namespace QuantExt;
39using namespace std;
40
41namespace ore {
42namespace data {
43
44InfDkBuilder::InfDkBuilder(const QuantLib::ext::shared_ptr<ore::data::Market>& market, const QuantLib::ext::shared_ptr<InfDkData>& data,
45 const std::string& configuration, const std::string& referenceCalibrationGrid,
46 const bool dontCalibrate)
47 : market_(market), configuration_(configuration), data_(data), referenceCalibrationGrid_(referenceCalibrationGrid),
48 dontCalibrate_(dontCalibrate) {
49
50 LOG("DkBuilder for " << data_->index());
51
52 if (!data_->calibrationBaskets().empty()) {
53 QL_REQUIRE(data_->calibrationBaskets().size() == 1, "If there is a calibration basket, expect exactly 1.");
54 const CalibrationBasket& cb = data_->calibrationBaskets()[0];
55 QL_REQUIRE(!cb.empty(), "If there is a calibration basket, expect it to be non-empty.");
56 optionActive_ = vector<bool>(cb.instruments().size(), false);
57 }
58
59 marketObserver_ = QuantLib::ext::make_shared<MarketObserver>();
60
61 // get market data
63 QuantLib::ext::dynamic_pointer_cast<ZeroInflationIndex>(*market_->zeroInflationIndex(data_->index(), configuration_));
64 QL_REQUIRE(inflationIndex_, "DkBuilder: requires ZeroInflationIndex, got " << data_->index());
65 rateCurve_ = market_->discountCurve(inflationIndex_->currency().code(), configuration_);
66 infVol_ = market_->cpiInflationCapFloorVolatilitySurface(data_->index(), configuration_);
67
68 // register with market observables except vols
69 marketObserver_->registerWith(inflationIndex_);
70 marketObserver_->registerWith(rateCurve_);
71
72 // register the builder with the market observer
73 registerWith(marketObserver_);
74 registerWith(infVol_);
75 // notify observers of all market data changes, not only when not calculated
76 alwaysForwardNotifications();
77
78 // build option basket and derive parametrization from it
79 const ReversionParameter& reversion = data_->reversion();
80 const VolatilityParameter& volatility = data_->volatility();
81 if (volatility.calibrate() || reversion.calibrate())
83
84 Array aTimes(volatility.times().begin(), volatility.times().end());
85 Array hTimes(reversion.times().begin(), reversion.times().end());
86 Array alpha(volatility.values().begin(), volatility.values().end());
87 Array h(reversion.values().begin(), reversion.values().end());
88
89 if (volatility.type() == ParamType::Constant) {
90 QL_REQUIRE(volatility.times().size() == 0, "empty alpha times expected");
91 QL_REQUIRE(volatility.values().size() == 1, "initial alpha array should have size 1");
92 } else if (volatility.type() == ParamType::Piecewise) {
93 if (volatility.calibrate() && data_->calibrationType() == CalibrationType::Bootstrap) {
94 if (volatility.times().size() > 0) {
95 DLOG("overriding alpha time grid with option expiries");
96 }
97 QL_REQUIRE(optionExpiries_.size() > 0, "empty option expiries");
98 aTimes = Array(optionExpiries_.begin(), optionExpiries_.end() - 1);
99 alpha = Array(aTimes.size() + 1, volatility.values()[0]);
100 } else { // use input time grid and input alpha array otherwise
101 QL_REQUIRE(alpha.size() == aTimes.size() + 1, "alpha grids do not match");
102 }
103 } else {
104 QL_FAIL("Expected DK model data volatility parameter to be Constant or Piecewise.");
105 }
106
107 if (reversion.type() == ParamType::Constant) {
108 QL_REQUIRE(reversion.values().size() == 1, "reversion grid size 1 expected");
109 QL_REQUIRE(reversion.times().size() == 0, "empty reversion time grid expected");
110 } else if (reversion.type() == ParamType::Piecewise) {
111 if (reversion.calibrate() && data_->calibrationType() == CalibrationType::Bootstrap) {
112 if (reversion.times().size() > 0) {
113 DLOG("overriding H time grid with option expiries");
114 }
115 QL_REQUIRE(optionExpiries_.size() > 0, "empty option expiries");
116 hTimes = Array(optionExpiries_.begin(), optionExpiries_.end() - 1);
117 h = Array(hTimes.size() + 1, reversion.values()[0]);
118 } else { // use input time grid and input alpha array otherwise
119 QL_REQUIRE(h.size() == hTimes.size() + 1, "H grids do not match");
120 }
121 } else {
122 QL_FAIL("Expected DK model data reversion parameter to be Constant or Piecewise.");
123 }
124
125 DLOG("before calibration: alpha times = " << aTimes << " values = " << alpha);
126 DLOG("before calibration: h times = " << hTimes << " values = " << h)
127
130 DLOG("INF parametrization: InfDkPiecewiseConstantHullWhiteAdaptor");
131 parametrization_ = QuantLib::ext::make_shared<InfDkPiecewiseConstantHullWhiteAdaptor>(
132 inflationIndex_->currency(), inflationIndex_->zeroInflationTermStructure(), aTimes, alpha, hTimes, h,
133 data_->index());
134 } else if (reversion.reversionType() == LgmData::ReversionType::HullWhite) {
135 DLOG("INF parametrization for " << data_->index() << ": InfDkPiecewiseConstant");
136 parametrization_ = QuantLib::ext::make_shared<InfDkPiecewiseConstantParametrization>(
137 inflationIndex_->currency(), inflationIndex_->zeroInflationTermStructure(), aTimes, alpha, hTimes, h,
138 data_->index());
139 } else {
140 parametrization_ = QuantLib::ext::make_shared<InfDkPiecewiseLinearParametrization>(
141 inflationIndex_->currency(), inflationIndex_->zeroInflationTermStructure(), aTimes, alpha, hTimes, h,
142 data_->index());
143 DLOG("INF parametrization for " << data_->index() << ": InfDkPiecewiseLinear");
144 }
145
146 DLOG("alpha times size: " << aTimes.size());
147 DLOG("lambda times size: " << hTimes.size());
148
149 DLOG("Apply shift horizon and scale");
150 Time horizon = data_->reversionTransformation().horizon();
151 Real scaling = data_->reversionTransformation().scaling();
152 QL_REQUIRE(horizon >= 0.0, "shift horizon must be non negative");
153 QL_REQUIRE(scaling > 0.0, "scaling must be positive");
154
155 if (horizon > 0.0) {
156 DLOG("Apply shift horizon " << horizon << " to the " << data_->index() << " DK model");
157 parametrization_->shift() = horizon;
158 }
159
160 if (scaling != 1.0) {
161 DLOG("Apply scaling " << scaling << " to the " << data_->index() << " DK model");
162 parametrization_->scaling() = scaling;
163 }
164}
165
166QuantLib::ext::shared_ptr<QuantExt::InfDkParametrization> InfDkBuilder::parametrization() const {
167 calculate();
168 return parametrization_;
169}
170
171std::vector<QuantLib::ext::shared_ptr<BlackCalibrationHelper>> InfDkBuilder::optionBasket() const {
172 calculate();
173 return optionBasket_;
174}
175
177 return (data_->volatility().calibrate() || data_->reversion().calibrate()) &&
178 (volSurfaceChanged(false) || marketObserver_->hasUpdated(false) || forceCalibration_);
179}
180
182 if (requiresRecalibration()) {
183 // build option basket
185 }
186}
187
189 // reset market observer updated flag
190 marketObserver_->hasUpdated(true);
191 // update vol cache
192 volSurfaceChanged(true);
193}
194
195Date InfDkBuilder::optionMaturityDate(const Size j) const {
196 Date today = Settings::instance().evaluationDate();
197 const auto& ci = data_->calibrationBaskets()[0].instruments();
198 QL_REQUIRE(j < ci.size(), "InfDkBuilder::optionMaturityDate(" << j << "): out of bounds, got " << ci.size()
199 << " calibration instruments");
200 auto cf = QuantLib::ext::dynamic_pointer_cast<CpiCapFloor>(ci.at(j));
201 QL_REQUIRE(cf, "InfDkBuilder::optionMaturityDate("
202 << j << "): expected CpiCapFloor calibration instruments, could not cast");
203 Date res = optionMaturity(cf->maturity(), inflationIndex_->fixingCalendar());
204 QL_REQUIRE(res > today, "expired calibration option expiry " << io::iso_date(res));
205 return res;
206}
207
208Real InfDkBuilder::optionStrikeValue(const Size j) const {
209 const auto& ci = data_->calibrationBaskets()[0].instruments();
210 QL_REQUIRE(j < ci.size(), "InfDkBuilder::optionMaturityDate(" << j << "): out of bounds, got " << ci.size()
211 << " calibration instruments");
212 auto cf = QuantLib::ext::dynamic_pointer_cast<CpiCapFloor>(ci.at(j));
213 QL_REQUIRE(cf,
214 "InfDkBuilder::optionStrike(" << j << "): expected CpiCapFloor calibration instruments, could not cast");
215 return cpiCapFloorStrikeValue(cf->strike(), *inflationIndex_->zeroInflationTermStructure(), optionMaturityDate(j));
216}
217
218bool InfDkBuilder::volSurfaceChanged(const bool updateCache) const {
219 bool hasUpdated = false;
221 return false;
222
223 QuantLib::ext::shared_ptr<QuantExt::CPICapFloorEngine> engine;
224
225 bool isLogNormalVol = QuantExt::ZeroInflation::isCPIVolSurfaceLogNormal(infVol_.currentLink());
226 if (isLogNormalVol) {
227 engine = QuantLib::ext::make_shared<QuantExt::CPIBlackCapFloorEngine>(rateCurve_, infVol_);
228 } else {
229 engine = QuantLib::ext::make_shared<QuantExt::CPIBachelierCapFloorEngine>(rateCurve_, infVol_);
230 }
231
232 Calendar fixCalendar = inflationIndex_->fixingCalendar();
233 BusinessDayConvention bdc = infVol_->businessDayConvention();
234 Date baseDate = inflationIndex_->zeroInflationTermStructure()->baseDate();
235 Real baseCPI = inflationIndex_->fixing(baseDate);
236 Period lag = infVol_->observationLag();
237
238 // if cache doesn't exist resize vector
239 if (infPriceCache_.size() != optionBasket_.size())
240 infPriceCache_ = vector<Real>(optionBasket_.size(), Null<Real>());
241
242 // Handle on calibration instruments. No checks this time.
243 const auto& ci = data_->calibrationBaskets()[0].instruments();
244
245 Real nominal = 1.0;
246 Size optionCounter = 0;
247 Date today = Settings::instance().evaluationDate();
248 for (Size j = 0; j < ci.size(); j++) {
249
250 if (!optionActive_[j])
251 continue;
252
253 auto cpiCapFloor = QuantLib::ext::dynamic_pointer_cast<CpiCapFloor>(ci[j]);
254 QL_REQUIRE(cpiCapFloor, "Expected CpiCapFloor calibration instruments in DK inflation model data.");
255
256 Date expiryDate = optionMaturityDate(j);
257 auto strikeValue = optionStrikeValue(j);
258
259 Option::Type capfloor = cpiCapFloor->type() == CapFloor::Cap ? Option::Call : Option::Put;
260
261 QuantLib::ext::shared_ptr<CPICapFloor> h =
262 QuantLib::ext::make_shared<CPICapFloor>(capfloor, nominal, today, baseCPI, expiryDate, fixCalendar, bdc,
263 fixCalendar, bdc, strikeValue, inflationIndex_, lag);
264 h->setPricingEngine(engine);
265 Real price = h->NPV();
266 if (!close_enough(infPriceCache_[optionCounter], price)) {
267 if (updateCache)
268 infPriceCache_[optionCounter] = price;
269 hasUpdated = true;
270 }
271 optionCounter++;
272 }
273 return hasUpdated;
274}
275
277
278 // Checks that there is a basket.
279 QL_REQUIRE(!data_->calibrationBaskets().empty(), "No calibration basket provided in inflation DK model data.");
280 const CalibrationBasket& cb = data_->calibrationBaskets()[0];
281 const auto& ci = cb.instruments();
282
283 QL_REQUIRE(ci.size() == optionActive_.size(), "Expected the option active vector size to equal the "
284 << "number of calibration instruments");
285 fill(optionActive_.begin(), optionActive_.end(), false);
286
287 DLOG("build reference date grid '" << referenceCalibrationGrid_ << "'");
288 Date lastRefCalDate = Date::minDate();
289 std::vector<Date> referenceCalibrationDates;
290 if (!referenceCalibrationGrid_.empty())
291 referenceCalibrationDates = DateGrid(referenceCalibrationGrid_).dates();
292
293 QuantLib::ext::shared_ptr<QuantExt::CPICapFloorEngine> engine;
294
295 bool isLogNormalVol = QuantExt::ZeroInflation::isCPIVolSurfaceLogNormal(infVol_.currentLink());
296 if (isLogNormalVol) {
297 engine = QuantLib::ext::make_shared<QuantExt::CPIBlackCapFloorEngine>(rateCurve_, infVol_);
298 } else {
299 engine = QuantLib::ext::make_shared<QuantExt::CPIBachelierCapFloorEngine>(rateCurve_, infVol_);
300 }
301
302 Calendar fixCalendar = inflationIndex_->fixingCalendar();
303 Date baseDate = inflationIndex_->zeroInflationTermStructure()->baseDate();
304 Real baseCPI = dontCalibrate_ ? 100. : inflationIndex_->fixing(baseDate);
305 BusinessDayConvention bdc = infVol_->businessDayConvention();
306 Period lag = infVol_->observationLag();
307 Handle<ZeroInflationIndex> hIndex(inflationIndex_);
308 Date startDate = Settings::instance().evaluationDate();
309 bool useInterpolatedCPIFixings = infVol_->indexIsInterpolated();
310 Real nominal = 1.0;
311 vector<Time> expiryTimes;
312 optionBasket_.clear();
313 for (Size j = 0; j < ci.size(); j++) {
314
315 auto cpiCapFloor = QuantLib::ext::dynamic_pointer_cast<CpiCapFloor>(ci[j]);
316 QL_REQUIRE(cpiCapFloor, "Expected CpiCapFloor calibration instruments in DK inflation model data.");
317
318 Date expiryDate = optionMaturityDate(j);
319
320 // check if we want to keep the helper when a reference calibration grid is given
321 auto refCalDate =
322 std::lower_bound(referenceCalibrationDates.begin(), referenceCalibrationDates.end(), expiryDate);
323 if (refCalDate == referenceCalibrationDates.end() || *refCalDate > lastRefCalDate) {
324 Real strikeValue = optionStrikeValue(j);
325 Option::Type capfloor = cpiCapFloor->type() == CapFloor::Cap ? Option::Call : Option::Put;
326 QuantLib::ext::shared_ptr<CPICapFloor> cf =
327 QuantLib::ext::make_shared<CPICapFloor>(capfloor, nominal, startDate, baseCPI, expiryDate, fixCalendar, bdc,
328 fixCalendar, bdc, strikeValue, inflationIndex_, lag);
329 cf->setPricingEngine(engine);
330 Real tte = inflationYearFraction(inflationIndex_->frequency(), useInterpolatedCPIFixings,
331 inflationIndex_->zeroInflationTermStructure()->dayCounter(), baseDate,
332 cf->fixingDate());
333
334 Real tteFromBase = infVol_->timeFromBase(expiryDate);
335
336 Real marketPrem;
337 if (dontCalibrate_)
338 marketPrem = 0.1;
339 else if (tte <= 0 || tteFromBase <= 0)
340 marketPrem = 0.00;
341 else
342 marketPrem = cf->NPV();
343
344 QuantLib::ext::shared_ptr<QuantExt::CpiCapFloorHelper> helper =
345 QuantLib::ext::make_shared<QuantExt::CpiCapFloorHelper>(capfloor, baseCPI, expiryDate, fixCalendar, bdc,
346 fixCalendar, bdc, strikeValue, hIndex, lag,
347 marketPrem);
348
349 // we might produce duplicate expiry times even if the fixing dates are all different
350 if (marketPrem > 0.0 && tte > 0 && tteFromBase > 0 &&
351 std::find_if(expiryTimes.begin(), expiryTimes.end(),
352 [tte](Real x) { return QuantLib::close_enough(x, tte); }) == expiryTimes.end()) {
353 optionBasket_.push_back(helper);
354 helper->performCalculations();
355 expiryTimes.push_back(tte);
356 DLOG("Added InflationOptionHelper index="
357 << data_->index() << ", type=" << (capfloor == Option::Type::Call ? "Cap" : "Floor") << ", expiry="
358 << QuantLib::io::iso_date(expiryDate) << ", baseCPI=" << baseCPI << ", strike=" << strikeValue
359 << ", lag=" << lag << ", marketPremium=" << marketPrem << ", tte=" << tte);
360 optionActive_[j] = true;
361 if (refCalDate != referenceCalibrationDates.end())
362 lastRefCalDate = *refCalDate;
363 } else {
364 if (data_->ignoreDuplicateCalibrationExpiryTimes()) {
365 DLOG("Skipped InflationOptionHelper index="
366 << data_->index() << ", type=" << (capfloor == Option::Type::Call ? "Cap" : "Floor")
367 << ", expiry=" << QuantLib::io::iso_date(expiryDate) << ", baseCPI=" << baseCPI
368 << ", strike=" << strikeValue << ", lag=" << lag << ", marketPremium=" << marketPrem
369 << ", tte=" << tte << " since we already have a helper with the same expiry time.");
370 } else {
371 QL_FAIL("InfDkBuilder: a CPI cap floor calibration instrument with the expiry time, "
372 << tte << ", was already added.");
373 }
374 }
375 }
376 }
377
378 std::sort(expiryTimes.begin(), expiryTimes.end());
379 auto itExpiryTime = unique(expiryTimes.begin(), expiryTimes.end());
380 expiryTimes.resize(distance(expiryTimes.begin(), itExpiryTime));
381
382 optionExpiries_ = Array(expiryTimes.size());
383 for (Size j = 0; j < expiryTimes.size(); j++)
384 optionExpiries_[j] = expiryTimes[j];
385}
386
388 forceCalibration_ = true;
390 forceCalibration_ = false;
391}
392
393} // namespace data
394} // namespace ore
virtual void forceRecalculate()
bool empty() const
Returns true if the calibration basket is empty.
const std::vector< QuantLib::ext::shared_ptr< CalibrationInstrument > > & instruments() const
Simulation Date Grid.
Definition: dategrid.hpp:38
const std::vector< QuantLib::Date > & dates() const
Definition: dategrid.hpp:81
QuantLib::ext::shared_ptr< QuantExt::InfDkParametrization > parametrization() const
QuantLib::ext::shared_ptr< QuantExt::InfDkParametrization > parametrization_
const std::string configuration_
void forceRecalculate() override
void performCalculations() const override
InfDkBuilder(const QuantLib::ext::shared_ptr< ore::data::Market > &market, const QuantLib::ext::shared_ptr< InfDkData > &data, const std::string &configuration=Market::defaultConfiguration, const std::string &referenceCalibrationGrid="", const bool dontCalibrate=false)
bool volSurfaceChanged(const bool updateCache) const
const QuantLib::ext::shared_ptr< InfDkData > data_
QuantLib::ext::shared_ptr< QuantExt::MarketObserver > marketObserver_
QuantLib::Array optionExpiries_
std::vector< QuantLib::ext::shared_ptr< BlackCalibrationHelper > > optionBasket() const
std::vector< QuantLib::Real > infPriceCache_
std::vector< QuantLib::ext::shared_ptr< BlackCalibrationHelper > > optionBasket_
void setCalibrationDone() const
bool requiresRecalibration() const override
QuantLib::Handle< QuantLib::CPIVolatilitySurface > infVol_
Handle< YieldTermStructure > rateCurve_
const QuantLib::ext::shared_ptr< ore::data::Market > market_
void buildCapFloorBasket() const
const std::string referenceCalibrationGrid_
std::vector< bool > optionActive_
Real optionStrikeValue(const Size j) const
QuantLib::ext::shared_ptr< QuantLib::ZeroInflationIndex > inflationIndex_
Date optionMaturityDate(const Size j) const
@ HullWhite
Parametrize volatility as HullWhite sigma(t)
const std::vector< QuantLib::Time > & times() const
const std::vector< QuantLib::Real > & values() const
LgmData::ReversionType reversionType() const
const boost::optional< LgmData::VolatilityType > & volatilityType() const
The date grid class.
Builder for a Dodgson-Kainth inflation model component.
Classes and functions for log message handling.
@ data
Definition: log.hpp:77
#define LOG(text)
Logging Macro (Level = Notice)
Definition: log.hpp:552
#define DLOG(text)
Logging Macro (Level = Debug)
Definition: log.hpp:554
class for holding details of a zero coupon CPI cap floor calibration instrument.
Shared utilities for model building and calibration.
bool isCPIVolSurfaceLogNormal(const boost::shared_ptr< QuantLib::CPIVolatilitySurface > &surface)
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
Date optionMaturity(const boost::variant< Date, Period > &maturity, const QuantLib::Calendar &calendar, const QuantLib::Date &referenceDate)
Definition: utilities.cpp:447
Real cpiCapFloorStrikeValue(const QuantLib::ext::shared_ptr< BaseStrike > &strike, const QuantLib::ext::shared_ptr< ZeroInflationTermStructure > &curve, const QuantLib::Date &optionMaturityDate)
Return a cpi cap/floor strike value, the input strike can be of type absolute or atm forward.
Definition: utilities.cpp:453
Serializable Credit Default Swap.
Definition: namespaces.docs:23
Map text representations to QuantLib/QuantExt types.
QuantLib::BootstrapHelper< QuantLib::OptionletVolatilityStructure > helper
strike description