Monday, November 17, 2014

Odds and ends: date calculations

Welcome back.

My blog schedule continues to be erratic, I know. But November is traditionally the month when writers get all motivated, so I might be working on some new book content (no, my book, not a novel). In the meantime, this post contains a few sections I had already published in the PDF version of the drafts, somewhat reworked to include some new information.

In other news, the QuantLib User Meeting 2014 is drawing near. I won't be giving a talk like I did last year, but I'm looking forward to be there anyway. I'll report on the talks in a future post.

Finally: as you might know if you are subscribed to the developers mailing list, it turns out that QuantLib 1.4 doesn't work with Clang 3.5 and the newly-released Boost 1.57. I'll be putting out a 1.4.1 release to fix the problem; as you read this, it might already be available from our download page. If not, try again in a day or two (but only if you're using Clang: if not, you don't need to upgrade).

Follow me on Twitter if you want to be notified of new posts, or add me to your circles, or subscribe via RSS: the widgets for that are in the sidebar, at the top right of the page. Also, make sure to check my Training page.

Odds and ends: date calculations

Date calculations are among the basic tools of quantitative finance. As can be expected, QuantLib provides a number of facilities for this task; I briefly describe some of them in the following subsections.

Dates and periods

An instance of the Date class represents a specific day such as November 15th, 2014—today's date as I write this post. This class provides a number of methods for retrieving basic information such as the weekday, the day of the month, or the year; static information such as the minimum and maximum date allowed (at this time, January 1st, 1901 and December 31st, 2199, respectively) or whether or not a given year is a leap year; or other information such as a date's Excel-compatible serial number or whether or not a given date is the last date of the month. The complete list of available methods and their interface is documented in the reference manual. No time information is included (although we've been talking about this).

Capitalizing on C++ features, the Date class also overloads a number of operators so that date algebra can be written in a natural way; for example, one can write expressions such as ++d, which advances the date d by one day; d + 2, which yields the date two days after the given date; d2 - d1, which yields the number of days between the two dates; d - 3*Weeks, which yields the date three weeks before the given date (and incidentally, features a member of the available TimeUnit enumeration, the other members being Days, Months, and Years); or d1 < d2, which yields true if the first date is earlier than the second one. The algebra implemented in the Date class works on calendar days; neither bank holidays nor business-day conventions are taken into account.

The Period class models lengths of time such as two days, three weeks, or five years by storing a TimeUnit and an integer. It provides a limited algebra and a partial ordering. For the non mathematically inclined, this means that two Period instances might or might not be compared to see which is the shorter; while it is clear that, say, 11 months are less than one year, it is not possible to determine whether 60 days are more or less than two months without knowing which two months. When the comparison cannot be decided, an exception is thrown.

And of course, even when the comparison seems obvious, we managed to sneak in a few surprises. For instance, the comparison
    Period(7,Days) == Period(1,Weeks)
returns true. It seems correct, right? Hold that thought.

Calendars

Holidays and business days are the domain of the Calendar class. Several derived classes exist which define holidays for a number of markets; the base class defines simple methods for determining whether or not a date corresponds to a holiday or a business day, as well as more complex ones for performing tasks such as adjusting a holiday to the nearest business day (where "nearest" can be defined according to a number of business-day conventions, listed in the BusinessDayConvention enumeration) or advancing a date by a given period or number of business days.

It might be interesting to see how the behavior of a calendar changes depending on the market it describes. One way would have been to store in the Calendar instance the list of holidays for the corresponding market; however, for maintainability we wanted to code the actual calendar rules (such as "the fourth Thursday in November" or "December 25th of every year") rather than enumerating the resulting dates for a couple of centuries. Another obvious way would have been to use polymorphism and the Template Method pattern; derived calendars would override the isBusinessDay method, from which all others could be implemented. This is fine, but it has the shortcoming that calendars would need to be passed and stored in shared_ptrs. The class is conceptually simple, though, and is used frequently enough that we wanted users to instantiate it and pass it around more easily—that is, without the added verbosity of dynamic allocation.

The final solution was the one shown in the listing below.
    class Calendar {
      protected:
        class Impl {
          public:
            virtual ~Impl() {}
            virtual bool isBusinessDay(const Date&) const = 0;
        };
        boost::shared_ptr<Impl> impl_;
      public:
        bool isBusinessDay(const Date& d) const {
            return impl_->isBusinessDay(d);
        }
        bool isHoliday(const Date& d) const {
            return !isBusinessDay(d);
        }
        Date adjust(const Date& d,
                    BusinessDayConvention c = Following) const {
            // uses isBusinessDay() plus some logic
        }
        Date advance(const Date& d,
                     const Period& period,
                     BusinessDayConvention c = Following,
                     bool endOfMonth = false) const {
            // uses isBusinessDay() and possibly adjust()
        }
        // more methods
    };
It is a variation of the pimpl idiom, also reminiscent of the Strategy or Bridge patterns; these days, the cool kids might call it type erasure, too. Long story short: Calendar declares a polymorphic inner class Impl to which the implementation of the business-day rules is delegated and stores a pointer to one of its instances. The non-virtual isBusinessDay method of the Calendar class forwards to the corresponding method in Calendar::Impl; following somewhat the Template Method pattern, the other Calendar methods are also non-virtual and implemented (directly or indirectly) in terms of isBusinessDay. (The same technique is used in a number of other classes, such as DayCounter in the next section or Parameter from this post.)

Derived calendar classes can provide specialized behavior by defining an inner class derived from Calendar::Impl; their constructor will create a shared pointer to an Impl instance and store it in the impl_ data member of the base class. The resulting calendar can be safely copied by any class that need to store a Calendar instance; even when sliced, it will maintain the correct behavior thanks to the contained pointer to the polymorphic Impl class. Finally, we can note that instances of the same derived calendar class can share the same Impl instance. This can be seen as an implementation of the Flyweight pattern—bringing the grand total to about two and a half patterns for one deceptively simple class.

Enough with the implementation of Calendar, and back to its behavior. Here's the surprise I mentioned in the previous section. Remember Period(1,Weeks) being equal to Period(7,Days)? Except that for the advance method of a calendar, 7 days means 7 business days. Thus, we have a situation in which two periods p1 and p2 are equal (that is, p1 == p2 returns true) but calendar.advance(p1) differs from calendar.advance(p2).

Yay, us.

I'm not sure I have a good idea for a solution here. If we want backwards compatibility, the current uses of Days must keep working in the same way; so it's not possible, say, to start interpreting calendar.advance(7, Days) as 7 calendar days. One way out might be to keep the current situation, introduce two new enumeration cases BusinessDays and CalendarDays that remove the ambiguity, and deprecate Days. Another is to just remove the inconsistency by dictating that a 7-days period do not, in fact, equal one week; I'm not overly happy about this one.

If we give up on backwards compatibility (the legendary QuantLib 2.0) then there are more possibilities. One is to always use Days as calendar days and add BusinessDays as a different enumeration case. Another, which I'm liking more and more as I think about it, would be to always use Days as calendar days and add a specific method advanceBusinessDays to Calendar (or maybe an overload advance(n, BusinessDays), with BusinessDays being an instance of a separate class); however, this would mean that 3 business days wouldn't be a period.

As I said, no obvious solution. If you have any other suggestions, I'm all ears.

Day-count conventions

The DayCounter class provides the means to calculate the distance between two dates, either as a number of days or a fraction of an year, according to different conventions. Derived classes such as Actual360 or Thirty360 exist; they implement polymorphic behavior by means of the same technique used by the Calendar class and described in the previous section.

Unfortunately, the interface has a bit of a rough edge. Instead of just taking two dates, the yearFraction method is declared as
    Time yearFraction(const Date&,
                      const Date&,
                      const Date& refPeriodStart = Date(),
                      const Date& refPeriodEnd = Date()) const;
The two optional dates are required by one specific day-count convention (namely, the ISMA actual/actual convention) that requires a reference period to be specified besides the two input dates. To keep a common interface, we had to add the two additional dates to the signature of the method for all day counters (most of which happily ignore them). This is not the only mischief caused by this day counter; you'll see another in the next section.

Schedules

The Schedule class, shown in the listing below, is used to generate sequences of coupon dates.
    class Schedule {
      public:
        Schedule(const Date& effectiveDate,
                 const Date& termination Date,
                 const Period& tenor,
                 const Calendar& calendar,
                 BusinessDayConvention convention,
                 BusinessDayConvention terminationDateConvention,
                 DateGeneration::Rule rule,
                 bool endOfMonth,
                 const Date& firstDate = Date(),
                 const Date& nextToLastDate = Date());
        Schedule(const std::vector<Date>&,
                 const Calendar& calendar = NullCalendar(),
                 BusinessDayConvention convention = Unadjusted);

        Size size() const;
        bool empty() const;
        const Date& operator[](Size i) const;
        const Date& at(Size i) const;
        const_iterator begin() const;
        const_iterator end() const;

        const Calendar& calendar() const;
        const Period& tenor() const;
        bool isRegular(Size i) const;
        Date previousDate(const Date& refDate) const;
        Date nextDate(const Date& refDate) const;
        ... // other inspectors and utilities
    };
Following practice and ISDA conventions, it has to accept a lot of parameters; you can see them as the argument list of its constructor. (Oh, and you'll forgive me if I don't go and explain all of them. I'm sure you can guess what they mean.) They're probably too many, which is why the library uses the Named Parameter Idiom (already described in this post) to provide a less unwieldy factory class. With its help, a schedule can be instantiated as
    Schedule s = MakeSchedule().from(startDate).to(endDate)
                 .withFrequency(Semiannual)
                 .withCalendar(TARGET())
                 .withNextToLastDate(stubDate)
                 .backwards();
Other methods include on the one hand, inspectors for the stored data; and on the other hand, methods to give the class a sequence interface, e.g., size, operator[], begin, and end.

The Schedule class has a second constructor, taking a precomputed vector of dates. It's only kind of working, though: the resulting Schedule instances simply don't have some of the data that their inspectors are supposed to return, so those methods will throw an exception if called. Among those, there are tenor and isRegular, about which I need to spend a couple of words.

First of all, isRegular(i) doesn't refer to the i-th date, but to the i-th interval; that is, the one between the i-th and (i+1)-th dates. This said, what does "regular" means? When a schedule is built based on a tenor, most intervals correspond to the passed tenor (and thus are regular) but the first and last intervals might be shorter or longer depending on whether we passed an explicit first or next-to-last date. We might do this, e.g., when we want to specify a short first coupon.

If we build the schedule with a precomputed set of dates, we don't have the tenor information and we can't tell if a given interval is regular. (Well, we could use heuristics, but it could get ugly fast.) The bad news is, this makes it impossible to use that schedule to build a sequence of bond coupons; if we pass it to the constructor of, say, a fixed-rate bond, we'll get an exception. And why, oh, why does the bond needs the missing info in order to build the coupons? Because the day-count convention of the bond might be ISMA actual/actual, which needs a reference period; and in order to calculate the reference period, we need to know the coupon tenor.

Fortunately, it shouldn't be difficult to fix this problem. One way would be to check the day-count convention, and only try to calculate the reference period when needed; this way the constructor would still raise an exception for ISMA actual/actual, but would succeed for all other conventions. Another way might be to add the tenor and regularity information to the schedule, so that the corresponding methods can work; but I'm not sure that this makes a lot of sense.

Liked this post? Share it: