Skip to content

Commit f2b0f4f

Browse files
author
sebastien.bouvard
committed
QPR-13774 Wildcard allowed for explicit configuration + fix Saccr test
1 parent c1ccc19 commit f2b0f4f

1 file changed

Lines changed: 80 additions & 24 deletions

File tree

OREData/ored/marketdata/equityvolcurve.cpp

Lines changed: 80 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -778,25 +778,45 @@ void EquityVolCurve::buildVolatility(const QuantLib::Date& asof, EquityVolatilit
778778
QL_REQUIRE(vdsc.quoteType() == MarketDatum::QuoteType::RATE_LNVOL,
779779
"EquityVolCurve: only quote type"
780780
<< " RATE_LNVOL is currently supported for a 2-D volatility delta strike surface.");
781+
782+
// Put Deltas may be configured with a wildcard or given explicitly
783+
bool putDeltaWc = false;
784+
vector<Real>putDeltas(1);
785+
if (find(vdsc.putDeltas().begin(), vdsc.putDeltas().end(), "*") != vdsc.putDeltas().end()) {
786+
putDeltaWc = true;
787+
QL_REQUIRE(vdsc.putDeltas().size() == 1, "Wild card expiry specified but more expiries also specified.");
788+
DLOG("EquityVolCurve: Have Put Deltas wildcard pattern " << vdsc.putDeltas()[0]);
789+
}else{
790+
// Parse, sort and check the vector of configured put deltas
791+
putDeltas = parseVectorOfValues<Real>(vdsc.putDeltas(), &parseReal);
792+
sort(putDeltas.begin(), putDeltas.end(), [](Real x, Real y) { return !close(x, y) && x < y; });
793+
QL_REQUIRE(adjacent_find(putDeltas.begin(), putDeltas.end(), [](Real x, Real y) { return close(x, y); }) ==
794+
putDeltas.end(),
795+
"EquityVolCurve: The configured put deltas contain duplicates");
796+
DLOG("EquityVolCurve: Parsed " << putDeltas.size() << " unique configured put deltas");
797+
DLOG("EquityVolCurve: Put deltas are: " << join(putDeltas | transformed([](Real d) { return ore::data::to_string(d); }), ","));
798+
}
799+
781800

782-
// Parse, sort and check the vector of configured put deltas
783-
vector<Real> putDeltas = parseVectorOfValues<Real>(vdsc.putDeltas(), &parseReal);
784-
sort(putDeltas.begin(), putDeltas.end(), [](Real x, Real y) { return !close(x, y) && x < y; });
785-
QL_REQUIRE(adjacent_find(putDeltas.begin(), putDeltas.end(), [](Real x, Real y) { return close(x, y); }) ==
786-
putDeltas.end(),
787-
"EquityVolCurve: The configured put deltas contain duplicates");
788-
DLOG("EquityVolCurve: Parsed " << putDeltas.size() << " unique configured put deltas");
789-
DLOG("EquityVolCurve: Put deltas are: " << join(putDeltas | transformed([](Real d) { return ore::data::to_string(d); }), ","));
790-
791-
// Parse, sort descending and check the vector of configured call deltas
792-
vector<Real> callDeltas = parseVectorOfValues<Real>(vdsc.callDeltas(), &parseReal);
793-
sort(callDeltas.begin(), callDeltas.end(), [](Real x, Real y) { return !close(x, y) && x > y; });
794-
QL_REQUIRE(adjacent_find(callDeltas.begin(), callDeltas.end(), [](Real x, Real y) { return close(x, y); }) ==
795-
callDeltas.end(),
796-
"EquityVolCurve: The configured call deltas contain duplicates");
797-
DLOG("EquityVolCurve: Parsed " << callDeltas.size() << " unique configured call deltas");
798-
DLOG("EquityVolCurve: Call deltas are: " << join(callDeltas | transformed([](Real d) { return ore::data::to_string(d); }), ","));
799-
801+
802+
// Call Deltas may be configured with a wildcard or given explicitly
803+
bool callDeltaWc = false;
804+
vector<Real>callDeltas(1);
805+
if (find(vdsc.callDeltas().begin(), vdsc.callDeltas().end(), "*") != vdsc.callDeltas().end()) {
806+
callDeltaWc = true;
807+
QL_REQUIRE(vdsc.callDeltas().size() == 1, "Wild card expiry specified but more expiries also specified.");
808+
DLOG("EquityVolCurve: Have Call Deltas wildcard pattern " << vdsc.callDeltas()[0]);
809+
}else{
810+
// Parse, sort descending and check the vector of configured call deltas
811+
callDeltas = parseVectorOfValues<Real>(vdsc.callDeltas(), &parseReal);
812+
sort(callDeltas.begin(), callDeltas.end(), [](Real x, Real y) { return !close(x, y) && x > y; });
813+
QL_REQUIRE(adjacent_find(callDeltas.begin(), callDeltas.end(), [](Real x, Real y) { return close(x, y); }) ==
814+
callDeltas.end(),
815+
"EquityVolCurve: The configured call deltas contain duplicates");
816+
DLOG("EquityVolCurve: Parsed " << callDeltas.size() << " unique configured call deltas");
817+
DLOG("EquityVolCurve: Call deltas are: " << join(callDeltas | transformed([](Real d) { return ore::data::to_string(d); }), ","));
818+
}
819+
800820
// Expiries may be configured with a wildcard or given explicitly
801821
bool expWc = false;
802822
if (find(vdsc.expiries().begin(), vdsc.expiries().end(), "*") != vdsc.expiries().end()) {
@@ -805,6 +825,46 @@ void EquityVolCurve::buildVolatility(const QuantLib::Date& asof, EquityVolatilit
805825
DLOG("EquityVolCurve: Have expiry wildcard pattern " << vdsc.expiries()[0]);
806826
}
807827

828+
// If delta wildcards are used, discover all available put/call deltas from market data
829+
std::ostringstream ss;
830+
ss << MarketDatum::InstrumentType::EQUITY_OPTION << "/" << vdsc.quoteType() << "/" << vc.equityId() << "/"
831+
<< vc.ccy() << "/*";
832+
Wildcard w(ss.str());
833+
auto loadedQuotes = loader.get(w, asof);
834+
if (putDeltaWc || callDeltaWc) {
835+
std::set<Real> discoveredPutDeltas, discoveredCallDeltas;
836+
for (const auto& md : loadedQuotes) {
837+
QL_REQUIRE(md->asofDate() == asof, "MarketDatum asofDate '" << md->asofDate() << "' <> asof '" << asof << "'");
838+
auto q = QuantLib::ext::dynamic_pointer_cast<EquityOptionQuote>(md);
839+
QL_REQUIRE(q, "Internal error: could not downcast MarketDatum '" << md->name() << "' to EquityOptionQuote");
840+
if (q->eqName() != vc.equityId() || q->ccy() != vc.ccy() || q->quoteType() != vdsc.quoteType())
841+
continue;
842+
if (!expWc) {
843+
auto itCfg = find(vc.quotes().begin(), vc.quotes().end(), q->name());
844+
if (itCfg == vc.quotes().end())
845+
continue;
846+
}
847+
auto ds = QuantLib::ext::dynamic_pointer_cast<DeltaStrike>(q->strike());
848+
if (!ds)
849+
continue;
850+
Real d = std::fabs(ds->delta());
851+
if (ds->optionType() == Option::Put && putDeltaWc)
852+
discoveredPutDeltas.insert(d);
853+
if (ds->optionType() == Option::Call && callDeltaWc)
854+
discoveredCallDeltas.insert(d);
855+
}
856+
if (putDeltaWc) {
857+
QL_REQUIRE(!discoveredPutDeltas.empty(), "EquityVolCurve: No put delta quotes found for wildcard configuration");
858+
putDeltas.assign(discoveredPutDeltas.begin(), discoveredPutDeltas.end());
859+
}
860+
if (callDeltaWc) {
861+
QL_REQUIRE(!discoveredCallDeltas.empty(), "EquityVolCurve: No call delta quotes found for wildcard configuration");
862+
callDeltas.assign(discoveredCallDeltas.begin(), discoveredCallDeltas.end());
863+
sort(callDeltas.begin(), callDeltas.end(), [](Real x, Real y) { return !close(x, y) && x > y; });
864+
}
865+
DLOG("EquityVolCurve: Discovered " << putDeltas.size() << " put deltas and " << callDeltas.size() << " call deltas via wildcard.");
866+
}
867+
808868
// Map to hold the rows of the equity volatility matrix. The keys are the expiry dates and the values are the
809869
// vectors of volatilities, one for each configured delta.
810870
map<Date, vector<Real>> surfaceData;
@@ -835,11 +895,7 @@ void EquityVolCurve::buildVolatility(const QuantLib::Date& asof, EquityVolatilit
835895
}
836896

837897
// Read the quotes to fill the expiry dates and vols matrix.
838-
std::ostringstream ss;
839-
ss << MarketDatum::InstrumentType::EQUITY_OPTION << "/" << vdsc.quoteType() << "/" << vc.equityId() << "/"
840-
<< vc.ccy() << "/*";
841-
Wildcard w(ss.str());
842-
for (const auto& md : loader.get(w, asof)) {
898+
for (const auto& md : loadedQuotes) {
843899

844900
QL_REQUIRE(md->asofDate() == asof, "MarketDatum asofDate '" << md->asofDate() << "' <> asof '" << asof << "'");
845901

@@ -908,7 +964,7 @@ void EquityVolCurve::buildVolatility(const QuantLib::Date& asof, EquityVolatilit
908964
DLOG("EquityVolCurve: EquityVolCurve: added " << quotesAdded << " quotes in building delta strike surface.");
909965

910966
// Check the data gathered.
911-
if (expWc) {
967+
if (expWc || putDeltaWc || callDeltaWc) {
912968
// If the expiries were configured via a wildcard, check that no surfaceData element has a Null<Real>().
913969
for (const auto& kv : surfaceData) {
914970
for (Size j = 0; j < numStrikes; j++) {

0 commit comments

Comments
 (0)