Holidays in QuantLib
Welcome back.
Here’s another post that was originally published in Wilmott Magazine; this time, in its January 2024 issue. The full source code is available on my Tutorial page, together with code from other articles and, when available, either the articles themselves or corresponding blog posts like this one.
Subscribe to my Substack to receive my posts in your inbox, or follow me on Twitter or LinkedIn if you want to be notified of new posts, or subscribe via RSS if you’re the tech type: the buttons for all that are in the footer. Also, I’m available for training, both online and (when possible) on-site: visit my Training page for more information.
Holidays in QuantLib
When this article was published on the online version of Wilmott magazine, stores all over the world were busy with the holiday season. However, I’m not going to address consumerism; instead, I’ll have a much less controversial look at the ways that QuantLib can manage holidays. And if the holiday season is not underway when you read this, well, I won’t let that get in the way of a good intro.
As usual, the C++ code I’ll describe is runnable (given a working QuantLib installation). Let’s get the includes out of the way first:
#include <ql/time/calendars/target.hpp>
#include <ql/time/calendars/unitedstates.hpp>
#include <ql/time/calendars/jointcalendar.hpp>
#include <ql/time/calendars/bespokecalendar.hpp>
#include <iostream>
Dates and calendars
The part of the library that deals with dates and calendars is
relatively self-contained; in fact, I’ve been told that some people
only use that part of the library. If you’re an R user, there is even
a package called qlcal
1 that makes it available in R with an
idiomatic syntax (kudos, Dirk!)
For the purposes of this example, the code initializes a couple of dates.
int main() {
using namespace QuantLib;
auto d1 = Date(1, May, 2023);
auto d2 = Date(19, June, 2023);
Depending on where you live, the order of the arguments in the constructor might seem unfamiliar; which is why the month needs to be passed as an enumeration and not as a number. This makes it impossible to get confused and initialize the dates in the wrong way—well, it’s still possible to get confused, but if you try to pass month, day, year as in the U.S. format you’ll get a compile-time error, and if you pass year, month, day as in the ISO format you’ll get a run-time error, so at least you won’t do the wrong thing without noticing.
auto cal1 = TARGET();
auto cal2 = UnitedStates(UnitedStates::GovernmentBond);
Calendars are usually declared by country, possibly with an additional enumeration specifying different calendars for particular exchanges or purposes; for example, the second calendar declared here is the one used for the U.S. government bond market. The first calendar we’re declaring is an exception to the rule; it’s the super-national TARGET calendar used for Euro. All in all, the library declares a few dozen calendars plus, as we’ll see later, a few classes that make it possible to build more.
A note for all you library implementers out there: within reason, it’s better for your interfaces to be as strict as possible from the beginning. In our original implementation of the U.S. calendar, the enumeration was an optional argument; if you didn’t pass it to the constructor, you’d get a default choice. Because of this, some people were silently getting a calendar they didn’t want and wondered why holidays didn’t match; and clearer-headed people on Quantitative Stack Exchange (hi, Dimitri) were rightly pointing out: “What do you mean by the U.S. calendar? There’s no single U.S. calendar.” A few releases ago, we finally deprecated and then removed the default argument, and caused some temporary confusion (“why my code doesn’t compile anymore?”) to people that, for whatever reason, didn’t read our release notes. So, word of advice: default arguments are useful but be careful that they make actual sense.
Holidays, adjustments, end of month
Now, what can we do with calendars? Their basic functionality is to tell us whether a given day is a holiday or not; for instance, you can see in the code how the TARGET calendar tells us that May 1st, Labor Day, is a holiday, while the U.S. Government Bond calendar tells us that it’s a business day; the opposite holds if we look at June 19th, which is celebrated as Juneteenth in the United States. There is no particular treatment for weekends (they are simply considered holidays) and of course they, too, can change depending on the calendar.
std::cout << std::boolalpha;
std::cout << cal1.isHoliday(d1)
<< std::endl; // prints true
std::cout << cal2.isHoliday(d1)
<< std::endl; // prints false
std::cout << cal1.isBusinessDay(d2)
<< std::endl; // prints true
std::cout << cal2.isBusinessDay(d2)
<< std::endl; // prints false
When passed a holiday, calendars can also adjust it to the nearest
business day, where “nearest” is determined according to a given
convention; for instance, our example code writes out that the first
business day after May 1st 2023, according to TARGET, was May 2nd,
whereas the first business day before Juneteenth 2023 was June 16th
(the 17th and 18th being Saturday and Sunday). When passed a business
day, the adjust
method returns it unmodified.
std::cout << cal1.adjust(d1, Following)
<< std::endl; // prints May 2nd, 2023
std::cout << cal2.adjust(d2, Preceding)
<< std::endl; // prints June 16th, 2023
Other methods can tell us whether a given date is the last business day of the month; or, given a date, which date is the last business day of that month. For some reason, there are no corresponding methods for the first of the month.
std::cout << cal1.isEndOfMonth(d1)
<< std::endl; // prints false
std::cout << cal1.endOfMonth(d1)
<< std::endl; // prints May 31st, 2023
Jumping…
I’m not going to show how to build coupon schedules today, but we can
see how calendars provide the basic building block for that task;
namely, how they can advance a date by a number of months or years and
get the nearest business day. The basic functionality for jumping
(either forwards or backwards) is already in the Date
class, as we
can see in the code; and the Calendar
class combines, in its
advance
method, a jump and an adjustment.
std::cout << d2 + Period(2, Months)
<< std::endl; // prints August 19th, 2023
std::cout << cal1.adjust(d2 + Period(2, Months))
<< std::endl; // prints August 21st, 2023
std::cout << cal1.advance(d2, Period(2, Months))
<< std::endl; // prints August 21st, 2023
…and walking
There is a case, though, in which calling the advance
method gives a
different result than increasing the date and then calling adjust
;
and that’s when the period we’re passing is measured in days. In this
specific case, the advance
method increases the date by the given
number of business days; as we need to do, for instance, when
calculating a settlement date, or a lagged payment date.
std::cout << d2 + Period(7, Days)
<< std::endl; // prints June 26th, 2023
std::cout << cal1.adjust(d2 + Period(7, Days))
<< std::endl; // prints June 26th, 2023
std::cout << cal1.advance(d2, Period(7, Days))
<< std::endl; // prints June 28th, 2023
Needless to say, this has caused some confusion from time to time.
The problem here is that we’re conflating in just one identifier,
Days
, two different concepts; those of calendar days and business
days. One of the items in my long-term to-do list is to introduce
specific identifiers for them and slowly phase out the generic Days
.
Unforeseen holidays
Most holidays are, as you might expect, defined as rules. Some are more or less easy to express in terms of the Western calendar; they are something like “May 1st, every year”, or “January 1st, possibly moved to Monday if it falls on a weekend”, or again “the last Monday of August”. In time, rules can change, too: for instance, Juneteenth was declared as a holiday in 2022 and markets started closing on that day in 2023, so the corresponding rule must include that condition.
Other holidays, mostly from Asian markets, have rules which are based on a lunar calendar and are therefore harder to express. In this case, we usually code them as specific days and update them as they are published on the market site; “we” meaning our esteemed contributors. The one exception is Easter, which is also based on a lunar calendar but can be found tabulated for the next couple of centuries and more.
Finally, some specific days can be declared one-off holidays by the relevant authority; for instance, in correspondence of a general election or, like this past year, for the death of a sovereign. Another, more idiosyncratic case? This year, SIFMA decided that Good Friday would only be a half-closure (and therefore a business day for the corresponding calendar) but SOFR would not be fixed on that day—making us realize that, grr, we needed a specialized SOFR calendar besides the existing ones.
So, what are you to do if a new holiday is published or declared but
the next QuantLib release is three months away? Fortunately, you
don’t need to wait for us to update the code; you don’t even need to
update the code yourself and recompile the library, if you’re not so
inclined. Calendars provide a method, addHoliday
, that can declare
a date as a new holiday at run time; you can see it in action in the
code. You can also see that, when you add a holiday to a calendar,
all instances of the same calendar (TARGET, in this case) are affected
by the change. A corresponding method removeHoliday
is available,
even though it’s less commonly used.
auto d3 = Date(7, June, 2023);
std::cout << cal1.isHoliday(d3)
<< std::endl; // prints false
cal1.addHoliday(d3);
auto cal1b = TARGET();
std::cout << cal1b.isHoliday(d3)
<< std::endl; // prints true
Joint calendars
Another case in which the provided calendars are not quite enough:
building the schedules for some deals (or, in general, some
sets of dates) requires to consider holidays from two or more
calendars. In this case, the JointCalendar
class can be used to
create the required calendar from a few underlying ones; as the
example code shows, it’s possible to specify if we want to join the
sets of their holidays (i.e., a day is considered a holiday if it is a
holiday for at least one of the underlying calendars) or the sets of
their business days (i.e., a day is considered a holiday if it is a
holiday for all of the underlying calendars.)
auto cal3 = JointCalendar(cal1, cal2,
JoinHolidays);
auto cal4 = JointCalendar(cal1, cal2,
JoinBusinessDays);
std::cout << cal3.isHoliday(d1)
<< std::endl; // prints true
std::cout << cal4.isHoliday(d1)
<< std::endl; // prints false
auto d4 = Date(25, December, 2023);
std::cout << cal4.isHoliday(d4)
<< std::endl; // prints true
Bespoke calendars
Finally, in a shocking turn of events, you can also choose to disregard all the calendars we’re providing. I did it myself on a past project; in that case, we already had a data provider who was feeding us updated lists of holidays for a number of calendars and joint calendars. Any new holiday would go into our database before we were aware of it, freeing us from having to maintain lists of holidays to add or remove from the calendars built into the library.
For that use case, the right thing to do was to use the
BespokeCalendar
class. As you can see from the code, each instance
of the class is a blank slate to be filled with a specification of the
weekends and a list of holidays. This gave us a generic way to
instantiate any calendar by reading the corresponding information from
the database. Also, using bespoke calendars gives you a way to create
a new calendar if you’re using QuantLib from another language (say,
Python or C#) and you don’t want to modify and recompile the
underlying C++ code.
auto cal5 = BespokeCalendar();
cal5.addWeekend(Saturday);
cal5.addWeekend(Sunday);
std::vector<Date> holidays = {
Date(15, April, 2022),
Date(18, April, 2022),
Date(26, December, 2022),
Date(7, April, 2023),
Date(10, April, 2023),
Date(1, May, 2023),
Date(7, June, 2023),
Date(25, December, 2023),
Date(26, December, 2023),
Date(1, January, 2024)
};
for (auto d : holidays) {
cal5.addHoliday(d);
}
std::cout << cal5.isHoliday(d1)
<< std::endl; // prints true
}
And with this, I’ll bring the article to a close. See you next time!
Footnote
-
qlcal
can be found at https://github.com/qlcal/qlcal-r. ↩