805 {
806 try {
807
809 QuantLib::ext::shared_ptr<Conventions> conventions = InstrumentConventions::instance().conventions();
810
817 "Unknown FX curve building dimension");
818
820
821
822
825 } else {
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)));
830 }
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)) {
835 try {
836
837 return std::get<1>(a) < std::get<1>(b);
838 } catch (...) {
839
840 return std::get<0>(a) < std::get<0>(b);
841 }
842 }
843
844 return std::get<2>(a) < std::get<2>(b);
845 });
846 Date lastDate = Date::maxDate();
847 for (Size i = tmp.size(); i > 0; --i) {
848 if (std::get<2>(tmp[i - 1]) == lastDate)
849 continue;
851 lastDate = std::get<2>(tmp[i - 1]);
852 }
853
854 DLOG(
"expiries in configuration:")
855 for (auto const& e : config->expiries()) {
857 }
858
859 DLOG(
"expiries after removing duplicate expiry dates and sorting:");
862 }
863 }
864
867 "no expiries after removing duplicate expiry dates");
868
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() << "'");
874
875 atmType_ = DeltaVolQuote::AtmType::AtmDeltaNeutral;
885
886 if (config->conventionsID() == "") {
888 << ", assuming defaults");
889 } else {
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");
899 }
907 if (fxConv) {
910 }
911 }
912
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");
916 fxSpot_ = fxSpots.getQuote(spotSpec->unitCcy() + spotSpec->ccy());
917 if (!config->fxDomesticYieldCurveID().empty())
918 domYts_ = getHandle<YieldTermStructure>(config->fxDomesticYieldCurveID(), yieldCurves);
919 if (!config->fxForeignYieldCurveID().empty())
920 forYts_ = getHandle<YieldTermStructure>(config->fxForeignYieldCurveID(), yieldCurves);
921
930 } else {
932 }
933
934
935
936 if (buildCalibrationInfo) {
937
938 DLOG(
"Building calibration info for fx vol surface");
939
941 WLOG(
"no domestic / foreign yield curves given in fx vol curve config for "
943 return;
944 }
945
947
948 bool reportOnDeltaGrid = *rc.reportOnDeltaGrid();
949 bool reportOnMoneynessGrid = *rc.reportOnMoneynessGrid();
950 std::vector<Real> moneyness = *rc.moneyness();
951 std::vector<std::string> deltas = *rc.deltas();
952 std::vector<Period> expiries = *rc.expiries();
953
954 calibrationInfo_ = QuantLib::ext::make_shared<FxEqCommVolCalibrationInfo>();
955
956 calibrationInfo_->dayCounter = config->dayCounter().empty() ?
"na" : config->dayCounter().name();
957 calibrationInfo_->calendar = config->calendar().empty() ?
"na" : config->calendar().name();
965
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());
977 }
978
981
982 Date switchExpiry =
984 Real switchTime =
vol_->dayCounter().empty() ? Actual365Fixed().yearFraction(asof, switchExpiry)
985 :
vol_->timeFromReference(switchExpiry);
987 switchTime = QL_MAX_REAL;
988
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));
991
993
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) {
1017 Real t = times[i];
1021 } else {
1024 }
1025
1026
1027 if (t > maxTime) {
1028 at = DeltaVolQuote::AtmDeltaNeutral;
1030 ? DeltaVolQuote::Fwd
1031 : DeltaVolQuote::PaFwd;
1032 }
1033 bool validSlice = true;
1034 for (Size j = 0; j < deltas.size(); ++j) {
1035 DeltaString d(deltas[j]);
1036 try {
1037 Real strike;
1038 if (d.isAtm()) {
1039 strike =
1041 } else if (d.isCall()) {
1043 domDisc[i], forDisc[i],
vol_, t);
1044 } else {
1046 domDisc[i], forDisc[i],
vol_, t);
1047 }
1048 Real stddev = std::sqrt(
vol_->blackVariance(t, strike));
1049 callPricesDelta[i][j] = blackFormula(Option::Call, strike, forwards[i], stddev);
1050
1051 if (d.isPut()) {
1052 calibrationInfo_->deltaPutPrices[i][j] = blackFormula(Option::Put, strike, forwards[i], stddev, domDisc[i]);
1053 } else {
1054 calibrationInfo_->deltaCallPrices[i][j] = blackFormula(Option::Call, strike, forwards[i], stddev, domDisc[i]);
1055 }
1056
1058 calibrationInfo_->deltaGridImpliedVolatility[i][j] = stddev / std::sqrt(t);
1059 } catch (const std::exception& e) {
1060 validSlice = false;
1061 TLOG(
"error for time " << t <<
" delta " << deltas[j] <<
": " << e.what());
1062 }
1063 }
1064 if (validSlice) {
1065 try {
1067 forwards[i], callPricesDelta[i]);
1068 calibrationInfo_->deltaGridCallSpreadArbitrage[i] = cm.callSpreadArbitrage();
1069 calibrationInfo_->deltaGridButterflyArbitrage[i] = cm.butterflyArbitrage();
1070 if (!cm.arbitrageFree())
1074 } catch (const std::exception& e) {
1075 TLOG(
"error for time " << t <<
": " << e.what());
1078 }
1079 } else {
1082 }
1083 }
1084 TLOG(
"Delta surface arbitrage analysis completed.");
1085 }
1086
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) {
1106 Real t = times[i];
1107 for (Size j = 0; j < moneyness.size(); ++j) {
1108 try {
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]);
1116 } else {
1117 calibrationInfo_->moneynessPutPrices[i][j] = blackFormula(Option::Put, strike, forwards[i], stddev, domDisc[i]);
1118 };
1119 } catch (const std::exception& e) {
1120 TLOG(
"error for time " << t <<
" moneyness " << moneyness[j] <<
": " << e.what());
1121 }
1122 }
1123 }
1124 if (!times.empty() && !moneyness.empty()) {
1125 try {
1127 callPricesMoneyness);
1128 for (Size i = 0; i < times.size(); ++i) {
1130 }
1131 calibrationInfo_->moneynessGridCallSpreadArbitrage = cm.callSpreadArbitrage();
1132 calibrationInfo_->moneynessGridButterflyArbitrage = cm.butterflyArbitrage();
1134 if (!cm.arbitrageFree())
1136 TLOG(
"Moneyness surface Arbitrage analysis result:");
1138 } catch (const std::exception& e) {
1139 TLOG(
"error: " << e.what());
1141 }
1142 TLOG(
"Moneyness surface Arbitrage analysis completed:");
1143 }
1144 }
1145
1146
1147
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.");
1155 }
1156 for (Size i = 0; i < bfrr->dates().
size(); ++i) {
1157 if (bfrr->smileHasError()[i]) {
1160 bfrr->smileErrorMessage()[i]);
1161 }
1162 }
1163 }
1164 }
1165
1166 DLOG(
"Building calibration info for fx vol surface completed.");
1167 }
1168
1169 } catch (std::exception& e) {
1170 QL_FAIL("fx vol curve building failed: " << e.what());
1171 } catch (...) {
1172 QL_FAIL("fx vol curve building failed: unknown error");
1173 }
1174}
const std::string & curveConfigID() 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::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_
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_
QuantLib::Option::Type riskReversalInFavorOf_
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.
Period parsePeriod(const string &s)
Convert text to QuantLib::Period.
#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)
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)
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