Skip to content

Commit 8dd578f

Browse files
committed
Merge remote-tracking branch 'origin/feature/enable-usage-of-wildcard-for-direct-segment-quotes' into github-191
2 parents d2e1ae6 + 6c61c4d commit 8dd578f

4 files changed

Lines changed: 127 additions & 22 deletions

File tree

Docs/UserGuide/curve_configurations/yieldcurves.tex

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,11 @@ \subsubsection*{Direct Segment}
121121
or discount factor. The \lstinline!Conventions! node contains the ID of a node in the {\tt conventions.xml} file
122122
described in section \ref{sec:conventions}. The \lstinline!Conventions! node associates conventions with the quotes.
123123
124+
For \emph{Discount} type segments, the quotes can be given using a wildcard. Any valid and matching quotes will then be loaded from the provided market data. An example wildcard is:
125+
\begin{itemize}
126+
\item {DISCOUNT/RATE/EUR/EUR3M/*}
127+
\end{itemize}
128+
124129
\begin{listing}[H]
125130
%\hrule\medskip
126131
\begin{minted}[fontsize=\footnotesize]{xml}

Docs/UserGuide/marketdata.tex

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ \subsection{Discount Factor}\label{ss:discount_rate}
144144
Examples with a Term and with a DiscountDate:
145145
\begin{itemize}
146146
\item {DISCOUNT/RATE/EUR/EUR3M/3Y}
147-
\item {DISCOUNT/RATE/EUR/EUR3M/A365F/12-05-2018}
147+
\item {DISCOUNT/RATE/EUR/EUR3M/12-05-2018}
148148
\end{itemize}
149149

150150
%- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -

OREData/ored/marketdata/yieldcurve.cpp

Lines changed: 52 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -892,39 +892,70 @@ void YieldCurve::buildDiscountCurve() {
892892
boost::shared_ptr<Conventions> conventions = InstrumentConventions::instance().conventions();
893893
boost::shared_ptr<Convention> convention;
894894

895-
for (Size i = 0; i < discountQuoteIDs.size(); ++i) {
896-
boost::shared_ptr<MarketDatum> marketQuote = loader_.get(discountQuoteIDs[i], asofDate_);
897-
if (marketQuote) {
898-
QL_REQUIRE(marketQuote->instrumentType() == MarketDatum::InstrumentType::DISCOUNT,
899-
"Market quote not of type Discount.");
900-
boost::shared_ptr<DiscountQuote> discountQuote = boost::dynamic_pointer_cast<DiscountQuote>(marketQuote);
895+
vector<string> quotes;
896+
quotes.reserve(discountQuoteIDs.size()); // Reserve space for efficiency
901897

902-
if(discountQuote->date() != Date()){
898+
std::transform(discountQuoteIDs.begin(), discountQuoteIDs.end(), std::back_inserter(quotes),
899+
[](const std::pair<string, bool>& pair) {
900+
return pair.first; // Extract only the quote part
901+
});
903902

904-
data[discountQuote->date()] = discountQuote->quote()->value();
903+
auto wildcard = getUniqueWildcard(quotes);
905904

906-
} else if (discountQuote->tenor() != Period()){
905+
std::set<boost::shared_ptr<MarketDatum>> marketData;
906+
if (wildcard) {
907+
marketData = loader_.get(*wildcard, asofDate_);
908+
} else {
909+
std::ostringstream ss;
910+
ss << MarketDatum::InstrumentType::DISCOUNT << "/" << MarketDatum::QuoteType::RATE << "/" << currency_ << "/*";
911+
Wildcard w(ss.str());
912+
marketData = loader_.get(w, asofDate_);
913+
}
907914

908-
if(!convention)
909-
convention = conventions->get(discountCurveSegment->conventionsID());
910-
boost::shared_ptr<ZeroRateConvention> zeroConvention = boost::dynamic_pointer_cast<ZeroRateConvention>(convention);
911-
QL_REQUIRE(zeroConvention, "could not cast to ZeroRateConvention");
915+
for (const auto& marketQuote : marketData) {
916+
QL_REQUIRE(marketQuote->instrumentType() == MarketDatum::InstrumentType::DISCOUNT,
917+
"Market quote not of type Discount.");
918+
boost::shared_ptr<DiscountQuote> discountQuote = boost::dynamic_pointer_cast<DiscountQuote>(marketQuote);
912919

913-
Calendar cal = zeroConvention->tenorCalendar();
914-
BusinessDayConvention rollConvention = zeroConvention->rollConvention();
915-
Date date = cal.adjust(cal.adjust(asofDate_, rollConvention) + discountQuote->tenor(), rollConvention);
916-
DLOG("YieldCurve::buildDiscountCurve - tenor " << discountQuote->tenor() << " to date " << io::iso_date(date));
917-
data[date] = discountQuote->quote()->value();
920+
// filtering
921+
if (!wildcard) {
922+
vector<string>::const_iterator it = find(quotes.begin(), quotes.end(), discountQuote->name());
923+
if (it == quotes.end())
924+
continue;
925+
}
918926

919-
} else {
920-
QL_FAIL("YieldCurve::buildDiscountCurve - neither date nor tenor recognised");
921-
}
927+
if (discountQuote->date() != Date()) {
922928

929+
data[discountQuote->date()] = discountQuote->quote()->value();
930+
931+
} else if (discountQuote->tenor() != Period()) {
932+
933+
if (!convention)
934+
convention = conventions->get(discountCurveSegment->conventionsID());
935+
boost::shared_ptr<ZeroRateConvention> zeroConvention =
936+
boost::dynamic_pointer_cast<ZeroRateConvention>(convention);
937+
QL_REQUIRE(zeroConvention, "could not cast to ZeroRateConvention");
938+
939+
Calendar cal = zeroConvention->tenorCalendar();
940+
BusinessDayConvention rollConvention = zeroConvention->rollConvention();
941+
Date date = cal.adjust(cal.adjust(asofDate_, rollConvention) + discountQuote->tenor(), rollConvention);
942+
DLOG("YieldCurve::buildDiscountCurve - tenor " << discountQuote->tenor() << " to date "
943+
<< io::iso_date(date));
944+
data[date] = discountQuote->quote()->value();
945+
946+
} else {
947+
QL_FAIL("YieldCurve::buildDiscountCurve - neither date nor tenor recognised");
923948
}
924949
}
925950

951+
// Some logging and checks
926952
QL_REQUIRE(data.size() > 0, "No market data found for curve spec " << curveSpec_.name() << " with as of date "
927953
<< io::iso_date(asofDate_));
954+
if (!wildcard) {
955+
QL_REQUIRE(data.size() == quotes.size(), "Found " << data.size() << " quotes, but "
956+
<< quotes.size()
957+
<< " quotes given in config " << curveConfig_->curveID());
958+
}
928959

929960
if (data.begin()->first > asofDate_) {
930961
DLOG("Insert discount curve point at time zero for " << curveSpec_.name());

OREData/test/yieldcurve.cpp

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,75 @@ BOOST_AUTO_TEST_CASE(testBootstrapAndFixings) {
246246
BOOST_CHECK_NO_THROW(YieldCurve jpyYieldCurve(asof, spec, curveConfigs, loader));
247247
}
248248

249+
BOOST_AUTO_TEST_CASE(testBuildDiscountCurveDirectSegment) {
250+
251+
Date asof(13, October, 2023);
252+
Settings::instance().evaluationDate() = asof;
253+
254+
YieldCurveSpec spec("EUR", "EUR-CURVE");
255+
256+
CurveConfigurations curveConfigs;
257+
258+
vector<string> quotes;
259+
quotes.emplace_back("DISCOUNT/RATE/EUR/EUR-CURVE/2023-10-14");
260+
quotes.emplace_back("DISCOUNT/RATE/EUR/EUR-CURVE/2023-10-15");
261+
262+
vector<boost::shared_ptr<YieldCurveSegment>> segments{boost::make_shared<DirectYieldCurveSegment>(
263+
"Discount", "", quotes)};
264+
265+
boost::shared_ptr<YieldCurveConfig> yCConfig =
266+
boost::make_shared<YieldCurveConfig>("EUR-CURVE", "ORE YieldCurve built from EUR-CURVE", "EUR", "", segments);
267+
curveConfigs.add(CurveSpec::CurveType::Yield, "EUR-CURVE", yCConfig);
268+
269+
vector<string> data{"2023-10-12 DISCOUNT/RATE/SEK/STINA-CURVE/2023-10-13 0.77",
270+
"2023-10-12 DISCOUNT/RATE/EUR/EUR-ANOTHER-CURVE/2023-10-13 0.95",
271+
"2023-10-13 DISCOUNT/RATE/EUR/EUR-ANOTHER-CURVE/2023-10-14 0.95",
272+
"2023-10-12 DISCOUNT/RATE/EUR/EUR-CURVE/2023-10-12 0.88",
273+
"2023-10-13 DISCOUNT/RATE/EUR/EUR-CURVE/2023-10-13 1.0",
274+
"2023-10-13 DISCOUNT/RATE/EUR/EUR-CURVE/2023-10-14 0.99",
275+
"2023-10-13 DISCOUNT/RATE/EUR/EUR-CURVE/2023-10-15 0.98",
276+
"2023-10-13 COMMODITY_FWD/PRICE/GOLD/USD/2023-10-31 1158.8",
277+
"2023-10-13 COMMODITY_FWD/PRICE/GOLD/USD/2023-11-01 1160.9",
278+
"2023-10-13 COMMODITY_FWD/PRICE/GOLD/USD/2023-11-02 1163.4"};
279+
MarketDataLoader loader(data);
280+
281+
BOOST_CHECK_NO_THROW(YieldCurve yieldCurve(asof, spec, curveConfigs, loader));
282+
}
283+
284+
BOOST_AUTO_TEST_CASE(testBuildDiscountCurveDirectSegmentWildcard) {
285+
286+
Date asof(13, October, 2023);
287+
Settings::instance().evaluationDate() = asof;
288+
289+
YieldCurveSpec spec("EUR", "EUR-CURVE");
290+
291+
CurveConfigurations curveConfigs;
292+
293+
vector<string> quotes;
294+
quotes.emplace_back("DISCOUNT/RATE/EUR/EUR-CURVE/*");
295+
296+
vector<boost::shared_ptr<YieldCurveSegment>> segments{
297+
boost::make_shared<DirectYieldCurveSegment>("Discount", "", quotes)};
298+
299+
boost::shared_ptr<YieldCurveConfig> yCConfig = boost::make_shared<YieldCurveConfig>(
300+
"EUR-CURVE", "ORE YieldCurve built from EUR-CURVE", "EUR", "", segments);
301+
curveConfigs.add(CurveSpec::CurveType::Yield, "EUR-CURVE", yCConfig);
302+
303+
vector<string> data{"2023-10-12 DISCOUNT/RATE/SEK/STINA-CURVE/2023-10-13 0.77",
304+
"2023-10-12 DISCOUNT/RATE/EUR/EUR-ANOTHER-CURVE/2023-10-13 0.95",
305+
"2023-10-13 DISCOUNT/RATE/EUR/EUR-ANOTHER-CURVE/2023-10-14 0.95",
306+
"2023-10-13 DISCOUNT/RATE/EUR/EUR-CURVE/2023-10-13 1.0",
307+
"2023-10-13 DISCOUNT/RATE/EUR/EUR-CURVE/2023-10-14 0.99",
308+
"2023-10-13 DISCOUNT/RATE/EUR/EUR-CURVE/2023-10-15 0.98",
309+
"2023-10-13 EQUITY_FWD/PRICE/SP5/USD/1Y 1500.00",
310+
"2023-10-13 EQUITY_FWD/PRICE/SP5/USD/20231014 1500.00",
311+
"2023-10-13 EQUITY_DIVIDEND/RATE/SP5/USD/20231015 0.00",
312+
"2023-10-13 EQUITY_DIVIDEND/RATE/SP5/USD/2Y 0.00"};
313+
MarketDataLoader loader(data);
314+
315+
BOOST_CHECK_NO_THROW(YieldCurve yieldCurve(asof, spec, curveConfigs, loader));
316+
}
317+
249318
// Test ARS-IN-USD failures using the old QuantLib::IterativeBootstrap parameters
250319
BOOST_DATA_TEST_CASE(testBootstrapARSinUSDFailures, bdata::make(curveConfigFiles), curveConfigFile) {
251320

0 commit comments

Comments
 (0)