Logo
Fully annotated reference manual - version 1.8.12
Loading...
Searching...
No Matches
correlationcurve.cpp
Go to the documentation of this file.
1/*
2 Copyright (C) 2019 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 <algorithm>
25#include <ql/cashflows/lineartsrpricer.hpp>
26#include <ql/pricingengines/capfloor/bacheliercapfloorengine.hpp>
27#include <ql/pricingengines/capfloor/blackcapfloorengine.hpp>
28#include <ql/time/daycounters/actual365fixed.hpp>
31
32#include <ql/math/optimization/levenbergmarquardt.hpp>
33#include <ql/math/optimization/problem.hpp>
34#include <ql/math/optimization/projectedconstraint.hpp>
35#include <ql/math/optimization/projection.hpp>
36#include <ql/models/calibrationhelper.hpp>
37#include <ql/quotes/simplequote.hpp>
38#include <ql/termstructures/volatility/optionlet/optionletvolatilitystructure.hpp>
39
40#include <ql/time/calendars/target.hpp>
41#include <ql/time/daycounters/actual360.hpp>
43using namespace QuantLib;
44using namespace std;
45
46namespace ore {
47namespace data {
48
49class CorrelationCurve::CalibrationFunction : public CostFunction {
50public:
51 CalibrationFunction(vector<Handle<Quote>>& correlations, const vector<QuantLib::ext::shared_ptr<QuantExt::CmsCapHelper>>& h,
52 const vector<Real>& weights, const Projection& projection)
53 : correlations_(correlations), instruments_(h), weights_(weights), projection_(projection) {}
54
55 virtual ~CalibrationFunction() {}
56
57 virtual Real value(const Array& params) const override {
58
59 for (Size i = 0; i < correlations_.size(); i++) {
60 QuantLib::ext::shared_ptr<SimpleQuote> q = QuantLib::ext::dynamic_pointer_cast<SimpleQuote>(*correlations_[i]);
61 q->setValue(params[i]);
62 LOG("set corr " << params[i]);
63 }
64 Real value = 0.0;
65 for (Size i = 0; i < instruments_.size(); i++) {
66 Real diff = instruments_[i]->calibrationError();
67 value += diff * diff * weights_[i];
68 }
69 return std::sqrt(value);
70 }
71
72 virtual Array values(const Array& params) const override {
73 for (Size i = 0; i < correlations_.size(); i++) {
74 QuantLib::ext::shared_ptr<SimpleQuote> q = QuantLib::ext::dynamic_pointer_cast<SimpleQuote>(*correlations_[i]);
75 q->setValue(params[i]);
76 }
77 Array values(instruments_.size());
78 for (Size i = 0; i < instruments_.size(); i++) {
79 values[i] = instruments_[i]->calibrationError() * std::sqrt(weights_[i]);
80 }
81 return values;
82 }
83
84 virtual Real finiteDifferenceEpsilon() const override { return 1e-6; }
85
86private:
87 vector<Handle<Quote>> correlations_;
88 mutable vector<QuantLib::ext::shared_ptr<QuantExt::CmsCapHelper>> instruments_;
89 vector<Real> weights_;
90 const Projection projection_;
91};
92
94 const QuantLib::ext::shared_ptr<CorrelationCurveConfig>& config, Date asof, const vector<Handle<Quote>>& prices,
95 vector<Handle<Quote>>& correlations, QuantLib::ext::shared_ptr<QuantExt::CorrelationTermStructure>& curve,
96 map<string, QuantLib::ext::shared_ptr<SwapIndex>>& swapIndices, map<string, QuantLib::ext::shared_ptr<YieldCurve>>& yieldCurves,
97 map<string, QuantLib::ext::shared_ptr<GenericYieldVolCurve>>& swaptionVolCurves) {
98
99 QuantLib::ext::shared_ptr<Conventions> conventions = InstrumentConventions::instance().conventions();
100
101 // build cms pricingengine
102 string ccy = config->currency();
103 string swaptionVol = "SwaptionVolatility/" + ccy + "/" + config->swaptionVolatility();
104 auto it = swaptionVolCurves.find(swaptionVol);
105
106 if (it == swaptionVolCurves.end())
107 QL_FAIL("The swaption vol curve not found " << swaptionVol);
108 Handle<SwaptionVolatilityStructure> vol(it->second->volTermStructure());
109
110 string dc = "Yield/" + ccy + "/" + config->discountCurve();
111 Handle<YieldTermStructure> yts;
112 auto it2 = yieldCurves.find(dc);
113 if (it2 != yieldCurves.end()) {
114 yts = it2->second->handle();
115 } else {
116 QL_FAIL("The discount curve not found, " << dc);
117 }
118
119 Real lower = (vol->volatilityType() == ShiftedLognormal) ? 0.0001 : -2;
120 Real upper = (vol->volatilityType() == ShiftedLognormal) ? 2.0000 : 2;
121
122 LinearTsrPricer::Settings settings;
123 settings.withRateBound(lower, upper);
124
125 Real rev = 0;
126 Handle<Quote> revQuote(QuantLib::ext::shared_ptr<Quote>(new SimpleQuote(rev)));
127
128 QuantLib::ext::shared_ptr<QuantLib::CmsCouponPricer> cmsPricer =
129 QuantLib::ext::make_shared<LinearTsrPricer>(vol, revQuote, yts, settings);
130
131 // build cms spread pricer
132 Handle<QuantExt::CorrelationTermStructure> ch(curve);
133 QuantLib::ext::shared_ptr<FloatingRateCouponPricer> pricer =
134 QuantLib::ext::make_shared<QuantExt::LognormalCmsSpreadPricer>(cmsPricer, ch, yts, 16);
135
136 // build instruments
137 auto it3 = swapIndices.find(config->index1());
138 if (it3 == swapIndices.end())
139 QL_FAIL("The swap index not found " << config->index1());
140
141 QuantLib::ext::shared_ptr<SwapIndex> index1 = it3->second;
142
143 auto it4 = swapIndices.find(config->index2());
144 if (it4 == swapIndices.end())
145 QL_FAIL("The swap index not found " << config->index2());
146
147 QuantLib::ext::shared_ptr<SwapIndex> index2 = it4->second;
148
149 vector<QuantLib::ext::shared_ptr<QuantExt::CmsCapHelper>> instruments;
150
151 QuantLib::ext::shared_ptr<Convention> tmp = conventions->get(config->conventions());
152 QL_REQUIRE(tmp, "no conventions found with id " << config->conventions());
153
154 QuantLib::ext::shared_ptr<CmsSpreadOptionConvention> conv = QuantLib::ext::dynamic_pointer_cast<CmsSpreadOptionConvention>(tmp);
155 QL_REQUIRE(conv != NULL, "CMS Correlation curves require CMSSpreadOption convention ");
156 Period forwardStart = conv->forwardStart();
157 Period spotDays = conv->spotDays();
158 Period cmsTenor = conv->swapTenor();
159 Natural fixingDays = conv->fixingDays();
160 Calendar calendar = conv->calendar();
161 DayCounter dcount = conv->dayCounter();
162 BusinessDayConvention bdc = conv->rollConvention();
163 vector<Period> optionTenors = parseVectorOfValues<Period>(config->optionTenors(), &parsePeriod);
164
165 for (Size i = 0; i < prices.size(); i++) {
166 QuantLib::ext::shared_ptr<QuantExt::CmsCapHelper> inst = QuantLib::ext::make_shared<QuantExt::CmsCapHelper>(
167 asof, index1, index2, yts, prices[i], correlations[i], optionTenors[i], forwardStart, spotDays, cmsTenor,
168 fixingDays, calendar, dcount, bdc, pricer, cmsPricer);
169 instruments.push_back(inst);
170 }
171
172 // set up problem
173 EndCriteria endCriteria(1000, 500, 1E-8, 1E-8, 1E-8);
174 BoundaryConstraint constraint(-1, 1);
175 vector<Real> weights(prices.size(), 1);
176
177 Array prms(prices.size(), 0);
178 vector<bool> all(prms.size(), false);
179 Projection proj(prms, all);
180 ProjectedConstraint pc(constraint, proj);
181
182 QuantLib::ext::shared_ptr<OptimizationMethod> method = QuantLib::ext::make_shared<LevenbergMarquardt>(1E-8, 1E-8, 1E-8);
183
184 CalibrationFunction c(correlations, instruments, weights, proj);
185
186 Problem prob(c, pc, proj.project(prms));
187
188 method->minimize(prob, endCriteria);
189
190 Array result(prob.currentValue());
191}
192
195 map<string, QuantLib::ext::shared_ptr<SwapIndex>>& swapIndices,
196 map<string, QuantLib::ext::shared_ptr<YieldCurve>>& yieldCurves,
197 map<string, QuantLib::ext::shared_ptr<GenericYieldVolCurve>>& swaptionVolCurves) {
198
199 try {
200
201 const QuantLib::ext::shared_ptr<CorrelationCurveConfig>& config =
202 curveConfigs.correlationCurveConfig(spec.curveConfigID());
203
204 // build default correlation termstructure
205 QuantLib::ext::shared_ptr<QuantExt::CorrelationTermStructure> corr;
206
207 if (config->quoteType() == MarketDatum::QuoteType::NONE) {
208
209 corr = QuantLib::ext::shared_ptr<QuantExt::CorrelationTermStructure>(
210 new QuantExt::FlatCorrelation(0, config->calendar(), 0.0, config->dayCounter()));
211
212 } else {
213 QL_REQUIRE(config->dimension() == CorrelationCurveConfig::Dimension::ATM ||
214 config->dimension() == CorrelationCurveConfig::Dimension::Constant,
215 "Unsupported correlation curve building dimension");
216
217 // Check if we are using a regular expression to select the quotes for the curve. If we are, the quotes should
218 // contain exactly one element.
219 auto wildcard = getUniqueWildcard(config->quotes());
220
221 // vector with times, quotes pairs
222 vector<pair<Real, Handle<Quote>>> quotePairs;
223 // Different approaches depending on whether we are using a regex or searching for a list of explicit quotes.
224 if (wildcard) {
225 QL_REQUIRE(config->dimension() == CorrelationCurveConfig::Dimension::ATM,
226 "CorrelationCurve: Wildcards only supported for curve dimension ATM");
227 LOG("Have single quote with pattern " << wildcard->pattern());
228
229 // Loop over quotes and process commodity option quotes matching pattern on asof
230 for (const auto& md : loader.get(*wildcard, asof)) {
231
232 QL_REQUIRE(md->asofDate() == asof, "MarketDatum asofDate '" << md->asofDate() << "' <> asof '" << asof << "'");
233
234 auto q = QuantLib::ext::dynamic_pointer_cast<CorrelationQuote>(md);
235 if (q && q->quoteType() == config->quoteType()) {
236
237 TLOG("The quote " << q->name() << " matched the pattern");
238
239 Date expiryDate = getDateFromDateOrPeriod(q->expiry(), asof, config->calendar(), config->businessDayConvention());
240 if (expiryDate > asof) {
241 // add a <time, quote> pair
242 quotePairs.push_back(make_pair(config->dayCounter().yearFraction(asof, expiryDate), q->quote()));
243 TLOG("Added quote " << q->name() << ": (" << io::iso_date(expiryDate) << "," << fixed
244 << setprecision(9) << q->quote()->value() << ")");
245 }
246 }
247 }
248
249 if (quotePairs.size() == 0) {
250 Real corr = config->index1() == config->index2() ? 1.0 : 0.0;
251 WLOG("CorrelationCurve: No quotes found for correlation curve: "
252 << config->curveID() << ", continuing with correlation " << corr << ".");
253 corr_ = QuantLib::ext::shared_ptr<QuantExt::CorrelationTermStructure>(
254 new QuantExt::FlatCorrelation(0, config->calendar(), corr, config->dayCounter()));
255 return;
256 }
257
258 } else {
259 vector<Period> optionTenors = parseVectorOfValues<Period>(config->optionTenors(), &parsePeriod);
260 quotePairs.resize(optionTenors.size());
261 bool failed = false;
262
263 // search market data loader for quotes, logging missing ones
264 for (auto& q : config->quotes()) {
265 if (loader.has(q, asof)) {
266 QuantLib::ext::shared_ptr<CorrelationQuote> c =
267 QuantLib::ext::dynamic_pointer_cast<CorrelationQuote>(loader.get(q, asof));
268
269 Size i = std::find(optionTenors.begin(), optionTenors.end(), parsePeriod(c->expiry())) -
270 optionTenors.begin();
271 QL_REQUIRE(i < optionTenors.size(), "CorrelationCurve: correlation tenor not found for " << q);
272
273 Real time;
274 // add the expiry time - don't need for Constant curves
275 if (config->dimension() != CorrelationCurveConfig::Dimension::Constant) {
276 Date d = config->calendar().advance(asof, optionTenors[i], config->businessDayConvention());
277 time = config->dayCounter().yearFraction(asof, d);
278 }
279 quotePairs[i] = make_pair(time, c->quote());
280
281 TLOG("CorrelationCurve: Added quote " << c->name() << ", tenor " << optionTenors[i] << ", with value "
282 << fixed << setprecision(9) << c->quote()->value() );
283 } else {
284 DLOGGERSTREAM("could not find correlation quote " << q);
285 failed = true;
286 }
287 }
288 // fail if any quotes missing
289 if (failed) {
290 QL_FAIL("could not build correlation curve");
291 }
292 }
293 vector<Handle<Quote>> corrs;
294
295 // sort the quotes
296 std::sort(quotePairs.begin(), quotePairs.end());
297 vector<Real> times;
298 vector<Handle<Quote>> quotes;
299 for (Size i = 0; i < quotePairs.size(); i++) {
300 if (config->quoteType() == MarketDatum::QuoteType::RATE) {
301 corrs.push_back(quotePairs[i].second);
302 } else {
303 Handle<Quote> q(QuantLib::ext::make_shared<SimpleQuote>(0));
304 corrs.push_back(q);
305 }
306 quotes.push_back(quotePairs[i].second);
307 times.push_back(quotePairs[i].first);
308 }
309
310 // build correlation termstructure
311 bool flat = (config->dimension() == CorrelationCurveConfig::Dimension::Constant || quotes.size() == 1);
312 LOG("building " << (flat ? "flat" : "interpolated curve") << " correlation termstructure");
313
314 if (flat) {
315 corr = QuantLib::ext::shared_ptr<QuantExt::CorrelationTermStructure>(
316 new QuantExt::FlatCorrelation(0, config->calendar(), corrs[0], config->dayCounter()));
317
318 corr->enableExtrapolation(config->extrapolate());
319 } else {
320
321 corr = QuantLib::ext::shared_ptr<QuantExt::CorrelationTermStructure>(
322 new QuantExt::InterpolatedCorrelationCurve<Linear>(times, corrs, config->dayCounter(),
323 config->calendar()));
324 }
325
326 if (config->quoteType() == MarketDatum::QuoteType::PRICE) {
327
328 if (config->correlationType() == CorrelationCurveConfig::CorrelationType::CMSSpread) {
329 calibrateCMSSpreadCorrelations(config, asof, quotes, corrs, corr, swapIndices,
330 yieldCurves, swaptionVolCurves);
331 } else {
332 QL_FAIL("price calibration only supported for CMSSpread correlations");
333 }
334 }
335
336 LOG("Returning correlation surface for config " << spec);
337 }
338 corr_ = corr;
339 } catch (std::exception& e) {
340 QL_FAIL("correlation curve building failed for curve " << spec.curveConfigID() << " on date "
341 << io::iso_date(asof) << ": " << e.what());
342 } catch (...) {
343 QL_FAIL("correlation curve building failed: unknown error");
344 }
345}
346} // namespace data
347} // namespace ore
const std::map< std::pair< std::string, std::string >, Handle< QuantExt::CorrelationTermStructure > > & correlations_
const CorrelationCurveSpec & spec() const
QuantLib::ext::shared_ptr< QuantExt::CorrelationTermStructure > corr_
CorrelationCurve()
Default constructor.
void calibrateCMSSpreadCorrelations(const QuantLib::ext::shared_ptr< CorrelationCurveConfig > &config, Date asof, const vector< Handle< Quote > > &prices, vector< Handle< Quote > > &quotes, QuantLib::ext::shared_ptr< QuantExt::CorrelationTermStructure > &curve, map< string, QuantLib::ext::shared_ptr< SwapIndex > > &swapIndices, map< string, QuantLib::ext::shared_ptr< YieldCurve > > &yieldCurves, map< string, QuantLib::ext::shared_ptr< GenericYieldVolCurve > > &swaptionVolCurves)
Correlation curve description.
Definition: curvespec.hpp:467
Container class for all Curve Configurations.
const std::string & curveConfigID() const
Definition: curvespec.hpp:83
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
virtual bool has(const std::string &name, const QuantLib::Date &d) const
Default implementation, returns false if get throws or returns a null pointer.
Definition: loader.cpp:53
SafeStack< ValueType > value
Currency and instrument specific conventions/defaults.
Date getDateFromDateOrPeriod(const string &token, Date asof, QuantLib::Calendar cal, QuantLib::BusinessDayConvention bdc)
Get a date from a date string or period.
Period parsePeriod(const string &s)
Convert text to QuantLib::Period.
Definition: parsers.cpp:171
Classes and functions for log message handling.
@ data
Definition: log.hpp:77
#define LOG(text)
Logging Macro (Level = Notice)
Definition: log.hpp:552
#define WLOG(text)
Logging Macro (Level = Warning)
Definition: log.hpp:550
#define TLOG(text)
Logging Macro (Level = Data)
Definition: log.hpp:556
#define DLOGGERSTREAM(text)
Definition: log.hpp:632
Market Datum parser.
Calendar calendar
Definition: utilities.cpp:441
boost::optional< Wildcard > getUniqueWildcard(const C &c)
checks if at most one element in C has a wild card and returns it in this case
Definition: wildcard.hpp:65
Serializable Credit Default Swap.
Definition: namespaces.docs:23
vector< string > curveConfigs
utilities for wildcard handling