Chapter 6, part 8 of 8: example
Greetings.
This is the last post in a series (started here) that covered chapter 6 of my book, viz., the Monte Carlo framework in QuantLib. I’ve no idea yet what to write in next post: we’ll see.
And of course, the weekly plug: you can still register for my Introduction to QuantLib Development course. It’s three days of presentations (for me) and exercises (for you) based on the material in my book. It’s going to be in London from September 22nd to 24th. Click the link above for more info.
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.
Example: basket option
To close this series, I’ll show and discuss an example of how to build a pricing engine with the Monte Carlo machinery I described so far. The instrument I’ll use is a simple European option on a basket of stocks, giving its owner the right to buy or sell the whole basket at an given price; the quantities of each of the stocks in the basket are also specified by the contract.
For brevity, I won’t show the implementation of the instrument class
itself, BasketOption
. (This class is not the same as the
BasketOption
class implemented in QuantLib. The one used here is
simplified for illustration purposes.) It would be quite similar to
the VanillaOption
class I’ve shown in this post, with the addition of a
data member for the quantities (added to both the instrument and its
arguments
class). Also, I won’t deal with the Greeks; but if the
BasketOption
class were to define them, methods such as delta
and
gamma
would return a vector.
The main class in this example is the one implementing the pricing
engine. It is the MCEuropeanBasketEngine
class template, shown in
listing 6.16.
Listing 6.16: Implementation of the McEuropeanBasketEngine
class template.
As expected, it inherits publicly from the BasketOption::engine
class; however, it also inherits privately from the McSimulation
class template.
In idiomatic C++, the use of private inheritance denotes an “is
implemented in terms of” relationship. We don’t want public
inheritance here, since that would imply an “is a” relationship; in
our conceptual model, MCEuropeanBasketEngine
is a pricing engine for
the basket option and not a simulation that could be used on its
own. The use of multiple inheritance is shunned by some, and in fact
it could be avoided in this case; our engine might use composition
instead, and contain an instance of McSimulation
. However, that
would require inheriting a new simulation class from McSimulation
in
order to implement its purely virtual methods (such as pathGenerator
or pathPricer
) and would have the effect to make the design of the
engine more complex; whereas, by inheriting from McSimulation
, we
can define such methods in the engine itself. (In order to avoid this
dilemma, we would have to rewrite the McSimulation
class so that it
doesn’t use the Template Method pattern; that is, we should make it a
concrete class which would be passed the used path generator and
pricer as constructor arguments. Apart from breaking backward
compatibility, I’m not sure this would be worth the hassle.) Finally,
note the template parameters: we leave to the user the choice of the
RNG traits, but since we’re modeling a basket option we specify the
MultiVariate
class as the Monte Carlo traits.
The constructor (whose implementation is not shown for brevity) takes
the stochastic process for the underlyings, a discount curve, and a
number of parameters related to the simulation. It copies the
arguments into the corresponding data members (except for the
antitheticVariate
flag, which is passed to the McSimulation
constructor) and registers the newly-built instance as an observer of
both the stochastic process and the discount curve.
The calculate
method, required by the PricingEngine
interface,
calls the method by the same name from McSimulation
and passes it
the needed parameters; then, it retrieves the statistics and stores
the mean value and, if possible, the error estimate. This behavior is
not specific of this particular option, and in fact it could be
abstracted out in some generic McEngine
class; but this would muddle
the conceptual model. Such a class would inherit from McSimulation
,
but it could be reasonably expected to inherit from PricingEngine
,
too. However, it we did that, our engine would inherit from both
McEngine
and BasketOption::engine
, leading to the dreaded
inheritance diamond. On the other hand, if we didn’t inherit it from
PricingEngine
(calling it McEngineAdapter
or something) it would
add more complexity to the inheritance hierarchy, since it would be an
additional layer between McSimulation
and our engine, and wouldn’t
remove all the scaffolding anyway: our engine would still need to
define a calculate
method forwarding to the one in the
adapter. Thus, I guess we’ll leave it at that.
The rest of listing 6.16 (continued below) shows the three methods
required to implement the McSimulation
interface and turn our class
into a working engine.
The timeGrid
method builds the grid used by the simulation by
specifying the end time (given by the exercise date) and the number of
steps. Depending on the parameters passed to the engine constructor,
they might have been specified as a given total number (the first if
clause) or a number per year (the second). In either case, the
specification is turned into a total number of steps and passed to the
TimeGrid
constructor. If neither was specified (the else
clause)
an error is raised.
The pathGenerator
method asks the process for the number of factors,
determines the number of time steps from the result of the timeGrid
method I just described, builds a random-sequence generator with the
correct dimensionality (which of course is the product of the two
numbers above) and uses it together with the underlying process and
the time grid to instantiate a multi-path generator.
Finally, the pathPricer
method collects the data required to
determine the payoff on each path—that is, the payoff object
itself, the quantities and the discount at the exercise date, that the
method precalculates—and builds an instance of the
EuropeanBasketPathPricer
class, sketched in listing 6.17.
Listing 6.17: Sketch of the EuropeanMultiPathPricer
class.
The path pricer stores the passed data and uses them to calculate the
realized value of the option in its operator()
goverloading, whose
implementation calculates the value of the basket at maturity and
returns the corresponding payoff discounted to the present time. The
calculation of the basket value is a straightforward loop that
combines the stored quantities with the asset values at maturity,
retrieved from the end points of the respective paths.
At this point, the engine is completed; but it’s still a bit unwieldy to instantiate. We’d want to add a factory class with a fluent interface, so that one can write
but I’m not showing its implementation here. There’s plenty of such examples in the library for your perusal.
Also, you’ll have noted that I kept the example as simple as possible, and for that reason I avoided a few possible generalizations. For instance, another pricer might need some other specific dates besides the maturity, and the time grid should be built accordingly; or the payoff might have been path-dependent, so that the path pricer should look at several path values besides the last. But I don’t think you’ll have any difficulties in writing the corresponding code.
After all this, you might still have a question: how generic is this
engine, really? Well, somewhat less that I’d like. It is generic in
the sense that you can plug in any process for N asset prices, and it
will work. It can even do more exotic stuff, such as quanto effects:
if you go the traditional way and model the effect as an correction
for the drift of the assets, you can write (or better yet, decorate)
your process so that its drift
or evolve
method takes into account
the correction, and you’ll be able to use it without changing the
engine code.
However, if you want to use a process that models other stochastic
variables besides the asset prices (say, a multi-asset Heston process)
you’re likely to get into some trouble. The problem is that you’ll end
up in the operator()
of the path pricer with N quantities and 2N
paths, without any indication of which are for the prices and which
for the volatilities. How should they be combined to get the correct
basket price? Of course, one can write a specific pricer for any
particular process; but I’d like to provide something more reusable.
One simple solution is for the process and the pricer to share some coding convention. For instance, if you arrange the process so that the first N paths are those of the prices and the last M are those of any other variables, the pricer in listing 6.17 will work; note that it loops over the number of quantities, not the size of the process. However, this leaves one wide open to errors that will go undetected if a process doesn’t conform to the convention.
Another possibility that comes to mind is to decorate the process with
a layer that would show the asset prices and hide the extra
variables. The decorator would appear as a process requiring the same
number of random variates as the original, but exposing less
stochastic variables. It would have to store some state in order to
keep track of the hidden variables (which wouldn’t be passed to its
methods): its evolve
method, to name one, would have to take the
current prices, add the extra variables stored in the instance, call
evolve
in the original process, store the new values of the extra
variables so that they’re available for next call, and return only the
new asset prices.
However, I’m not a fan of this solution. It would only work when the decorated methods are called in the expected order, and would break otherwise; for instance, if you called a method twice with the exact same arguments (starting prices and random variates) you would get different return, due to the changed internal state. The long way to express this is that the methods of the decorated process would lose referential transparency. The short way is that its interface would be a lie.
A more promising possibility (albeit one that requires some changes to
the framework) would be to let the path generator do the filtering,
with some help from the process. If the process could provide a method
returning some kind of mask—say, a list of the indices of the
assets into the whole set of variables, or a vector of booleans where
the i-th element would be set to true
if the i-th path corresponds
to an asset price—then the generator could use it to drive the
evolution of the variables correctly, and at the same time write in
the multi-path only the asset prices. To make the whole thing
backward-compatible, the mask would be passed to the generator only
optionally, and the base StochasticProcess
class would have a
default implementation of the method returning an empty mask. If
either way no mask were provided, the generator would revert to the
current behavior. If needed, the method could be generalized to return
other masks besides that for the prices; for instance, a variance swap
would be interested in the volatility but not the price.
Finally, note that some assumptions are built right into the engine and cannot be relaxed without rewriting it. For instance, we’re assuming that the discount factor for the exercise date is deterministic. If you wrote a process that models stochastic interest rates as well as asset prices, and therefore wanted to discount on each path according to the realization of the rates on that path, you’d have to write a custom engine and path pricer to be used with your specific process. Fortunately, that would be the only things you’d have to write; you’d still be able to reuse the other pieces of the framework.
Aside: need-to-know basis
As usual, I didn’t show in which files one should put the several
classes I described. Well, as a general principle, the more
encapsulation the better; thus, to begin with, helper classes should
be hidden from the public interface. For instance, the
EuropeanBasketPathPricer
class is only used by the engine and could
be hidden from client code. The best way would be to define such
classes inside an anonymous namespace in a .cpp
file, but that’s not
always possible: in our case, the engine is a class template, so
that’s not an option. The convention we’re using in QuantLib is that
helper classes that must be declared in a header file are placed in a
nested namespace detail
, and the user agrees to be a gentleman (or a
lady, of course) and to leave it alone.
If, later on, we find out that we need the class elsewhere (for
instance, because we want to use EuropeanBasketPathPricer
as a
control-variate path pricer for another engine) we can move it to a
header file—or, if it’s already in one, to the main QuantLib
namespace—and make it accessible to everybody.