37#include <ql/termstructures/volatility/equityfx/blackconstantvol.hpp>
38#include <ql/termstructures/volatility/equityfx/blackvariancecurve.hpp>
39#include <ql/time/calendars/target.hpp>
40#include <ql/time/daycounters/actual365fixed.hpp>
51template <
class T,
class K> Handle<T> getHandle(
const string& spec,
const map<
string, QuantLib::ext::shared_ptr<K>>& m) {
52 auto it = m.find(spec);
53 QL_REQUIRE(it != m.end(),
"FXVolCurve: Can't find spec " << spec);
54 return it->second->handle();
65 const map<
string, QuantLib::ext::shared_ptr<YieldCurve>>& yieldCurves,
66 const std::map<
string, QuantLib::ext::shared_ptr<FXVolCurve>>& fxVols,
67 const map<
string, QuantLib::ext::shared_ptr<CorrelationCurve>>& correlationCurves,
68 const bool buildCalibrationInfo) {
69 init(asof,
spec, loader,
curveConfigs, fxSpots, yieldCurves, fxVols, correlationCurves, buildCalibrationInfo);
73 QuantLib::ext::shared_ptr<FXVolatilityCurveConfig> config,
const FXTriangulation& fxSpots,
74 const map<
string, QuantLib::ext::shared_ptr<YieldCurve>>& yieldCurves) {
75 vector<Period> unsortedExp;
77 vector<std::pair<Real, string>> putDeltas, callDeltas;
80 for (
auto const& delta : config->deltas()) {
85 putDeltas.push_back(std::make_pair(d.
delta(), delta));
87 callDeltas.push_back(std::make_pair(d.
delta(), delta));
90 Calendar cal = config->calendar();
93 auto comp = [](
const std::pair<Real, string>& x,
const std::pair<Real, string>& y) {
return x.first > y.first; };
94 std::sort(putDeltas.begin(), putDeltas.end(), comp);
95 std::sort(callDeltas.begin(), callDeltas.end(), comp);
98 Matrix blackVolMatrix;
103 std::vector<std::string> deltaNames;
104 for (
auto const& d : putDeltas) {
105 deltaNames.push_back(d.second);
108 deltaNames.push_back(
"ATM");
110 for (
auto const& d : callDeltas) {
111 deltaNames.push_back(d.second);
117 std::vector<QuantLib::ext::shared_ptr<MarketDatum>>
data;
118 std::vector<std::string> expiriesStr;
120 std::ostringstream ss;
124 for (
const auto& md : loader.
get(w, asof)) {
125 QL_REQUIRE(md->asofDate() == asof,
"MarketDatum asofDate '" << md->asofDate() <<
"' <> asof '" << asof <<
"'");
126 QuantLib::ext::shared_ptr<FXOptionQuote> q = QuantLib::ext::dynamic_pointer_cast<FXOptionQuote>(md);
127 QL_REQUIRE(q,
"Internal error: could not downcast MarketDatum '" << md->name() <<
"' to FXOptionQuote");
129 "FXOptionQuote unit ccy '" << q->unitCcy() <<
"' <> FXVolatilityCurveSpec unit ccy '" <<
spec.
unitCcy() <<
"'");
130 QL_REQUIRE(q->ccy() ==
spec.
ccy(),
131 "FXOptionQuote ccy '" << q->ccy() <<
"' <> FXVolatilityCurveSpec ccy '" <<
spec.
ccy() <<
"'");
134 vector<string> tokens;
135 boost::split(tokens, md->name(), boost::is_any_of(
"/"));
136 QL_REQUIRE(tokens.size() == 6,
"6 tokens expected in " << md->name());
142 expiriesStr.push_back(tokens[4]);
151 vector<Size> validExpiryIdx;
152 Matrix tmpMatrix(
expiries_.size(), config->deltas().size());
153 for (Size i = 0; i <
expiries_.size(); i++) {
154 Size idx = std::find(unsortedExp.begin(), unsortedExp.end(),
expiries_[i]) - unsortedExp.begin();
155 string e = expiriesStr[idx];
156 for (Size j = 0; j < deltaNames.size(); ++j) {
157 string qs = base + e +
"/" + deltaNames[j];
158 QuantLib::ext::shared_ptr<MarketDatum> md;
159 for (
auto& m :
data) {
160 if (m->name() == qs) {
165 QuantLib::ext::shared_ptr<FXOptionQuote> q = QuantLib::ext::dynamic_pointer_cast<FXOptionQuote>(md);
167 DLOG(
"missing " << qs <<
", expiry " << e <<
" will be excluded");
170 tmpMatrix[i][j] = q->quote()->value();
172 if (j == deltaNames.size() - 1) {
173 dates.push_back(cal.advance(asof,
expiries_[i]));
174 validExpiryIdx.push_back(i);
179 QL_REQUIRE(validExpiryIdx.size() > 0,
"no valid FxVol expiries found");
180 DLOG(
"found " << validExpiryIdx.size() <<
" valid expiries:");
181 for (
auto& e : validExpiryIdx)
184 blackVolMatrix = Matrix(validExpiryIdx.size(), config->deltas().size());
185 for (Size i = 0; i < validExpiryIdx.size(); i++) {
186 for (Size j = 0; j < deltaNames.size(); ++j) {
187 blackVolMatrix[i][j] = tmpMatrix[validExpiryIdx[i]][j];
196 blackVolMatrix = Matrix(
expiries_.size(), config->deltas().size());
197 for (Size i = 0; i <
expiries_.size(); i++) {
198 Size idx = std::find(unsortedExp.begin(), unsortedExp.end(),
expiries_[i]) - unsortedExp.begin();
200 dates.push_back(cal.advance(asof,
expiries_[i]));
201 for (Size j = 0; j < deltaNames.size(); ++j) {
202 string qs = base + e +
"/" + deltaNames[j];
203 QuantLib::ext::shared_ptr<MarketDatum> md = loader.
get(qs, asof);
204 QuantLib::ext::shared_ptr<FXOptionQuote> q = QuantLib::ext::dynamic_pointer_cast<FXOptionQuote>(md);
205 QL_REQUIRE(q,
"quote not found, " << qs);
206 blackVolMatrix[i][j] = q->quote()->value();
213 interp = QuantExt::InterpolatedSmileSection::InterpolationMethod::Linear;
215 interp = QuantExt::InterpolatedSmileSection::InterpolationMethod::CubicSpline;
217 QL_FAIL(
"Delta FX vol surface: invalid interpolation, expected Linear, Cubic");
220 bool flatExtrapolation =
true;
223 DLOG(
"Smile extrapolation switched to using interpolator.");
224 flatExtrapolation =
false;
226 DLOG(
"Smile extrapolation cannot be turned off on its own so defaulting to flat.");
228 DLOG(
"Smile extrapolation has been set to flat.");
230 DLOG(
"Smile extrapolation " << smileExtrapType <<
" not expected so defaulting to flat.");
235 DayCounter dc = config->dayCounter();
236 std::vector<Real> putDeltasNum, callDeltasNum;
237 std::transform(putDeltas.begin(), putDeltas.end(), std::back_inserter(putDeltasNum),
238 [](
const std::pair<Real, string>& x) { return x.first; });
239 std::transform(callDeltas.begin(), callDeltas.end(), std::back_inserter(callDeltasNum),
240 [](
const std::pair<Real, string>& x) { return x.first; });
241 vol_ = QuantLib::ext::make_shared<QuantExt::BlackVolatilitySurfaceDelta>(
242 asof, dates, putDeltasNum, callDeltasNum, hasATM, blackVolMatrix, dc, cal,
fxSpot_,
domYts_,
forYts_,
246 vol_->enableExtrapolation();
250 QuantLib::ext::shared_ptr<FXVolatilityCurveConfig> config,
const FXTriangulation& fxSpots,
251 const map<
string, QuantLib::ext::shared_ptr<YieldCurve>>& yieldCurves) {
255 std::set<Period> expiriesTmp;
257 std::vector<QuantLib::ext::shared_ptr<FXOptionQuote>>
data;
258 std::ostringstream ss;
262 for (
const auto& md : loader.
get(w, asof)) {
263 QuantLib::ext::shared_ptr<FXOptionQuote> q = QuantLib::ext::dynamic_pointer_cast<FXOptionQuote>(md);
264 QL_REQUIRE(q,
"Internal error: could not downcast MarketDatum '" << md->name() <<
"' to FXOptionQuote");
266 "FXOptionQuote unit ccy '" << q->unitCcy() <<
"' <> FXVolatilityCurveSpec unit ccy '" <<
spec.
unitCcy() <<
"'");
267 QL_REQUIRE(q->ccy() ==
spec.
ccy(),
268 "FXOptionQuote ccy '" << q->ccy() <<
"' <> FXVolatilityCurveSpec ccy '" <<
spec.
ccy() <<
"'");
271 vector<string> tokens;
272 boost::split(tokens, md->name(), boost::is_any_of(
"/"));
273 QL_REQUIRE(tokens.size() == 6,
"6 tokens expected in " << md->name());
275 expiriesTmp.insert(q->expiry());
282 expiriesTmp = std::set<Period>(tmp.begin(), tmp.end());
287 std::vector<Size> smileDeltas = config->smileDelta();
288 std::sort(smileDeltas.begin(), smileDeltas.end());
290 std::vector<std::vector<Real>> bfQuotesTmp(expiriesTmp.size(), std::vector<Real>(smileDeltas.size(), Null<Real>()));
291 std::vector<std::vector<Real>> rrQuotesTmp(expiriesTmp.size(), std::vector<Real>(smileDeltas.size(), Null<Real>()));
292 std::vector<Real> atmQuotesTmp(expiriesTmp.size(), Null<Real>());
294 for (
auto const& q :
data) {
295 Size expiryIdx = std::distance(expiriesTmp.begin(), expiriesTmp.find(q->expiry()));
296 if (expiryIdx >= expiriesTmp.size())
300 atmQuotesTmp[expiryIdx] = q->quote()->value();
302 Size deltaIdx = std::distance(smileDeltas.begin(), std::find(smileDeltas.begin(), smileDeltas.end(),
303 static_cast<Size
>(s.
value + 0.5)));
304 if (deltaIdx >= smileDeltas.size())
307 bfQuotesTmp[expiryIdx][deltaIdx] = q->quote()->value();
309 rrQuotesTmp[expiryIdx][deltaIdx] = q->quote()->value();
316 std::vector<bool> dataComplete(expiriesTmp.size(),
true);
318 for (Size i = 0; i < expiriesTmp.size(); ++i) {
319 for (Size j = 0; j < smileDeltas.size(); ++j) {
320 if (bfQuotesTmp[i][j] == Null<Real>() || rrQuotesTmp[i][j] == Null<Real>() ||
321 atmQuotesTmp[i] == Null<Real>())
322 dataComplete[i] =
false;
330 for (
auto const& e : expiriesTmp) {
331 QL_REQUIRE(dataComplete[i++],
"BFRR FX vol surface: incomplete data for expiry " << e);
338 for (
auto const& e : expiriesTmp) {
339 if (dataComplete[i++]) {
341 TLOG(
"adding expiry " << e <<
" with complete data");
343 TLOG(
"removing expiry " << e <<
", because data is not complete");
347 std::vector<std::vector<Real>> bfQuotes(
expiries_.size(), std::vector<Real>(smileDeltas.size()));
348 std::vector<std::vector<Real>> rrQuotes(
expiries_.size(), std::vector<Real>(smileDeltas.size()));
349 std::vector<Real> atmQuotes(
expiries_.size());
352 for (Size i = 0; i < expiriesTmp.size(); ++i) {
353 if (!dataComplete[i])
355 atmQuotes[row] = atmQuotesTmp[i];
356 for (Size j = 0; j < smileDeltas.size(); ++j) {
357 bfQuotes[row][j] = bfQuotesTmp[i][j];
358 rrQuotes[row][j] = rrQuotesTmp[i][j];
365 DLOG(
"build BFRR fx vol surface with " <<
expiries_.size() <<
" expiries and " << smileDeltas.size()
370 interp = QuantExt::BlackVolatilitySurfaceBFRR::SmileInterpolation::Linear;
372 interp = QuantExt::BlackVolatilitySurfaceBFRR::SmileInterpolation::Cubic;
374 QL_FAIL(
"BFRR FX vol surface: invalid interpolation, expected Linear, Cubic");
377 std::vector<Date> dates;
379 [&asof, &config](
const Period& p) { return config->calendar().advance(asof, p); });
381 std::vector<Real> smileDeltasScaled;
382 std::transform(smileDeltas.begin(), smileDeltas.end(), std::back_inserter(smileDeltasScaled),
383 [](Size d) { return static_cast<Real>(d) / 100.0; });
385 vol_ = QuantLib::ext::make_shared<QuantExt::BlackVolatilitySurfaceBFRR>(
386 asof, dates, smileDeltasScaled, bfQuotes, rrQuotes, atmQuotes, config->dayCounter(), config->calendar(),
390 vol_->enableExtrapolation();
394 QuantLib::ext::shared_ptr<FXVolatilityCurveConfig> config,
396 const map<
string, QuantLib::ext::shared_ptr<YieldCurve>>& yieldCurves) {
399 Natural smileDelta = 0;
403 QL_REQUIRE(config->smileDelta().size() == 1,
404 "Exactly one SmileDelta required for VannaVolga Curve (got " << config->smileDelta().size() <<
")");
405 smileDelta = config->smileDelta().front();
412 Size n = isATM ? 1 : 3;
413 vector<vector<QuantLib::ext::shared_ptr<FXOptionQuote>>> quotes(n);
415 QL_REQUIRE(!
expiriesWildcard_ || isATM,
"wildcards only supported for ATM, Delta, BFRR FxVol Curves");
417 vector<Period> cExpiries;
418 vector<vector<Period>> expiries;
422 expiries = vector<vector<Period>>(n, cExpiries);
426 std::vector<QuantLib::ext::shared_ptr<FXOptionQuote>>
data;
427 std::ostringstream ss;
431 for (
const auto& md : loader.
get(w, asof)) {
433 QuantLib::ext::shared_ptr<FXOptionQuote> q = QuantLib::ext::dynamic_pointer_cast<FXOptionQuote>(md);
434 QL_REQUIRE(q,
"Internal error: could not downcast MarketDatum '" << md->name() <<
"' to FXOptionQuote");
436 "FXOptionQuote unit ccy '" << q->unitCcy() <<
"' <> FXVolatilityCurveSpec unit ccy '" <<
spec.
unitCcy() <<
"'");
437 QL_REQUIRE(q->ccy() ==
spec.
ccy(),
438 "FXOptionQuote ccy '" << q->ccy() <<
"' <> FXVolatilityCurveSpec ccy '" <<
spec.
ccy() <<
"'");
442 if (q->strike() ==
"ATM")
444 else if (!isATM && q->strike() == deltaRr)
446 else if (!isATM && q->strike() == deltaBf)
450 if ((isATM && idx == 0) || (!isATM && idx <= 2)) {
452 vector<string> tokens;
453 boost::split(tokens, md->name(), boost::is_any_of(
"/"));
454 QL_REQUIRE(tokens.size() == 6,
"6 tokens expected in " << md->name());
456 quotes[idx].push_back(q);
459 auto it = std::find(expiries[idx].begin(), expiries[idx].end(), q->expiry());
460 if (it != expiries[idx].end()) {
462 quotes[idx].push_back(q);
464 expiries[idx].erase(it);
469 if (expiries[0].empty() && (isATM || (expiries[1].empty() && expiries[2].empty())))
477 LOG(
"FXVolCurve: read " << quotes[0].
size() <<
" ATM vols");
479 QL_REQUIRE(expiries[0].
size() == 0,
480 "No ATM quote found for spec " <<
spec <<
" with expiry " << expiries[0].front());
483 QL_REQUIRE(quotes[0].
size() > 0,
"No ATM quotes found for spec " <<
spec);
486 LOG(
"FXVolCurve: read " << quotes[1].
size() <<
" RR and " << quotes[2].
size() <<
" BF quotes");
487 QL_REQUIRE(expiries[1].
size() == 0,
488 "No RR quote found for spec " <<
spec <<
" with expiry " << expiries[1].front());
489 QL_REQUIRE(expiries[2].
size() == 0,
490 "No BF quote found for spec " <<
spec <<
" with expiry " << expiries[2].front());
494 for (Size i = 0; i < n; i++) {
495 std::sort(quotes[i].begin(), quotes[i].end(),
496 [](
const QuantLib::ext::shared_ptr<FXOptionQuote>& a,
const QuantLib::ext::shared_ptr<FXOptionQuote>& b) ->
bool {
497 return a->expiry() < b->expiry();
503 DayCounter dc = config->dayCounter();
504 Calendar cal = config->calendar();
507 if (isATM && quotes[0].
size() == 1) {
508 vol_ = QuantLib::ext::shared_ptr<BlackVolTermStructure>(
509 new BlackConstantVol(asof, config->calendar(), quotes[0].front()->quote()->value(), dc));
510 expiries_ = {quotes[0].front()->expiry()};
513 Size numExpiries = quotes[0].size();
514 vector<Date> dates(numExpiries);
515 vector<vector<Volatility>> vols(n, vector<Volatility>(numExpiries));
517 for (Size i = 0; i < numExpiries; i++) {
518 dates[i] = cal.advance(asof, quotes[0][i]->expiry());
519 expiries_.push_back(quotes[0][i]->expiry());
520 DLOG(
"Spec Tenor Vol Variance");
521 for (Size idx = 0; idx < n; idx++) {
522 vols[idx][i] = quotes[idx][i]->quote()->value();
523 Real
variance = vols[idx][i] * vols[idx][i] * (dates[i] - asof) / 365.0;
524 DLOG(
spec <<
" " << quotes[0][i]->expiry() <<
" " << vols[idx][i] <<
" " <<
variance);
531 vol_ = QuantLib::ext::shared_ptr<BlackVolTermStructure>(
new BlackVarianceCurve(asof, dates, vols[0], dc,
false));
534 bool vvFirstApprox =
false;
536 vvFirstApprox =
true;
539 vol_ = QuantLib::ext::make_shared<QuantExt::FxBlackVannaVolgaVolatilitySurface>(
540 asof, dates, vols[0], vols[1], vols[2], dc, cal,
fxSpot_,
domYts_,
forYts_,
false, vvFirstApprox,
544 vol_->enableExtrapolation();
548 QuantLib::ext::shared_ptr<FXVolatilityCurveConfig> config,
const FXTriangulation& fxSpots,
549 const map<
string, QuantLib::ext::shared_ptr<YieldCurve>>& yieldCurves) {
552 std::set<Period> expiriesTmp;
554 std::vector<QuantLib::ext::shared_ptr<FXOptionQuote>>
data;
555 std::ostringstream ss;
558 for (
const auto& md : loader.
get(w, asof)) {
559 QuantLib::ext::shared_ptr<FXOptionQuote> q = QuantLib::ext::dynamic_pointer_cast<FXOptionQuote>(md);
560 QL_REQUIRE(q,
"Internal error: could not downcast MarketDatum '" << md->name() <<
"' to FXOptionQuote");
561 QL_REQUIRE(q->unitCcy() ==
spec.
unitCcy(),
"FXOptionQuote unit ccy '" << q->unitCcy()
562 <<
"' <> FXVolatilityCurveSpec unit ccy '"
564 QL_REQUIRE(q->ccy() ==
spec.
ccy(),
565 "FXOptionQuote ccy '" << q->ccy() <<
"' <> FXVolatilityCurveSpec ccy '" <<
spec.
ccy() <<
"'");
568 vector<string> tokens;
569 boost::split(tokens, md->name(), boost::is_any_of(
"/"));
570 QL_REQUIRE(tokens.size() == 6,
"6 tokens expected in " << md->name());
572 expiriesTmp.insert(q->expiry());
579 expiriesTmp = std::set<Period>(tmp.begin(), tmp.end());
583 std::vector<std::map<Real, Real>> strikeQuotesTmp(expiriesTmp.size());
585 for (
auto const& q :
data) {
586 Size expiryIdx = std::distance(expiriesTmp.begin(), expiriesTmp.find(q->expiry()));
587 if (expiryIdx >= expiriesTmp.size())
591 if (strikeQuotesTmp[expiryIdx].
count(s.
value) == 0)
592 strikeQuotesTmp[expiryIdx][s.
value] = q->quote()->value();
596 std::vector<bool> dataComplete(expiriesTmp.size(),
true);
598 for (Size i = 0; i < expiriesTmp.size(); ++i) {
599 if (strikeQuotesTmp[i].empty())
600 dataComplete[i] =
false;
607 for (
auto const& e : expiriesTmp) {
608 QL_REQUIRE(dataComplete[i++],
"Absolute FX vol surface: missing data for expiry " << e);
614 for (
auto const& e : expiriesTmp) {
615 if (dataComplete[i++]) {
617 TLOG(
"adding expiry " << e <<
" with at least one strike quote");
619 TLOG(
"removing expiry " << e <<
", no strike quote found");
623 std::vector<std::vector<Real>> strikeQuotes,
strikes;
624 std::vector<Real> strikeQuote, strike;
626 for (Size i = 0; i < expiriesTmp.size(); ++i) {
627 if (!dataComplete[i])
629 for (
auto const& quote : strikeQuotesTmp[i]) {
630 strike.push_back(quote.first);
631 strikeQuote.push_back(quote.second);
633 strikeQuotes.push_back(strikeQuote);
641 DLOG(
"build Absolute fx vol surface with " <<
expiries_.size() <<
" expiries");
645 interp = QuantExt::BlackVolatilitySurfaceAbsolute::SmileInterpolation::Linear;
647 interp = QuantExt::BlackVolatilitySurfaceAbsolute::SmileInterpolation::Cubic;
649 QL_FAIL(
"Absolute FX vol surface: invalid interpolation, expected Linear, Cubic");
652 std::vector<Date> dates;
654 [&asof, &config](
const Period& p) { return config->calendar().advance(asof, p); });
656 vol_ = QuantLib::ext::make_shared<QuantExt::BlackVolatilitySurfaceAbsolute>(
657 asof, dates,
strikes, strikeQuotes, config->dayCounter(), config->calendar(),
661 vol_->enableExtrapolation();
664Handle<QuantExt::CorrelationTermStructure>
666 const map<
string, QuantLib::ext::shared_ptr<CorrelationCurve>>& correlationCurves) {
668 auto tmpCorr = correlationCurves.find(
"Correlation/" + index1 +
"&" + index2);
669 if (tmpCorr != correlationCurves.end()) {
670 return Handle<QuantExt::CorrelationTermStructure>(tmpCorr->second->corrTermStructure());
673 tmpCorr = correlationCurves.find(
"Correlation/" + index2 +
"&" + index1);
674 if (tmpCorr != correlationCurves.end()) {
675 return Handle<QuantExt::CorrelationTermStructure>(tmpCorr->second->corrTermStructure());
678 tmpCorr = correlationCurves.find(
"Correlation/" +
inverseFxIndex(index1) +
"&" + index2);
679 if (tmpCorr != correlationCurves.end()) {
680 Handle<QuantExt::CorrelationTermStructure> h =
681 Handle<QuantExt::CorrelationTermStructure>(tmpCorr->second->corrTermStructure());
682 return Handle<QuantExt::CorrelationTermStructure>(
683 QuantLib::ext::make_shared<QuantExt::NegativeCorrelationTermStructure>(h));
685 tmpCorr = correlationCurves.find(
"Correlation/" + index2 +
"&" +
inverseFxIndex(index1));
686 if (tmpCorr != correlationCurves.end()) {
687 Handle<QuantExt::CorrelationTermStructure> h =
688 Handle<QuantExt::CorrelationTermStructure>(tmpCorr->second->corrTermStructure());
689 return Handle<QuantExt::CorrelationTermStructure>(
690 QuantLib::ext::make_shared<QuantExt::NegativeCorrelationTermStructure>(h));
693 tmpCorr = correlationCurves.find(
"Correlation/" + index1 +
"&" +
inverseFxIndex(index2));
694 if (tmpCorr != correlationCurves.end()) {
695 Handle<QuantExt::CorrelationTermStructure> h =
696 Handle<QuantExt::CorrelationTermStructure>(tmpCorr->second->corrTermStructure());
697 return Handle<QuantExt::CorrelationTermStructure>(
698 QuantLib::ext::make_shared<QuantExt::NegativeCorrelationTermStructure>(h));
700 tmpCorr = correlationCurves.find(
"Correlation/" +
inverseFxIndex(index2) +
"&" + index1);
701 if (tmpCorr != correlationCurves.end()) {
702 Handle<QuantExt::CorrelationTermStructure> h =
703 Handle<QuantExt::CorrelationTermStructure>(tmpCorr->second->corrTermStructure());
704 return Handle<QuantExt::CorrelationTermStructure>(
705 QuantLib::ext::make_shared<QuantExt::NegativeCorrelationTermStructure>(h));
709 if (tmpCorr != correlationCurves.end()) {
710 return Handle<QuantExt::CorrelationTermStructure>(tmpCorr->second->corrTermStructure());
713 if (tmpCorr != correlationCurves.end()) {
714 return Handle<QuantExt::CorrelationTermStructure>(tmpCorr->second->corrTermStructure());
717 QL_FAIL(
"no correlation curve found for " << index1 <<
":" << index2);
721 QuantLib::ext::shared_ptr<FXVolatilityCurveConfig> config,
const FXTriangulation& fxSpots,
722 const map<
string, QuantLib::ext::shared_ptr<YieldCurve>>& yieldCurves,
723 const map<
string, QuantLib::ext::shared_ptr<FXVolCurve>>& fxVols,
724 const map<
string, QuantLib::ext::shared_ptr<CorrelationCurve>>& correlationCurves) {
726 DLOG(
"Triangulating FxVol curve " << config->curveID() <<
" from baseVols " << config->baseVolatility1() <<
":"
727 << config->baseVolatility2());
730 QL_REQUIRE(config->baseVolatility1().size() == 6,
"invalid ccy pair length for baseVolatility1");
731 auto forBase1 = config->baseVolatility1().substr(0, 3);
732 auto domBase1 = config->baseVolatility1().substr(3);
733 std::string spec1 =
"FXVolatility/" + forBase1 +
"/" + domBase1 +
"/" + config->baseVolatility1();
735 bool base1Inverted =
false;
738 base1Inverted =
true;
739 std::string tmp = forBase1;
744 "FxVol: mismatch in the baseVolatility1 " << config->baseVolatility1() <<
" and Target Pair "
749 QL_REQUIRE(config->baseVolatility2().size() == 6,
"invalid ccy pair length for baseVolatility2");
750 auto forBase2 = config->baseVolatility2().substr(0, 3);
751 auto domBase2 = config->baseVolatility2().substr(3);
752 std::string spec2 =
"FXVolatility/" + forBase2 +
"/" + domBase2 +
"/" + config->baseVolatility2();
753 bool base2Inverted =
false;
755 QL_REQUIRE(forBase2 == baseCcy || domBase2 == baseCcy,
756 "baseVolatility2 must share a ccy code with the baseVolatility1");
760 std::string tmp = forBase2;
763 base2Inverted =
true;
766 auto tmp = fxVols.find(spec1);
767 QL_REQUIRE(tmp != fxVols.end(),
"fx vol not found for " << config->baseVolatility1());
768 Handle<BlackVolTermStructure> forBaseVol;
770 auto h = Handle<BlackVolTermStructure>(tmp->second->volTermStructure());
772 forBaseVol = Handle<BlackVolTermStructure>(QuantLib::ext::make_shared<QuantExt::BlackInvertedVolTermStructure>(h));
774 forBaseVol = Handle<BlackVolTermStructure>(tmp->second->volTermStructure());
776 forBaseVol->enableExtrapolation();
778 tmp = fxVols.find(spec2);
779 QL_REQUIRE(tmp != fxVols.end(),
"fx vol not found for " << config->baseVolatility2());
780 Handle<BlackVolTermStructure> domBaseVol;
782 auto h = Handle<BlackVolTermStructure>(tmp->second->volTermStructure());
784 domBaseVol = Handle<BlackVolTermStructure>(QuantLib::ext::make_shared<QuantExt::BlackInvertedVolTermStructure>(h));
787 domBaseVol = Handle<BlackVolTermStructure>(tmp->second->volTermStructure());
789 domBaseVol->enableExtrapolation();
791 string forIndex =
"FX-" + config->fxIndexTag() +
"-" +
sourceCcy_ +
"-" + baseCcy;
792 string domIndex =
"FX-" + config->fxIndexTag() +
"-" +
targetCcy_ +
"-" + baseCcy;
794 Handle<QuantExt::CorrelationTermStructure> rho =
getCorrelationCurve(forIndex, domIndex, correlationCurves);
796 vol_ = QuantLib::ext::make_shared<QuantExt::BlackTriangulationATMVolTermStructure>(forBaseVol, domBaseVol, rho);
797 vol_->enableExtrapolation();
802 const map<
string, QuantLib::ext::shared_ptr<YieldCurve>>& yieldCurves,
803 const std::map<
string, QuantLib::ext::shared_ptr<FXVolCurve>>& fxVols,
804 const map<
string, QuantLib::ext::shared_ptr<CorrelationCurve>>& correlationCurves,
805 const bool buildCalibrationInfo) {
809 QuantLib::ext::shared_ptr<Conventions> conventions = InstrumentConventions::instance().conventions();
817 "Unknown FX curve building dimension");
826 std::vector<std::tuple<std::string, Period, Date>> tmp;
827 for (
auto const& e : config->expiries()) {
829 tmp.push_back(std::make_tuple(e, p, config->calendar().advance(asof, p)));
831 std::sort(tmp.begin(), tmp.end(),
832 [](
const std::tuple<std::string, Period, Date>& a,
833 const std::tuple<std::string, Period, Date>& b) ->
bool {
834 if (std::get<2>(a) == std::get<2>(b)) {
837 return std::get<1>(a) < std::get<1>(b);
840 return std::get<0>(a) < std::get<0>(b);
844 return std::get<2>(a) < std::get<2>(b);
846 Date lastDate = Date::maxDate();
847 for (Size i = tmp.size(); i > 0; --i) {
848 if (std::get<2>(tmp[i - 1]) == lastDate)
851 lastDate = std::get<2>(tmp[i - 1]);
854 DLOG(
"expiries in configuration:")
855 for (
auto const& e : config->expiries()) {
859 DLOG(
"expiries after removing duplicate expiry dates and sorting:");
867 "no expiries after removing duplicate expiry dates");
869 std::vector<std::string> tokens;
870 boost::split(tokens, config->fxSpotID(), boost::is_any_of(
"/"));
871 QL_REQUIRE(tokens.size() == 3,
"Expected 3 tokens in fx spot id '" << config->fxSpotID() <<
"'");
875 atmType_ = DeltaVolQuote::AtmType::AtmDeltaNeutral;
886 if (config->conventionsID() ==
"") {
888 <<
", assuming defaults");
890 auto fxOptConv = QuantLib::ext::dynamic_pointer_cast<FxOptionConvention>(conventions->get(config->conventionsID()));
891 QL_REQUIRE(fxOptConv,
892 "unable to cast convention '" << config->conventionsID() <<
"' into FxOptionConvention");
893 QuantLib::ext::shared_ptr<FXConvention> fxConv;
894 if (!fxOptConv->fxConventionID().empty()) {
895 fxConv = QuantLib::ext::dynamic_pointer_cast<FXConvention>(conventions->get(fxOptConv->fxConventionID()));
896 QL_REQUIRE(fxConv,
"unable to cast convention '" << fxOptConv->fxConventionID()
897 <<
"', from FxOptionConvention '"
898 << config->conventionsID() <<
"' into FxConvention");
913 auto spotSpec = QuantLib::ext::dynamic_pointer_cast<FXSpotSpec>(
parseCurveSpec(config->fxSpotID()));
914 QL_REQUIRE(spotSpec !=
nullptr,
915 "could not parse '" << config->fxSpotID() <<
"' to FXSpotSpec, expected FX/CCY1/CCY2");
917 if (!config->fxDomesticYieldCurveID().empty())
918 domYts_ = getHandle<YieldTermStructure>(config->fxDomesticYieldCurveID(), yieldCurves);
919 if (!config->fxForeignYieldCurveID().empty())
920 forYts_ = getHandle<YieldTermStructure>(config->fxForeignYieldCurveID(), yieldCurves);
936 if (buildCalibrationInfo) {
938 DLOG(
"Building calibration info for fx vol surface");
941 WLOG(
"no domestic / foreign yield curves given in fx vol curve config for "
950 std::vector<Real> moneyness = *rc.
moneyness();
951 std::vector<std::string> deltas = *rc.
deltas();
952 std::vector<Period> expiries = *rc.
expiries();
954 calibrationInfo_ = QuantLib::ext::make_shared<FxEqCommVolCalibrationInfo>();
956 calibrationInfo_->dayCounter = config->dayCounter().empty() ?
"na" : config->dayCounter().name();
957 calibrationInfo_->calendar = config->calendar().empty() ?
"na" : config->calendar().name();
966 std::vector<Real> times, forwards, domDisc, forDisc;
968 for (
auto const& p : expiries) {
969 Date d =
vol_->optionDateFromTenor(p);
972 times.push_back(
vol_->dayCounter().empty() ? Actual365Fixed().yearFraction(asof, d)
973 :
vol_->timeFromReference(d));
974 domDisc.push_back(
domYts_->discount(settlFwd) /
domYts_->discount(settl));
975 forDisc.push_back(
forYts_->discount(settlFwd) /
forYts_->discount(settl));
976 forwards.push_back(
fxSpot_->value() / domDisc.back() * forDisc.back());
984 Real switchTime =
vol_->dayCounter().empty() ? Actual365Fixed().yearFraction(asof, switchExpiry)
985 :
vol_->timeFromReference(switchExpiry);
987 switchTime = QL_MAX_REAL;
989 std::vector<std::vector<Real>> callPricesDelta(times.size(), std::vector<Real>(deltas.size(), 0.0));
990 std::vector<std::vector<Real>> callPricesMoneyness(times.size(), std::vector<Real>(moneyness.size(), 0.0));
994 if (reportOnDeltaGrid) {
997 std::vector<std::vector<Real>>(times.size(), std::vector<Real>(deltas.size(), 0.0));
999 std::vector<std::vector<Real>>(times.size(), std::vector<Real>(deltas.size(), 0.0));
1001 std::vector<std::vector<Real>>(times.size(), std::vector<Real>(deltas.size(), 0.0));
1003 std::vector<std::vector<Real>>(times.size(), std::vector<Real>(deltas.size(), 0.0));
1005 std::vector<std::vector<Real>>(times.size(), std::vector<Real>(deltas.size(), 0.0));
1007 std::vector<std::vector<bool>>(times.size(), std::vector<bool>(deltas.size(),
true));
1009 std::vector<std::vector<bool>>(times.size(), std::vector<bool>(deltas.size(),
true));
1010 DeltaVolQuote::DeltaType dt;
1011 DeltaVolQuote::AtmType at;
1012 TLOG(
"Delta surface arbitrage analysis result (no calendar spread arbitrage included):");
1013 Real maxTime = QL_MAX_REAL;
1016 for (Size i = 0; i < times.size(); ++i) {
1028 at = DeltaVolQuote::AtmDeltaNeutral;
1030 ? DeltaVolQuote::Fwd
1031 : DeltaVolQuote::PaFwd;
1033 bool validSlice =
true;
1034 for (Size j = 0; j < deltas.size(); ++j) {
1043 domDisc[i], forDisc[i],
vol_, t);
1046 domDisc[i], forDisc[i],
vol_, t);
1048 Real stddev = std::sqrt(
vol_->blackVariance(t, strike));
1049 callPricesDelta[i][j] = blackFormula(Option::Call, strike, forwards[i], stddev);
1052 calibrationInfo_->deltaPutPrices[i][j] = blackFormula(Option::Put, strike, forwards[i], stddev, domDisc[i]);
1054 calibrationInfo_->deltaCallPrices[i][j] = blackFormula(Option::Call, strike, forwards[i], stddev, domDisc[i]);
1058 calibrationInfo_->deltaGridImpliedVolatility[i][j] = stddev / std::sqrt(t);
1059 }
catch (
const std::exception& e) {
1061 TLOG(
"error for time " << t <<
" delta " << deltas[j] <<
": " << e.what());
1067 forwards[i], callPricesDelta[i]);
1074 }
catch (
const std::exception& e) {
1075 TLOG(
"error for time " << t <<
": " << e.what());
1084 TLOG(
"Delta surface arbitrage analysis completed.");
1087 if (reportOnMoneynessGrid) {
1090 std::vector<std::vector<Real>>(times.size(), std::vector<Real>(moneyness.size(), 0.0));
1092 std::vector<std::vector<Real>>(times.size(), std::vector<Real>(moneyness.size(), 0.0));
1094 std::vector<std::vector<Real>>(times.size(), std::vector<Real>(moneyness.size(), 0.0));
1096 std::vector<std::vector<Real>>(times.size(), std::vector<Real>(moneyness.size(), 0.0));
1098 std::vector<std::vector<Real>>(times.size(), std::vector<Real>(moneyness.size(), 0.0));
1100 std::vector<std::vector<bool>>(times.size(), std::vector<bool>(moneyness.size(),
true));
1102 std::vector<std::vector<bool>>(times.size(), std::vector<bool>(moneyness.size(),
true));
1104 std::vector<std::vector<bool>>(times.size(), std::vector<bool>(moneyness.size(),
true));
1105 for (Size i = 0; i < times.size(); ++i) {
1107 for (Size j = 0; j < moneyness.size(); ++j) {
1109 Real strike = moneyness[j] * forwards[i];
1111 Real stddev = std::sqrt(
vol_->blackVariance(t, strike));
1112 callPricesMoneyness[i][j] = blackFormula(Option::Call, strike, forwards[i], stddev);
1113 calibrationInfo_->moneynessGridImpliedVolatility[i][j] = stddev / std::sqrt(t);
1114 if (moneyness[j] >= 1) {
1115 calibrationInfo_->moneynessCallPrices[i][j] = blackFormula(Option::Call, strike, forwards[i], stddev, domDisc[i]);
1117 calibrationInfo_->moneynessPutPrices[i][j] = blackFormula(Option::Put, strike, forwards[i], stddev, domDisc[i]);
1119 }
catch (
const std::exception& e) {
1120 TLOG(
"error for time " << t <<
" moneyness " << moneyness[j] <<
": " << e.what());
1124 if (!times.empty() && !moneyness.empty()) {
1127 callPricesMoneyness);
1128 for (Size i = 0; i < times.size(); ++i) {
1136 TLOG(
"Moneyness surface Arbitrage analysis result:");
1138 }
catch (
const std::exception& e) {
1139 TLOG(
"error: " << e.what());
1142 TLOG(
"Moneyness surface Arbitrage analysis completed:");
1148 if (reportOnDeltaGrid || reportOnMoneynessGrid) {
1149 if (
auto bfrr = QuantLib::ext::dynamic_pointer_cast<QuantExt::BlackVolatilitySurfaceBFRR>(
vol_)) {
1150 if (bfrr->deltas().size() != bfrr->currentDeltas().size()) {
1152 "Warning: Used only " + std::to_string(bfrr->currentDeltas().size()) +
" deltas of the " +
1153 std::to_string(bfrr->deltas().size()) +
1154 " deltas that were initially provided, because all smiles were invalid.");
1156 for (Size i = 0; i < bfrr->dates().
size(); ++i) {
1157 if (bfrr->smileHasError()[i]) {
1160 bfrr->smileErrorMessage()[i]);
1166 DLOG(
"Building calibration info for fx vol surface completed.");
1169 }
catch (std::exception& e) {
1170 QL_FAIL(
"fx vol curve building failed: " << e.what());
1172 QL_FAIL(
"fx vol curve building failed: unknown error");
const std::vector< bool > & butterflyArbitrage() const
bool arbitrageFree() const
const std::vector< bool > & callSpreadArbitrage() const
const std::vector< Real > & density() const
const std::vector< std::vector< bool > > & butterflyArbitrage() const
bool arbitrageFree() const
const std::vector< std::vector< bool > > & calendarArbitrage() const
const std::vector< std::vector< bool > > & callSpreadArbitrage() const
const std::vector< CarrMadanMarginalProbability > & timeSlices() const
Container class for all Curve Configurations.
const std::string & curveConfigID() const
Utility class for handling delta strings ATM, 10P, 25C, ... used e.g. for FX Surfaces.
QuantLib::Handle< QuantLib::Quote > getQuote(const std::string &pair) const
void buildVannaVolgaOrATMCurve(Date asof, FXVolatilityCurveSpec spec, const Loader &loader, QuantLib::ext::shared_ptr< FXVolatilityCurveConfig > config, const FXTriangulation &fxSpots, const map< string, QuantLib::ext::shared_ptr< YieldCurve > > &yieldCurves)
QuantLib::DeltaVolQuote::AtmType atmType_
bool butterflyIsBrokerStyle_
void buildSmileDeltaCurve(Date asof, FXVolatilityCurveSpec spec, const Loader &loader, QuantLib::ext::shared_ptr< FXVolatilityCurveConfig > config, const FXTriangulation &fxSpots, const map< string, QuantLib::ext::shared_ptr< YieldCurve > > &yieldCurves)
boost::optional< Wildcard > expiriesWildcard_
QuantLib::Period switchTenor_
QuantLib::DeltaVolQuote::DeltaType longTermDeltaType_
QuantLib::ext::shared_ptr< FxEqCommVolCalibrationInfo > calibrationInfo_
FXVolCurve()
Default constructor.
QuantLib::DeltaVolQuote::AtmType longTermAtmType_
void buildSmileAbsoluteCurve(Date asof, FXVolatilityCurveSpec spec, const Loader &loader, QuantLib::ext::shared_ptr< FXVolatilityCurveConfig > config, const FXTriangulation &fxSpots, const map< string, QuantLib::ext::shared_ptr< YieldCurve > > &yieldCurves)
void buildATMTriangulated(Date asof, FXVolatilityCurveSpec spec, const Loader &loader, QuantLib::ext::shared_ptr< FXVolatilityCurveConfig > config, const FXTriangulation &fxSpots, const map< string, QuantLib::ext::shared_ptr< YieldCurve > > &yieldCurves, const std::map< std::string, QuantLib::ext::shared_ptr< FXVolCurve > > &fxVols, const map< string, QuantLib::ext::shared_ptr< CorrelationCurve > > &correlationCurves)
QuantLib::DeltaVolQuote::DeltaType deltaType_
QuantLib::ext::shared_ptr< BlackVolTermStructure > vol_
void init(Date asof, FXVolatilityCurveSpec spec, const Loader &loader, const CurveConfigurations &curveConfigs, const FXTriangulation &fxSpots, const map< string, QuantLib::ext::shared_ptr< YieldCurve > > &yieldCurves, const std::map< string, QuantLib::ext::shared_ptr< FXVolCurve > > &fxVols, const map< string, QuantLib::ext::shared_ptr< CorrelationCurve > > &correlationCurves, const bool buildCalibrationInfo)
void buildSmileBfRrCurve(Date asof, FXVolatilityCurveSpec spec, const Loader &loader, QuantLib::ext::shared_ptr< FXVolatilityCurveConfig > config, const FXTriangulation &fxSpots, const map< string, QuantLib::ext::shared_ptr< YieldCurve > > &yieldCurves)
std::vector< string > expiriesNoDuplicates_
std::vector< Period > expiries_
Handle< YieldTermStructure > forYts_
Handle< YieldTermStructure > domYts_
const FXVolatilityCurveSpec & spec() const
QuantLib::Option::Type riskReversalInFavorOf_
FX Volatility curve description.
const string & ccy() const
const string & unitCcy() const
Market data loader base class.
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
const boost::optional< bool > reportOnDeltaGrid() const
const boost::optional< std::vector< std::string > > & deltas() const
const boost::optional< bool > reportOnMoneynessGrid() const
const boost::optional< std::vector< Period > > & expiries() const
const boost::optional< std::vector< Real > > & moneyness() const
Wrapper class for building FX volatility structures.
QuantLib::ext::shared_ptr< CurveSpec > parseCurveSpec(const string &s)
function to convert a string into a curve spec
Calendar parseCalendar(const string &s)
Convert text to QuantLib::Calendar.
Strike parseStrike(const std::string &s)
Convert text to Strike.
Period parsePeriod(const string &s)
Convert text to QuantLib::Period.
Map text representations to QuantLib/QuantExt types.
Classes and functions for log message handling.
#define LOG(text)
Logging Macro (Level = Notice)
#define DLOG(text)
Logging Macro (Level = Debug)
#define TLOGGERSTREAM(text)
#define WLOG(text)
Logging Macro (Level = Warning)
#define TLOG(text)
Logging Macro (Level = Data)
Real getAtmStrike(DeltaVolQuote::DeltaType dt, DeltaVolQuote::AtmType at, Real spot, Real domDiscount, Real forDiscount, boost::shared_ptr< BlackVolTermStructure > vol, Real t, Real accuracy, Size maxIterations)
Filter close_enough(const RandomVariable &x, const RandomVariable &y)
RandomVariable variance(const RandomVariable &r)
std::string arbitrageAsString(const CarrMadanMarginalProbabilityClass &cm)
Real getStrikeFromDelta(Option::Type optionType, Real delta, DeltaVolQuote::DeltaType dt, Real spot, Real domDiscount, Real forDiscount, boost::shared_ptr< BlackVolTermStructure > vol, Real t, Real accuracy, Size maxIterations)
ReportConfig effectiveReportConfig(const ReportConfig &globalConfig, const ReportConfig &localConfig)
Size size(const ValueType &v)
std::string to_string(const LocationInfo &l)
Handle< QuantExt::CorrelationTermStructure > getCorrelationCurve(const std::string &index1, const std::string &index2, const map< string, QuantLib::ext::shared_ptr< CorrelationCurve > > &correlationCurves)
std::string inverseFxIndex(const std::string &indexName)
Extrapolation parseExtrapolation(const string &s)
Parse Extrapolation from string.
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
Serializable Credit Default Swap.
Error for market data or curve.
vector< string > curveConfigs
string conversion utilities