Chapter 2, part 3 of 4: Pricing engines
Hello everybody. This is the third in a series of four posts covering chapter 2 of the book; part 1, 2 and 4 can be found here, here and here. Feedback is welcome.
Registration for the next Introduction to QuantLib Development course is still open; it is the course that I teach based on the contents of the Implementing QuantLib blog and book, 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 buttons for that are in the footer. Also, make sure to check my Training page, even if you can’t make it to the next course.
Financial instruments and pricing engines
Pricing engines
We now turn to the second of the requirements I stated in the previous posts. For any given instrument, it is not always the case that a unique pricing method exists; moreover, one might want to use multiple methods for different reasons. Let’s take the classic textbook example—the European equity option. One might want to price it by means of the analytic Black-Scholes formula in order to retrieve implied volatilities from market prices; by means of a stochastic volatility model in order to calibrate the latter and use it for more exotic options; by means of a finite-difference scheme in order to compare the results with the analytic ones and validate one’s finite-difference implementation; or by means of a Monte Carlo model in order to use the European option as a control variate for a more exotic one.
Therefore, we want it to be possible for a single instrument to be
priced in different ways. Of course, it is not desirable to give
different implementations of the performCalculations
method, as this
would force one to use different classes for a single instrument
type. In our example, we would end up with a base EuropeanOption
class from which AnalyticEuropeanOption
, McEuropeanOption
and
others would be derived. This is wrong in at least two ways. On a
conceptual level, it would introduce different entities when a single
one is needed: a European option is a European option is a European
option, as Gertrude Stein said. On a usability level, it would make it
impossible to switch pricing methods at run-time.
The solution is to use the Strategy pattern, i.e., to let the
instrument take an object encapsulating the computation to be
performed. We called such an object a pricing engine. A given
instrument would be able to take any one of a number of available
engines (of course corresponding to the instrument type), pass the
chosen engine the needed arguments, have it calculate the value of the
instrument and any other desired quantities, and fetch the
results. Therefore, the performCalculations
method would be
implemented roughly as follows:
void SomeInstrument::performCalculations() const {
NPV_ = engine_->calculate(arg1, arg2, ... , argN);
}
where we assumed that a virtual calculate
method is defined in the
engine interface and implemented in the concrete engines.
Unfortunately, the above approach won’t work as such. The problem is,
we want to implement the dispatching code just once, namely, in the
Instrument
class. However, that class doesn’t know the number and
type of arguments; different derived classes are likely to have data
members differing wildly in both number and type. The same goes for
the returned results; for instance, an interest-rate swap might return
fair values for its fixed rate and floating spread, while the
ubiquitous European option might return any number of Greeks.
An interface passing explicit arguments to the engine through a method, as the one outlined above, would thus lead to undesirable consequences. Pricing engines for different instruments would have different interfaces, which would prevent us from defining a single base class; therefore, the code for calling the engine would have to be replicated in each instrument class. This way madness lies.
The solution we chose was that arguments and results be passed and
received from the engines by means of opaque structures aptly called
arguments
and results
. Two structures derived from those and
augmenting them with instrument-specific data will be stored in any
pricing engine; an instrument will write and read such data in order
to exchange information with the engine.
Listing 2.5 shows the interface of the resulting PricingEngine
class, as well as its inner argument
and results
classes and a
helper GenericEngine
class template. The latter implements most of
the PricingEngine
interface, leaving only the implementation of the
calculate
method to developers of specific engines. The arguments
and results
classes were given methods which ease their use as drop
boxes for data: arguments::validate
is to be called after input data
are written to ensure that their values lie in valid ranges, while
results::reset
is to be called before the engine starts calculating
in order to clean previous results.
Listing 2.5: Interface of PricingEngine
and of related classes.
class PricingEngine : public Observable {
public:
class arguments;
class results;
virtual ~PricingEngine() {}
virtual arguments* getArguments() const = 0;
virtual const results* getResults() const = 0;
virtual void reset() const = 0;
virtual void calculate() const = 0;
};
class PricingEngine::arguments {
public:
virtual ~arguments() {}
virtual void validate() const = 0;
};
class PricingEngine::results {
public:
virtual ~results() {}
virtual void reset() = 0;
};
// ArgumentsType must inherit from arguments;
// ResultType from results.
template <class ArgumentsType, class ResultsType>
class GenericEngine : public PricingEngine {
public:
PricingEngine::arguments* getArguments() const {
return &arguments_;
}
const PricingEngine::results* getResults() const {
return &results_;
}
void reset() const { results_.reset(); }
protected:
mutable ArgumentsType arguments_;
mutable ResultsType results_;
};
Armed with our new classes, we can now write a generic
performCalculation
method. Besides the already mentioned Strategy
pattern, we will use the Template Method pattern to allow any given
instrument to fill the missing bits. The resulting implementation is
shown in listing 2.6. Note that an inner class Instrument::result
was defined; it inherits from PricingEngine::results
and contains
the results that have to be provided for any instrument. (The
Instrument::results
class also contains a std::map
where pricing
engines can store additional results. The relevant code is here
omitted for clarity.)
Listing 2.6: Excerpt of the Instrument
class.
class Instrument : public LazyObject {
public:
class results;
virtual void performCalculations() const {
QL_REQUIRE(engine_, "null pricing engine");
engine_->reset();
setupArguments(engine_->getArguments());
engine_->getArguments()->validate();
engine_->calculate();
fetchResults(engine_->getResults());
}
virtual void setupArguments(
PricingEngine::arguments*) const {
QL_FAIL("setupArguments() not implemented");
}
virtual void fetchResults(
const PricingEngine::results* r) const {
const Instrument::results* results =
dynamic_cast<const Value*>(r);
QL_ENSURE(results != 0, "no results returned");
NPV_ = results->value;
errorEstimate_ = results->errorEstimate;
}
template <class T> T result(const string& tag) const;
protected:
boost::shared_ptr<PricingEngine> engine_;
};
class Instrument::results
: public virtual PricingEngine::results {
public:
Value() { reset(); }
void reset() {
value = errorEstimate = Null<Real>();
}
Real value;
Real errorEstimate;
};
As for performCalculation
, the actual work is split between a number
of collaborating classes—the instrument, the pricing engine, and
the arguments and results classes. The dynamics of such a
collaboration (described in the following paragraphs) might be best
understood with the help of the UML sequence diagram shown in figure
2.2, at the bottom of this post; the static relationship between the
classes (and a possible concrete instrument) is shown in figure 2.3.
A call to the NPV
method of the instrument eventually triggers (if
the instrument is not expired and the relevant quantities need to be
calculated) a call to its performCalculations
method. Here is where
the interplay between instrument and pricing engine begins. First of
all, the instrument verifies that an engine is available, aborting the
calculation if this is not the case. If one is found, the instrument
prompts it to reset itself. The message is forwarded to the
instrument-specific result structure by means of its reset
method;
after it executes, the structure is a clean slate ready for writing
the new results.
At this point, the Template Method pattern enters the scene. The
instrument asks the pricing engine for its argument structure, which
is returned as a pointer to arguments
. The pointer is then passed to
the instrument’s setupArguments
method, which acts as the variable
part in the pattern. Depending on the specific instrument, such method
verifies that the passed argument is of the correct type and proceeds
to fill its data members with the correct values. Finally, the
arguments are asked to perform any needed checks on the newly-written
values by calling the validate
method.
The stage is now ready for the Strategy pattern. Its arguments set,
the chosen engine is asked to perform its specific calculations,
implemented in its calculate
method. During the processing, the
engine will read the inputs it needs from its argument structure and
write the corresponding outputs into its results structure.
After the engine completes its work, the control returns to the
Instrument
instance and the Template Method pattern continues
unfolding. The called method, fetchResults
, must now ask the engine
for the results, downcast them to gain access to the contained data,
and copy such values into its own data members. The Instrument
class
defines a default implementation which fetches the results common to
all instruments; derived classes might extend it to read specific
results.
Aside: impure virtual methods
Upon looking at listing 2.6, you might wonder why the setupArguments
method is defined as throwing an exception rather than declared as a
pure virtual method. The reason is not to force developers of new
instruments to implement a meaningless method, were they to decide
that some of their classes should simply override the
performCalculation
method.
Next post: an example.
Figure 2.2: Sequence diagram of the interplay between instruments and pricing engines
Figure 2.3: Class diagram of Instrument
, PricingEngine
, and
related classes