@@ -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