Monday, September 30, 2013

Chapter 3, part 2 of n: Yield term structures

Hello everybody.

This post is the second in a series of a still undetermined number; the first part is here.

Call me a slowcoach (or whatever the expression might be in your part of the world), but I only found out this week that there's a Twitter feed for the Quantitative Finance Stack Exchange site. I'll be retweeting the QuantLib-related questions when the answers are useful, so you can push that "follow" button on the right if you're interested in those but don't want to get the full site feed. Or you can push it anyway. I won't mind.

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.

Term Structures

Yield Term Structures

The YieldTermStructure class predates TermStructure—in fact, it was even called TermStructure back in the day, when it was the only kind of term structure in the library and we still hadn't seen the world. Its interface provides the means to forecast interest rates and discount factors at any date in the curve domain; also, it implements some machinery to ease the task of writing a concrete yield curve.

Interface and implementation

The interface of the YieldTermStructure class is sketched in listing 3.3.

Listing 3.3: Partial interface of the YieldTermStructure class.
    class YieldTermStructure : public TermStructure {
      public:
        YieldTermStructure(const DayCounter& dc = DayCounter());
        YieldTermStructure(const Date& referenceDate,
                           const Calendar& cal = Calendar(),
                           const DayCounter& dc = DayCounter());
        YieldTermStructure(Natural settlementDays,
                           const Calendar&,
                           const DayCounter& dc = DayCounter());

        InterestRate zeroRate(const Date& d,
                              const DayCounter& dayCounter,
                              Compounding compounding,
                              Frequency frequency = Annual,
                              bool extrapolate = false) const;

        InterestRate zeroRate(Time t,
                              Compounding compounding,
                              Frequency frequency = Annual,
                              bool extrapolate = false) const;

        DiscountFactor discount(const Date&,
                                bool extrapolate = false) const;
        // same at time t

        InterestRate forwardRate(const Date& d1,
                                 const Date& d2,
                                 const DayCounter& dayCounter,
                                 Compounding compounding,
                                 Frequency frequency = Annual,
                                 bool extrapolate = false) const;
        // same starting from date d and spanning a period p
        // same between times t1 and t2

        // ...more methods
      protected:
        virtual DiscountFactor discountImpl(Time) const = 0;
    };
The constructors just forward their arguments to the corresponding constructors in the TermStructure class—nothing to write home about. The other methods return information on the yield structure in different ways: on the one hand, they can return zero rates, forward rates, and discount factors (rates are returned as instances of the InterestRate class, that I'll describe briefly in a future post); on the other hand, they are overloaded so that they can return information as function of either dates or times.

Of course, there is a relationship between zero rates, forward rates, and discount factors; the knowledge of any one of them is sufficient to deduce the others. (I won't bore you with the formulas here—you know them.) This is reflected in the implementation, outlined in listing 3.4; the Template Method patterns is used to implement all public methods directly or indirectly in terms of the protected discountImpl abstract method. Derived classes only need to implement the latter in order to return any of the above quantities.

Listing 3.4: Partial implementation of the YieldTermStructure class.
    InterestRate YieldTermStructure::zeroRate(
                                    const Date& d,
                                    const DayCounter& dayCounter,
                                    Compounding comp,
                                    Frequency freq,
                                    bool extrapolate) const {
        // checks and/or special cases
        Real compound = 1.0/discount(d, extrapolate);
        return InterestRate::impliedRate(compound,
                                         referenceDate(), d,
                                         dayCounter, comp, freq);
    }

    DiscountFactor YieldTermStructure::discount(
                                        const Date& d,
                                        bool extrapolate) const {
        checkRange(d, extrapolate);
        return discountImpl(timeFromReference(d));
    }

Discount, forward-rate, and zero-rate curves

What if the author of a derived class doesn't want to implement discountImpl, though? After all, one might want to describe a yield curve in terms, say, of zero rates. Ever ready to serve (just like Jeeves in the P. G. Wodehouse novels—not that you're Bernie Wooster, of course) QuantLib provides a couple of classes to be used in this case. The two classes (outlined in listing 3.5) are called ZeroYieldStructure and ForwardRateStructure. They use the Adapter pattern (in case you're keeping count, this would be another notch in the spine of our Gang-of-Four book) to transform the discount-based interface of YieldTermStructure into interfaces based on zero-yield and instantaneous-forward rates, respectively.

Listing 3.5: Outline of the ZeroYieldStructure and ForwardRateStructure classes.
    class ZeroYieldStructure : public YieldTermStructure {
      public:
        // forwarding constructors, not shown
      protected:
        virtual Rate zeroYieldImpl(Time) const = 0;
        DiscountFactor discountImpl(Time t) const {
            Rate r = zeroYieldImpl(t);
            return std::exp(-r*t);
        }
    };

    class ForwardRateStructure : public YieldTermStructure {
      public:
        // forwarding constructors, not shown
      protected:
        virtual Rate forwardImpl(Time) const = 0;
        virtual Rate zeroYieldImpl(Time t) const {
            // averages \texttt{forwardImpl} between 0 and \texttt{t}
        }
        DiscountFactor discountImpl(Time t) const {
            Rate r = zeroYieldImpl(t);
            return std::exp(-r*t);
        }
    };

The implementation of ZeroYieldStructure is simple enough. A few constructors (not shown here) forward their arguments to the corresponding constructors in the parent YieldTermStructure class. The Adapter pattern is implemented in the protected section: an abstract zeroYieldImpl method is declared and used to implement the discountImpl method. Thus, authors of derived classes only need to provide an implementation of zeroYieldImpl to obtain a fully functional yield curve. (Of course, the other required methods (such as maxDate) must be implemented as well.) Note that, due to the formula used to obtain the discount factor, such method must return zero yields as continuously-compounded annualized rates.

In a similar way, the ForwardRateStructure class provides the means to describe the curve in terms of instantaneous forward rates (again, on an annual basis) by implementing a forwardImpl method in derived classes. However, it has an added twist. In order to obtain the discount at a given time T, we have to average the instantaneous forwards between 0 and T, thus retrieving the corresponding zero-yield rate. This class can't make any assumption on the shape of the forwards; therefore, all it can do is to perform a numerical integration—an expensive calculation. In order to provide a hook for optimization, the average is performed in a virtual zeroYieldImpl method that can be overridden if a faster calculation is available. You might object that if an expression is available for the zero yields, one can inherit from ZeroYieldStructure and be done with it; however, it is conceptually cleaner to express the curve in terms of the forwards if they were the actual focus of the model.

The two adapter classes I just described and the base YieldTermStructure class itself were used to implement interpolated discount, zero-yield, and forward curves. Listing 3.6 outlines the InterpolatedZeroCurve class template; the other two (InterpolatedForwardCurve and InterpolatedDiscountCurve) are implemented in the same way.

Listing 3.6: Outline of the InterpolatedZeroCurve class template.
    template <class Interpolator>
    class InterpolatedZeroCurve : public ZeroYieldStructure {
      public:
        // constructor
        InterpolatedZeroCurve(
                      const std::vector<Date>& dates,
                      const std::vector<Rate>& yields,
                      const DayCounter& dayCounter,
                      const Interpolator& interpolator
                                            = Interpolator())
        : ZeroYieldStructure(dates.front(), Calendar(),
                             dayCounter),
          dates_(dates), yields_(yields),
          interpolator_(interpolator) {
            // check that dates are sorted, that there are
            // as many rates as dates, etc.

            // convert dates_ into times_
            
            interpolation_ =
                interpolator_.interpolate(times_.begin(),
                                          times_.end(),
                                          data_.begin());
        }
        Date maxDate() const {
            return dates_.back();
        }
        // other inspectors, not shown
      protected:
        // other constructors, not shown
        Rate zeroYieldImpl(Time t) const {
            return interpolation_(t, true);
        }
        mutable std::vector<Date> dates_;
        mutable std::vector<Time> times_;
        mutable std::vector<Rate> data_;
        mutable Interpolation interpolation_;
        Interpolator interpolator_;
    };

The template argument Interpolator has a twofold task. On the one hand, it acts as a traits class [1]. It specifies the kind of interpolation to be used as well as a few of its properties, namely, how many points are required (e.g., at least two for a linear interpolation) and whether the chosen interpolation is global (i.e., whether or not moving a data point changes the interpolation in intervals that do not contain such point; this is the case, e.g., for cubic splines). On the other hand, it doubles as a poor man's factory; when given a set of data points, it is able to build and return the corresponding Interpolation instance. (The Interpolation class will be described in a later post).

The public constructor takes the data needed to build the curve: the set of dates over which to interpolate, the corresponding zero yields, the day counter to be used, and an optional Interpolator instance. For most interpolations, the last parameter is not needed; it can be passed when the interpolation needs parameters. The implementation forwards to the parent ZeroYieldStructure class the first of the passed dates, assumed to be the reference date for the curve, and the day counter; the other arguments are stored in the corresponding data members. After performing a few consistency checks, it converts the dates into times (using, of course, the passed reference date and day counter), asks the interpolator to create an Interpolation instance, and stores the result.

At this point, the curve is ready to be used. The other required methods can be implemented as one-liners; maxDate returns the latest of the passed dates, and zeroYieldImpl returns the interpolated value of the zero yield. Since the TermStructure machinery already takes care of range-checking, the call to the Interpolation instance includes a true argument. This causes the value to be extrapolated if the passed time is outside the given range.

Finally, the InterpolatedZeroCurve class also defines a few protected constructors. They take the same arguments as the constructors of its parent class ZeroYieldStructure, as well as an optional Interpolator instance, and forward them to the corresponding parent-class constructors; however, they don't create the interpolation—they cannot, since they don't take any zero-yield data. These constructors are defined so that it is possible to inherit from InterpolatedZeroCurve; derived classes will provide the data and create the interpolation based on whatever arguments they take (an example of this will be shown in the remainder of this section). For the same reason, most data members are declared as mutable; this makes it possible for derived classes to update the interpolation lazily, should their data change.

Aside: symmetry break.

You might argue that, as in George Orwell's Animal Farm, some term structures are more equal than others. The discount-based implementation seems to have a privileged role, being used in the base YieldTermStructure class. A more symmetric implementation might define three abstract methods in the base class (discountImpl, zeroYieldImpl, and forwardImpl, to be called from the corresponding public methods) and provide three adapters, adding a DiscountStructure class to the existing ones.

Well, the argument is sound; in fact, the very first implementation of the YieldTermStructure class was symmetric. The switch to the discount-based interface and the reasons thereof are now lost in the mists of time, but might have to do with the use of InterestRate instances; since they can require changes of frequency or compounding, zeroYield (to name one method) wouldn't be allowed to return the result of zeroYieldImpl directly anyway.

Aside: twin classes.

You might guess that code for interpolated discount and forward curves would be very similar to that for the interpolated zero-yield curve described here. The question naturally arises: would it be possible to abstract out common code? Or maybe we could even do with a single class template?

The answers are yes and no, respectively. Some code can be abstracted in a template class (in fact, this has been done already). However, the curves must implement three different abstract methods (discountImpl, forwardImpl, and zeroYieldImpl) so we still need all three classes as well as the one containing the common code.

Bibliography

[1] N.C. Myers, Traits: a new and useful template technique. In The C++ Report, June 1995.

Share this post: