2. Financial instruments and pricing engines
The statement that a financial library must provide the means to price financial instruments would certainly have appealed to Monsieur de La Palisse. However, that is only a part of the whole problem; a financial library must also provide developers with the means to extend it by adding new pricing functionality.
Foreseeable extensions are of two kinds, and the library must allow either one. On the one hand, it must be possible to add new financial instruments; on the other hand, it must be feasible to add new means of pricing an existing instrument. Both kinds have a number of requirements, or in pattern jargon, forces that the solution must reconcile. This chapter details such requirements and describes the design that allows QuantLib to satisfy them.
2.1 The Instrument class
In our domain, a financial instrument is a concept in its own right. For this reason alone, any self-respecting object-oriented programmer will code it as a base class from which specific instruments will be derived.
The idea, of course, is to be able to write code such as
for (i = portfolio.begin(); i != portfolio.end(); ++i)
totalNPV += i->NPV();
where we don’t have to care about the specific type of each
instrument. However, this also prevents us from knowing what
arguments to pass to the NPV method, or even what methods to
call. Therefore, even the two seemingly harmless lines above tell us
that we have to step back and think a bit about the interface.
2.1.1 Interface and requirements
The broad variety of traded assets—which range from the simplest to
the most exotic—implies that any method specific to a given class of
instruments (say, equity options) is bound not to make sense for some
other kind (say, interest-rate swaps). Thus, very few methods were
singled out as generic enough to belong to the Instrument
interface. We limited ourselves to those returning its present value
(possibly with an associated error estimate) and indicating whether or
not the instrument has expired; since we can’t specify what arguments
are needed,4 the methods take none; any needed input will
have to be stored by the instrument. The resulting interface is shown
in the listing below.
Instrument class. class Instrument {
public:
virtual ~Instrument();
virtual Real NPV() const = 0;
virtual Real errorEstimate() const = 0;
virtual bool isExpired() const = 0;
};
As is good practice, the methods were first declared as pure virtual ones; but—as Sportin’ Life points out in Gershwin’s Porgy and Bess—it ain’t necessarily so. There might be some behavior that can be coded in the base class. In order to find out whether this was the case, we had to analyze what to expect from a generic financial instrument and check whether it could be implemented in a generic way. Two such requirements were found at different times, and their implementation changed during the development of the library; I present them here in their current form.
One is that a given financial instrument might be priced in different ways (e.g., with one or more analytic formulas or numerical methods) without having to resort to inheritance. At this point, you might be thinking “Strategy pattern”. It is indeed so; I devote section 2.2 to its implementation.
The second requirement came from the observation that the value of a financial instrument depends on market data. Such data are by their nature variable in time, so that the value of the instrument varies in turn; another cause of variability is that any single market datum can be provided by different sources. We wanted financial instruments to maintain links to these sources so that, upon different calls, their methods would access the latest values and recalculate the results accordingly; also, we wanted to be able to transparently switch between sources and have the instrument treat this as just another change of the data values.
We were also concerned with a potential loss of efficiency. For instance, we could monitor the value of a portfolio in time by storing its instruments in a container, periodically poll their values, and add the results. In a simple implementation, this would trigger recalculation even for those instruments whose inputs did not change. Therefore, we decided to add to the instrument methods a caching mechanism: one that would cause previous results to be stored and only recalculated when any of the inputs change.
2.1.2 Implementation
The code managing the caching and recalculation of the instrument value was written for a generic financial instrument by means of two design patterns.
When any of the inputs change, the instrument is notified by means of the Observer pattern (Gamma et al, 1995). The pattern itself is briefly described5 in appendix A; I describe here the participants.
Obviously enough, the instrument plays the role of the observer while
the input data play that of the observables. In order to have access
to the new values after a change is notified, the observer needs to
maintain a reference to the object representing the input. This might
suggest some kind of smart pointer; however, the behavior of a pointer
is not sufficient to fully describe our problem. As I already
mentioned, a change might come not only from the fact that values from
a data feed vary in time; we might also want to switch to a different
data feed. Storing a (smart) pointer would give us access to the
current value of the object pointed; but our copy of the pointer,
being private to the observer, could not be made to point to a
different object. Therefore, what we need is the smart equivalent of a
pointer to pointer. This feature was implemented in QuantLib as a
class template and given the name of Handle. Again, details are
given in appendix A; relevant to this discussion is the
fact that copies of a given Handle share a link to an
object. When the link is made to point to another object, all copies
are notified and allow their holders to access the new
pointee. Furthermore, Handles forward any notifications from
the pointed object to their observers.
Finally, classes were implemented which act as observable data and can
be stored into Handles. The most basic is the Quote
class, representing a single varying market value. Other inputs for
financial instrument valuation can include more complex objects such
as yield or volatility term structures.6
Another problem was to abstract out the code for storing and
recalculating cached results, while still leaving it to derived
classes to implement any specific calculations.
In earlier versions of QuantLib, the functionality was included in the
Instrument class itself; later, it was extracted and coded into
another class—somewhat unsurprisingly called LazyObject—which is
now reused in other parts of the library. An outline of the class is
shown in the following listing.
LazyObject class. class LazyObject : public virtual Observer,
public virtual Observable {
protected:
mutable bool calculated_;
virtual void performCalculations() const = 0;
public:
void update() { calculated_ = false; }
virtual void calculate() const {
if (!calculated_) {
calculated_ = true;
try {
performCalculations();
} catch (...) {
calculated_ = false;
throw;
}
}
}
};
The code is not overly complex. A boolean data member calculated_ is
defined which keeps track of whether results were calculated and still
valid. The update method, which implements the Observer
interface and is called upon notification from observables, sets such
boolean to false and thus invalidates previous results.
The calculate method is implemented by means of the Template Method
pattern (Gamma et al, 1995), sometimes also called
non-virtual interface. As explained in the Gang of Four book, the
constant part of the algorithm (in this case, the management of the cached
results) is implemented in the base class; the varying parts (here,
the actual calculations) are delegated to a virtual method, namely,
performCalculations, which is called in the body of the
base-class method. Therefore, derived classes will only implement
their specific calculations without having to care about caching: the
relevant code will be injected by the base class.
The logic of the caching is simple. If the current results are no longer valid, we let the derived class perform the needed calculations and flag the new results as up to date. If the current results are valid, we do nothing.
However, the implementation is not as simple. You might
wonder why we had to insert a try block setting
calculated_ beforehand and a handler rolling back the change
before throwing the exception again. After all, we could have written
the body of the algorithm more simply—for instance, as in the
following, seemingly equivalent code, that doesn’t catch and rethrow
exceptions:
if (!calculated_) {
performCalculations();
calculated_ = true;
}
The reason is that there are cases (e.g., when the lazy object is a
yield term structure which is bootstrapped lazily) in which
performCalculations happens to recursively call
calculate. If calculated_ were not set to true,
the if condition would still hold and performCalculations
would be called again, leading to infinite recursion. Setting such
flag to true prevents this from happening; however, care must
now be taken to restore it to false if an exception is
thrown. The exception is then rethrown so that it can be caught by the
installed error handlers.
A few more methods are provided in LazyObject which enable
users to prevent or force a recalculation of the results. They are not
discussed here. If you’re interested, you can heed the advice often
given by master Obi-Wan Kenobi: “Read the source, Luke.”
The Instrument class inherits from LazyObject. In order
to implement the interface outlined earlier,
it decorates the calculate method with code specific to
financial instruments. The resulting method is shown in
the listing below, together with other bits of supporting
code.
Instrument class. class Instrument : public LazyObject {
protected:
mutable Real NPV_;
public:
Real NPV() const {
calculate();
return NPV_;
}
void calculate() const {
if (isExpired()) {
setupExpired();
calculated_ = true;
} else {
LazyObject::calculate();
}
}
virtual void setupExpired() const {
NPV_ = 0.0;
}
};
Once again, the added code follows the Template Method pattern to
delegate instrument-specific calculations to derived classes. The
class defines an NPV_ data member to store the result of the
calculation; derived classes can declare other data members to store
specific results.7 The body of the calculate method calls
the virtual isExpired method to check whether the instrument is an
expired one. If this is the case, it calls another virtual method,
namely, setupExpired, which has the responsibility of giving
meaningful values to the results; its default implementation sets
NPV_ to 0 and can be called by derived classes. The calculated_
flag is then set to true. If the instrument is not expired, the
calculate method of LazyObject is called instead, which in turn
will call performCalculations as needed. This imposes a contract on
the latter method, namely, its implementations in derived classes are
required to set NPV_ (as well as any other instrument-specific data
member) to the result of the calculations. Finally, the NPV method
ensures that calculate is called before returning the answer.
2.1.3 Example: interest-rate swap
I end this section by showing how a specific financial instrument can be implemented based on the described facilities.
The chosen instrument is the interest-rate swap. As you surely know, it is a contract which consists in exchanging periodic cash flows. The net present value of the instrument is calculated by adding or subtracting the discounted cash-flow amounts depending on whether the cash flows are paid or received.
Not surprisingly, the swap is implemented8 as a new class
deriving from Instrument. Its outline is shown in
the following listing.
Swap class.class Swap : public Instrument {
public:
Swap(const vector<shared_ptr<CashFlow> >& firstLeg,
const vector<shared_ptr<CashFlow> >& secondLeg,
const Handle<YieldTermStructure>& termStructure);
bool isExpired() const;
Real firstLegBPS() const;
Real secondLegBPS() const;
protected:
// methods
void setupExpired() const;
void performCalculations() const;
// data members
vector<shared_ptr<CashFlow> > firstLeg_, secondLeg_;
Handle<YieldTermStructure> termStructure_;
mutable Real firstLegBPS_, secondLegBPS_;
};
It contains as data members the objects
needed for the calculations—namely, the cash flows on the first and
second leg and the yield term structure used to discount their
amounts—and two variables used to store additional
results. Furthermore, it declares methods implementing the
Instrument interface and others returning the swap-specific
results. The class diagram of Swap and the related classes is shown
in the figure below.
Swap class.The fitting of the class to the Instrument framework is done in
three steps, the third being optional depending on the derived class;
the relevant methods are shown in the next listing.
Swap class.Swap::Swap(const vector<shared_ptr<CashFlow> >& firstLeg,
const vector<shared_ptr<CashFlow> >& secondLeg,
const Handle<YieldTermStructure>& termStructure)
: firstLeg_(firstLeg), secondLeg_(secondLeg),
termStructure_(termStructure) {
registerWith(termStructure_);
vector<shared_ptr<CashFlow> >::iterator i;
for (i = firstLeg_.begin(); i!= firstLeg_.end(); ++i)
registerWith(*i);
for (i = secondLeg_.begin(); i!= secondLeg_.end(); ++i)
registerWith(*i);
}
bool Swap::isExpired() const {
Date settlement = termStructure_->referenceDate();
vector<shared_ptr<CashFlow> >::const_iterator i;
for (i = firstLeg_.begin(); i!= firstLeg_.end(); ++i)
if (!(*i)->hasOccurred(settlement))
return false;
for (i = secondLeg_.begin(); i!= secondLeg_.end(); ++i)
if (!(*i)->hasOccurred(settlement))
return false;
return true;
}
void Swap::setupExpired() const {
Instrument::setupExpired();
firstLegBPS_= secondLegBPS_ = 0.0;
}
void Swap::performCalculations() const {
NPV_ = - Cashflows::npv(firstLeg_,**termStructure_)
+ Cashflows::npv(secondLeg_,**termStructure_);
errorEstimate_ = Null<Real>();
firstLegBPS_ = - Cashflows::bps(firstLeg_, **termStructure_);
secondLegBPS_ = Cashflows::bps(secondLeg_, **termStructure_);
}
Real Swap::firstLegBPS() const {
calculate();
return firstLegBPS_;
}
Real Swap::secondLegBPS() const {
calculate();
return secondLegBPS_;
}
The first step is performed in the class constructor, which takes as arguments (and copies into the corresponding data members) the two sequences of cash flows to be exchanged and the yield term structure to be used for discounting their amounts. The step itself consists in registering the swap as an observer of both the cash flows and the term structure. As previously explained, this enables them to notify the swap and trigger its recalculation each time a change occurs.
The second step is the implementation of the required interface. The
logic of the isExpired method is simple enough; its body loops
over the stored cash flows checking their payment dates. As soon as it
finds a payment which still has not occurred, it reports the swap as
not expired. If none is found, the instrument has expired. In this
case, the setupExpired method will be called. Its
implementation calls the base-class one, thus taking care of the data
members inherited from Instrument; it then sets to 0 the
swap-specific results.
The last required method is performCalculations. The calculation is
performed by calling two external functions from the Cashflows
class.9 The first one, namely, npv, is a straightforward
translation of the algorithm outlined above: it cycles on a sequence
of cash flows adding the discounted amount of its future cash flows.
We set the NPV_ variable to the difference of the results from the
two legs. The second one, bps, calculates the basis-point
sensitivity (BPS) of a sequence of cash flows. We call it once per leg
and store the results in the corresponding data members. Since the
result carries no numerical error, the errorEstimate_ variable is
set to Null<Real>()—a specific floating-point value which is used
as a sentinel value indicating an invalid number.10
The third and final step only needs to be performed if—as in this
case—the class defines additional results. It consists in writing
corresponding methods (here, firstLegBPS and
secondLegBPS) which ensure that the calculations are (lazily)
performed before returning the stored results.
The implementation is now complete. Having been written on top of the
Instrument class, the Swap class will benefit from its
code. Thus, it will automatically cache and recalculate results
according to notifications from its inputs—even though no related
code was written in Swap except for the registration calls.
2.1.4 Further developments
You might have noticed a shortcoming in my treatment of the previous
example and of the Instrument class in general. Albeit
generic, the Swap class we implemented cannot manage
interest-rate swaps in which the two legs are paid in different
currencies. A similar problem would arise if you wanted to add the
values of two instruments whose values are not in the same currency;
you would have to convert manually one of the values to the
currency of the other before adding them together.
Such problems stem from a single weakness of the implementation: we
used the Real type (i.e., a simple floating-point number) to
represent the value of an instrument or a cash flow. Therefore, such
results miss the currency information which is attached to them in the
real world.
The weakness might be removed if we were to express such results by
means of the Money class. Instances of such class contain
currency information; moreover, depending on user settings, they are
able to automatically perform conversion to a common currency upon
addition or subtraction.
However, this would be a major change, affecting a large part of the code base in a number of ways. Therefore, it will need some serious thinking before we tackle it (if we do tackle it at all).
Another (and more subtle) shortcoming is that the Swap class
fails to distinguish explicitly between two components of the
abstraction it represents. Namely, there is no clear separation
between the data specifying the contract (the cash-flow specification)
and the market data used to price the instrument (the current discount
curve).
The solution is to store in the instrument only the first group of data (i.e., those that would be in its term sheet) and keep the market data elsewhere.11 The means to do this are the subject of the next section.
2.2 Pricing engines
We now turn to the second of the requirements I stated in the previous section. 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.
The listing below 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.
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 the next listing. 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 instrument12
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:
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
the first of the next two figures; the static relationship between the
classes (and a possible concrete instrument) is shown in
the second.
Instrument, PricingEngine, and related classes
including a derived instrument class.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.
2.2.1 Example: plain-vanilla option
At this point, an example is necessary. A word of
warning, though: although a class exists in QuantLib which implements
plain-vanilla options—i.e., simple call and put equity options with
either European, American or Bermudan exercise—such class is
actually the lowermost leaf of a deep class hierarchy. Having the
Instrument class at its root, such hierarchy specializes it
first with an Option class, then again with a
OneAssetOption class generalizing options on a single
underlying, passing through another class or two until it finally
defines the VanillaOption class we are interested in.
There are good reasons for this; for instance, the code
in the OneAssetOption class can naturally be reused for, say,
Asian options, while that in the Option class lends itself for
reuse when implementing all kinds of basket options. Unfortunately,
this causes the code for pricing a plain option to be spread among all
the members of the described inheritance chain, which would not make
for an extremely clear example. Therefore, I will describe a simplified
VanillaOption class with the same implementation as the one in
the library, but inheriting directly from the Instrument class;
all code implemented in the intermediate classes will be
shown as if it were implemented in the example
class rather than inherited.
The listing below shows the interface of our
vanilla-option class. It declares the required methods from the
Instrument interface, as well as accessors for additional
results, namely, the greeks of the options; as pointed out in the
previous section, the corresponding data members are declared as
mutable so that their values can be set in the logically constant
calculate method.
VanillaOption class.class VanillaOption : public Instrument {
public:
// accessory classes
class arguments;
class results;
class engine;
// constructor
VanillaOption(const shared_ptr<Payoff>&,
const shared_ptr<Exercise>&);
// implementation of instrument method
bool isExpired() const;
void setupArguments(Arguments*) const;
void fetchResults(const Results*) const;
// accessors for option-specific results
Real delta() const;
Real gamma() const;
Real theta() const;
// ...more greeks
protected:
void setupExpired() const;
// option data
shared_ptr<Payoff> payoff_;
shared_ptr<Exercise> exercise_;
// specific results
mutable Real delta_;
mutable Real gamma_;
mutable Real theta_;
// ...more
};
Besides its own data and methods, VanillaOption declares a
number of accessory classes: that is, the specific argument and result
structures and a base pricing engine. They are defined as
inner classes to highlight the relationship between them and the
option class; their interface is shown in the next listing.
VanillaOption inner classes.class VanillaOption::arguments
: public PricingEngine::arguments {
public:
// constructor
arguments();
void validate() const;
shared_ptr<Payoff> payoff;
shared_ptr<Exercise> exercise;
};
class Greeks : public virtual PricingEngine::results {
public:
Greeks();
Real delta, gamma;
Real theta;
Real vega;
Real rho, dividendRho;
};
class VanillaOption::results : public Instrument::results,
public Greeks {
public:
void reset();
};
class VanillaOption::engine
: public GenericEngine<VanillaOption::arguments,
VanillaOption::results> {};
Two comments can be made on such accessory classes. The first is that,
making an exception to what I said in my introduction to the example,
I didn’t declare all data members into the results class. This
was done in order to point out an implementation detail. One might
want to define structures holding a few related and commonly used
results; such structures can then be reused by means of inheritance,
as exemplified by the Greeks structure that is here composed
with Instrument::results to obtain the final structure. In
this case, virtual inheritance from PricingEngine::results must
be used to avoid the infamous inheritance diamond (see, for instance,
Stroustrup, 2013; the name is in the index).
The second comment is that, as shown, it is sufficient to inherit from
the class template GenericEngine (instantiated with the right
argument and result types) to provide a base class for
instrument-specific pricing engines. We will see that derived classes
only need to implement their calculate method.
We now turn to the implementation of the VanillaOption class,
shown in the following listing.
VanillaOption class. VanillaOption::VanillaOption(
const shared_ptr<StrikedTypePayoff>& payoff,
const shared_ptr<Exercise>& exercise)
: payoff_(payoff), exercise_(exercise) {}
bool VanillaOption::isExpired() const {
Date today = Settings::instance().evaluationDate();
return exercise_->lastDate() < today;
}
void VanillaOption::setupExpired() const {
Instrument::setupExpired();
delta_ = gamma_ = theta_ = ... = 0.0;
}
void VanillaOption::setupArguments(
PricingEngine::arguments* args) const {
VanillaOption::arguments* arguments =
dynamic_cast<VanillaOption::arguments*>(args);
QL_REQUIRE(arguments != 0, "wrong argument type");
arguments->exercise = exercise_;
arguments->payoff = payoff_;
}
void VanillaOption::fetchResults(
const PricingEngine::results* r) const {
Instrument::fetchResults(r);
const VanillaOption::results* results =
dynamic_cast<const VanillaOption::results*>(r);
QL_ENSURE(results != 0, "wrong result type");
delta_ = results->delta;
... // other Greeks
}
Real VanillaOption::delta() const {
calculate();
QL_ENSURE(delta_ != Null<Real>(), "delta not given");
return delta_;
}
Its constructor takes a few objects defining the instrument. Most of them will be described in later chapters or in appendix A. For the time being, suffice to say that the payoff contains the strike and type (i.e., call or put) of the option, and the exercise contains information on the exercise dates and variety (i.e., European, American, or Bermudan). The passed arguments are stored in the corresponding data members. Also, note that they do not include market data; those will be passed elsewhere.
The methods related to expiration are straightforward; isExpired
checks whether the latest exercise date is passed, while
setupExpired calls the base-class implementation and sets the
instrument-specific data to 0.
The setupArguments and fetchResults methods are a bit
more interesting. The former starts by downcasting the generic
argument pointer to the actual type required, raising an
exception if another type was passed; it then turns to the actual
work. In some cases, the data members are just copied verbatim into
the corresponding argument slots. However, it might be the case that
the same calculations (say, converting dates into times) will be
needed by a number of engines; setupArguments provides a place
to write them just once.
The fetchResults method is the dual of
setupArguments. It also starts by downcasting the passed
results pointer; after verifying its actual type, it
copies the results into his own data members.
Any method that returns additional results, such as delta, will do
as NPV does: it will call calculate, check that the corresponding
result was cached (because any given engine might or might not be able
to calculate it) and return it.
The above implementation is everything we needed to have a working instrument—working, that is, once it is set an engine which will perform the required calculations. Such an engine is sketched in the listing below and implements the analytic Black-Scholes-Merton formula for European options.
VanillaOption class. class AnalyticEuropeanEngine
: public VanillaOption::engine {
public:
AnalyticEuropeanEngine(
const shared_ptr<GeneralizedBlackScholesProcess>&
process)
: process_(process) {
registerWith(process);
}
void calculate() const {
QL_REQUIRE(
arguments_.exercise->type() == Exercise::European,
"not an European option");
shared_ptr<PlainVanillaPayoff> payoff =
dynamic_pointer_cast<PlainVanillaPayoff>(
arguments_.payoff);
QL_REQUIRE(process, "Black-Scholes process needed");
... // other requirements
Real spot = process_->stateVariable()->value();
... // other needed quantities
BlackCalculator black(payoff, forwardPrice,
stdDev, discount);
results_.value = black.value();
results_.delta = black.delta(spot);
... // other greeks
}
private:
shared_ptr<GeneralizedBlackScholesProcess> process_;
};
Its constructor takes (and registers itself with) a Black-Scholes
stochastic process that contains market-data information about the
underlying including present value, risk-free rate, dividend yield,
and volatility. Once again, the actual calculations are hidden behind
the interface of another class, namely, the BlackCalculator
class. However, the code has enough detail to show a few relevant
features.
The method starts by verifying a few preconditions. This might come as
a surprise, since the arguments of the calculations were already
validated by the time the calculate method is called. However,
any given engine can have further requirements to be fulfilled before
its calculations can be performed. In the case of our engine, one such
requirement is that the option is European and that the payoff is a
plain call/put one, which also means that the payoff will be cast down
to the needed class.13
In the middle section of the method, the engine extracts from the passed arguments any information not already presented in digested form. Shown here is the retrieval of the spot price of the underlying; other quantities needed by the engine, e.g., the forward price of the underlying and the risk-free discount factor at maturity, are also extracted.14
Finally, the calculation is performed and the results are stored in
the corresponding slots of the results structure. This concludes both
the calculate method and the example.