Skip to content

Commit 938a2fa

Browse files
nathaniel.volfangojenkins
authored andcommitted
QPR-12492 -- Add EOM convention
1 parent 40f3219 commit 938a2fa

4 files changed

Lines changed: 65 additions & 32 deletions

File tree

OREData/ored/portfolio/schedule.cpp

Lines changed: 50 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ void ScheduleRules::fromXML(XMLNode* node) {
9292
}
9393
rule_ = XMLUtils::getChildValue(node, "Rule");
9494
endOfMonth_ = XMLUtils::getChildValue(node, "EndOfMonth");
95+
endOfMonthConvention_ = XMLUtils::getChildValue(node, "EndOfMonthConvention");
9596
firstDate_ = XMLUtils::getChildValue(node, "FirstDate");
9697
lastDate_ = XMLUtils::getChildValue(node, "LastDate");
9798
removeFirstDate_ = XMLUtils::getChildValueAsBool(node, "RemoveFirstDate", false, false);
@@ -109,6 +110,8 @@ XMLNode* ScheduleRules::toXML(XMLDocument& doc) const {
109110
XMLUtils::addChild(doc, rules, "TermConvention", termConvention_);
110111
XMLUtils::addChild(doc, rules, "Rule", rule_);
111112
XMLUtils::addChild(doc, rules, "EndOfMonth", endOfMonth_);
113+
if (!endOfMonthConvention_.empty())
114+
XMLUtils::addChild(doc, rules, "EndOfMonthConvention", endOfMonthConvention_);
112115
XMLUtils::addChild(doc, rules, "FirstDate", firstDate_);
113116
XMLUtils::addChild(doc, rules, "LastDate", lastDate_);
114117
if(removeFirstDate_)
@@ -131,10 +134,10 @@ void ScheduleDates::fromXML(XMLNode* node) {
131134
XMLNode* ScheduleDates::toXML(XMLDocument& doc) const {
132135
XMLNode* node = doc.allocNode("Dates");
133136
XMLUtils::addChild(doc, node, "Calendar", calendar_);
134-
if (convention_ != "")
137+
if (!convention_.empty())
135138
XMLUtils::addChild(doc, node, "Convention", convention_);
136139
XMLUtils::addChild(doc, node, "Tenor", was1T_ ? "1T" : tenor_);
137-
if (endOfMonth_ != "")
140+
if (!endOfMonth_.empty())
138141
XMLUtils::addChild(doc, node, "EndOfMonth", endOfMonth_);
139142
XMLUtils::addChildren(doc, node, "Dates", "Date", dates_);
140143
return node;
@@ -261,25 +264,28 @@ Schedule makeSchedule(const ScheduleDates& data) {
261264
QL_REQUIRE(data.dates().size() > 0, "Must provide at least 1 date for Schedule");
262265
Calendar calendar = parseCalendar(data.calendar());
263266
BusinessDayConvention convention = ModifiedFollowing;
264-
if (data.convention() != "")
267+
if (!data.convention().empty())
265268
convention = parseBusinessDayConvention(data.convention());
266269
// Avoid compiler warning on gcc
267270
// https://www.boost.org/doc/libs/1_74_0/libs/optional/doc/html/boost_optional/tutorial/
268271
// gotchas/false_positive_with__wmaybe_uninitialized.html
269272
auto tenor = boost::make_optional(false, Period());
270-
if (data.tenor() != "")
273+
if (!data.tenor().empty())
271274
tenor = parsePeriod(data.tenor());
272275
bool endOfMonth = false;
273-
if (data.endOfMonth() != "")
276+
if (!data.endOfMonth().empty())
274277
endOfMonth = parseBool(data.endOfMonth());
278+
ext::optional<BusinessDayConvention> endOfMonthConvention = boost::none;
279+
if (!data.endOfMonthConvention().empty())
280+
endOfMonthConvention = parseBusinessDayConvention(data.endOfMonthConvention());
275281

276282
// Ensure that Schedule ctor is passed a vector of unique ordered dates.
277283
std::set<Date> uniqueDates;
278284
for (const string& d : data.dates())
279285
uniqueDates.insert(calendar.adjust(parseDate(d), convention));
280286

281-
return Schedule(vector<Date>(uniqueDates.begin(), uniqueDates.end()), calendar, convention, boost::none, tenor,
282-
boost::none, endOfMonth);
287+
return QuantLib::Schedule(vector<Date>(uniqueDates.begin(), uniqueDates.end()), calendar, convention, boost::none,
288+
tenor, boost::none, endOfMonth, vector<bool>(0), false, false, endOfMonthConvention);
283289
}
284290

285291
Schedule makeSchedule(const ScheduleDerived& data, const Schedule& baseSchedule) {
@@ -314,9 +320,14 @@ Schedule makeSchedule(const ScheduleDerived& data, const Schedule& baseSchedule)
314320
derivedDate = calendar.advance(d, shift, convention);
315321
derivedDates.push_back(derivedDate);
316322
}
317-
return Schedule(vector<Date>(derivedDates.begin(), derivedDates.end()), calendar, convention, boost::none,
318-
baseSchedule.tenor(), boost::none, baseSchedule.endOfMonth(), std::vector<bool>(0),
319-
data.removeFirstDate(), data.removeLastDate());
323+
ext::optional<BusinessDayConvention> endOfMonthConvention = boost::none;
324+
if (baseSchedule.hasEndOfMonthBusinessDayConvention())
325+
endOfMonthConvention = baseSchedule.endOfMonthBusinessDayConvention();
326+
327+
return QuantLib::Schedule(vector<Date>(derivedDates.begin(), derivedDates.end()), calendar, convention, boost::none,
328+
baseSchedule.tenor(), boost::none, baseSchedule.endOfMonth(), std::vector<bool>(0),
329+
data.removeFirstDate(), data.removeLastDate(),
330+
endOfMonthConvention);
320331
}
321332

322333
Schedule makeSchedule(const ScheduleRules& data, const Date& openEndDateReplacement) {
@@ -349,6 +360,7 @@ Schedule makeSchedule(const ScheduleRules& data, const Date& openEndDateReplacem
349360
BusinessDayConvention bdcEnd = ModifiedFollowing;
350361
DateGeneration::Rule rule = DateGeneration::Forward;
351362
bool endOfMonth = false;
363+
ext::optional<BusinessDayConvention> endOfMonthConvention = boost::none;
352364

353365
// now check the strings, if they are empty we take defaults
354366
if (!data.convention().empty())
@@ -360,6 +372,8 @@ Schedule makeSchedule(const ScheduleRules& data, const Date& openEndDateReplacem
360372

361373
if (!data.endOfMonth().empty())
362374
endOfMonth = parseBool(data.endOfMonth());
375+
if (!data.endOfMonthConvention().empty())
376+
endOfMonthConvention = parseBusinessDayConvention(data.endOfMonthConvention());
363377

364378
if (!data.rule().empty()) {
365379

@@ -369,12 +383,12 @@ Schedule makeSchedule(const ScheduleRules& data, const Date& openEndDateReplacem
369383
auto dates = everyWeekDayDates(startDate, endDate, firstDate, QuantLib::Thursday);
370384
for (auto& d : dates)
371385
d = calendar.adjust(d, bdc);
372-
return Schedule(dates, calendar, bdc, bdcEnd, tenor, rule, endOfMonth);
386+
return QuantLib::Schedule(dates, calendar, bdc, bdcEnd, tenor, rule, endOfMonth, std::vector<bool>(0), false, false, endOfMonthConvention);
373387
} else if (data.rule() == "BusinessWeek" || data.rule() == "CalendarWeek") {
374388
auto dates = weeklyDates(startDate, endDate, firstDate, data.rule() == "CalendarWeek");
375389
for (auto& d : dates)
376390
d = calendar.adjust(d, bdc);
377-
return Schedule(dates, calendar, bdc, bdcEnd, tenor, rule, endOfMonth, std::vector<bool>(0), data.removeFirstDate(), data.removeLastDate());
391+
return QuantLib::Schedule(dates, calendar, bdc, bdcEnd, tenor, rule, endOfMonth, std::vector<bool>(0), data.removeFirstDate(), data.removeLastDate(), endOfMonthConvention);
378392
}
379393

380394
// parse rule for further processing below
@@ -391,21 +405,23 @@ Schedule makeSchedule(const ScheduleRules& data, const Date& openEndDateReplacem
391405
// first (last) date of the schedule built in QL with a given first (last) date
392406
// The schedule builder in QL itself is not capable of doing this, it just throws an exception
393407
// if a first (last) date is given in combination with a CDS / CDS2015 date generation rule.
394-
std::vector<Date> dates = Schedule(startDate, endDate, tenor, calendar, bdc, bdcEnd, rule, endOfMonth).dates();
408+
std::vector<Date> dates = QuantLib::Schedule(startDate, endDate, tenor, calendar, bdc, bdcEnd, rule, endOfMonth,
409+
Date(), Date(), false, false, endOfMonthConvention)
410+
.dates();
395411
QL_REQUIRE(!dates.empty(),
396412
"got empty CDS or CDS2015 schedule, startDate = " << startDate << ", endDate = " << endDate);
397413
if (firstDate != Date())
398414
dates.front() = firstDate;
399415
if (lastDate != Date())
400416
dates.back() = lastDate;
401-
return Schedule(dates, calendar, bdc, bdcEnd, tenor, rule, endOfMonth, std::vector<bool>(0),
402-
data.removeFirstDate(), data.removeLastDate());
417+
return QuantLib::Schedule(dates, calendar, bdc, bdcEnd, tenor, rule, endOfMonth, std::vector<bool>(0),
418+
data.removeFirstDate(), data.removeLastDate(), endOfMonthConvention);
403419
}
404420

405421
// default handling (QuantLib scheduler)
406422

407-
return Schedule(startDate, endDate, tenor, calendar, bdc, bdcEnd, rule, endOfMonth, firstDate, lastDate,
408-
data.removeFirstDate(), data.removeLastDate());
423+
return QuantLib::Schedule(startDate, endDate, tenor, calendar, bdc, bdcEnd, rule, endOfMonth, firstDate, lastDate,
424+
data.removeFirstDate(), data.removeLastDate(), endOfMonthConvention);
409425
}
410426

411427
namespace {
@@ -465,9 +481,11 @@ Schedule makeSchedule(const ScheduleData& data, const Date& openEndDateReplaceme
465481
Period tenor;
466482
DateGeneration::Rule rule = DateGeneration::Zero; // initialization prevents gcc warning
467483
bool endOfMonth = false; // initialization prevents gcc warning
484+
BusinessDayConvention endOfMonthConvention = Null<BusinessDayConvention>();
468485
bool hasCalendar = false, hasConvention = false, hasTermConvention = false, hasTenor = false, hasRule = false,
469-
hasEndOfMonth = false, hasConsistentCalendar = true, hasConsistentConvention = true,
470-
hasConsistentTenor = true, hasConsistentRule = true, hasConsistentEndOfMonth = true;
486+
hasEndOfMonth = false, hasEndOfMonthConvention = false, hasConsistentCalendar = true,
487+
hasConsistentConvention = true, hasConsistentTenor = true, hasConsistentRule = true,
488+
hasConsistentEndOfMonth = true, hasConsistentEndOfMonthConvention = true;
471489
for (auto& d : data.dates()) {
472490
updateData<Calendar>(d.calendar(), calendar, hasCalendar, hasConsistentCalendar, parseCalendarTemp);
473491
updateData<BusinessDayConvention>(d.convention(), convention, hasConvention, hasConsistentConvention,
@@ -480,6 +498,8 @@ Schedule makeSchedule(const ScheduleData& data, const Date& openEndDateReplaceme
480498
parseBusinessDayConvention);
481499
updateData<Period>(d.tenor(), tenor, hasTenor, hasConsistentTenor, parsePeriod);
482500
updateData<bool>(d.endOfMonth(), endOfMonth, hasEndOfMonth, hasConsistentEndOfMonth, parseBool);
501+
updateData<BusinessDayConvention>(d.endOfMonthConvention(), endOfMonthConvention, hasEndOfMonthConvention,
502+
hasConsistentEndOfMonthConvention, parseBusinessDayConvention);
483503
updateData<DateGeneration::Rule>(d.rule(), rule, hasRule, hasConsistentRule, parseDateGenerationRule);
484504
if (d.termConvention() != "") {
485505
hasTermConvention = true;
@@ -518,13 +538,17 @@ Schedule makeSchedule(const ScheduleData& data, const Date& openEndDateReplaceme
518538
}
519539

520540
// 4) Build schedule
521-
return Schedule(dates, hasCalendar && hasConsistentCalendar ? calendar : NullCalendar(),
522-
hasConvention && hasConsistentConvention ? convention : Unadjusted,
523-
hasTermConvention ? boost::optional<BusinessDayConvention>(termConvention) : boost::none,
524-
hasTenor && hasConsistentTenor ? boost::optional<Period>(tenor) : boost::none,
525-
hasRule && hasConsistentRule ? boost::optional<DateGeneration::Rule>(rule) : boost::none,
526-
hasEndOfMonth && hasConsistentEndOfMonth ? boost::optional<bool>(endOfMonth) : boost::none,
527-
isRegular);
541+
return QuantLib::Schedule(
542+
dates, hasCalendar && hasConsistentCalendar ? calendar : NullCalendar(),
543+
hasConvention && hasConsistentConvention ? convention : Unadjusted,
544+
hasTermConvention ? ext::optional<BusinessDayConvention>(termConvention) : boost::none,
545+
hasTenor && hasConsistentTenor ? ext::optional<Period>(tenor) : boost::none,
546+
hasRule && hasConsistentRule ? ext::optional<DateGeneration::Rule>(rule) : boost::none,
547+
hasEndOfMonth && hasConsistentEndOfMonth ? ext::optional<bool>(endOfMonth) : boost::none, isRegular,
548+
false, false,
549+
hasEndOfMonthConvention && hasConsistentEndOfMonthConvention
550+
? ext::optional<BusinessDayConvention>(endOfMonthConvention)
551+
: boost::none);
528552
}
529553
}
530554
} // namespace data

OREData/ored/portfolio/schedule.hpp

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,12 @@ class ScheduleRules : public XMLSerializable {
4242
ScheduleRules(const string& startDate, const string& endDate, const string& tenor, const string& calendar,
4343
const string& convention, const string& termConvention, const string& rule,
4444
const string& endOfMonth = "N", const string& firstDate = "", const string& lastDate = "",
45-
const bool removeFirstDate = false, const bool removeLastDate = false)
45+
const bool removeFirstDate = false, const bool removeLastDate = false,
46+
const string& endOfMonthConvention = "")
4647
: startDate_(startDate), endDate_(endDate), tenor_(tenor), calendar_(calendar), convention_(convention),
47-
termConvention_(termConvention), rule_(rule), endOfMonth_(endOfMonth), firstDate_(firstDate),
48-
lastDate_(lastDate), removeFirstDate_(removeFirstDate), removeLastDate_(removeLastDate) {}
48+
termConvention_(termConvention), rule_(rule), endOfMonth_(endOfMonth),
49+
endOfMonthConvention_(endOfMonthConvention), firstDate_(firstDate), lastDate_(lastDate),
50+
removeFirstDate_(removeFirstDate), removeLastDate_(removeLastDate) {}
4951

5052
//! Check if key attributes are empty
5153
const bool hasData() const { return !startDate_.empty() && !tenor_.empty(); }
@@ -61,6 +63,7 @@ class ScheduleRules : public XMLSerializable {
6163
const string& termConvention() const { return termConvention_; }
6264
const string& rule() const { return rule_; }
6365
const string& endOfMonth() const { return endOfMonth_; }
66+
const string& endOfMonthConvention() const { return endOfMonthConvention_; }
6467
const string& firstDate() const { return firstDate_; }
6568
const string& lastDate() const { return lastDate_; }
6669
bool removeFirstDate() const { return removeFirstDate_; }
@@ -74,6 +77,7 @@ class ScheduleRules : public XMLSerializable {
7477
string& modifyCalendar() { return calendar_; }
7578
string& modifyConvention() { return convention_; }
7679
string& modifyTermConvention() { return termConvention_; }
80+
string& modifyEndOfMonthConvention() { return endOfMonthConvention_; }
7781
//@}
7882

7983
//! \name Serialisation
@@ -90,6 +94,7 @@ class ScheduleRules : public XMLSerializable {
9094
string termConvention_;
9195
string rule_;
9296
string endOfMonth_;
97+
string endOfMonthConvention_;
9398
string firstDate_;
9499
string lastDate_;
95100
bool adjustEndDateToPreviousMonthEnd_{false};
@@ -108,8 +113,9 @@ class ScheduleDates : public XMLSerializable {
108113
ScheduleDates() {}
109114
//! Constructor
110115
ScheduleDates(const string& calendar, const string& convention, const string& tenor, const vector<string>& dates,
111-
const string& endOfMonth = "")
112-
: calendar_(calendar), convention_(convention), tenor_(tenor), endOfMonth_(endOfMonth), dates_(dates) {}
116+
const string& endOfMonth = "", const string& endOfMonthConvention = "")
117+
: calendar_(calendar), convention_(convention), tenor_(tenor), endOfMonth_(endOfMonth),
118+
endOfMonthConvention_(endOfMonthConvention), dates_(dates) {}
113119

114120
//! Check if key attributes are empty
115121
bool hasData() const { return dates_.size() > 0 && !tenor_.empty(); }
@@ -120,6 +126,7 @@ class ScheduleDates : public XMLSerializable {
120126
const string& convention() const { return convention_; }
121127
const string& tenor() const { return tenor_; }
122128
const string& endOfMonth() const { return endOfMonth_; }
129+
const string& endOfMonthConvention() const { return endOfMonthConvention_; }
123130
const vector<string>& dates() const { return dates_; }
124131
//@}
125132

@@ -138,6 +145,7 @@ class ScheduleDates : public XMLSerializable {
138145
string convention_;
139146
string tenor_;
140147
string endOfMonth_;
148+
string endOfMonthConvention_;
141149
vector<string> dates_;
142150
bool was1T_ = false;
143151
};

QuantLib

Submodule QuantLib updated from f8955cf to a30de00

xsd/ore_types.xsd

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,7 @@
835835
<xs:element type="businessDayConvention" name="TermConvention" minOccurs="0"/>
836836
<xs:element type="dateRule" name="Rule" minOccurs="0"/>
837837
<xs:element type="bool" name="EndOfMonth" minOccurs="0"/>
838+
<xs:element type="businessDayConvention" name="EndOfMonthConvention" minOccurs="0"/>
838839
<xs:element type="date" name="FirstDate" minOccurs="0"/>
839840
<xs:element type="date" name="LastDate" minOccurs="0"/>
840841
<xs:element type="xs:boolean" name="RemoveFirstDate" minOccurs="0"/>

0 commit comments

Comments
 (0)