Monday, August 5, 2013

Chapter 5, part 1 of n: Parameterized models and calibration


Hello everybody.

New series of post, this time on chapter 5. This is content that I haven't yet published in book form: in fact, I'm still writing it (as you might have guessed from the title of the post: I'm not sure how many posts this series will last). I look forward to your feedback.

Registration for the next Introduction to QuantLib Development course is still open: it is the three-day course that I teach based on the contents of this blog and of my book (plus several exercises; bring your compiler) and you can find more information, a brochure and a booking form by clicking on this link.

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.

Parameterized models and calibration


CRITICS OF the practice of calibration argue that its very existence is a sign of a problem. After all, if physicists had to recalibrate the universal constant of gravitation yearly, it would probably mean that the formula is invalid (or that there's something wrong with the idea of natural laws altogether, which is way scarier. This doesn't seem to be the case for physics. The jury is still out for quantitative finance.)

For better or for worse, QuantLib supports calibration to market data because, well, that's what people need to do. Much like C++ or Smith & Wesson, we might add some safety but we assume that users know what they're doing, even if this gives them the possibility to shoot their foot off.

The calibration framework is one of the oldest parts of the library and has received little attention in the last few years; so it's likely that, as I write this chapter, I'll find and describe a number of things that could be improved—by breaking backward compatibility, I'm afraid, so they'll have to wait. In the meantime, you can learn from our blunders.


Onwards. The framework enables us to write code such as, for instance,
    HullWhite model(termStructure);
    Simplex optimizer(0.01);
    model.calibrate(marketSwaptions,
                    optimizer,
                    EndCriteria(maxIterations, ...));
    check(model.endCriteria());
    // go on using the model
The above works because of the interplay of two classes called CalibratedModel and CalibrationHelper; the HullWhite class inherits from the former, while the elements of marketSwaptions are instances of a class that inherits from the latter (we're also using accessory classes, such as Simplex, that implement optimization methods; but I'll postpone their description to appendix A). Since they work together, describing either class before the other will cause some vagueness and hand-waving. Bear with me as I try to minimize the inconvenience (pun not intended).

The CalibrationHelper class

I'll describe the CalibrationHelper class first, since it depends only indirectly on the model (in fact, it doesn't use the model interface directly at all). Its implementation is shown in listing 5.1.

Listing 5.1: Implementation of the CalibrationHelper class.
    class CalibrationHelper : public LazyObject {
      public:
        enum CalibrationErrorType {
                  RelativePriceError, PriceError, ImpliedVolError};

        CalibrationHelper(
            const Handle<Quote>& volatility,
            const Handle<YieldTermStructure>& termStructure,
            CalibrationErrorType calibrationErrorType
                                             = RelativePriceError);

        void performCalculations() const {
            marketValue_ = blackPrice(volatility_->value());
        }
        virtual Real blackPrice(Volatility volatility) const = 0;
        Real marketValue() const {
            calculate(); return marketValue_;
        }

        virtual Real modelValue() const = 0;
        virtual Real calibrationError();
        void setPricingEngine(
                      const shared_ptr<PricingEngine>& engine) {
            engine_ = engine;
        }

        Volatility impliedVolatility(Real targetValue,
                                     Real accuracy,
                                     Size maxEvaluations,
                                     Volatility minVol,
                                     Volatility maxVol) const;
        virtual void addTimesTo(list<Time>& times) const = 0;

      protected:
        mutable Real marketValue_;
        Handle<Quote> volatility_;
        Handle<YieldTermStructure> termStructure_;
        shared_ptr<PricingEngine> engine_;
      private:
        class ImpliedVolatilityHelper;
        const CalibrationErrorType calibrationErrorType_;
    };

    class CalibrationHelper::ImpliedVolatilityHelper {
      public:
        ImpliedVolatilityHelper(const CalibrationHelper& helper,
                                Real value);
        Real operator()(Volatility x) const {
            return value_ - helper_.blackPrice(x);
        }
        ...
    };

    Volatility CalibrationHelper::impliedVolatility(
            Real targetValue, Real accuracy, Size maxEvaluations,
            Volatility minVol, Volatility maxVol) const {
        ImpliedVolatilityHelper f(*this,targetValue);
        Brent solver;
        solver.setMaxEvaluations(maxEvaluations);
        return solver.solve(f,accuracy,volatility_->value(),
                            minVol,maxVol);
    }

    Real CalibrationHelper::calibrationError() {
        Real error;
        switch (calibrationErrorType_) {
          case RelativePriceError:
            error = fabs(marketValue()-modelValue())/marketValue();
            break;
          case PriceError:
            error = marketValue() - modelValue();
            break;
          case ImpliedVolError: {
              const Real modelPrice = modelValue();
              // check for bounds, not shown
              Volatility implied = this->impliedVolatility(
                              modelPrice, 1e-12, 5000, 0.001, 10);
              error = implied - volatility_->value();
            }
            break;
          default:
            QL_FAIL("unknown Calibration Error Type");
        }
        return error;
    }

The purpose of the class is similar—the name is a giveaway, isn't it?—to that of the BootstrapHelper class, described in chapter 3 (which, unfortunately, didn't show up as posts yet). It models a single quoted instrument (a "node" of the model, whatever that might be) and provides the means to calculate the instrument value according to the model and to check how far off it is from the market value. Actually, the value isn't the only possibility; we'll get to this in a bit.

CalibrationHelper inherits from LazyObject, that you know by now. The reason is that it might need some preliminary calculation: the target value of the optimization (say, the value) might not be available directly, for instance because the market quotes the corresponding implied volatility instead. The calculation to go from the one to the other must be done just once, before the calibration, and is done lazily as the market quote changes.

The constructor takes three arguments—each one maybe a bit less generic than I'd like, even though I only have minor complaints. The first argument is a handle to the quoted volatility; the assumption here is that, whatever the model is, that's how the market quotes the relevant instruments.

The second argument is a handle to a term structure, that we assume to need for the calculations. That's probably true, but it is only ever used by derived classes, not here; so I would have preferred it to be declared there, along with any other data they might need.

Finally, the third argument specifies how the calibration error is defined. It's an enumeration that can take one of three values, meaning to take the relative error between the market price and the model price, or the absolute error between the prices, or the absolute error between the quoted volatility and the (Black) volatility implied by the model price. In principle, we might have used a Strategy pattern instead; but I'm not sure that the generalization is worth the added complexity, especially as I don't have a possible fourth case in mind.

The body of the constructor is not shown here for brevity, but it does the usual things: it stores its arguments in the corresponding data members and registers with those that might change.

As I said, the LazyObject machinery is used when market data change; accordingly, the required performCalculations method transforms the quoted volatility into a market price and stores it. The actual calculation depends on the particular instrument, so it's delegated to a purely virtual blackPrice method; the evident assumption is that a Black model was used to quote the market volatility. Finally, a marketValue method exposes the calculated price.

Note that, unfortunately, we need all three of the above methods. Yes, I know, it bugs me too. Obviously, performCalculations is required by the LazyObject interface; but what about blackPrice and marketValue? Can't we collapse them into one? Well, no. We want the marketValue inspector to be lazy, and therefore it must call performCalculations; thus, it can't be the same as blackPrice, which is called by the performCalculations method. (They also have a different interface, since blackPrice takes the volatility as an argument; but that could have been managed by giving it a default argument falling back to the stored volatility value.)

The next set of methods deals with the model-based calculations which are executed during calibration. The purely virtual modelValue, when implemented in derived classes, must return the value of the instrument according to the model; the calibrationError method, that I'll describe in more detail later, returns some kind of difference between the market and model values; and the setPricingEngine brings the model into play.

The idea here is that the engine that we're storing has a pointer to the model, and can be used to price the instrument represented by the helper and thus give us a market value. All the helpers currently in the library implement the modelValue method as a straightforward translation of the idea: they set the given engine to the instrument they store, and ask it for its NPV (you'll see it spelled out in next post.)

In fact, the implementations are so similar that I wonder if we could have provided a common one in the base class. Had we added a pointer to an Instrument instance as a data member, the method would just be:
    void CalibrationHelper::modelValue() const {
        instrument_->setPricingEngine(engine_);
        return instrument_->NPV();
    }
and with a bit more care, we could have set the pricing engine just once, at the beginning of the calibration, instead of each time the model value is recalculated. The downside of this would have been that the results of the methods would have been dependent on the order in which they were called; for instance, a call to the modelValue right after a call to marketValue might have returned the Black price if the latter had set a different engine to the instrument. According to Murphy's law, this would have bitten us back.

A last remark about the setPricingEngine method: when I saw it, I first thought that there was a bug in it, and that it should register with the engine. Actually, it shouldn't: the engine is used for the model value only, and must not trigger the LazyObject machinery that recalculates the market value.

The last two methods are utilities that can be used to help the calibration. The impliedVolatility method uses a one-dimensional solver to invert the blackPrice method; that is, to find the volatility that yields the corresponding Black price. Its implementation is shown in the listing along with the sketch of an accessory inner class ImpliedVolatilityHelper that provides the objective function for the solver.

The addTimesTo method is to be used with tree-based methods (we'll get to those in chapter 7). It adds to the passed list a set of times that are of importance to the underlying instrument (e.g., payment times, exercise times, or fixing times) and therefore must be included in any time grid to be used. If I were to write it now, I'd just return the times instead of extending the given list, but that's a minor point. Another point is that, as I said, this method is tied to a particular category of models, that is, tree-based ones, and might not make sense for all helpers. Thus, I would provide an empty implementation so that derived classes don't necessarily have to provide one. However, I wouldn't try to move this method in some other class—say, a derived class meant to model helpers for tree-based models. On the one hand, it would add complexity and give almost no upside; and on the other hand, we can't even categorize helpers in this way: any given helper could be used with both types of models.

Finally, the listing shows the implementation of the calibrationError method. It is called by the calibration routine every time new parameters are set to the method, and returns an error that tells up how far we are from market data. The definition of "how far" is given by the stored enumeration value; if can be the relative difference of the model and market prices, their absolute difference, or the difference between the quoted volatility and the one implied by the model price.

In next post: an example of calibration helper.

Share this post: