Detailed constructor.
46 {
47
48 try {
49
51
52 dc_ = Actual365Fixed();
53 if (config->dayCountID() == "") {
55 } else {
57 }
58
59
61 if (!config->calendar().empty()) {
62 try {
64 } catch (exception& ex) {
65 WLOG(
"Failed to get Calendar name for " << config->calendar() <<
":" << ex.what());
66 }
67 }
70 }
71
72
74
75
76 Handle<Quote> equitySpot;
77 Handle<YieldTermStructure> forecastYieldTermStructure;
78 Handle<YieldTermStructure> dividendYieldTermStructure;
79
80
81 YieldCurveSpec ycspec(config->currency(), config->forecastingCurve());
82
83
84
85
86 auto itr = requiredYieldCurves.find(ycspec.name());
87 QL_REQUIRE(itr != requiredYieldCurves.end(),
88 "Yield Curve Spec - " << ycspec.name() << " - not found during equity curve build");
89 QuantLib::ext::shared_ptr<YieldCurve> yieldCurve = itr->second;
90 forecastYieldTermStructure = yieldCurve->handle();
91
92
95
96
97
98
99
100 vector<QuantLib::ext::shared_ptr<EquityForwardQuote>> qt;
101 vector<QuantLib::ext::shared_ptr<EquityOptionQuote>> oqt;
102 Size quotesRead = 0;
103 Size quotesExpired = 0;
104
106
110 wildcard) {
111 DLOG(
"Wild card quote specified for " << config->curveID())
112 } else {
114 oqt.reserve(config->fwdQuotes().size());
115 } else {
116 quotes_.reserve(config->fwdQuotes().size());
117 terms_.reserve(config->fwdQuotes().size());
118 }
119 }
120
121
122
123 auto q = QuantLib::ext::dynamic_pointer_cast<EquitySpotQuote>(loader.get(config->equitySpotQuoteID(), asof));
124 QL_REQUIRE(q != nullptr, "expected '" << config->equitySpotQuoteID() << "' to be an EquitySpotQuote");
126
127 equitySpot = Handle<Quote>(QuantLib::ext::make_shared<SimpleQuote>(spot));
128
131 if (wildcard) {
132 for (auto const& md : loader.get(*wildcard, asof)) {
133 auto q = QuantLib::ext::dynamic_pointer_cast<EquityForwardQuote>(md);
134 QL_REQUIRE(q != nullptr, "expected '" << md->name() << "' to be an EquityForwardQuote");
135 QL_REQUIRE(find(qt.begin(), qt.end(), q) == qt.end(),
136 "duplicate market datum found for " << q->name());
137 if (asof < q->expiryDate()) {
138 DLOG(
"EquityCurve Forward Price found for quote: " << q->name());
139 qt.push_back(q);
140 quotesRead++;
141 } else {
142 ++quotesExpired;
143 DLOG(
"Ignore expired ForwardPrice/ForwardDividendPrice quote "
144 << q->name() << ", expired at " << io::iso_date(q->expiryDate()));
145 }
146 }
147 } else {
148 for (auto const& md :
149 loader.get(std::set<std::string>(config->fwdQuotes().begin(), config->fwdQuotes().end()), asof)) {
150 auto q = QuantLib::ext::dynamic_pointer_cast<EquityForwardQuote>(md);
151 QL_REQUIRE(q != nullptr, "expected '" << md->name() << "' to be an EquityForwardQuote");
152 if (asof < q->expiryDate()) {
154 QL_REQUIRE(isUnique, "duplicate market datum found for " << q->name());
155 terms_.push_back(q->expiryDate());
156
158 quotesRead++;
159 } else {
160 DLOG(
"Ignore expired ForwardPrice/ForwardDividendPrice quote "
161 << q->name() << ", expired at " << io::iso_date(q->expiryDate()));
162 ++quotesExpired;
163 }
164 }
165 }
166 }
167
169 if (wildcard) {
170 for (auto const& md : loader.get(*wildcard, asof)) {
171 auto q = QuantLib::ext::dynamic_pointer_cast<EquityOptionQuote>(md);
172 QL_REQUIRE(q != nullptr, "expected '" << md->name() << "' to be an EquityOptionQuote");
173 QL_REQUIRE(find(oqt.begin(), oqt.end(), q) == oqt.end(),
174 "duplicate market datum found for " << q->name());
176 DLOG(
"EquityCurve Volatility Price found for quote: " << q->name());
177 oqt.push_back(q);
178 quotesRead++;
179 } else {
180 ++quotesExpired;
181 DLOG(
"Ignore expired OptionPremium quote " << q->name() <<
", expired at " << q->expiry());
182 }
183 }
184 } else {
185 for (auto const& md :
186 loader.get(std::set<std::string>(config->fwdQuotes().begin(), config->fwdQuotes().end()), asof)) {
187 auto q = QuantLib::ext::dynamic_pointer_cast<EquityOptionQuote>(md);
188 QL_REQUIRE(q != nullptr, "expected '" << md->name() << "' to be an EquityOptionQuote");
190 DLOG(
"EquityCurve Volatility Price found for quote: " << q->name());
191 oqt.push_back(q);
192 quotesRead++;
193 } else {
194 ++quotesExpired;
195 DLOG(
"Ignore expired OptionPremium quote " << q->name() <<
", expired at " << q->expiry());
196 }
197 }
198 }
199 }
200
202 for (auto const& md :
203 loader.get(std::set<std::string>(config->fwdQuotes().begin(), config->fwdQuotes().end()), asof)) {
204 QuantLib::ext::shared_ptr<EquityDividendYieldQuote> q =
205 QuantLib::ext::dynamic_pointer_cast<EquityDividendYieldQuote>(md);
206 QL_REQUIRE(q != nullptr, "expected '" << md->name() << "' to be an EquityDividendYieldQuote");
207 if (q->tenorDate() > asof) {
209 QL_REQUIRE(isUnique, "duplicate market datum found for " << q->name());
210 DLOG(
"EquityCurve Dividend Yield found for quote: " << q->name());
211 terms_.push_back(q->tenorDate());
212 quotes_.push_back(q->quote()->value());
213 quotesRead++;
214 } else {
215 DLOG(
"Ignore expired DividendYield quote " << q->name() <<
", expired at "
216 << io::iso_date(q->tenorDate()));
217 ++quotesExpired;
218 }
219 }
220 }
221
222
223 DLOG(
"EquityCurve: read " << quotesRead + quotesExpired <<
" quotes of type " << config->type());
224 DLOG(
"EquityCurve: ignored " << quotesExpired <<
" expired quotes.");
225
226 if (!wildcard) {
227 QL_REQUIRE(quotesRead + quotesExpired == config->fwdQuotes().size(),
228 "read " << quotesRead << "quotes and " << quotesExpired << " expire quotes, but "
229 << config->fwdQuotes().size() << " required.");
230 }
231
232
233
234 QL_REQUIRE(
terms_.size() ==
quotes_.size(),
"Internal error: terms and quotes mismatch");
236 vector<pair<Date, Real>> tmpSort;
237
239 [](const Date& d, const Real& q) { return make_pair(d, q); });
240
241 std::sort(tmpSort.begin(), tmpSort.end(), [](const pair<Date, Real>& left, const pair<Date, Real>& right) {
242 return left.first < right.first;
243 });
244
245 for (Size i = 0; i < tmpSort.size(); ++i) {
246 terms_[i] = tmpSort[i].first;
247 quotes_[i] = tmpSort[i].second;
248 }
249
250 for (Size i = 0; i <
terms_.size(); i++) {
251 QL_REQUIRE(
terms_[i] > asof,
"Invalid Fwd Expiry " <<
terms_[i] <<
" vs. " << asof);
252 if (i > 0) {
253 QL_REQUIRE(
terms_[i] >
terms_[i - 1],
"terms must be increasing in curve config");
254 }
255 }
256 }
257
258
260
261
264
265 if (qt.size() > 0) {
266 DLOG(
"Building Equity Dividend Yield curve from Forward/Future prices");
267
268
269 if (wildcard) {
270 QL_REQUIRE(quotesRead > 0, "Wild card quote specified, but no quotes read.");
271
272
273 std::sort(qt.begin(), qt.end(),
274 [](const QuantLib::ext::shared_ptr<EquityForwardQuote>& a,
275 const QuantLib::ext::shared_ptr<EquityForwardQuote>& b) -> bool {
276 return a->expiryDate() < b->expiryDate();
277 });
278
279
280 for (Size i = 0; i < qt.size(); i++) {
281 terms_.push_back(qt[i]->expiryDate());
282
284 }
285 }
286 }
288 DLOG(
"No Equity Forward quotes provided for " << config->curveID()
289 << ", continuing without dividend curve.");
291 }
293
294 if (oqt.size() == 0) {
295 DLOG(
"No Equity Option quotes provided for " << config->curveID()
296 << ", continuing without dividend curve.");
298 } else {
299 DLOG(
"Building Equity Dividend Yield curve from Option Volatilities");
300 vector<QuantLib::ext::shared_ptr<EquityOptionQuote>> calls, puts;
301 vector<Date> callDates, putDates;
302 vector<Real> callStrikes, putStrikes;
303 vector<Real> callPremiums, putPremiums;
304 Calendar cal = NullCalendar();
305
306
307 for (auto q : oqt) {
308 if (q->quote()->value() > 0) {
309 if (q->isCall()) {
310 calls.push_back(q);
311 } else {
312 puts.push_back(q);
313 }
314 }
315 }
316
317
318
319 for (Size i = 0; i < calls.size(); i++) {
320 for (Size j = 0; j < puts.size(); j++) {
321 if (calls[i]->expiry() == puts[j]->expiry()) {
322 auto callAbsoluteStrike = QuantLib::ext::dynamic_pointer_cast<AbsoluteStrike>(calls[i]->strike());
323 auto putAbsoluteStrike = QuantLib::ext::dynamic_pointer_cast<AbsoluteStrike>(puts[j]->strike());
324 QL_REQUIRE(callAbsoluteStrike,
"Expected absolute strike for quote " << calls[i]->
name());
325 QL_REQUIRE(putAbsoluteStrike,
"Expected absolute strike for quote " << puts[j]->
name());
326 if (*calls[i]->strike() == *puts[j]->strike()) {
327 TLOG(
"Adding Call and Put for strike/expiry pair : " << calls[i]->expiry() <<
"/"
328 << calls[i]->strike()->toString());
331
332
333 Real callStrike =
335 Real putStrike =
337 callStrikes.push_back(callStrike);
338 putStrikes.push_back(putStrike);
339
340 callPremiums.push_back(
342 putPremiums.push_back(
344 }
345 }
346 }
347 }
348
349 if (callDates.size() > 0 && putDates.size() > 0) {
350 DLOG(
"Found " << callDates.size() <<
" Call and Put Option Volatilities");
351
352 DLOG(
"Building a Sparse Volatility surface for calls and puts");
353
354 QuantLib::ext::shared_ptr<OptionPriceSurface> callSurface =
355 QuantLib::ext::make_shared<OptionPriceSurface>(asof, callDates, callStrikes, callPremiums,
dc_);
356 QuantLib::ext::shared_ptr<OptionPriceSurface> putSurface =
357 QuantLib::ext::make_shared<OptionPriceSurface>(asof, putDates, putStrikes, putPremiums,
dc_);
358 DLOG(
"CallSurface contains " << callSurface->expiries().size() <<
" expiries.");
359
360 DLOG(
"Stripping equity forwards from the option premium surfaces");
361 QuantLib::ext::shared_ptr<EquityForwardCurveStripper> efcs = QuantLib::ext::make_shared<EquityForwardCurveStripper>(
362 callSurface, putSurface, forecastYieldTermStructure, equitySpot, config->exerciseStyle());
363
364
365 terms_ = efcs->expiries();
367 } else {
369 << " building NoDividends curve");
371 }
372 }
373 }
374
375
376 vector<Rate> dividendRates;
380
381
382
383 for (Size i = 0; i <
quotes_.size(); i++) {
385 <<
", expiry: " <<
terms_[i]);
386 Time t =
dc_.yearFraction(asof,
terms_[i]);
387 Rate ir_rate = forecastYieldTermStructure->zeroRate(t, Continuous);
388 dividendRates.push_back(
::log(equitySpot->value() /
quotes_[i]) / t + ir_rate);
389 }
391 DLOG(
"Building Equity Dividend Yield curve from Dividend Yield rates");
394 DLOG(
"Building flat Equity Dividend Yield curve as no quotes provided");
395
396 dividendYieldTermStructure = Handle<YieldTermStructure>(QuantLib::ext::make_shared<FlatForward>(asof, 0.0,
dc_));
399 equitySpot, forecastYieldTermStructure, dividendYieldTermStructure);
400 return;
401 } else
402 QL_FAIL(
"Invalid Equity curve configuration type for " <<
spec_.
name());
403
404 QL_REQUIRE(dividendRates.size() > 0,
"No dividend yield rates extracted for " <<
spec_.
name());
405 QL_REQUIRE(dividendRates.size() ==
terms_.size(),
"vector size mismatch - dividend rates ("
406 << dividendRates.size() <<
") vs terms (" <<
terms_.size()
407 << ")");
408
409
410 vector<Real> dividendDiscountFactors;
411 for (Size i = 0; i <
quotes_.size(); i++) {
412 Time t =
dc_.yearFraction(asof,
terms_[i]);
413 dividendDiscountFactors.push_back(std::exp(-dividendRates[i] * t));
414 }
415
416 QuantLib::ext::shared_ptr<YieldTermStructure> baseDivCurve;
417
418 if (dividendRates.size() == 1) {
419
420 baseDivCurve.reset(
new FlatForward(asof, dividendRates[0],
dc_));
421 } else {
422
424 vector<Date> dates(n + 1);
425 vector<Rate> rates(n + 1);
426 vector<Real> discounts(n + 1);
427 for (Size i = 0; i < n; i++) {
429 rates[i + 1] = dividendRates[i];
430 discounts[i + 1] = dividendDiscountFactors[i];
431 }
432 dates[0] = asof;
433 rates[0] = rates[1];
434 discounts[0] = 1.0;
435 if (forecastYieldTermStructure->maxDate() > dates.back()) {
436 dates.push_back(forecastYieldTermStructure->maxDate());
437 rates.push_back(rates.back());
438 Time maxTime =
dc_.yearFraction(asof, forecastYieldTermStructure->maxDate());
439 discounts.push_back(
440 std::exp(-rates.back() * maxTime));
441 }
446 } else {
447 QL_FAIL("Unsupported interpolation variable for dividend yield curve");
448 }
449 }
450
451 QuantLib::ext::shared_ptr<YieldTermStructure> divCurve;
452 if (config->extrapolation()) {
453 divCurve = baseDivCurve;
454 divCurve->enableExtrapolation();
455 } else {
456 divCurve = QuantLib::ext::make_shared<FlatForwardDividendCurve>(
457 asof, Handle<YieldTermStructure>(baseDivCurve), forecastYieldTermStructure);
458 if (config->dividendExtrapolation())
459 divCurve->enableExtrapolation();
460 }
461
462 dividendYieldTermStructure = Handle<YieldTermStructure>(divCurve);
463
466 equitySpot, forecastYieldTermStructure, dividendYieldTermStructure);
467
468 if (buildCalibrationInfo) {
469
470
471
476 Date d = asof + p;
478 calibrationInfo_->zeroRates.push_back(dividendYieldTermStructure->zeroRate(d,
dc_, Continuous));
479 calibrationInfo_->discountFactors.push_back(dividendYieldTermStructure->discount(d));
480 calibrationInfo_->times.push_back(dividendYieldTermStructure->timeFromReference(d));
481 }
482 }
483
484 } catch (std::exception& e) {
485 QL_FAIL("equity curve building failed: " << e.what());
486 } catch (...) {
487 QL_FAIL("equity curve building failed: unknown error");
488 }
489};
const std::string & curveConfigID() const
string name() const
returns the unique curve name
Type
Supported equity curve types.
const EquityCurveSpec & spec() const
EquityCurveConfig::Type curveType_
YieldCurve::InterpolationVariable dividendInterpVariable_
YieldCurve::InterpolationMethod dividendInterpMethod_
QuantLib::ext::shared_ptr< YieldCurveCalibrationInfo > calibrationInfo_
QuantLib::ext::shared_ptr< QuantExt::EquityIndex2 > equityIndex_
SafeStack< ValueType > value
Date getDateFromDateOrPeriod(const string &token, Date asof, QuantLib::Calendar cal, QuantLib::BusinessDayConvention bdc)
Get a date from a date string or period.
Calendar parseCalendar(const string &s)
Convert text to QuantLib::Calendar.
Date parseDate(const string &s)
Convert std::string to QuantLib::Date.
QuantLib::Real convertMinorToMajorCurrency(const std::string &s, QuantLib::Real value)
Convert a value from a minor ccy to major.
Currency parseCurrency(const string &s)
Convert text to QuantLib::Currency.
DayCounter parseDayCounter(const string &s)
Convert text to QuantLib::DayCounter.
#define DLOG(text)
Logging Macro (Level = Debug)
#define WLOG(text)
Logging Macro (Level = Warning)
#define TLOG(text)
Logging Macro (Level = Data)
RandomVariable log(RandomVariable x)
YieldCurve::InterpolationVariable parseYieldCurveInterpolationVariable(const string &s)
Helper function for parsing interpolation variable.
QuantLib::ext::shared_ptr< YieldTermStructure > discountcurve(const vector< Date > &dates, const vector< DiscountFactor > &dfs, const DayCounter &dayCounter, YieldCurve::InterpolationMethod interpolationMethod, Size n)
Create a Interpolated Discount Curve and apply interpolators.
YieldCurve::InterpolationMethod parseYieldCurveInterpolationMethod(const string &s)
Helper function for parsing interpolation method.
QuantLib::ext::shared_ptr< YieldTermStructure > zerocurve(const vector< Date > &dates, const vector< Rate > &yields, const DayCounter &dayCounter, YieldCurve::InterpolationMethod interpolationMethod, Size n)
Create a Interpolated Zero Curve and apply interpolators.
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
static const std::vector< QuantLib::Period > defaultPeriods
vector< string > curveConfigs