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

  1. qlcal can be found at https://github.com/qlcal/qlcal-r