Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
inflationcurve.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
22
24
25#include <ql/cashflows/couponpricer.hpp>
26#include <ql/cashflows/yoyinflationcoupon.hpp>
27#include <ql/pricingengines/swap/discountingswapengine.hpp>
28#include <ql/termstructures/inflation/piecewiseyoyinflationcurve.hpp>
30#include <ql/time/daycounters/actual365fixed.hpp>
32#include <algorithm>
33
34using namespace QuantLib;
35using namespace std;
36using namespace ore::data;
37
38namespace ore {
39namespace data {
40
43 map<string, QuantLib::ext::shared_ptr<YieldCurve>>& yieldCurves,
44 const bool buildCalibrationInfo) {
45
46 try {
47
48 const QuantLib::ext::shared_ptr<InflationCurveConfig>& config = curveConfigs.inflationCurveConfig(spec.curveConfigID());
49
50 const QuantLib::ext::shared_ptr<Conventions>& conventions = InstrumentConventions::instance().conventions();
51 QuantLib::ext::shared_ptr<InflationSwapConvention> conv =
52 QuantLib::ext::dynamic_pointer_cast<InflationSwapConvention>(conventions->get(config->conventions()));
53
54 QL_REQUIRE(conv != nullptr, "convention " << config->conventions() << " could not be found.");
55
56 Handle<YieldTermStructure> nominalTs;
57 auto it = yieldCurves.find(config->nominalTermStructure());
58 if (it != yieldCurves.end()) {
59 nominalTs = it->second->handle();
60 } else {
61 QL_FAIL("The nominal term structure, " << config->nominalTermStructure()
62 << ", required in the building "
63 "of the curve, "
64 << spec.name() << ", was not found.");
65 }
66
67 // We loop over all market data, looking for quotes that match the configuration
68
69 const std::vector<string> strQuotes = config->swapQuotes();
70 std::vector<Handle<Quote>> quotes(strQuotes.size(), Handle<Quote>());
71 std::vector<Period> terms(strQuotes.size());
72 std::vector<bool> isZc(strQuotes.size(), true);
73
74 std::ostringstream ss1;
76 Wildcard w1(ss1.str());
77 auto data1 = loader.get(w1, asof);
78
79 std::ostringstream ss2;
81 Wildcard w2(ss2.str());
82 auto data2 = loader.get(w2, asof);
83
84 data1.merge(data2);
85
86 for (const auto& md : data1) {
87
88 QL_REQUIRE(md->asofDate() == asof, "MarketDatum asofDate '" << md->asofDate() << "' <> asof '" << asof << "'");
89
90 if ((md->instrumentType() == MarketDatum::InstrumentType::ZC_INFLATIONSWAP ||
91 (md->instrumentType() == MarketDatum::InstrumentType::YY_INFLATIONSWAP &&
92 config->type() == InflationCurveConfig::Type::YY))) {
93
94 QuantLib::ext::shared_ptr<ZcInflationSwapQuote> q = QuantLib::ext::dynamic_pointer_cast<ZcInflationSwapQuote>(md);
95 if (q) {
96 auto it = std::find(strQuotes.begin(), strQuotes.end(), q->name());
97 if (it != strQuotes.end()) {
98 QL_REQUIRE(quotes[it - strQuotes.begin()].empty(), "duplicate quote " << q->name());
99 quotes[it - strQuotes.begin()] = q->quote();
100 terms[it - strQuotes.begin()] = q->term();
101 isZc[it - strQuotes.begin()] = true;
102 }
103 }
104
105 QuantLib::ext::shared_ptr<YoYInflationSwapQuote> q2 = QuantLib::ext::dynamic_pointer_cast<YoYInflationSwapQuote>(md);
106 if (q2) {
107 auto it = std::find(strQuotes.begin(), strQuotes.end(), q2->name());
108 if (it != strQuotes.end()) {
109 QL_REQUIRE(quotes[it - strQuotes.begin()].empty(), "duplicate quote " << q2->name());
110 quotes[it - strQuotes.begin()] = q2->quote();
111 terms[it - strQuotes.begin()] = q2->term();
112 isZc[it - strQuotes.begin()] = false;
113 }
114 }
115 }
116 }
117
118 // do we have all quotes and do we derive yoy quotes from zc ?
119 for (Size i = 0; i < strQuotes.size(); ++i) {
120 QL_REQUIRE(!quotes[i].empty(), "quote " << strQuotes[i] << " not found in market data.");
121 QL_REQUIRE(isZc[i] == isZc[0], "mixed zc and yoy quotes");
122 }
123 bool derive_yoy_from_zc = (config->type() == InflationCurveConfig::Type::YY && isZc[0]);
124
125 // construct seasonality
126 QuantLib::ext::shared_ptr<Seasonality> seasonality;
127 if (config->seasonalityBaseDate() != Null<Date>()) {
128 if (config->overrideSeasonalityFactors().empty()) {
129 std::vector<string> strFactorIDs = config->seasonalityFactors();
130 std::vector<double> factors(strFactorIDs.size());
131 for (Size i = 0; i < strFactorIDs.size(); i++) {
132 QuantLib::ext::shared_ptr<MarketDatum> marketQuote = loader.get(strFactorIDs[i], asof);
133 // Check that we have a valid seasonality factor
134 if (marketQuote) {
135 QL_REQUIRE(marketQuote->instrumentType() == MarketDatum::InstrumentType::SEASONALITY,
136 "Market quote (" << marketQuote->name() << ") not of type seasonality.");
137 // Currently only monthly seasonality with 12 multiplicative factors os allowed
138 QL_REQUIRE(config->seasonalityFrequency() == Monthly && strFactorIDs.size() == 12,
139 "Only monthly seasonality with 12 factors is allowed. Provided "
140 << config->seasonalityFrequency() << " with " << strFactorIDs.size()
141 << " factors.");
142 QuantLib::ext::shared_ptr<SeasonalityQuote> sq =
143 QuantLib::ext::dynamic_pointer_cast<SeasonalityQuote>(marketQuote);
144 QL_REQUIRE(sq, "Could not cast to SeasonalityQuote, internal error.");
145 QL_REQUIRE(sq->type() == "MULT",
146 "Market quote (" << sq->name() << ") not of multiplicative type.");
147 Size seasBaseDateMonth = ((Size)config->seasonalityBaseDate().month());
148 int findex = sq->applyMonth() - seasBaseDateMonth;
149 if (findex < 0)
150 findex += 12;
151 QL_REQUIRE(findex >= 0 && findex < 12, "Unexpected seasonality index " << findex);
152 factors[findex] = sq->quote()->value();
153 } else {
154 QL_FAIL("Could not find quote for ID " << strFactorIDs[i] << " with as of date "
155 << io::iso_date(asof) << ".");
156 }
157 }
158 QL_REQUIRE(!factors.empty(), "no seasonality factors found");
159 seasonality = QuantLib::ext::make_shared<MultiplicativePriceSeasonality>(
160 config->seasonalityBaseDate(), config->seasonalityFrequency(), factors);
161 } else {
162 // override market data by explicit list
163 seasonality = QuantLib::ext::make_shared<MultiplicativePriceSeasonality>(config->seasonalityBaseDate(),
164 config->seasonalityFrequency(),
165 config->overrideSeasonalityFactors());
166 }
167 }
168
169 // Get helper start date and curve's obs lag.
170 auto p = getStartAndLag(asof, *conv);
171 Date swapStart = p.first;
172 Period curveObsLag = p.second == Period() ? config->lag() : p.second;
173
174 // construct curve (ZC or YY depending on configuration)
175 std::vector<Date> pillarDates;
176
177 interpolatedIndex_ = conv->interpolated();
178 CPI::InterpolationType observationInterpolation = interpolatedIndex_ ? CPI::Linear : CPI::Flat;
179 QuantLib::ext::shared_ptr<YoYInflationIndex> zc_to_yoy_conversion_index;
180 if (config->type() == InflationCurveConfig::Type::ZC || derive_yoy_from_zc) {
181 // ZC Curve
182 std::vector<QuantLib::ext::shared_ptr<QuantExt::ZeroInflationTraits::helper>> instruments;
183 QuantLib::ext::shared_ptr<ZeroInflationIndex> index = conv->index();
184 for (Size i = 0; i < strQuotes.size(); ++i) {
185 // QL conventions do not incorporate settlement delay => patch here once QL is patched
186 Date maturity = swapStart + terms[i];
187 QuantLib::ext::shared_ptr<QuantExt::ZeroInflationTraits::helper> instrument =
188 QuantLib::ext::make_shared<ZeroCouponInflationSwapHelper>(
189 quotes[i], conv->observationLag(), maturity, conv->fixCalendar(), conv->fixConvention(),
190 conv->dayCounter(), index, observationInterpolation, nominalTs, swapStart);
191 // The instrument gets registered to update on change of evaluation date. This triggers a
192 // rebootstrapping of the curve. In order to avoid this during simulation we unregister from the
193 // evaluationDate.
194 instrument->unregisterWith(Settings::instance().evaluationDate());
195 instruments.push_back(instrument);
196 }
197 // base zero / yoy rate: if given, take it, otherwise set it to observered zeroRate
198 Real baseRate = quotes[0]->value();
199 if (config->baseRate() != Null<Real>()) {
200 baseRate = config->baseRate();
201 } else if (index) {
202 try {
204 config->useLastAvailableFixingAsBaseDate(), swapStart, asof, terms[0], conv->dayCounter(),
205 conv->observationLag(), quotes[0]->value(), curveObsLag, config->dayCounter(), index,
206 interpolatedIndex_, seasonality);
207 } catch (const std::exception& e) {
208 WLOG("base rate estimation failed with " << e.what() << ", fallback to use first quote");
209 baseRate = quotes[0]->value();
210 }
211 }
212 curve_ = QuantLib::ext::shared_ptr<QuantExt::PiecewiseZeroInflationCurve<Linear>>(
214 asof, config->calendar(), config->dayCounter(), curveObsLag, config->frequency(), baseRate,
215 instruments, config->tolerance(), index, config->useLastAvailableFixingAsBaseDate()));
216
217 // force bootstrap so that errors are thrown during the build, not later
218 QuantLib::ext::static_pointer_cast<QuantExt::PiecewiseZeroInflationCurve<Linear>>(curve_)->zeroRate(QL_EPSILON);
219 if (derive_yoy_from_zc) {
220 // set up yoy wrapper with empty ts, so that zero index is used to forecast fixings
221 // for this link the appropriate curve to the zero index
222 zc_to_yoy_conversion_index = QuantLib::ext::make_shared<QuantExt::YoYInflationIndexWrapper>(
223 index->clone(Handle<ZeroInflationTermStructure>(
224 QuantLib::ext::dynamic_pointer_cast<ZeroInflationTermStructure>(curve_))),
226 }
227 }
228 if (config->type() == InflationCurveConfig::Type::YY) {
229 // YOY Curve
230 std::vector<QuantLib::ext::shared_ptr<YoYInflationTraits::helper>> instruments;
231 QuantLib::ext::shared_ptr<ZeroInflationIndex> zcindex = conv->index();
232 QuantLib::ext::shared_ptr<YoYInflationIndex> index =
233 QuantLib::ext::make_shared<QuantExt::YoYInflationIndexWrapper>(zcindex, interpolatedIndex_);
234 QuantLib::ext::shared_ptr<InflationCouponPricer> yoyCpnPricer =
235 QuantLib::ext::make_shared<YoYInflationCouponPricer>(nominalTs);
236 for (Size i = 0; i < strQuotes.size(); ++i) {
237 Date maturity = swapStart + terms[i];
238 Real effectiveQuote = quotes[i]->value();
239 if (derive_yoy_from_zc) {
240 // construct a yoy swap just as it is done in the yoy inflation helper
241 Schedule schedule = MakeSchedule()
242 .from(swapStart)
243 .to(maturity)
244 .withTenor(1 * Years)
245 .withConvention(Unadjusted)
246 .withCalendar(conv->fixCalendar())
247 .backwards();
248 YearOnYearInflationSwap tmp(YearOnYearInflationSwap::Payer, 1000000.0, schedule, 0.02,
249 conv->dayCounter(), schedule, zc_to_yoy_conversion_index,
250 conv->observationLag(), 0.0, conv->dayCounter(), conv->fixCalendar(),
251 conv->fixConvention());
252 for (auto& c : tmp.yoyLeg()) {
253 auto cpn = QuantLib::ext::dynamic_pointer_cast<YoYInflationCoupon>(c);
254 QL_REQUIRE(cpn, "yoy inflation coupon expected, could not cast");
255 cpn->setPricer(yoyCpnPricer);
256 }
257 QuantLib::ext::shared_ptr<PricingEngine> engine =
258 QuantLib::ext::make_shared<QuantLib::DiscountingSwapEngine>(nominalTs);
259 tmp.setPricingEngine(engine);
260 effectiveQuote = tmp.fairRate();
261 DLOG("Derive " << terms[i] << " yoy quote " << effectiveQuote << " from zc quote "
262 << quotes[i]->value());
263 }
264 // QL conventions do not incorporate settlement delay => patch here once QL is patched
265 QuantLib::ext::shared_ptr<YoYInflationTraits::helper> instrument =
266 QuantLib::ext::make_shared<YearOnYearInflationSwapHelper>(
267 Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(effectiveQuote)), conv->observationLag(),
268 maturity, conv->fixCalendar(), conv->fixConvention(), conv->dayCounter(), index, nominalTs,
269 swapStart);
270 instrument->unregisterWith(Settings::instance().evaluationDate());
271 instruments.push_back(instrument);
272 pillarDates.push_back(instrument->pillarDate());
273 }
274 // base zero rate: if given, take it, otherwise set it to first quote
275 Real baseRate =
276 config->baseRate() != Null<Real>() ? config->baseRate() : instruments.front()->quote()->value();
277 curve_ = QuantLib::ext::shared_ptr<PiecewiseYoYInflationCurve<Linear>>(new PiecewiseYoYInflationCurve<Linear>(
278 asof, config->calendar(), config->dayCounter(), curveObsLag, config->frequency(), interpolatedIndex_,
279 baseRate, instruments, config->tolerance()));
280 // force bootstrap so that errors are thrown during the build, not later
281 QuantLib::ext::static_pointer_cast<PiecewiseYoYInflationCurve<Linear>>(curve_)->yoyRate(QL_EPSILON);
282 }
283 if (seasonality != nullptr) {
284 curve_->setSeasonality(seasonality);
285 }
286 curve_->enableExtrapolation(config->extrapolate());
287 curve_->unregisterWith(Settings::instance().evaluationDate());
288
289 // set calibration info
290
291 if (buildCalibrationInfo) {
292
293 if (pillarDates.empty()) {
294 // default: fill pillar dates with monthly schedule up to last term given in the curve config (but max
295 // 60y)
296 Date maturity = swapStart + terms.back();
297 for (Size i = 1; i < 60 * 12; ++i) {
298 Date current = inflationPeriod(curve_->baseDate() + i * Months, curve_->frequency()).first;
299 if (current + curve_->observationLag() <= maturity)
300 pillarDates.push_back(current);
301 else
302 break;
303 }
304 }
305
306 if (config->type() == InflationCurveConfig::Type::YY) {
307 auto yoyCurve = QuantLib::ext::dynamic_pointer_cast<YoYInflationCurve>(curve_);
308 QL_REQUIRE(yoyCurve, "internal error: expected YoYInflationCurve (inflation curve builder)");
309 auto calInfo = QuantLib::ext::make_shared<YoYInflationCurveCalibrationInfo>();
310 calInfo->dayCounter = config->dayCounter().name();
311 calInfo->calendar = config->calendar().empty() ? "null" : config->calendar().name();
312 calInfo->baseDate = curve_->baseDate();
313 for (Size i = 0; i < pillarDates.size(); ++i) {
314 calInfo->pillarDates.push_back(pillarDates[i]);
315 calInfo->yoyRates.push_back(yoyCurve->yoyRate(pillarDates[i], 0 * Days));
316 calInfo->times.push_back(yoyCurve->timeFromReference(pillarDates[i]));
317 }
318 calibrationInfo_ = calInfo;
319 }
320
321 if (config->type() == InflationCurveConfig::Type::ZC) {
322 auto zcCurve = QuantLib::ext::dynamic_pointer_cast<ZeroInflationTermStructure>(curve_);
323 QL_REQUIRE(zcCurve, "internal error: expected ZeroInflationCurve (inflation curve builder)");
324 auto zcIndex = conv->index()->clone(Handle<ZeroInflationTermStructure>(zcCurve));
325 auto calInfo = QuantLib::ext::make_shared<ZeroInflationCurveCalibrationInfo>();
326 calInfo->dayCounter = config->dayCounter().name();
327 calInfo->calendar = config->calendar().empty() ? "null" : config->calendar().name();
328 calInfo->baseDate = curve_->baseDate();
329 auto lim = inflationPeriod(curve_->baseDate(), curve_->frequency());
330 try {
331 calInfo->baseCpi = conv->index()->fixing(lim.first);
332 } catch (...) {
333 }
334 for (Size i = 0; i < pillarDates.size(); ++i) {
335 calInfo->pillarDates.push_back(pillarDates[i]);
336 calInfo->zeroRates.push_back(zcCurve->zeroRate(pillarDates[i], 0 * Days));
337 calInfo->times.push_back(zcCurve->timeFromReference(pillarDates[i]));
338 Real cpi = 0.0;
339 try {
340 cpi = zcIndex->fixing(pillarDates[i]);
341 } catch (...) {
342 }
343 calInfo->forwardCpis.push_back(cpi);
344 }
345 calibrationInfo_ = calInfo;
346 }
347 }
348
349 } catch (std::exception& e) {
350 QL_FAIL("inflation curve building failed: " << e.what());
351 } catch (...) {
352 QL_FAIL("inflation curve building failed: unknown error");
353 }
354}
355
356} // namespace data
357} // namespace ore
Container class for all Curve Configurations.
const std::string & curveConfigID() const
Definition: curvespec.hpp:83
string name() const
returns the unique curve name
Definition: curvespec.hpp:78
QuantLib::ext::shared_ptr< InflationCurveCalibrationInfo > calibrationInfo_
QuantLib::ext::shared_ptr< InflationTermStructure > curve_
const InflationCurveSpec & spec() const
getters
Inflation curve description.
Definition: curvespec.hpp:314
Market data loader base class.
Definition: loader.hpp:47
virtual QuantLib::ext::shared_ptr< MarketDatum > get(const std::string &name, const QuantLib::Date &d) const
get quote by its unique name, throws if not existent, override in derived classes for performance
Definition: loader.cpp:24
SafeStack< ValueType > value
inflation curve class
Classes and functions for log message handling.
@ data
Definition: log.hpp:77
#define DLOG(text)
Logging Macro (Level = Debug)
Definition: log.hpp:554
#define WLOG(text)
Logging Macro (Level = Warning)
Definition: log.hpp:550
Time maturity
Definition: utilities.cpp:66
QuantLib::Rate guessCurveBaseRate(const bool baseDateLastKnownFixing, const QuantLib::Date &swapStart, const QuantLib::Date &asof, const QuantLib::Period &swapTenor, const QuantLib::DayCounter &swapZCLegDayCounter, const QuantLib::Period &swapObsLag, const QuantLib::Rate zeroCouponRate, const QuantLib::Period &curveObsLag, const QuantLib::DayCounter &curveDayCounter, const boost::shared_ptr< QuantLib::ZeroInflationIndex > &index, const bool interpolated, const boost::shared_ptr< QuantLib::Seasonality > &seasonality)
std::pair< QuantLib::Date, QuantLib::Period > getStartAndLag(const QuantLib::Date &asof, const InflationSwapConvention &conv)
Serializable Credit Default Swap.
Definition: namespaces.docs:23
vector< string > curveConfigs