Hello everybody.

A bit of trivia: one week ago, on April 20th, the QuantLib repository was forked for the 400th time (and they keep coming: we’re up to 407 already as I write this post). Kudos to GitHub user donglijiujiu—which doesn’t win any prize, apart from this shout-out.

This week: the final part of the series on tree that started a few weeks ago.

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.

Tree-based engines

As you might have guessed, a tree-based pricing engine will perform few actual computations; its main job will rather be to instantiate and drive the needed discretized asset and lattice. (If you’re pattern-minded, you can have your pick here. This implementation has suggestions of the Adapter, Mediator, or Facade pattern, even though it doesn’t match any of them exactly.)

Example: callable fixed-rate bonds

As an example, I’ll sketch the implementation of a tree-based pricing engine for callable fixed-rate bonds. For the sake of brevity, I’ll skip the description of the CallableBond class. (To be specific, the class name should be CallableFixedRateBond; but that would get old very quickly here, so please allow me to use the shorter name.) Instead, I’ll just show its inner arguments and results classes, which act as its interface with the pricing engine and which you can see in the listing below together with the corresponding engine class. If you’re interested in a complete implementation, you can look for it in QuantLib’s experimental folder.

    class CallableBond::arguments : public PricingEngine::arguments {
      public:
        std::vector<Date> couponDates;
        std::vector<Real> couponAmounts;
        Date redemptionDate;
        Real redemptionAmount;
        std::vector<Callability::Type> callabilityTypes;
        std::vector<Date> callabilityDates;
        std::vector<Real> callabilityPrices;
        void validate() const;
    };

    class CallableBond::results : public Instrument::results {
      public:
        Real settlementValue;
    };

    class CallableBond::engine
        : public GenericEngine<CallableBond::arguments,
                               CallableBond::results> {};

Now, let’s move into engine territory. In order to implement the behavior of the instrument, we’ll need a discretized asset; namely, the DiscretizedCallableBond class, shown in the next listing.

    class DiscretizedCallableBond : public DiscretizedAsset {
      public:
        DiscretizedCallableBond(const CallableBond::arguments& args,
                                const Date& referenceDate,
                                const DayCounter& dayCounter)
        : arguments_(args) {
            redemptionTime_ =
                dayCounter.yearFraction(referenceDate,
                                        args.redemptionDate);

            couponTimes_.resize(args.couponDates.size());
            for (Size i=0; i<couponTimes_.size(); ++i)
                couponTimes_[i] =
                    dayCounter.yearFraction(referenceDate,
                                            args.couponDates[i]);
            // same for callability times
        }
        std::vector<Time> mandatoryTimes() const {
            std::vector<Time> times;

            Time t = redemptionTime_;
            if (t >= 0.0)
                times.push_back(t);
            // also add non-negative coupon times and callability times

            return times;
        }
        void reset(Size size) {
            values_ = Array(size, arguments_.redemptionAmount);
            adjustValues();
        }
      protected:
        void preAdjustValuesImpl();
        void postAdjustValuesImpl();
      private:
        CallableBond::arguments arguments_;
        Time redemptionTime_;
        std::vector<Time> couponTimes_;
        std::vector<Time> callabilityTimes_;
        void applyCallability(Size i);
        void addCoupon(Size i);
    };

To prevent much aggravation, its constructor takes and stores an instance of the arguments class. This avoids having to spell out the list of needed data in at least three places: the declaration of the data members, the constructor, and the client code that instantiates the discretized asset. Besides the arguments instance, the constructor is also passed a reference date and a day counter that are used in its body to convert the several bond dates into corresponding times. (The conversion is somewhat verbose, which suggests that we might be missing an abstraction here. However, “time converter” sounds a bit too vague. If you find it, please do let me know. The thing has been bugging me for a while.)

Next comes the required DiscretizedAsset interface. The mandatoryTimes method collects the redemption time, the coupon times, and the callability times filtering out the negative ones; and the reset method resizes the array of the values, sets each one to the redemption amount, and proceeds to perform the needed adjustments—that is, the more interesting part of the class.

Being rather specialized, it is pretty unlikely that this class will be composed with others; therefore, it doesn’t really matter in this case whether the adjustments go into preAdjustValuesImpl or postAdjustValuesImpl. However, for sake of illustration, I’ll separate the callability from the coupon payments and manage them as pre- and post-adjustment, respectively.

    void DiscretizedCallableBond::preAdjustValuesImpl() {
        for (Size i=0; i<callabilityTimes_.size(); i++) {
            Time t = callabilityTimes_[i];
            if (t >= 0.0 && isOnTime(t)) {
                applyCallability(i);
            }
        }
    }

    void DiscretizedCallableBond::postAdjustValuesImpl() {
        for (Size i=0; i<couponTimes_.size(); i++) {
            Time t = couponTimes_[i];
            if (t >= 0.0 && isOnTime(t)) {
                addCoupon(i);
            }
        }
    }

The preAdjustValuesImpl loops over the callability times, checks whether any of them equals the current time, and calls the applyCallability} method if this is the case. The postAdjustValuesImpl does the same, but checking the coupon times and calling the addCoupon method instead.

    void DiscretizedCallableBond::applyCallability(Size i) {
        switch (arguments_.callabilityTypes[i]) {
          case Callability::Call:
            for (Size j=0; j<values_.size(); j++) {
                values_[j] =
                    std::min(arguments_.callabilityPrices[i],
                             values_[j]);
            }
            break;
          case Callability::Put:
            for (Size j=0; j<values_.size(); j++) {
                values_[j] =
                    std::max(arguments_.callabilityPrices[i],
                             values_[j]);
            }
            break;
          default:
            QL_FAIL("unknown callability type");
        }
    }

    void DiscretizedCallableBond::addCoupon(Size i) {
        values_ += arguments_.couponAmounts[i];
    }

The applyCallability method is passed the index of the callability being exercised; it checks its type (both callable and puttable bonds are supported) and sets the value at each node to the value after exercise. The logic is simple enough: at each node, given the estimated value of the rest of the bond (that is, the current asset value) and the exercise premium, the issuer will choose the lesser of the two values while the holder will choose the greater. The addCoupon method is much simpler, and just adds the coupon amount to each of the values.

As you might have noticed, this class assumes that the exercise dates coincide with the coupon dates; it won’t work if an exercise date is a few days before a coupon payment (the coupon amount would be added to the asset values before the exercise condition is checked). Of course, this is often the case, and it should be accounted for. Currently, the library implementation sidesteps the problem by adjusting each exercise date so that it equals the nearest coupon date. A better choice would be to detect which coupons are affected; each of them would be put into a new asset, rolled back until the relevant exercise time, and added after the callability adjustment.

Finally, the listing below shows the TreeCallableBondEngine class.

    class TreeCallableBondEngine : public CallableBond::engine {
      public:
        TreeCallableBondEngine(
                       const Handle<ShortRateModel>& model,
                       const Size timeSteps,
                       const Date& referenceDate = Date(),
                       const DayCounter& dayCounter = DayCounter());
        : model_(model), timeSteps_(timeSteps),
          referenceDate_(referenceDate), dayCounter_(dayCounter) {
            registerWith(model_);
        }
        void calculate() const {
            Date referenceDate;
            DayCounter dayCounter;

            // try to extract the reference date and the day counter
            // from the model, use the stored ones otherwise.

            DiscretizedCallableBond bond(arguments_,
                                         referenceDate,
                                         dayCounter);

            std::vector<Time> times = bond.mandatoryTimes();
            TimeGrid grid(times.begin(), times.end(), timeSteps_);
            boost::shared_ptr<Lattice> lattice = model_->tree(grid);

            Time redemptionTime =
                dayCounter.yearFraction(referenceDate,
                                        arguments_.redemptionDate);
            bond.initialize(lattice, redemptionTime);
            bond.rollback(0.0);
            results_.value = bond.presentValue();
        }
    };

Its constructor takes and stores a handle to a short-rate model that will provide the lattice, the total number of time steps we want the lattice to have, and an optional reference date and day counter; the body just registers to the handle.

The calculate method is where everything happens. By the time it is called, the engine arguments have been filled by the instrument, so that base is covered; the other data we need are a date and a day counter for time conversion. Not all short-rate models can provide them, so, in a boring few lines of code not shown here, the engine tries to downcast the model to some specific class that does; if it fails, it falls back to using the ones optionally passed to the constructor.

At that point, the actual calculations can begin. The engine instantiates the discretized bond, asks it for its mandatory times, and uses them to build a time grid; then, the grid is passed to the model which returns a corresponding lattice based on the short-rate dynamics. All that remains is to initialize the bond at its redemption time (which in the current code is recalculated explicitly, but could be retrieved as the largest of the mandatory times), roll it back to the present time, and read its value.