CPP Quant Finance Ebook 20131003
CPP Quant Finance Ebook 20131003
CPP Quant Finance Ebook 20131003
1.1
Introduction to QuantStart . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.2
1.3
1.4
1.5
Software Requirements . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.6
Book Structure . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1.7
10
1.8
10
2 Introduction to C++ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 11
2.1
11
2.2
11
2.3
12
2.4
12
2.5
13
2.6
Object-Oriented Programming . . . . . . . . . . . . . . . . . . . . . . . . . . . .
13
2.7
Generic Programming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
14
2.8
14
2.9
15
15
16
17
3.2
18
3.3
18
3.4
19
2
3.5
20
3.6
21
3.7
24
3.8
29
31
4.2
32
4.3
33
4.4
33
4.5
PayOffCall Header . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
34
4.6
PayOffDoubleDigital Header . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
35
4.7
36
4.8
36
4.9
Virtual Destructors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
37
41
5.2
Template Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
41
5.3
42
5.4
42
5.5
43
5.6
SimpleMatrix Declaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
44
5.7
SimpleMatrix Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
47
5.8
51
. . . . . . . . . . . . . . . . . 53
6.1
53
6.2
Containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
54
6.2.1
Sequence Containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
54
6.2.2
Associative Containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
55
6.2.3
Container Adaptors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
56
Iterators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
56
6.3.1
Iterator Categories . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
56
6.3.2
Iterator Adaptors . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
57
6.3
6.4
6.5
6.3.3
Const Iterators . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
58
6.3.4
Iterator Traits . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
59
Algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
59
6.4.1
Algorithm Categories . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
59
6.4.2
Nonmodifying Algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . .
60
6.4.3
Modifying Algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
61
6.4.4
Removal Algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
62
6.4.5
Mutating Algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
63
6.4.6
Sorting Algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
63
6.4.7
64
6.4.8
Numeric Algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
65
C++11 STL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
66
6.5.1
Containers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
66
6.5.2
Algorithms . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
66
7 Function Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 69
7.1
Function Pointers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
70
7.2
72
8.2
76
8.1.1
76
8.1.2
77
8.1.3
Full Declaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
79
8.1.4
81
8.1.5
81
8.1.6
84
8.1.7
89
8.1.8
96
97
8.2.1
Installation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
98
8.2.2
Basic Usage . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
98
8.2.3
99
8.2.4
Expression Templates . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
99
8.2.5
4
8.2.6
8.2.7
8.2.8
8.2.9
Reduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 105
Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 107
9.2
LU Decomposition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
9.3
9.2.1
Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 108
9.2.2
9.4
9.5
Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117
9.4.2
QR Decomposition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
9.5.1
Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 119
9.5.2
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
5
12.4 Path Generation Header . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161
12.5 Asian Option Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
12.6 The Main Program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
13 Implied Volatility . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
13.1 Motivation
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173
. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 215
6
16.5 Mathematical Model . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223
16.6 Euler Discretisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 224
16.6.1 Correlated Asset Paths . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226
16.6.2 Monte Carlo Algorithm . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226
16.7 C++ Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
16.7.1 PayOff Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
16.7.2 Option Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 227
16.7.3 Statistics and CorrelatedSND Classes . . . . . . . . . . . . . . . . . . . . 229
16.7.4 HestonEuler Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 229
16.7.5 Main Program . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 231
17 Single Factor Black-Scholes with Finite Difference Methods . . . . . . . . . . 237
17.1 Black-Scholes PDE for a European Call Option . . . . . . . . . . . . . . . . . . . 238
17.2 Finite Difference Discretisation . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239
17.3 Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 240
17.3.1 PayOff Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 241
17.3.2 VanillaOption Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 243
17.3.3 PDE Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245
17.3.4 FDM Class . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248
17.3.5 Main Implementation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 256
17.4 Execution and Output . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
Chapter 1
Introduction to QuantStart
QuantStart was founded by Michael Halls-Moore, in 2010, to help junior quantitative analysts
(QAs) find jobs in the tough economic climate. It now includes book reviews, quant finance
articles and programming tips. Since March 2010, QuantStart has helped over 20,000 visitors
to become quantitative analysts. You can always contact QuantStart by sending an email to
[email protected].
1.2
The C++ For Quantitative Finance book is designed to teach junior/prospective quants with
some basic C++ skills to be a professional grade quantitative analyst with advanced C++
skills. The book describes advanced features of C++ such as templates, STL, inheritance,
polymorphism and design patterns. In addition, the book applies these features to numerical
methods used by quants, such as Finite Differences and Monte Carlo, to numerically determine
the price of derivatives. The book is driven by examples and includes many projects to implement
the principles and methods taught.
1.3
The book has been written for prospective and junior quantitative analysts who have some exposure to programming with C++ and wish to learn how to program in a professional environment.
It is designed for those who enjoy self-study and can learn by example. Much of the book is about
7
8
actual programming and implementation - you cant be a good quant unless you implement your
mathematical models!
Senior quants will also find the content useful. Alternative implementations of a particular
model or tips and tricks to improve compiler efficiency and software project management will
come in useful, even for seasoned financial engineers.
1.4
You should have a strong mathematical background. Calculus, linear algebra and stochastic
calculus will be necessary for you to understand the methods such as Finite Differences and
Monte Carlo that we will be implementing.
You should be aware of elementary programming concepts such as variable declaration, flowcontrol (if-else), looping (for/while) and the basic compilation process for C++ programs. You
should be familiar with an IDE or C++ compiler (see below).
You should be familiar with basic quantitative finance concepts such as pricing by binomial
trees, stochastic calculus and risk neutrality. You should be aware of the common types of
financial derivatives and payoffs, such as vanilla calls and puts and exotic options such as Asian
or Barriers.
If you are rusty on this material, or it is new to you, have a look at the QuantStart reading list: http://www.quantstart.com/articles/Quant-Reading-List-Derivative-Pricing and look
for Mathematical Finance.
1.5
Software Requirements
Quantitative finance applications in C++ can be developed in Windows, Mac OSX or Linux.
This book is agnostic to the end software, so it is best to use whatever C++ environment youre
currently comfortable with.
If you use Windows, you can download Microsoft Visual Studio Express Edition 2012, which
comes with Visual C++. There are some great tutorials on how to get started with it, but this
book assumes you know how to use an Integrated Development Environment (IDE).
If you use Mac OSX or Linux you can use a text editor such as Vim or Emacs, along with
the GCC compiler toolset. On Mac OSX specifically, you can download the XCode Development
Tools as an alternative IDE.
1.6
Book Structure
The book is broken down into chapters, each of which will concentrate either on a feature of C++
or on a particular implementation of a pricing model. The first set of chapters concentrate on
intermediate features of C++, along with implementation examples of basic quantitative finance
concepts. These initial chapters will teach you about the language features, which will be applied
to the implementation of quantitative models later on in the book.
You will cover the following topics:
Object Oriented Programming (OOP)
Option and PayOff Classes
Pass-by-Value/Pass-by-Reference
Const Keyword
Inheritance
Abstract Base Classes
Virtual Destructors
Function Objects
Operator Overloading
Generic/Template Programming
Standard Template Library (STL)
Later classes will discuss mathematics and statistics. You will look at how to matrices in
C++, how to solve matrix equations and how to generate random numbers and statistical classes.
These will be essential tools in our C++ quant toolbox.
Then we will discuss options pricing techniques using Monte Carlo Methods (MCM) and
Finite Difference Methods (FDM) in detail. We will create basic MCM and FDM solvers for
some options and then use them to calculate the Greeks (option price sensitivities) and how to
hedge. Beyond MCM and FDM, we will study advanced pricing engines. In particular, we will
study Exotic/Path-Dependent options, Jump-Diffusion Models and Stochastic Volatility Models.
10
1.7
This is not a beginner book on C++, programming or quantitative finance. It will not teach
you about variable declaration, branching, looping or how to compile a program. If you need to
brush up on those concepts, take a look at this article on the QuantStart website:
http://www.quantstart.com/articles/Top-5-Essential-Beginner-C-Books-for-Financial-Engineers
The book has not been designed to teach you what a Partial Differential Equation (PDE)
is or what a derivative is (mathematical or financial!). It certainly is not an introduction to
stochastic calculus. Make sure you come into this book with a good mathematical background
(an undergraduate degree in Mathematics, Physics or Engineering is approximately the correct
level).
1.8
The best place to look for help is the articles list, found at http://www.quantstart.com/articles
or by contacting me at [email protected]. Ive written many articles about basic quantitative
finance, so you can brush up by reading some of these. Thanks for pre-ordering the book and
helping to support me while I write more content - it is very much appreciated. Good luck with
your quant career plans! Now onto some quant finance...
Chapter 2
Introduction to C++
2.1
C++ was created in 1979 by Bjarne Stroustrup. He provisionally called it C with Classes,
as the C programming language does not possess any real object orientated features. Although
some think C++ was the first official object oriented language, Simula holds the distinction
instead.
In 1983 the name changed to C++. This was a pun on the increment operator (++),
hence it referred to an incremental improvement in C. New features were added such as virtual
functions and operator overloading. In 1989 version 2.0 was released with multiple inheritance,
abstract classes, const members and protected members. Later revisions of C++ in 1990 added
templates, exceptions, namespaces and new casts.
In 2011 a fully-updated version (C++11) was released with extensive multi-threading and
generic programming support. This is the version we will be using. However, the reality is that
many firms are not using C++11 compliant compilers at this stage. Hence we will not be making
heavy use of the new features.
2.2
As a language C++ is very close to the hardware. This means that one can allocate and
deallocate memory in a custom-defined manner, as well as optimise iteration and looping to a
significant degree. This can lead to extremely high performance assuming the programmer has
the skills to take advantage of both the hardware and software optimisations available.
C++ allows multi-paradigm programming. This means that it is extremely versatile with re11
12
gards to how one should program with it. C++ is often referred to as a federation of languages.
Procedural, object-oriented and functional styles are all supported in various guises.
Generic programming and the Standard Template Library (STL) provide sophisticated typeindependent classes and extensive, well-optimised code out of the box. You can spend more
time implementing financial models and less time worrying about how to optimise loops or
iteration. This is a fantastic time-saver.
Further, C++ is an ISO standard, so it will be around for a long time. Becoming a very good
C++ programmer is certainly a secure career path!
2.3
C++ is not a beginner language compared to more modern programming options such as
Python, Ruby, MatLab or R. It provides far more control than is often required and thus can be
confusing.
It is very easy to make a mistake, create bugs or write poorly optimised code in C++,
especially where pointers are involved. Despite the fact it has a compilation process, many bugs
will slip through at compile-time. Debugging C++ programs is not straightforward especially
where memory allocation is concerned.
Compared to languages like Python, MatLab or R, C++ will often require more lines of code
(LOC). This means greater maintenance overhead, more testing and thus more chances for bugs
to appear. It is not a language to create model prototypes with.
The language is so large and feature-heavy that two experienced C++ programmers are able
to solve problems in vastly different ways - this leads to interoperability issues on large projects.
Standards must be enforced from the outset for effective development.
2.4
As previously stated, C++ can be seen as a federation of many programming styles. Five
fundamental components of C++ are listed here, although there are many more subcomponents,
which we will be studying in depth:
Procedural, C-Style - Functions and modules, but no object-orientation or templates
Object-Orientation and Classes - Encapsulation of member data and access
Generic Programming and Templates - Type-independent classes for code re-use
13
Standard Template Library - Optimised code for containers, algorithms and I/O
Boost Libraries - Well-tested additional third-party utility libraries
We will now look at each of these five components in turn.
2.5
C++ is a superset of the C language. Hence any C program will compile in a C++ compiler
(in almost all cases!). In particular, this means that C libraries will still function in your C++
code. Be aware though that the interfaces are likely to differ substantially. C function interfaces
generally make heavy use of pointers and function pointers, whereas C++ interfaces will often
utilise an OOP approach, making use of templates, references and the STL. For that reason,
many popular C libraries have had wrappers written for them that provide a C++ interface over
older C libraries.
It is possible to program procedurally in C++, without any reference to classes or templates.
Functions are often grouped into modules of similar functionality. However, just remember that
your interfaces will be constrained in the manner described above. It is often more practical,
when programming procedurally in C++, to utilise more advanced libraries from the STL such
as the iostream, vector and string libraries.
2.6
Object-Oriented Programming
C++ provides extensive data encapsulation features. Objects can be created to represent entities,
via the notion of a class. The job of a class is to encapsulate all functionality related to a particular
entity, exposing the necessary parts as a public interface and hiding any implementation details
via a private interface. C++ classes encapsulate data and data manipulation through the public,
private and protected keywords. This allows detailed specification of interfaces to member data.
These terms will be explained in detail when we begin creating our first objects.
C++ classes support virtual functions, operator overloading, multiple inheritance and polymorphism. These concepts allow the modelling of the is-a relationship as well as the has-a
relationship between objects. The latter is known as composition. These paradigms promote
code-reuse, maintainability and extensibility. We will be making extensive use of these objectoriented techniques for our quantitative finance code.
Best practice design patterns have emerged over the years to encourage solutions and implementation procedures for specific object-oriented design problems. Patterns are useful because
14
if the underlying mechanic is understood for a particular model implementation, it is more
straightforward for additional quants/programmers to familiarise themselves with the codebase,
thus saving extensive amounts of time.
2.7
Generic Programming
2.8
The STL is a collection of optimised template libraries for common data structures and algorithms
applied to those structures. It contains many subcomponents:
Sequence Containers, e.g. Vectors, Lists, Stacks, Queues and Deques (double-ended
queues).
Associative Containers, e.g Sets, Maps, Multi-Maps
bf Algorithms, e.g. Sort, Copy, Reverse, Min, Max
Iterators - Abstracted objects that allow iteration across (stepping through) a container
Input/Output - iostream, fstream, string
15
We will discuss components of the STL related to quantitative finance in significant depth
throughout the course.
2.9
Boost is a set of third-party libraries that include many additional features for C++ development.
Many of the libraries have made it into the TR1 C++ standard and have appeared in C++11.
It includes support for many day to day activities such as File System interface abstraction,
regular expressions (regex), threading, smart pointers and networking. It is well-tested and is
used in a significant number of high-end production applications.
As quantitative analysts, we are particularly interested in the mathematics and statistics
libraries, which we will discuss in later lessons.
Visit the site: www.boost.org
2.10
C++ has a long history within quantitative finance. The majority of investment banks and
many hedge funds make use of C++ for their derivative pricing libraries and quantitative trading
applications, due to its speed and extensibility. It is an ISO standard so will be around for a
long time.
QuantLib, the C++ quantitative finance library, makes extensive use of the third-party Boost
libraries. It contains a wealth of well-tested derivatives pricing code. It can be somewhat daunting
for the beginner quant as it contains a substantial amount of code.
One question we need to answer is why learn C++ over Python, MatLab or R? The short
answer is that most of the quantitative analysis positions in bulge bracket banks will almost
certainly see it as a requirement. It will definitely set you ahead of the pack if you know it
inside-and-out, especially its use in quantitative finance. The longer answer is that it will teach
you to be an extremely competent programmer and other quantitative languages such as Python,
MatLab or R will be easy to pick up afterwards.
What C++ gains in flexibility and speed of execution, it loses in added complexity. It is a
very comprehensive tool, but also one that can be difficult to use - especially for the beginner.
16
2.11
I have already mentioned in the previous chapter that this book has been written to teach you the
intermediate/advanced features of the language in a quantitative finance setting, via extensive
implementation and examples. We will be making use of many of the aforementioned paradigms
when designing our derivatives pricing engines. In particular, we will be using:
Procedural/C-Style - Functionality modules, independent of encapsulation
STL - Matrix Classes, Matrix Solvers, Asset Price Paths
Object Orientation - MCM/FDM solvers, PayOff hierarchies, function objects
Generic Programming - Matrix/Vector operations on differing function types
Boost Libraries - Smart Pointers for memory allocation and object instantiation
Chapter 3
Our goal for this chapter is to create a basic VanillaOption class, which will encapsulate the
necessary data around a European vanilla call option derivative. We will make use of C++
object-oriented programming (OOP) techniques to implement this class. In particular, we will
use private member data to encode the option parameters and expose access to this data via
accessor methods.
In C++, each class is generally specified by two separate files that describe it in full. The
first is a declaration file, also known as the header file. Header files have a file extension denoted
by .h or .hpp. The second file is the implementation file, also known as the source file. Source
files have a file extension denoted by .cpp.
The header file specifies the declaration and the interface of the class. It does not include
any implementation, unless some of the functions or methods are declared inline. The source
file describes the implementation of the class. In particular, the mechanics of all methods and
any static functions are described here. The separation of these two files aids readability and
maintainability over a large codebase.
Note: A good question to ask at this point is Why are the header and source file separated?.
This is a legacy issue from C. When you #include a file in C++ the preprocessor step carries
out a direct text substitution of the header file. Hence, if all of the functions and methods were
declared inline, the preprocessor would be pulling all of this implementation into the compilation
17
18
step, which is unnecessary. In languages such as Java and C#, forward declaration occurs and
so there is no need, beyond style/readability reasons, to have separate files.
3.2
We will now discuss what classes really are. A C++ class is designed to encapsulate data and
methods (class functions) acting upon this data. A class hides details of its operations from
other program components that do not need access to, or need to be aware of, those underlying
details.
A class in C++ consists of member data, which can possess three levels of access:
Private member data can only be accessed by methods within the class
Public member data can be accessed by any other aspect or component of the whole
program
Protected member data can only be accessed by inherited classes of this class (more on
this later)
Class methods are separated into four categories: Constructors, destructor, selectors and
modifiers, which we will now proceed to describe.
3.3
A constructor is a special method on a C++ class. It is the first method to be called when a
C++ class object is instantiated (created!). There are three types of constructor in C++: the
default constructor, the parameter constructor and the copy constructor.
A default constructor allows a class object to be instantiated without the need to specify any
input data. Default constructors do not have to be implemented in a class. However, if you wish
to use a class in a sequence container, for instance, such as a vector of VanillaOption classes (e.g.
vector<VanillaOption>), then the class must provide a default constructor.
A parameter constructor requires one or more parameters in order to be called. Usually these
parameters are tied into the initialisation of member data. For instance, we may wish to store
a strike price as member data and thus the parameter constructor would contain a strike value
as a parameter. In some situations the member data can be initialised directly from the body of
the constructor. In other instances a member initialisation list is necessary. The latter situation
19
is necessary when the member data is not a simple type (and thus a separate class) and requires
its own parameters for construction.
The final type of constructor is the copy constructor. A copy constructor allows an object
to be cloned from another, by providing a reference to the first as the only parameter in the
copy constructor signature. It is only necessary to specify a copy constructor explcitly when you
wish to override the default provided by the compiler. This is the case when there are special
memory allocation needs, such as when allocating memory via the new operator.
A closely related method, although not technically a constructor, is the assignment operator. It allows one class instance to be set to another via the = operator. One only needs to
explicitly define an assignment operator in times of explicit memory allocation, as with the copy
constructor. Otherwise the compiler will generate one.
A destructor provides the implementation on how to deallocate any memory allocated upon
object creation. They are necessary methods because if memory is allocated by the class upon
instantiation, it needs to be freed up (i.e. returned to the operating system) when the object
goes out of scope. Since all objects are defined with regards to a particular block of code, i.e. a
scope, as soon as the code path leaves that scope, the object ceases to exist. Any reference to that
object subsequently or prior to entering this scope will cause a compilation error as the object
is essentially undefined at that point because either it doesnt exist yet or has been destroyed.
3.4
C++ provides very fine-grained control of how member data can be accessed by other components of a program. This means that an interface to data can be extremely well specified. In
quantitative finance, this means that we can make the end users life (known as the client)
much easier by only exposing to them aspects of the object that they will need to see and interact
with. For instance, if we create an Option model, we may expose methods to modify the option
parameters and calculate Greeks/sensitivities, but we do not need to allow the user to interact
with the underlying pricing engine, such as Monte Carlo or Finite Differences. This is the essence
of encapsulation.
Selector methods have the ability to read member data and provide calculations based upon
that data. Crucially, they are unable to modify any of the member data. An example of a
selector method would be a getter, which is a method used to return a value of some member
data. Another example would be a price calculation method that has no need to modify the
data, only the need to read it and calculate with it.
20
Modifier methods are able to read member data as well as change that data. They can
accomplish everything a selector can do, with additional privileges. An example of a selector
mehtod is a setter, which is a method used to set directly a value of some member data. We need
to be careful to distinguish between methods that cannot modify their parameters and methods
that cannot modify anything at all. This is where the const keyword comes in. It greatly aids
readability for our interface definitions.
The const keyword, as well as the mechanisms of pass by value and pass by reference (all
discussed later) determine whether a function/method is a selector or modifier.
3.5
We have now reached the point where we wish to implement some C++ code for a simple European option! Before we even open our favourite C++ coding environment, we must determine
our class specification. This is a very good habit to form as it sets expectations between other
clients of our code. It also allows you to consider flow-paths, edge cases and other issues that
could crop up before you set finger to keyboard.
Note: Client in this instance could be another quant on your team, a trader who uses your
code via an Excel plugin or a quant developer who needs to optimise this code.
We know that the price of a European vanilla option, at least in a Black-Scholes world, is
characterised by five parameters. In our VanillaOption class, we will implement these values as
private member data. This is because once they have been set, via construction of the class, we
do not wish to modify them further. Later on we will consider class interfaces where we may
wish to modify this data at code run-time. The five parameters of interest are:
Strike price, K
Interest Rate (risk free rate), r
Expiry time of the option, T
Underlying asset price, S
Volatility of the underlying,
In addition, European vanilla call and put options are specified by their pay-off functions, f :
Call: fC (S) = max(S K, 0)
Put: fP (S) = max(K S, 0)
21
We know that the price of such options have analytical solutions. We can implement these
as selector methods of our VanillaOption class, since calculating a price does not need to modify
any underlying member data.
3.6
Since this our first C++ file, we will explain everything in depth so theres no confusion!
At the top and bottom of this header file you will notice a set of preprocessor directives. The
preprocessor step is carried out prior to code compilation. Essentially, it modifies the textual basis
of the code that is sent to the compiler. It is a legacy feature from the days of C programming.
Preprocessor directives are always preceeded by a # symbol and only take up one line, without
the use of a semi-colon, unlike the C++ code itself.
#i f n d e f
VANILLA OPTION H
#define
VANILLA OPTION H
..
..
#endif
In this instance we are using a conditional directive that states if the VANILLA OPTION H
identifier has not previously been defined, then define it and add the code until the #ENDIF
directive. This is a mechanism that stops us importing the same code twice for the compiler,
which we obviously do not wish to do! It is helpful to use an indentifier that references the
specific file (notice I have included the H at the end of the identifier) as we wish to carry this
step out for both the header and source files.
The next section of the code declares the VanillaOption class. It is telling any other files about
the member data and methods that exist within the class, without detailing the implementation,
which occurs separately in the source file. It is customary to capitalise class names and to avoid
the use of an underscore separator, which are used instead for functions and methods. This helps
programmers rapidly distinguish them as classes as opposed to functions, methods or variables.
There are two sections within the class that declare the private and public members and
methods:
class VanillaOption {
private :
..
public :
22
..
};
The private section contains two methods: init and copy. These are helper methods. Helper
methods are not called directly by the client, but are used by constructors and assignment
operators to assign member data upon class initialisation and copying. Notice that the copy
method has const VanillaOption& rhs as its parameter. This is known as a pass-by-reference-toconst. We will explain this in depth later on, but for now you can think of it as saying to the
compiler do not copy the rhs parameter and do not modify it either, when the parameter is
passed.
Note: rhs is an abbreviation for right hand side, a common style idiom for copy constructors
and assignment operators.
Beneath the private methods are the double precision member data values for each of the
five previously discussed European vanilla option parameters. Not that both calls and puts have
exactly the same parameters, it is only their pay-off functions that differ.
private :
void i n i t ( ) ;
void copy ( const V a n i l l a O p t i o n& r h s ) ;
double K;
// S t r i k e p r i c e
double r ;
// Riskf r e e r a t e
double T ;
// M a t u r i t y time
double S ;
// U n d e r l y i n g a s s e t p r i c e
double sigma ;
// V o l a t i l i t y o f u n d e r l y i n g a s s e t
The public section contains two constructors - one default and one parameter-based - an
assignment operator and a destructor. The destructor is prepended via the virtual keyword.
We will go into a substantial amount of depth about virtual methods and virtual destructors in
particular later on in the book. For now you should note that a virtual destructor is necessary
for correct memory deallocation to occur when we use the process of class inheritance. It is a
best practice to almost always set your destructors to be virtual.
Notice that the copy constructor and assignment operator both use the mechanism of passby-reference-to-const, which we briefly discussed above. This is because the copy constructor
and assignment operator do not need to modify the object they are copying from, nor do they
wish to directly copy it before using it in the method, as copying can be an expensive operation
in terms of processor usage and memory allocation.
23
The assignment operator returns a reference to a VanillaOption&, not a VanillaOption directly.
Although this is not strictly necessary (you can return void), by returning a reference to a
VanillaOption, it allows what is known as chained assignment. Chained assignment allows you to
// D e f a u l t c o n s t r u c t o r has no p a r a m e t e r s
const double&
sigma ) ;
r,
S,
// Parameter c o n s t r u c t o r
V a n i l l a O p t i o n ( const V a n i l l a O p t i o n& r h s ) ;
// Copy c o n s t r u c t o r
// Assignment
operator
virtual VanillaOption ( ) ;
// D e s t r u c t o r i s
virtual
..
The next set of methods in the public section are selectors (getters) used to access the private
member data. The public block also contains two calculation (selector) methods for the call and
put prices, respectively. All of these selector methods return a non-void value (in fact they return
a double precision value).
All of the selector methods are post-marked with const. This means that these methods are
unable to modify member data or anything else! Note that the const keyword appears at the
end of the method prototype. If it was placed at the beginning it would mean that the method
was returning a const double value, which is entirely different! A const double value cannot be
modified once created, which is not the intended behaviour here.
public :
..
// S e l e c t o r ( g e t t e r ) methods f o r our o p t i o n p a r a m e t e r s
double getK ( ) const ;
double g e t r ( ) const ;
double getT ( ) const ;
double g e t S ( ) const ;
double g e t s i g m a ( ) const ;
24
// Option p r i c e c a l c u l a t i o n methods
double c a l c c a l l p r i c e ( ) const ;
double c a l c p u t p r i c e ( ) const ;
One lesson to learn here is that the const keyword has many different meanings and it is
extremely sensitive to placement. One could argue that because of these semantic issues it would
be easier not to use it anywhere in the code. This is extremely bad practice, because it states to
clients of the code that all functions/methods are capable of modification and that any object can
be copied. Do not fall into this seemingly beneficial trap!
3.7
As with the header file describe above, I have included two preprocessor directives to handle
potential code duplication. The logic is identical to that of the header file:
#i f n d e f
#define
..
..
#endif
The source file is used to specify the implementation of the previously declared class in
the header file. Hence it is necessary to import the header file so the compiler understands the
declarations. This is the job of the preprocessor, which is why the #include statement is prefixed
via a # symbol. We have also imported the cmath library containing the mathematical functions
we need such as sqrt (square root), exp (exponential function) and log (natural logarithm).
Notice that vanillaoption .h has been imported with quotes (), while cmath has been imported with delimiters (<>). The quotes tell the compiler to look in the current directory for
vanillaoption .h, while the delimiters let it know that it should search its own installed location
After the import preprocessor directives, the source file begins by implementing all of the
methods previously described in the header file. The first implemented method is init , which
possesses a return type of void. Initialisation of data does not need to return any value.
The init () method signature is prepended with VanillaOption:: . The double colon (::) is
known as the scope resolution operator. It states that init is only defined while within the scope
25
of VanillaOption and hence it is meaningless to call the init method as a naked function, i.e.
unless it is attached to a VanillaOption instance.
init is a helper method used by the default constructor. The initialisation of the member
data is separated this way, as init is used by other methods to initialise member data and hence
it stops repeated code. This pattern is known as Do-not Repeat Yourself (DRY).
In the following code we have implemented some reasonable defaults for our option parameters:
// I n i t i a l i s e s t h e member d a t a
void V a n i l l a O p t i o n : : i n i t ( ) {
K = 100.0;
r = 0.05;
// 5% i n t e r e s t r a t e
T = 1.0;
// One y e a r u n t i l m a t u r i t y
S = 100.0;
// Option i s a t t h e money as s p o t e q u a l s t h e s t r i k e .
sigma = 0 . 2 ;
// V o l a t i l i t y i s 20%
The next implemented method is copy, again with a return type of void, because it also does
not need to return any values. The copy method is another helper method used by the copy
constructor and assignment operator. It is once again designed to stop repeated code, via the
DRY pattern. Notice that the member data of the right-hand side (rhs) VanillaOption is obtained
via getter/selector methods, using the . operator.
The copy method uses the pass-by-reference-to-const pattern to once again reduce copying
and restrict modification, for performance reasons:
// C o p i e s t h e member d a t a
void V a n i l l a O p t i o n : : copy ( const V a n i l l a O p t i o n& r h s ) {
K = r h s . getK ( ) ;
r = rhs . getr () ;
T = r h s . getT ( ) ;
S = rhs . getS ( ) ;
sigma = r h s . g e t s i g m a ( ) ;
}
Following init and copy are the constructors. Constructors are special methods for classes,
straightforwardly identified by their lack of return type (even void). In our code there are two
constructors: Default and parameter-based.
26
The default constructor simply calls the init helper method and does nothing else. The
parameter constructor takes in all five parameters necessary to price a European option and then
initialises the underlying member data. Notice the underscores ( ) prepended to the parameters.
This is to avoid having to have alternative names for the option parameters, but still keeping them
unique from the member data. The parameters are once again passed-by-ref-to-const. This is not
strictly necessary here as they are simple types. Copying them would not generate unnecessary
overhead.
VanillaOption : : VanillaOption () { i n i t () ; }
r,
S,
const double&
sigma ) {
K = K;
r =
r;
T = T;
S =
S;
sigma =
sigma ;
Following the default and parameter constructors are the copy constructor and assignment
operator. Once again, as the copy constructor IS a constructor it does not possess a return type.
All it does it call the copy helper method. In fact both methods make use of the copy method,
ensuring DRY. We have described copy above.
Assignment operators (operator=) can be tricky to understand at first glance. The following
code states that if the = operator is used to assign an object to itself (i.e. code such as my option
= my option;) then it should not perform the copying (because this is a waste of resources),
instead it should just return the original object. However, if they are not equal then it should
perform a proper (deep) copy and then return the new copied object.
The mechanism through which this occurs is via a pointer known as this. this always points
to the underlying object, so it is only available within the scope of a class. In order to return a
reference to the underlying object (VanillaOption&), the pointer must be dereferenced with the
dereference (*) operator. Hence this, when coded within a class method, means get me a
reference to the underlying object which this method belongs to.
Pointers and references are very confusing topics, particularly for beginner C++ programmers. If you are somewhat unsure of pointers and references the following article at QuantStart.com
27
will provide a list of helpful books to help you get up to scratch:
http://www.quantstart.com/articles/Top-5-Essential-Beginner-C-Books-for-Financial-Engineers
// Copy c o n s t r u c t o r
V a n i l l a O p t i o n : : V a n i l l a O p t i o n ( const V a n i l l a O p t i o n& r h s ) {
copy ( r h s ) ;
}
// Assignment o p e r a t o r
V a n i l l a O p t i o n& V a n i l l a O p t i o n : : operator=(const V a n i l l a O p t i o n& r h s ) {
i f ( t h i s == &r h s ) return t h i s ;
copy ( r h s ) ;
return t h i s ;
}
In this class the destructor is extremely simple. It contains no implementation! This does
NOT mean that the compiled program ends up with no implementation, however. Instead the
compiler is smart enough to provide one for us. The VanillaOption class only contains private
member data based on simple types (double precision values). Hence the compiler will deallocate
the memory for this data upon object destruction - specifically, when the object goes out of scope.
There is no need to add in specific code to handle more sophisticated memory deallocation, as
might be the case when using the new operator. Notice also that the destructor has no return
type (not even void), as with constructors.
The destructor does not have to be declared virtual again in the source file, as there can only
ever be one destructor per class and hence there is no ambiguity with the header declaration file.
This is not the case with constructors, where adding const, for instance, actually implements a
new constructor, from one that does not possess the const keyword.
// D e s t r u c t o r
VanillaOption : : VanillaOption () {
// Empty , as t h e c o m p i l e r d o e s t h e work o f c l e a n i n g up t h e s i m p l e t y p e s
f o r us
}
Following the destructor are the getter methods used to publicly obtain the values for the
private member data. A legitimate question to ask at this stage is Why are we declaring the data
private and then exposing access via a getter method? In this instance of our class specification
it makes the underlying member data read-only. There is no means of modifying the member
28
data once set via the initial construction of the object, in our interface. In other class designs
we might want to use setters, which are modifier methods used to set member data. It will all
depend upon the client requirements and how the code will fit into the grander scheme of other
objects.
I will only display one method here (in this instance the retrieval of the strike price, K) as
the rest are extremely similar.
// P u b l i c a c c e s s f o r t h e s t r i k e p r i c e , K
double V a n i l l a O p t i o n : : getK ( ) const { return K; }
The final methods to implement are calc call price and calc put price . They are both public
and const. This forces them to be selector methods. This is what we want because a calculation
should not modify the underlying member data of the class instance. These methods produce
the analytical price of calls and puts based on the necessary five parameters.
The only undefined component of this method is the N(.) function. This is the cumulative
distribution function of the normal distribution. It has not been discussed here, but it is implemented in the original header file for completeness. I have done this because I dont think it
is instructive in aiding our object oriented example of a VanillaOption, rather it is a necessary
function for calculating the analytical price.
I wont dwell on the specifics of the following formulae. They can be obtained from any
introductory quantitative analysis textbook. I would hope that you recognise them! If you
are unfamiliar with these functions, then I suggest taking a look at Joshi[12], Wilmott[26] or
Hull[10] to brush up on your derivatives pricing theory. The main issue for us is to study the
implementation.
double V a n i l l a O p t i o n : : c a l c c a l l p r i c e ( ) const {
double s i g m a s q r t T = sigma s q r t (T) ;
double d 1 = ( l o g ( S/K) + ( r + sigma sigma 0 . 5 ) T ) / s i g m a s q r t T
;
double d 2 = d 1 s i g m a s q r t T ;
return S N( d 1 ) K exp( r T) N( d 2 ) ;
}
double V a n i l l a O p t i o n : : c a l c p u t p r i c e ( ) const {
double s i g m a s q r t T = sigma s q r t (T) ;
double d 1 = ( l o g ( S/K) + ( r + sigma sigma 0 . 5 ) T ) / s i g m a s q r t T
;
29
double d 2 = d 1 s i g m a s q r t T ;
return K exp( r T) N( d 2 ) S N( d 1 ) ;
}
3.8
Previously we have mentioned the concepts of passing by value and passing by reference. These
concepts refer to how a parameter is passed to a function or method. When passing by value,
the type or object being passed is copied. Any modification or access to that object, within
the function, occurs on the copy and not on the original passed object. If the type has a small
memory footprint, such as an int or double this copying process is rapid.
If the object being passed by value is a large matrix containing double values, for instance,
it can be extremely expensive to copy as all values within the matrix need to be duplicated and
stored. We usually want to avoid situations like this. Following is an example of a function
prototype passing a double precision type by value:
double m y f u n c t i o n ( double s t r i k e p r i c e ) ;
The following is an example of an object (in this case a Matrix) being passed by reference
into a matrix norm method. The norm returns a double precision value:
double norm ( Matrix& mat ) ;
When an object is passed by reference, the function can modify the underlying object. We
need to be careful that this is the intended behaviour, otherwise we can end up modifying an
object in place. The compiler would not stop us from doing so, even if we intended not to. This
can lead to bugs!
What if we want to restrict copying for performance reasons (and hence pass by reference),
but do not want the function to ever modify the passed object data? We use the const keyword
and pass-by-reference-to-const. The compiler will catch any attempts to modify our Matrix,
mat, within the function. This is why getting into the habit of using const liberally in your
class definitions is so important. It not only helps reduce bugs, it provides a contract with
clients that lets them know what functions/methods are able to do to their data. Following is
an example of pass-by-reference-to-const:
double norm ( const Matrix& mat ) ;
We can also append the const keyword to the function prototype. This stops the function
modifying any data whatsoever. In this case, it might be data which resides in a larger scope
30
than the function itself. A norm function should not really be modifying any data at all so we
postfix it with const:
double norm ( const Matrix& mat ) const ;
In summary, this states that norm accepts a non-modifiable, non-copied Matrix object and
does not have the ability to modify anything else while returning a double precision value exactly what we want!
This concludes our first quantitative finance C++ program. In the next chapter we will look
at how to deal with option pay-off functions in a maintainable, reusable and efficient way.
Chapter 4
The key benefit is that inheritance allow us to create interfaces that accept superclasses as
parameters, but can also subsequently handle subclasses. As an example, we could create a
matrix transpose function, which is able to accept dense, sparse, banded or block-banded matrix
objects as parameters, when it has been defined as accepting base matrix objects.
31
32
4.2
Let us consider a problem: What if we wish to modify our VanillaOption class from the previous
chapter to separate out the pricing of the option from the option class itself? Ideally we would
like to create a second object OptionSolver or similar, which can accept an Option class and
provide a price. This option solver would likely make use of Monte Carlo or Finite Difference
methods (which we will describe in full later). At the moment we only have analytical prices
for calls and puts, but we would like to allow for additional option types such as digital options,
double digital options or power options.
This is not as straightforward as it seems, for the following reasons:
Other types of options may not have straightforward analytical pricing functions, so we
need to use numerical methods.
We would need to create new, extra selector methods for calculation of these prices and
thus must modify our interface and inform our clients.
We must add extra parameters to the Option class constructor. For instance, a double
digital option requires two barrier values, while a power option requires the value of the
power as well as spot.
Every time we add a new pricing function we must re-edit and re-compile both the header
and source file for every modification, which could be an expensive operation in a large
codebase.
Is there a better way? Yes! It involves separating the pay-off for a type of option from the
Option class itself. Our plan will be to create a new class, called PayOff. We will then create
further subclasses, which inherit properties from the PayOff class. For instance, we could create
PayOffCall, PayOffPut or PayOffDoubleDigital. All of these inherited subclasses will contain the
properties and interface of PayOff, but will be able to extend and override it in their specific
cases. This is the essence of object-oriented inheritance. Further, our Option class can accept
a base class PayOff as a constructor parameter and we are still able to provide a subclass as a
parameter, since the expected interface will still be present. This means that Option will not
actually know what type of PayOff it will receive, since as far as it is concerned it is dealing with
the superclass interface.
33
4.3
The first step towards implementation of a pay-off hierarchy in C++ classes is determining the
interface to our base class, PayOff.
We want to restrict the base class itself from ever being instantiated as there is no default
behaviour for an option pay-off. This means that PayOff will become an abstract base class. An
abstract base class is a class that has at least one pure virtual method. A pure virtual method is
a method that does not possess an implementation in the base class itself, but is implemented in
an inherited subclass. The compiler restricts the client from instantiating an abstract base class
if it detects a pure virtual method within its definition, so it is really just defining an interface.
An inherited subclass of PayOff will provide a virtual public method, which takes an underlying asset spot price as a parameter and provides the pay-off price at expiry (for European
options). That is the most general interface we will support at this stage in the book. It is up
to each subclass to provide specific implementation details for their own pay-off functions.
Rather than generate a specific method called pay off (const double& spot price), or similar,
we will use another C++ feature known as operator overloading. We will provide our own
overriden implementation of the operator() method. This is a special operator that allows
us to call the class instance as if it were a function. Since the PayOff class and its subclasses
are really just wrappers for the pay-off function, it makes sense to specify the interface in this
manner. It is clearer for clients and describes the intent of the class hierarchy with more clarity.
These types of classes are known as function objects or functors. We will have more to say about
functors later on in the book.
4.4
The first line in the PayOff header file, beyond the preprocessor directives (which we wont
discuss), is the inclusion of the algorithm library. This is necessary as we need to make use of the
max function to determine the larger of two parameters in our pay-off functions:
#include <a l g o r i t h m >
The PayOff base class has no private members or methods, as it is never going to be instantiated. It is only designed to provide an interface, which will be carried through in its subclasses.
It does possess a single (default) constructor and virtual destructor.
The pure virtual overloaded method, operator(), defines PayOff as an (abstract) function
object. It takes the underlying asset spot price, S, and provides the value of the pay-off. As
34
stated above this method is not implemented within this class directly. Instead an inherited
subclass provides the implementation, specific to that subclass. To specify a virtual method as
pure virtual, we need to append = 0 to the end of the function prototype.
We have already mentioned that operator() allows the class to be called like a function.
This is an alternative to using C-style function pointers. We will discuss function pointers and
function objects in detail later in the book.
c l a s s PayOff {
public :
PayOff ( ) { } ;
// D e f a u l t ( no parameter ) c o n s t r u c t o r
v i r t u a l PayOff ( ) { } ;
// V i r t u a l d e s t r u c t o r
// O v e r l o a d e d o p e r a t o r ( ) , t u r n s t h e PayOff i n t o an a b s t r a c t f u n c t i o n
object
v i r t u a l double operator ( ) ( const double S ) const = 0 ;
// Pure
v i r t u a l method
};
4.5
PayOffCall Header
The first type of option we wish to create is a European vanilla call option. We will name our
class PayOffCall. It inherits from the PayOff base class.
PayOffCall is an inherited subclass of PayOff. It requires greater detail than provided in the
abstract base class. In particular we need to implement the pay-off function for a vanilla European
call option. We inherit PayOffCall from PayOff using the class PayOffCall : public PayOff syntax.
The PayOffCall subclass requires a single strike value K in its constructor. It also requires
the implementation of operator() to provide the pay-off function itself.
Note: We will not discuss PayOffPut. It is almost identical to the code below.
c l a s s P a y O f f C a l l : public PayOff {
private :
double K;
// S t r i k e p r i c e
public :
P a y O f f C a l l ( const double K ) { } ;
virtual PayOffCall ( ) {};
inheritance
// No d e f a u l t c o n s t r u c t o r
// D e s t r u c t o r v i r t u a l f o r f u r t h e r
35
// Payo f f i s max ( S
K, 0 )
};
4.6
PayOffDoubleDigital Header
We have created two base classes at this point: PayOffCall and PayOffPut. We are also going to
create PayOffDigitalCall and PayOffDigitalPut, which represent digital call and put options. We
wont describe their declaration here, but for completeness, we will add them to the header and
source files.
We have only described PayOffCall in any detail at this stage. Now we will consider a double
digital option. If you are unfamiliar with these options, then they are very similar to a digital/binary option except that they have two strike prices. If the spot price ends up between the two
strike values at expiry, then the value of 1 unit of currency is paid in the respective denominating
currency. If spot falls outside of these values at expiry, then the option pays nothing. Our double
digital subclass will be denoted PayOffDoubleDigital.
As PayOffDoubleDigital is also a subclass of PayOff, it requires greater detail than provided in
the abstract base class, PayOff. Specifically, a double digital option has two strike barriers, U and
D, representing the upper and lower barrier, with U > D. operator() also needs implementation,
by specification of the double digital pay-off function:
c l a s s P a y O f f D o u b l e D i g i t a l : public PayOff {
private :
double U;
// Upper s t r i k e p r i c e
double D;
// Lower s t r i k e p r i c e
public :
// Two s t r i k e p a r a m e t e r s
P a y O f f D o u b l e D i g i t a l ( const double U , const double D ) ;
virtual PayOffDoubleDigital ( ) ;
// Payo f f i s 1 i f s p o t w i t h i n s t r i k e b a r r i e r s , 0 o t h e r w i s e
v i r t u a l double operator ( ) ( const double S ) const ;
};
36
4.7
Please note that PayOff is an abstract base class and hence does not need an implementation in
the source file, as it is never actually instantiated. However, the remaining pay-off subclasses do
require implementation. We will begin with PayOffCall.
The constructor of PayOffCall is straightforward. It assigns the strike parameter to the strike
member data, which thus makes it a parameter-based constructor. The destructor does not
require the virtual keyword in the implementation source file, only in the header, so we omit it.
For PayOffCall, operator() simply returns the pay-off function for a European call option. It
is marked const, which states to the compiler that the method cannot modify any member data
(or any other data for that matter!). The parameter S is the underlying asset spot price, which
we have modelled as a double precision value:
// C o n s t r u c t o r w i t h s i n g l e s t r i k e parameter
P a y O f f C a l l : : P a y O f f C a l l ( const double
K) { K = K ; }
// D e s t r u c t o r ( no need t o u s e v i r t u a l keyword i n s o u r c e f i l e )
P a y O f f C a l l : : P a y O f f C a l l ( ) {}
// S ta n da r d European c a l l payo f f
4.8
As with the header file, we wont delve too deeply into the put option versions of these pay-off
classes as they are almost identical to the call versions. They are specified in the source file itself,
so if you wish to study them, you can look at the file directly.
PayOffDoubleDigital has a slightly modified interface to the PayOffCall, as it requires two strike
barriers, rather than a single strike parameter. Hence we modify the constructor to take an upper
value, U and a lower value, D. The (parameter) constructor simply assigns these parameters to
the respective member data. Later on in the book we will use a more sophisticated mechanism,
known as the member initialisation list, to carry out this initialisation procedure.
operator() still takes a spot price S. This is because the calling interface for all pay-off classes
37
in our hierarchy is identical. It is only the member data and constructors which change. For a
double digital option the pay-off function returns 1.0 if the spot lies between the barriers and
zero otherwise, at maturity:
// C o n s t r u c t o r w i t h two s t r i k e parameters , upper and l o w e r b a r r i e r
P a y O f f D o u b l e D i g i t a l : : P a y O f f D o u b l e D i g i t a l ( const double
U , const double
D)
{
U = U;
D = D;
}
..
This concludes our overview of the PayOff files. We will now discuss what a virtual destructor
is and why you would need to prefix a destructor with the virtual keyword.
4.9
Virtual Destructors
All of our PayOff class and subclass destructors have so far been set to virtual, using the prefixed
virtual keyword. Why are we doing this and what happens if we do not use this keyword?
In simple terms, a virtual destructor ensures that when derived subclasses go out of scope or
are deleted the order of destruction of each class in a hierarchy is carried out correctly. If the
destruction order of the class objects is incorrect, in can lead to what is known as a memory leak.
This is when memory is allocated by the C++ program but is never deallocated upon program
termination. This is undesirable behaviour as the operating system has no mechanism to regain
the lost memory (because it does not have any references to its location!). Since memory is a
finite resource, if this leak persists over continued program usage, eventually there will be no
38
available RAM (random access memory) to carry out other programs.
For instance, consider a pointer to a base class (such as PayOff) being assigned to a derived
class object address via a reference. If the object that the pointer is pointing to is deleted, and
the destructor is not set to virtual, then the base class destructor will be called instead of the
derived class destructor. This can lead to a memory leak. Consider the following code:
c l a s s Base {
public :
Base ( ) ;
Base ( ) ;
};
c l a s s D e r i v e d : public Base {
private :
double v a l ;
public :
D e r i v e d ( const double
val ) ;
Derived ( ) ;
}
void d o s o m e t h i n g ( ) {
Base p = new D e r i v e d ;
delete p ;
// D e r i v e d d e s t r u c t o r n o t c a l l e d ! !
What is happening here? Firstly, we create a base class called Base and a subclass called
Derived. The destructors are NOT set to virtual. In our do something() function, a pointer p to
a Base class is created and a reference to a new Derived class is assigned to it. This is legal as
Derived is a Base.
However, when we delete p the compiler only knows to call Bases destructor as the pointer
is pointing to a Base class. The destructor associated with Derived is not called and val is not
deallocated. A memory leak occurs!
Now consider the amended code below. The virtual keyword has been added to the destructors:
c l a s s Base {
public :
Base ( ) ;
39
v i r t u a l Base ( ) ;
};
c l a s s D e r i v e d : public Base {
private :
double v a l ;
public :
D e r i v e d ( const double
val ) ;
virtual Derived ( ) ;
}
void d o s o m e t h i n g ( ) {
Base p = new D e r i v e d ;
delete p ;
// D e r i v e d d e s t r u c t o r i s c a l l e d
What happens now? Once do something() is called, delete is invoked on the pointer p. At code
execution-time, the correct destructor is looked up in an object known as a vtable. Hence the
destructor associated with Derived will be called prior to a further call to the destructor associated
with Base. This is the behaviour we originally desired. val will be correctly deallocated. No
memory leak this time!
40
Chapter 5
In 1990 templates were added to C++. This enabled a new paradigm of programming known as
generic programming.
In order to explain how generic programming differs from object-oriented programming
(OOP) we need to review how classes work. Recall that classes allow encapsulation of data.
They provide a declaration of an interface, which determines how an external client accesses that
data. Instance objects are created from these classes. You can think of classes as a mould or
pattern for creating objects. Normal classes are bound to the data types specified for their
member data. This means that if we want to store values or objects within that class, we must
specify the type of data upfront.
5.2
Template Classes
Template classes behave in a different way. They allow us to define classes without the need to
specify the types of data that the classes will utilise upfront. This is a very powerful feature as
it allows us to create generic classes that arent restricted to a particular set of data types.
This is quite a complicated abstract concept to grasp at first. So lets consider an example,
which is a common pattern within the C++ standard template library (STL).
41
42
5.3
Consider a mathematical matrix object that uses double precision types to store its values. This
class could be implemented as a single array of double values or even an array of arrays of double
values. Irrespective of how we implement this class our matrix implementation is likely to make
use of pointers to double values.
Lets imagine that our application now requires matrices to store complex numbers. This is
a very common situation in computational physics, particularly in quantum mechanics. I realise
Im deviating from quantitative finance here, but bear with me! This brings up many questions:
How we will we implement such a class? What effect will it have on our code duplication
and external client code?
Will we replace our double precision matrix code to make use of the C++ complex library?
This would require modifying all of the implementation code to store complex types, instead
of doubles. Further, any client code that makes use of this matrix class will need to be
modified to handle complex numbers.
How would we store a real-numbered matrix? Would we set all imaginary parts of each
complex component to zero? This would seem like an awful waste of memory!
To create a new matrix object that stores complex numbers, the entire matrix code would
need duplication and replacement with the complex type. This is poor practice. Perhaps there
is a better way?
Templates allow code to be written once and then parametrised with a concrete type such as
int, double or complex. This means that we can write the code for our matrix object once and
then supply various concrete types at the point we wish to use such a matrix.
Templates can thought of as moulds or patterns for creating classes, in exactly the same way
that classes can be thought of as moulds or patterns for creating instance objects.
5.4
Lets start gently by studying the syntax of declaring a template, with the aforementioned matrix
class example.
To create a template class we prefix the usual class declaration syntax with the template
keyword, using <> delimiters to pass data types as arguments via the typename keyword:
43
Note: You can use class in place of typename in the following declaration, as the syntax is
synonymous. I am using typename because I find it less confusing!
template <typename T> c l a s s Matrix {
// A l l p r i v a t e , p r o t e c t e d and p u b l i c members and methods
};
Here we have created a template class representing a matrix and are passing it a single type,
T. In other areas of this code we could instantiate a template class by passing concrete types
(such as double and int) into the constructor syntax, as with the following examples:
A matrix parametrised with a double precision type:
Matrix<double> d o u b l e m a t r i x ( . . . ) ;
Templates can also be nested. That is, we can use one template class (which requires its
own type) within the typename of another. For example, consider the aforementioned matrix of
complex numbers. If we wanted to create such a class with our template matrix, we would need
to not only let the matrix know that it should use the complex type, but we also need to provide
the complex type with an underlying storage mechanism (in this case double precision):
#include <complex>
Note that you must place an additional space ( ) between the two right hand delimiters
(> >) otherwise the compiler will think you are trying to invoke the bit shift >> operator, which
is not the desired behaviour.
5.5
In quantitative finance, nearly all values required of any data structure are real numbers. These
are often stored as float (single precision) or double (double precision) types. Writing out
extensive type parameter lists when declaring classes can make syntax unwieldy. Thankfully,
C++ allows default template values, which provide an antidote to this problem. The default
type is simply appended to the type name keyword with the equals sign:
44
Note: Since templates support multiple type parameters, if one parameter has been specified
as a default all subsequent parameters listed must also have defaults.
5.6
SimpleMatrix Declaration
We will now flesh out the Matrix class described above as a first example for generic programming
with C++. At this stage we will simply use it to store values. It will not have any additional
functionality, such as matrix multiplication, at the moment. We will add this functionality in a
later chapter.
So what does our Matrix need to do? In its simplest form we must be able to define the type
of data being stored, the number of rows and columns as well as provide access to the values in
a simplified manner.
Here is the listing for SimpleMatrix.hpp:
#IFNDEF
SIMPLE MATRIX H
#DEFINE
SIMPLE MATRIX H
// Need t h i s t o s t o r e m a t r i x v a l u e s
// Use a v e c t o r o f v e c t o r s t o s t o r e t h e
values
public :
SimpleMatrix ( ) ;
// D e f a u l t c o n s t r u c t o r
// Copy c o n s t r u c t o r
SimpleMatrix ( const SimpleMatrix<Type>&
rhs ) ;
45
// Assignment o p e r a t o r o v e r l o a d e d
SimpleMatrix<Type>& operator= ( const SimpleMatrix<Type>&
v i r t u a l SimpleMatrix ( ) ;
rhs ) ;
// D e s t r u c t o r
#ENDIF
We wont discuss the pre-processor macros, since weve discussed them at length in previous
chapters.
This is our first encounter with the vector component of the standard template library. The
vector class is itself a template class. We will be using a vector as the underlying storage mechanism for our types. Thus we need to include the vector library:
#include <v e c t o r >
Recall the syntax for creating a template class with a default type:
template <typename Type = double> c l a s s SimpleMatrix { . . .
};
The following line requires some explanation. This code is telling the compiler that we wish
to create a private data member element called mat, which is a vector. This vector requires an
underlying type (since a vector is a template class). The underlying type for mat is a vector<
Type>, i.e. a vector of our as-yet-unspecified Types. This means that mat is really a set of rows,
storing columns of Types. Thus is it is a vector of vectors and allows us to store our matrix
values:
private :
v e c t o r <v e c t o r <Type> > mat ;
The default constructor is unremarkable, but the parameter constructor requires a bit of
discussion. We have three parameters, the first two of which are integer values and the latter is
a Type. The two integer values tell the compiler how many rows and columns the matrix will
have, while the third Type parameter tells the compiler the quantity to fill the matrix values
with:
public :
46
SimpleMatrix ( ) ;
// D e f a u l t c o n s t r u c t o r
Notice that in the copy constructor we also have to specify the type in the <> delimiters,
otherwise the compiler will not know the concrete object to accept as a parameter when copying
the SimpleMatrix. The overloaded assignment operator returns a reference to a SimpleMatrix<
Type>, as once again, the compiler must have a concrete type when implementing this code. In
rhs ) ;
rhs ) ;
..
We wont discuss the destructor, other than to mention that it is set to be virtual, which is
a good practice that we have outlined in previous chapters.
The final set of public methods are get mat() and value (...) . They allow direct access to both
the matrix itself and the values at a particular location, specified by the row and column index,
starting from 0 rather than 1.
get mat() returns a vector<vector<Type> >, not a reference to this object! Hence mat will be
copied whenever this method is called - a rather inefficient process if our matrix is large. We
have implemented it this way only to highlight the syntax of creating a template class, not to
show ideal matrix optimisation (which will come later on in the course).
value (...) returns a reference to Type as this allows direct access to the values within the
At this stage our SimpleMatrix is not very exciting. We can create it, store values within it
and then access/change those values via row/column indices. However, what it does have going
for it is that it can handle multiple types! We could use int, double or complex types within our
code, thus saving ourselves a lot of duplicated coding.
47
5.7
SimpleMatrix Implementation
Now that weve discussed the declaration of the template matrix class, we need to code up the
implementation. Here is the listing for simplematrix.cpp in full:
#IFNDEF
#DEFINE
#include s i m p l e m a t r i x . h
// D e f a u l t c o n s t r u c t o r
template <typename Type>
SimpleMatrix<Type > : : SimpleMatrix ( ) {
// No need f o r i m p l e m e n t a t i o n , as t h e v e c t o r mat
// w i l l c r e a t e t h e n e c e s s a r y s t o r a g e
}
// C o n s t r u c t o r w i t h row/ c o l s p e c i f i c a t i o n and d e f a u l t v a l u e s
template <typename Type>
SimpleMatrix<Type > : : SimpleMatrix ( const i n t& rows , const i n t& c o l s ,
const Type& v a l ) {
f o r ( i n t i =0; i <rows ; i ++) {
s t d : : v e c t o r <Type> c o l v e c ( c o l s , v a l ) ;
mat . push back ( c o l v e c ) ;
}
}
// Copy c o n s t r u c t o r
template <typename Type>
SimpleMatrix<Type > : : SimpleMatrix ( const SimpleMatrix<Type>&
mat =
rhs ) {
r h s . g et m at ( ) ;
// O v e r l o a d e d a s s i g n m e n t o p e r a t o r
template <typename Type>
SimpleMatrix<Type>& SimpleMatrix<Type > : : operator= ( const SimpleMatrix<Type
>&
{
rhs )
48
i f ( t h i s == & r h s ) return t h i s ;
mat =
// H a n d l i n g a s s i g n m e n t t o s e l f
r h s . g et m at ( ) ;
return t h i s ;
}
// D e s t r u c t o r
template <typename Type>
SimpleMatrix<Type > : : SimpleMatrix ( ) {
// No need f o r i m p l e m e n t a t i o n , as t h e r e i s no
// manual dynamic memory a l l o c a t i o n
}
// Matrix a c c e s s method , v i a c o p y i n g
template <typename Type>
SimpleMatrix<Type> SimpleMatrix<Type > : : ge t m at ( ) const {
return mat ;
}
#ENDIF
As with previous source files we are using pre-processor macros and have included the respective header file. We will begin by discussing the default constructor syntax:
// D e f a u l t c o n s t r u c t o r
template <typename Type>
SimpleMatrix<Type > : : SimpleMatrix ( ) {
// No need f o r i m p l e m e n t a t i o n , as t h e v e c t o r mat
// w i l l c r e a t e t h e n e c e s s a r y s t o r a g e
}
Beyond the comment, the first line states to the compiler that we are defining a function
template with a single typename, Type. The following line uses the scope resolution operator
49
( :: ) to specify the implementation of the default constructor. The key point to note here is that
although the class itself is referred to as SimpleMatrix<Type>, the constructor does not include
the type and is simply written as SimpleMatrix(). Because the scope of the class already includes
the type it is not necessary to repeat it within the constructor signature itself.
The next method to be defined is the parameter constructor, necessary to specify the number
of rows, columns and an initial value for all matrix cells:
// C o n s t r u c t o r w i t h row/ c o l s p e c i f i c a t i o n and d e f a u l t v a l u e s
template <typename Type>
SimpleMatrix<Type > : : SimpleMatrix ( const i n t& rows , const i n t& c o l s ,
const Type& v a l ) {
f o r ( i n t i =0; i <rows ; i ++) {
s t d : : v e c t o r <Type> c o l v e c ( c o l s , v a l ) ;
mat . push back ( c o l v e c ) ;
}
}
As with the default constructor, we need to specify the class scope by including the type, but
not in the constructor name itself. The constructor takes three parameters, which are respectively
the number of rows, the number of columns and an initial value to fill all matrix cells with.
The implementation loops over the number of rows and adds a new vector of length cols ,
with value val , to each element of mat. Thus mat ends up as a vector of vectors.
Subsequent to the parameter constructor is the implementation of the copy constructor:
// Copy c o n s t r u c t o r
template <typename Type>
SimpleMatrix<Type > : : SimpleMatrix ( const SimpleMatrix<Type>&
mat =
rhs ) {
r h s . g et m at ( ) ;
This is a straightforward implementation. It simple states that the new mat instance should
be copied from the rhs (right hand side) reference SimpleMatrix object, via the get mat()
function. Recall that get mat() is an expensive method to call, so in later lessons we will
implement a more efficient matrix class.
After the copy constructor we implement the overloaded assignment operator:
// O v e r l o a d e d a s s i g n m e n t o p e r a t o r
template <typename Type>
50
// H a n d l i n g a s s i g n m e n t t o s e l f
r h s . g et m at ( ) ;
return t h i s ;
}
The assignment operator implementation is not too dissimilar to that of the copy constructor.
It basically states that if we try and assign the object to itself (i.e. my matrix = my matrix;) then
it should return a dereferenced pointer to this, the reference object to which the method is being
called on. If a non-self assignment occurs, then copy the matrix as in the copy constructor and
then return a dereferenced pointer to this.
The destructor does not have an implementation as there is no dynamic memory to deallocate:
// D e s t r u c t o r
template <typename Type>
SimpleMatrix<Type > : : SimpleMatrix ( ) {
// No need f o r i m p l e m e n t a t i o n , as t h e r e i s no
// manual dynamic memory a l l o c a t i o n
}
The last two public methods involve access to the underlying matrix data. get mat() returns
a copy of the matrix mat and, as stated above, is an inefficient method, but is still useful for
describing template syntax. Notice also that it is a const method, since it does not modify
anything:
// Matrix a c c e s s method , v i a c o p y i n g
template <typename Type>
SimpleMatrix<Type> SimpleMatrix<Type > : : ge t m at ( ) const {
return mat ;
}
The final method, value (...) returns a direct reference to the underlying data type at a
particular row/column index. Hence it is not marked as const, because it is feasible (and
intended) that the data can be modified via this method. The method makes use of the []
operator, provided by the vector class in order to ease access to the underlying data:
// Matrix a c c e s s method , v i a row and column i n d e x
template <typename Type>
51
This completes the listing for the SimpleMatrix object. We will extend our matrix class in
later lessons to be far more efficient and useful.
5.8
One common question that arises when discussing templates is Why use templates over normal
object orientation?. Answers include:
Generally more errors caught at compile time and less at run-time. Thus, there isnt as
much need for try-catch exception blocks in your code.
When using container classes with iteration. Modelling a Time Series or Binomial Lattice,
for instance. Generic programming offers a lot of flexibility here.
The STL itself is dependent upon generic programming and so it is necessary to be familiar
with it.
However, templates themselves can lead to extremely cryptic compiler error messages, which
can take a substantial amount of time to debug. More often than not, these errors are due to
simple syntax errors. This is probably why generic programming has not been taken up as much
as the object oriented paradigm.
52
Chapter 6
6.1
54
Containers - Data structures designed to efficiently store information across a variety of
generic types.
Iterators - Methods by which to traverse/access the data within the containers.
Algorithms - Basic algorithmic capabilities, such as sort, find, max, min, replace. Makes
use of iterators to allow container independent code.
Function Objects - a.k.a. functors. Used to make templates classes callable to allow
modelling of mathematical functions.
There are extra smaller components, but these are the four that will interest us for quantitative finance work.
6.2
Containers
Containers allow certain types of data to be grouped into logical structures. The STL provides
generic containers so that the same data structure can handle multiple different types without
any additional required coding. For instance, we could create a std :: vector<double> or a std ::
vector<int>. The former would be a vector of double precision values, while the latter is a vector
of integer precision values. Notice that we are not required to code anything else to let the vector
know we are using different types!
6.2.1
Sequence Containers
Sequence containers are extremely common within quantitative finance applications. They are
often used to store numerical data sequentially. For instance we may want to store components
of an element of a vector field or matrix of complex numbers. There are three main types of
sequential containers that tend to be used the most:
Lists - The two types of list are the singly-linked list and the doubly-linked list. A singlylinked list only has forward pointers to the next element, whereas a doubly-linked list has
forward and backward pointers. They do not allow random access to elements, but do
allow fast inserts and removals.
Vectors - Vectors allow fast random access to elements, but are slow for inserts and
removals within the vector (as all elements need to be copied and moved). They allow fast
appends and pops (i.e. removals from the end).
55
Deques - i.e. double-ended queues. Deques are similar to vectors except that they allow
fast appends/removals to both ends of the queue.
There is also the valarray sequence container, which is (supposedly) optimised for numerical
work. However, in practice valarray is harder to use and many modern compilers do not implement
the optimisations well enough to warrant using them as an alternative to vectors.
6.2.2
Associative Containers
Associative containers are different to sequential containers in that there is no direct linear
sequence to the elements within the container. Instead, they are sorted based on a comparison
criterion. This criterion is often determined via the context. For instance, in a std :: set<int>,
the comparison operator is the less than (<) operator. There are four main types of associative
container, which differ in terms of duplicate elements and whether the key data and value data
coincide:
Set - A set provides a container that does not allow duplicate elements and where the data
is itself the key and value. Sets can be sorted according to comparison functions. Here is
an example of a set of integers: {1, 2, 3, 4}.
Multiset - A multiset is very similar to a set except that it can possess multiple duplicate
values. Here is an example of a multiset of integers: {1, 2, 3, 3, 4}. Notice the duplicate 3s.
Map - A map contains pairs of elements, the two components of which are known as the key
and the value. The key is used to look up the value data. Maps do not allow duplicate
keys, but they do provide a great deal of flexibility in what types of data can represent the
keys and values. An example of a map would be a set of date keys, each of which possessed
a price value, without duplicate dates.
Multimap - A multimap is similar to a map except that duplicate keys are allowed.
This is possible because the values can be iterated over in a sorted fashion, using the
aforementioned comparison function.
Although it might initially seem that associative containers are less appropriate for handling
numerical data within quantitative finance, this is not actually the case. A map is ideal for
holding the values of sparse matrices (such as tridiagonal or banded), which crop up in finite
difference methods, for instance.
56
6.2.3
Container Adaptors
Although not strictly containers in themselves container adaptors are a very useful component
of the STL. Container adaptors are designed to adapt the behaviour of the aforementioned
containers above such that their behaviour is restricted or modified in some fashion. The three
container adaptors that are of interest to us are as follows:
Stacks - A stack is a common data structure within computer science as well in day-to-day
life. It represents the Last-In-First-Out (LIFO) concept, where elements are placed upon
each other and the first one to be removed was the last one to be added.
Queues -A queue differs from a stack in that it represents the First-In-First-Out (FIFO)
concept, where the first element added to the queue becomes the first to be removed.
Queues are often used to represent task lists of background jobs that might need to be
carried out, such as heavy-duty numerical processing.
Priority Queues -A priority queue is similar to a queue, except that elements are removed
from it based on a priority comparison function. This is particularly common in operating
system design where many tasks are constantly being added to a queue, but some are more
essential than others and so will be executed first, even if they were added at a later point.
6.3
Iterators
Now that weve discussed STL Containers, it is time to turn our attention to iterators. Iterators
are objects that can navigate or iterate over elements in a container. They are essentially a
generalisation of pointers and provide similar, but more advanced, behaviour. Their main benefit
is that they allow the decoupling of the implementation of a container with an algorithm that
can iterate over it.
Iterators are often tricky for beginning C++ programmers to get to grips with as there are
many different categories and adaptors that can be applied to modify their behaviour. Were are
going to describe the taxonomy that exists to help you choose the correct iterator type for your
quantitative finance algorithm implementations.
6.3.1
Iterator Categories
Iterators are grouped according to their category. There are five separate iterators categories in
C++: Input, Output, Forward, Bidirectional and Random Access:
57
Input - An input iterator is a single-pass read-only iterator. In order to traverse elements,
the ++ increment operator is used. However, elements can only be read once. Input
iterators do not support the decrement operator, which is why they are termed singlepass. Crucially, an input iterator is unable to modify elements. To access an element, the
pointer dereference operator is utilised.
Output - An output iterator is very similar to an input iterator with the major exception
that the iterator writes values instead of reading them. As with input iterators, writes can
only occur once and there is no means of stepping backwards. Also, it is only possible to
assign a value once to any individual element.
Forward - A forward iterator is a combination of an input and output iterator. As before
the increment operator (++) allows forward stepping, but there is no decrement operator
support. To read or write to an element the pointer derefernce operator () must be used.
However, unlike the previous two iterator categories, an element can be read or written to
multiple times.
Bidirectional - A bidirectional iterator is similar to a forward iterator except that it
supports backward stepping via the decrement operator ().
Random Access - A random access iterator is the most versatile iterator category and is
very much like a traditional C-style pointer. It possesses all the abilities of a bidirectional
iterator, with the addition of being able to access any index via the [] subscript operator.
Random access iterators also support pointer arithmetic so integer stepping is allowed.
Iterators are divided into these categories mainly for performance reasons. Certain iterators
are not supported with certain containers when C++ deems it likely that iteration will lead to
poor performance. For instance, random access iteration is not supported on the std :: list as
access to a random element would potentially require traversal of the entire linked-list used to
store the data. This is in contrast to a std :: vector where pointer arithmetic can be used to step
directly to the desired item.
6.3.2
Iterator Adaptors
58
Reverse Iterators
Reverse iterator adaptors essentially swap the increment (++) and decrement () operators
so that any algorithms making use of these iterators will be carried out in reverse. Every STL
container allows reverse iteration. It is possible to convert standard iterators to reverse iterators,
although any converted iterator must support both increment and decrement (i.e. it must be
bidirectional).
Insertion Iterators
Insertion iterators adapt normal output iterators to perform insertion instead of overwriting.
They only allow writing, as with normal output iterators. There are three separate insertion
iterators in the STL: back, front and general. They all behave differently with regards to the
position of the inserted element:
Back inserters utilise the push back method of the container it is acting upon and so will
append values at the end. Hence they only work on a subset of containers that support
the method (strings, lists, deques and vectors).
Front inserters utilise the push front method of the container and is only available for
two containers in the STL - the deque and the list.
General inserters utilise the insert method of the container being acted upon, which is
supported by all STL containers.
Stream Iterators
Stream iterators are adapted iterators that permit reading and writing to and from input and
output streams. istream iterators are used to read elements from an input stream, making use
of the >> operator. ostream iterators are the writing counterpart and can write to an output
stream via the << operator.
6.3.3
Const Iterators
Containers allow both iterator and const iterator types. const iterators are returned by certain
container methods (such as begin or end) when the container itself is const.
One must be careful to distinguish between const iterator and const iterator (notice the lack
of underscore!). The former is a non-const object with iterator type, so that it returns objects
59
which cannot be modified. The latter is a constant iterator (i.e. it cannot be incremented!). It
is possible to convert an iterator to a const iterator , but going the other way is not possible.
const iterators are preferred (if possible) because they aid readability. They let programmers
know that the iterator is only iterating over the container and not modifying it.
6.3.4
Iterator Traits
A common requirement is to be able to use pointers in place of iterators when carrying out an
algorithm. Since they have nearly identical behaviour, it makes sense to allow this. The way this
is achieved is through iterator traits. The iterators traits class template provided by the STL is
designed to allow a common interface for any type of iterator.
When creating algorithms that iterate over a container it is possible to make use of the
iterator traits to obtain the underlying type being referred to by the presented iterator (or
pointer), without the need to know whether an iterator or pointer is actually being presented.
This makes the code more maintainable and versatile.
6.4
Algorithms
Were now ready to discuss STL algorithms, which are operations that act on the containers via
the aforementioned STL Iterators. STL algorithms are extremely useful because they reduce or
eliminate the need to reinvent the wheel when implementing common algorithmic functionality.
In quantitative finance coding they can save a significant amount of time.
While some of the algorithms are designed to modify the individual values within ranges
(certain STL containers or arrays of values), they do not modify the containers themselves - i.e.
there is no reallocation or size modification to the containers.
6.4.1
Algorithm Categories
To use these algorithms it is necessary to include the <algorithm> header file. For the numeric
algorithms, it is necessary to include the <numeric> header file. As with containers and iterators,
algorithms are categorised according to their behaviour and application:
Nonmodifying algorithms - Nonmodifying algorithms do not change the value of any
element, nor do they modify the order in which the elements appear. They can be called
for all of the standard STL container objects, as they make use of the simpler forward
iterators.
60
Modifying algorithms - Modifying algorithms are designed to alter the value of elements
within a container. This can either be done on the container directly or via a copy into
another container range. Some algorithms classed as modifying algorithms can change
the order of elements (in particular copy backward()), but most do not.
Removal algorithms - Removal algorithms are, by definition, modifying algorithms, but
they are designed to remove elements in a container or when copying into another container.
Mutating algorithms - Once again, mutating algorithms are modifying algorithms, but
they are designed specifically to modify the order of elements (e.g. a random shuffle or
rotation).
Sorting algorithms - Sorting algorithms are modifying algorithms specifically designed
for efficient sorting of elements in a container (or into a range container).
Sorted range algorithms - Sorted range algorithms are special sorting algorithms designed to function on a container which is already sorted according to a particular sorting
criterion. This allows for greater efficiency.
Numeric algorithms - Numeric algorithms are designed to work on numerical data in
some fashion. The principal algorithm in this category is accumulate(), which allows mathematical operators to be applied to all elements in a container.
Well now take a look at these categories in depth. If you would like to find out more about
each subsequent algorithm, you can click on the name of any algorithm below.
6.4.2
Nonmodifying Algorithms
for each () - This is possibly the most important algorithm in this section, as it allows
any unary function (i.e. a function of one argument) to be applied to each element in a
range/container. Note that this function can actually also be modifying (hence why it is
included below). It is often better to use a more specific algorithm, if one exists, than to
use this, as specialist implementations will be more efficient.
count() - This returns the number of elements in a range or container.
count if () - This counts how many elements in a range or container much a particular
criterion.
61
min element() - Returns the element that has the smallest value, making use of the relation
to perform comparison. It can accept a custom binary function to perform the comparison
instead.
max element() - Returns the element that has the largest value, making use of the relation
to perform comparison. It can accept a custom binary function to perform the comparison
instead.
find () - Finds the first element in a range or container that equals a passed value.
find if () - Finds the first element in a range or container that matches a particular criterion,
rather than a passed value.
search n() - This is like find AND find if except that it looks for the first N occurances of
such a value OR the first N occurances where a relational predicate is met.
search() - This searches for the first occurance of a subrange within a range/container and
can do so either by first/last value of the subrange or via a predicate matching all the
values of the desired first/last subrange.
find end() - Similar to search, except that it finds the last occurance of such a subrange.
find first of () - Finds the first element in a subrange of a range or container. Can make
use of a binary predicate function, otherwise uses direct value.
adjacent find () - Returns the location (an iterator) to the first matching consecutive pair
of values in a range/container. Can also match via a binary predicate.
equal() - Compares two ranges to see if they are equal.
mismatch() - Compares two ranges and returns a pair of iterators containing the points at
which the ranges differ.
lexicographical compare() - The lexicographical comparison is used to sort elements in a
manner similar to how words are ordered in a dictionary. It can either use operator< or
make use of a binary predicate function to perform the comparison.
6.4.3
Modifying Algorithms
for each () - This is the same as the for each we discussed above, but I have included it in
the Modifying section to reinforce that it can be used this way too!
62
copy() - Copies a range/container of elements into another range.
copy backward() - Copy a range/container of elements into another range, starting from the
last element and working backwards.
transform() - Transform is quite a flexible algorithm. It works in two ways. A unary
operation can be applied to the source range, on a per element basis, which ouputs the
results in the destination range. A binary operation can be applied to both elements in the
source and destination range, subsequently overwriting elements in the destination range.
merge() - Merge is intended to take two sorted ranges and combine them to produce a
merged sorted range. However, it is possible to utilise unsorted ranges as arguments but
this then leads to an unsorted merge! For this reason Ive decided to include it in the
modifying category, rather than the category for sorted ranges, below.
swap ranges() - This swaps the elements of two ranges.
fill () - This replaces each element in a range with a specific value.
fill n () - Similar to fill , but replaces the first N elements in a range with a specific value.
generate() - This replaces each element in a range with the result of an operation of a
generator function.
generate n() - Similar to generate, but replaces the first N elements in a range with the
result of an operation of a generator function.
replace () - This replaces elements matching a specific value with another specific value.
replace if () - This replaces elements matching a specific criterion with another specific
value.
replace copy() - Similar to replace , except that the result is copied into another range.
replace copy if () - Similar to replace if , except that the result is copied into another range.
6.4.4
Removal Algorithms
remove() - This removes elements from a range that match a specific value.
remove if() - This removes elements from a range that match a specific criterion, as determined via a unary predicate.
63
remove copy() - Similar to remove, except that elements are copied into another range.
remove copy if() - Similar to remove if, except that elements are copied into another range.
unique() - This is quite a useful algorithm. It removes adjacent duplicate elements, i.e.
consecutive elements with specific values.
unique copy() - Similar to unique, except that it copies the elements into another range.
6.4.5
Mutating Algorithms
reverse () - This simply reverses the order of the elements in a range or container.
reverse copy() - Similar to reverse , except that the results of the reversal are copied into
another range.
rotate () - By choosing a middle element in a range, this algorithm will cyclically rotate
the elements such that the middle element becomes the first.
rotate copy() - Similar to rotate , except that the result is copied into another range.
next permutation() - This rearranges the elements in a range to produce the next lexicographically higher permutation, using operator<. It is also possible to use a binary
predicate comparison function instead of operator<.
prev permutation() - Similar to next permutation, except that it rearranges to produce the
next lexicographically lower permutation.
random shuffle() - Rearranges the list of elements in a range in a random fashion. The
source of randomness can be supplied as a random number generator argument.
partition () - Rearranges a range/container such that the elements matching a predicate are
at the front. Does NOT guarantee relative ordering from the original range.
stable partition () - Similar to partition , except that it does guarantee relative ordering from
the original range.
6.4.6
Sorting Algorithms
sort () - Sorts the elements into ascending order, using operator< or another supplied
comparison function.
64
stable sort () - This is similar to sort . It is used when you need to ensure that elements
remain in the same order when they are tied for the same position. This often comes up
when dealing with priorities of tasks. Note also that the performance guarantee is different.
partial sort () - Similar to sort , except that it only sorts the first N elements and terminates
after theyre sorted.
partial sort copy () - Similar to partial sort except that it copies the results into a new
range.
nth element() - This allows you to ensure that an element at position n is in the correct
position, were the rest of the list to be sorted. It only guarantees that the elements
preceeding n are less than in value (in the sense of operator<) and that proceeding elements
are greater than in value.
partition () - See above for partition .
stable partition () - See above for stable partition .
make heap() - Rearranges the elements in a range such that they form a heap, i.e. allowing
fast retrieval of elements of the highest value and fast insertion of new elements.
push heap() - This adds an element to a heap.
pop heap() - This removes an element from a heap.
sort heap() - This sorts the elements in a heap, with the caveat that the range is no longer
a heap subsequent to the function call.
6.4.7
binary search() - Searches the range for any matches of a specific value.
includes () - Determines whether each element in one range is also an element in another
range.
lower bound() - Searches for the first element in a range which does not compare less than
a specific value. Can also use a custom comparison function.
upper bound() - Searches for the first element in a range which does not compare greater
than a specific value. Can also use a custom comparison function.
65
equal range() - Finds a subrange within a range which contains values equal to a specific
value.
merge() - See above for merge.
set union() - Creates a set union of two sorted ranges. Thus the destination range will
include elements from either or both of the source ranges. Since this is a set operation,
duplicates are eliminated.
set intersection () - Creates a set intersection of two sorted ranges. Thus the destination
range will include elements that exist only in both of the source ranges.
set difference () - Creates a set difference of two sorted ranges. Thus the destation range
will include elements from the first range that are not in the second range.
set symmetric difference () - This is the symmetric version of set difference . It creates a set
symmetric difference, which is formed by the elements that are found in one of the sets,
but not in the other.
inplace merge() - This combines two sorted ranges into a destination range, which is also
sorted. It is also stable as it will preserve ordering for subranges.
6.4.8
Numeric Algorithms
66
6.5
C++11 STL
This section introduces, in a reference fashion, some of the new algorithms and containers that
are now part of the C++11 STL. Highlights include a hash object (unordered map), a singlylinked list ( forward list ) and many algorithms which attempt to fill in the missing pieces left
by C++03, which we have described above.
6.5.1
Containers
The new containers have essentially been introduced for performance reasons. C++ now has a
hash table, a singly-linked list and an array object as part of the STL standard.
unordered set - As with a set , the unordered set contains at most one of each value and allow
fast retrieveal of elements. Allows forward iterators.
unordered multiset - As with a multiset , the unordered multiset can contain multiple copies
of the same value and allows fast retrieval of elements. Allows forward iterators.
unordered map - A hash table! As with a map, the unordered map can contain as most one
of each key with fast retrieval. Allows forward iterators.
unordered multimap - As with multimap, the unordered multimap can contain multiple copes
of the same key. Allows forward iterators.
forward list - This is a singly-linked list. It provides constant time inserts and erases, but
no random access to elements.
array - C++11 now has an STL array. It stores n elements of type T contiguously in
memory. It differs from a vector as it cannot be resized once created.
6.5.2
Algorithms
C++11 has introduced some new algorithms to fill in the blanks left by the previous C++03
standard. Other algorithms exist to simplify common programming tasks, either by using a
simpler interace or better parameter lists. As of 2013, compiler support for these algorithms is
relatively good, but you should consult the documentation of your compilers STL implementation.
all of - This returns true if all the values in the range satisfy a predicate, or the range is
empty.
67
any of - This returns true if any of the values in the range satisfy a predicate, or the range
is empty.
none of - This returns true if none of the values in the range satisfy a predicate, or the
range is empty.
find if not - This returns an iterator to the first value that causes a predicate to be false
(similar to partition point ). This uses a linear search mechanism.
copy if - This copies all elements that satisfy a predicate into another range.
copy n - This copies n elements from a range into another range.
unitialized copy n - Similar to unitialized copy , except that it works for n elements.
move - This moves elements from one range into another.
move backward - This moves elements from one range into another, reversing the order of
the move.
is partitioned - This returns true if all of the elements in a range that satisfy a predicate
are before all of those that do not. It also returns true if the range is empty.
partition copy - This copies elements from a source range into two separate destination
ranges based on whether the elements satisfy a predicate or not.
partition point - This returns an iterator to the first value that causes a predicate to be
false (similar to find if not ). This uses a binary search mechanism.
partial sort copy - This copies all sorted elements from a source to a range. The number
of elements copied is determined by the smaller of the sorted source range and the result
range. Can also use an optional sorting comparison operator.
is sorted - This returns true if the range is sorted. Can also use an optional sorting
comparison operator.
is sorted until - This returns an iterator to the last position for which the range is sorted.
Can also use an optional sorting comparison operator.
is heap - This returns true if the range is a heap, i.e. the first element is the largest. Can
also use an optional sorting comparison operator.
is heap until - This returns an iterator to the last position for which the range is a heap.
Can also use an optional sorting comparison operator.
68
min - Finds the smallest value in the parameter list. Can also use an optional sorting
comparison operator.
max - Finds the largest value in the parameter list. Can also use an optional sorting
comparison operator.
minmax - This returns a pair of the smallest and largest elements. Can also use an optional
sorting comparison operator.
minmax element - This returns two iterators, one pointing to the smallest element in the
range and the other pointing to the largest element in the range. Can also use an optional
sorting comparison operator.
iota - This creates a range of sequentially increasing values, making use of the pre-increment
operator (++i) to create the sequence.
This concludes our tour of the C++ Standard Template Library. In the subsequent chapters
we will make heavy use of the STL to carry out quantitative finance work.
Chapter 7
Function Objects
Many of the concepts within quantitative finance are represented by functions or groups of
functions. The types of these functions can vary considerably. Not all of the functions we
consider are real-valued, for instance. Many functions take other functions as argument or are
vector-valued.
Our goal in this chapter is to develop some intuition about basic mathematical functions
and attempt to model their behaviour in C++. Because C++ is a federation of languages
and supports multi-paradigm programming, there are many options available to us. There is no
right answer as to how to model functions in C++, rather there exist different methods, some
of which are more optimal in certain situations.
There are plenty of examples of functions from within mathematics, and quantitative finance
in particular. Some concrete examples from quantitative finance include:
Pay-off functions - These take a real-valued asset spot and strike price, generally, to
provide a real-valued option value at expiry.
Differential Equation Coefficients - ODEs and PDEs possess coefficients dependent
upon various parameters, which can potentially be real-valued functions.
Matrices - Linear Algebra tells us that matrices are in fact linear maps, i.e. functions
between vector spaces.
We will strike a balance between re-use, efficiency and maintainability when modelling functions in C++, so as to improve productivity without creating complicated code. C++ presents
us with a number of alternatives for modelling functions. In particular:
69
70
Function Pointers - These are a feature of the C language and so form part of the C++
standard. A function pointer allows a pointer to a function to be passed as a parameter to
another function.
Function Objects (Functors) - C++ allows the function call operator() to be overloaded, such that an object instantiated from a class can be called like a function.
STL Functions - The Standard Template Library (STL) provides three types of template
function objects: Generator, unary and binary functions.
C++11 <function> - C++11 brought new changes to how functors were handled. In
addition, anonymous functions (lambdas) are now supported.
7.1
Function Pointers
Function pointers are a legacy feature from the C language. C++ is a superset of C and so
includes function pointer syntax. In essence, function pointers point to executable code at a
particular piece of memory, rather than a data value as with other pointers. Dereferencing the
function pointer allows the code in the memory block to be executed. In addition, arguments
can be passed to the function pointer, which are then passed to the executed code block. The
main benefit of function pointers is that they provide a straightforward mechanism for choosing
a function to execute at run-time.
Here is a C++ example which makes use of an add function and a multiply function to sum
and multiply two double values:
#include <i o s t r e a m >
71
i n t main ( ) {
double a = 5 . 0 ;
double b = 1 0 . 0 ;
return 0 ;
}
72
Adaptation - Function pointers have fixed parameter types and quantities. Thus they are
not particularly flexible when external functions with differing parameter types could be
used. Although adapting the function pointers (by wrapping the external functions with
hard-coded parameters) is possible, it leads to poor flexibility and bloated code.
The solution to these problems is to make use of the C++ function object (also known as a
functor ).
7.2
// A b s t r a c t b a s e c l a s s
class BinaryFunction {
public :
BinaryFunction ( ) { } ;
v i r t u a l double operator ( ) ( double l e f t , double r i g h t ) = 0 ;
};
// Add two d o u b l e s
c l a s s Add : public B i n a r y F u n c t i o n {
public :
Add ( ) { } ;
v i r t u a l double operator ( ) ( double l e f t , double r i g h t ) { return l e f t +r i g h t
; }
};
// M u l t i p l y two d o u b l e s
c l a s s M u l t i p l y : public B i n a r y F u n c t i o n {
public :
73
Multiply () {};
v i r t u a l double operator ( ) ( double l e f t , double r i g h t ) { return l e f t r i g h t
; }
};
i n t main ( ) {
double a = 5 . 0 ;
double b = 1 0 . 0 ;
delete pAdd ;
delete p M u l t i p l y ;
return 0 ;
}
Firstly, note that there is a lot more happening in the code! We have created an abstract base
class, called BinaryFunction and then inherited Add and Multiply classes. Since BinaryFunction is
abstract, it cannot be instantiated. Hence we need to make use of pointers to pass in pAdd and
pMultiply to the new binary op function.
An obvious question is What do we gain from all this extra code/syntax?. The main benefit
is that we are now able to add state to the function objects. For Add and Multiply this is likely be
to unnecessary. However, if our inheritance hierarchy were modelling connections to a database,
then we might require information about how many connections currently exist (as we wouldnt
want to open too many for performance reasons). We might also want to add extra database
types. An inheritance hierarchy like this allows us to easily create more database connection
classes without modifying any other code.
74
Chapter 8
The STL container(s) that will be used to actually store the values
The mathematical operations that will be available such as matrix addition, matrix
multiplication, taking the transpose or elemental access
How the matrix will interact with other objects, such as vectors and scalars
75
76
8.1
8.1.1
C++ provides many container classes via the Standard Template Library (STL). The most
appropriate choices here are std :: valarray and std :: vector. The std :: vector template class is more
frequently used due to its generality. std :: vector can handle many different types (including
pointers and smart pointer objects), whereas std :: valarray is designed solely for numerical values.
In theory this would allow the compiler to make certain optimisations for such numerical work.
At first glance std :: valarray would seem like a great choice to provide storage for our matrix
values. However, in reality it turns out that compiler support for the numerical optimisations
that std :: valarray is supposed to provide does not really exist. Not only that but the std :: valarray
has a poorer application programming interface (API) and isnt as flexible as a std :: vector. Thus
it makes sense to use the std :: vector template class for our underlying storage mechanism.
Supposing that our matrix has M rows and N columns, we could either create a single std
:: vector of length N M or create a vector of vector. The latter creates a single vector of
length M , which takes a std :: vector<T> of types as its type. The inner vectors will each be of
length N . We will utilise the vector of vector approach. The primary reason to use such a
mechanism is that we gain a good API, helpful in accessing elements of such a vector for free.
We do not need to use a look-up formula to find the correct element as we would need to do in
the single large vector approach. The declaration for this type of storage mechanism is given by:
Where T is our type placeholder. Note: For nearly all of the quantitative work we carry out,
we will use the double precision type for numerical storage.
Note the extra space between the last two delimiters: > >. This is to stop the compiler into
believing we are trying to access the bit shift >> operator.
The interface to the matrix class will be such that we could reconfigure the underlying storage
mechanism to be more efficient for a particular use case, but the external client code would remain
identical and would never need know of any such changes. Thus we could entirely replace our
std :: vector storage with std :: valarray storage and our client calling code would be none the wiser.
This is one of the benefits of the object oriented approach, and in particular encapsulation.
77
8.1.2
A flexible matrix class should support a wide variety of mathematical operations in order to
reduce the need for the client to write excess code. In particular, the basic binary operators
should be supported for various matrix interactions. We would like to be able to add, subtract and
multiply matrices, take their transpose, multiply a matrix and vector, as well as add, subtract,
multiply or divide all elements by a scalar value. We will need to access elements individually
by their row and column index.
We will also support an additional operation, which creates a vector of the diagonal elements
of the matrix. This last method is useful within numerical linear algebra. We could also add
the ability to calculate a determinant or an inverse (although there are good performance-based
reasons not to do this directly). However, at this stage we wont support the latter two operations.
Here is a partial listing for the declaration of the matrix operations we will support:
// Matrix m a t h e m a t i c a l o p e r a t i o n s
QSMatrix<T> operator+(const QSMatrix<T>& r h s ) ;
QSMatrix<T>& operator+=(const QSMatrix<T>& r h s ) ;
QSMatrix<T> operator (const QSMatrix<T>& r h s ) ;
QSMatrix<T>& operator=(const QSMatrix<T>& r h s ) ;
QSMatrix<T> operator ( const QSMatrix<T>& r h s ) ;
QSMatrix<T>& operator=(const QSMatrix<T>& r h s ) ;
QSMatrix<T> t r a n s p o s e ( ) ;
We are making use of the object-oriented operator overloading technique. Essentially we are
redefining how the individual mathematical operators (such as +, -, *) will behave when we
apply them to other matrices as a binary operator. Lets consider the syntax for the addition
operator:
QSMatrix<T> operator+(const QSMatrix<T>& r h s ) ;
The function is returning a QSMatrix<T>. Note that were not returning a reference to this
object, were directly returning the object itself. Thus a new matrix is being allocated when this
method is called. The method requires a const reference to another matrix, which forms the
right hand side (rhs) of the binary operation. It is a const reference because we dont want
the matrix to be modified when the operation is carried out (the const part) and is passed by
reference (as opposed to value) as we do not want to generate an expensive copy of this matrix
when the function is called.
This operator will allow us to produce code such as the following:
78
// C r e a t e and i n i t i a l i s e two s q u a r e m a t r i c e s w i t h N = M = 10
// u s i n g e l e m e n t v a l u e s 1 . 0 and 2 . 0 , r e s p e c t i v e l y
QSMatrix<double> mat1 ( 1 0 , 1 0 , 1 . 0 ) ;
QSMatrix<double> mat2 ( 1 0 , 1 0 , 2 . 0 ) ;
The second type of mathematical operator we will study is the operation assignment variant.
This is similar to the binary operator but will assign the result of the operation to the object
calling it. Hence instead of creating a separate matrix C in the equation C = A + B, we will
assign the result of A + B into A. Here is the declaration syntax for such an operator overload:
QSMatrix<T>& operator+=(const QSMatrix<T>& r h s ) ;
The major difference between this and the standard binary addition operator is that we are
now returning a reference to a matrix object, not the object itself by value. This is because we
need to return the original matrix that will hold the final result, not a copy of it. The method
is implemented slightly differently, which will be outlined below in the source implementation.
The remaining matrix mathematical operators are similar to binary addition. We are going
to support addition, subtraction and multiplication. We will leave out division as dividing one
matrix by another is ambiguous and ill-defined. The transpose() method simply returns a new
result matrix which holds the transpose of the original matrix, so we wont dwell too much on it
here.
We also wish to support scalar addition, subtraction, multiplication and division. This means
we will apply a scalar operation to each element of the matrix. The method declarations are
given below:
// Matrix / s c a l a r o p e r a t i o n s
QSMatrix<T> operator+(const T& r h s ) ;
QSMatrix<T> operator (const T& r h s ) ;
QSMatrix<T> operator ( const T& r h s ) ;
QSMatrix<T> operator / ( const T& r h s ) ;
The difference between these and those provided for the equivalent matrix operations is that
the right hand side element is now a type, not a matrix of types. C++ allows us to reuse
overloaded operators with the same method name but with a differing method signature. Thus
79
we can use the same operator for multiple contexts, when such contexts are not ambiguous. We
will see how these methods are implemented once we create the source file.
We also wish to support matrix/vector multiplication. The method signature declaration is
given below:
s t d : : v e c t o r <T> operator ( const s t d : : v e c t o r <T>& r h s ) ;
The method returns a vector of types and takes a const reference vector of types on the right
hand side. This exactly mirrors the mathematical operation, which applies a matrix to a vector
(right multiplication) and produces a vector as output.
The final aspect of the header file that requires discussion is the access to individual elements
via the operator(). Usually this operator is used to turn the object into a functor (i.e. a function
object). However, in this instance we are going to overload it to represent operator access. It is
one of the only operators in C++ that can be used with multiple parameters. The parameters
in question are the row and column indices (zero-based, i.e. running from 0 to N 1).
We have created two separate overloads of this method. The latter is similar to the first except
we have added the const keyword in two places. The first const states that we are returning a
constant type which should not be modified. The second const states that the method itself will
not modify any values. This is necessary if we wish to have read-only access to the elements of
the matrix. It prevents other const methods from throwing an error when obtaining individual
element access.
The two methods are given below:
// Access t h e i n d i v i d u a l e l e m e n t s
T& operator ( ) ( const unsigned& row , const unsigned& c o l ) ;
const T& operator ( ) ( const unsigned& row , const unsigned& c o l ) const ;
8.1.3
Full Declaration
For completeness, the full listing of the matrix header file is given below:
#i f n d e f
QS MATRIX H
#define
QS MATRIX H
80
public :
QSMatrix ( unsigned
rows , unsigned
c o l s , const T&
initial );
// Operator o v e r l o a d i n g , f o r s t a n d a r d m a t h e m a t i c a l m a t r i x o p e r a t i o n s
QSMatrix<T>& operator=(const QSMatrix<T>& r h s ) ;
// Matrix m a t h e m a t i c a l o p e r a t i o n s
QSMatrix<T> operator+(const QSMatrix<T>& r h s ) ;
QSMatrix<T>& operator+=(const QSMatrix<T>& r h s ) ;
QSMatrix<T> operator (const QSMatrix<T>& r h s ) ;
QSMatrix<T>& operator=(const QSMatrix<T>& r h s ) ;
QSMatrix<T> operator ( const QSMatrix<T>& r h s ) ;
QSMatrix<T>& operator=(const QSMatrix<T>& r h s ) ;
QSMatrix<T> t r a n s p o s e ( ) ;
// Matrix / s c a l a r o p e r a t i o n s
QSMatrix<T> operator+(const T& r h s ) ;
QSMatrix<T> operator (const T& r h s ) ;
QSMatrix<T> operator ( const T& r h s ) ;
QSMatrix<T> operator / ( const T& r h s ) ;
// Matrix / v e c t o r o p e r a t i o n s
s t d : : v e c t o r <T> operator ( const s t d : : v e c t o r <T>& r h s ) ;
s t d : : v e c t o r <T> d i a g v e c ( ) ;
// Access t h e i n d i v i d u a l e l e m e n t s
T& operator ( ) ( const unsigned& row , const unsigned& c o l ) ;
const T& operator ( ) ( const unsigned& row , const unsigned& c o l ) const ;
81
unsigned g e t c o l s ( ) const ;
};
#endif
You may have noticed that the matrix source file has been included before the final preprocessor directive:
#include matrix . cpp
This is actually a necessity when working with template classes. The compiler needs to
see both the declaration and the implementation within the same file. This occurs here as the
compiler replaces the include statement with the source file text directly in the preprocessor step,
prior to any usage in an external client code. If this is not carried out the compiler will throw
obscure-looking linker errors, which can be tricky to debug. Make sure you always include your
source file at the bottom of your declaration file if you make use of template classes.
8.1.4
Our task with the source file is to implement all of the methods outlined in the header file. In
particular we need to implement methods for the following:
Constructors (parameter and copy), destructor and assignment operator
Matrix mathematical methods: Addition, subtraction, multiplication and the transpose
Matrix/scalar element-wise mathematical methods: Addition, substraction, multiplication
and division
Matrix/vector multiplication methods
Element-wise access (const and non-const)
We will begin with the construction, assignment and destruction of the class.
8.1.5
The first method to implement is the constructor, with paramaters. The constructor takes three
arguments - the number of rows, the number of columns and an initial type value to populate the
82
matrix with. Since the vector of vectors constructor has already been called at this stage, we
need to call its resize method in order to have enough elements to act as the row containers. Once
the matrix mat has been resized, we need to resize each individual vector within the rows to the
length representing the number of columns. The resize method can take an optional argument,
which will initialise all elements to that particular value. Finally we adjust the private rows and
cols unsigned integers to store the new row and column counts:
// Parameter C o n s t r u c t o r
template<typename T>
QSMatrix<T> : : QSMatrix ( unsigned
rows , unsigned
c o l s , const T&
initial ) {
mat . r e s i z e ( r o w s ) ;
f o r ( unsigned i =0; i <mat . s i z e ( ) ; i ++) {
mat [ i ] . r e s i z e ( c o l s ,
initial );
}
rows =
rows ;
cols =
cols ;
The copy constructor has a straightforward implementation. Since we have not used any
dynamic memory allocation, we simply need to copy each private member from the corresponding
copy matrix rhs:
// Copy C o n s t r u c t o r
template<typename T>
QSMatrix<T> : : QSMatrix ( const QSMatrix<T>& r h s ) {
mat = r h s . mat ;
rows = r h s . g e t r o w s ( ) ;
c o l s = rhs . g e t c o l s () ;
}
The destructor is even simpler. Since there is no dynamic memory allocation, we dont need
to do anything. We can let the compiler handle the destruction of the individual type members
(mat, rows and cols ):
// ( V i r t u a l ) D e s t r u c t o r
template<typename T>
QSMatrix<T> : : QSMatrix ( ) {}
The assignment operator is somewhat more complicated than the other construction/destruction methods. The first two lines of the method implementation check that the addresses of the
83
two matrices arent identical (i.e. were not trying to assign a matrix to itself). If this is the
case, then just return the dereferenced pointer to the current object (this). This is purely for
performance reasons. Why go through the process of copying exactly the same data into itself if
it is already identical?
However, if the matrix in-memory addresses differ, then we resize the old matrix to the be
the same size as the rhs matrix. Once that is complete we then populate the values elementwise and finally adjust the members holding the number of rows and columns. We then return
the dereferenced pointer to this. This is a common pattern for assignment operators and is
considered good practice:
// Assignment Operator
template<typename T>
QSMatrix<T>& QSMatrix<T> : : operator=(const QSMatrix<T>& r h s ) {
i f (& r h s == t h i s )
return t h i s ;
return t h i s ;
}
84
8.1.6
The next part of the implementation concerns the methods overloading the binary operators
that allow matrix algebra such as addition, subtraction and multiplication. There are two types
of operators to be overloaded here. The first is operation without assignment. The second is
operation with assignment. The first type of operator method creates a new matrix to store the
result of an operation (such as addition), while the second type applies the result of the operation
into the left-hand argument. For instance the first type will produce a new matrix C, from the
equation C = A + B. The second type will overwrite A with the result of A + B.
The first operator to implement is for addition without assignment. A new matrix result is
created with initial filled values equal to 0. Then each element is iterated through to be the
pairwise sum of the this matrix and the new right hand side matrix rhs. Notice that we use the
pointer dereferencing syntax with this when accessing the element values: this>mat[i][j]. This
is identical to writing (this).mat[i ][ j ] . We must dereference the pointer before we can access
the underlying object. Finally, we return the result:
Note that this can be a particularly expensive operation. We are creating a new matrix for
every call of this method. However, modern compilers are smart enough to make sure that this
operation is not as performance heavy as it used to be, so for our current needs we are justified
in creating the matrix here. Note again that if we were to return a matrix by reference and then
create the matrix within the class via the new operator, we would have an error as the matrix
object would go out of scope as soon as the method returned.
// A d d i t i o n o f two m a t r i c e s
template<typename T>
QSMatrix<T> QSMatrix<T> : : operator+(const QSMatrix<T>& r h s ) {
QSMatrix r e s u l t ( rows , c o l s , 0 . 0 ) ;
return r e s u l t ;
}
The operation with assignment method for addition is carried out slightly differently. It
85
DOES return a reference to an object, but this is fine since the object reference it returns is
to this, which exists outside of the scope of the method. The method itself makes use of the
operator+= that is bound to the type object. Thus when we carry out the line this>mat
[i][j] += rhs(i,j) ; we are making use of the types own operator overload. Finally, we return a
return t h i s ;
}
The two matrix subtraction operators operator and operator= are almost identical to
the addition variants, so I wont explain them here. If you wish to see their implementation,
have a look at the full listing below.
I will discuss the matrix multiplication methods though as their syntax is sufficiently different
to warrant explanation. The first operator is that without assignment, operator. We can use
this to carry out an equation of the form C = A B. The first part of the method creates a
new result matrix that has the same size as the right hand side matrix, rhs. Then we perform
the triple loop associated with matrix multiplication. We iterate over each element in the result
matrix and assign it the value of this>mat[i][k] rhs(k,j ), i.e. the value of Aik Bkj , for
k {0, ..., M 1}:
// L e f t m u l t i p l i c a t i o n o f t h i s m a t r i x and a n o t h e r
template<typename T>
QSMatrix<T> QSMatrix<T> : : operator ( const QSMatrix<T>& r h s ) {
unsigned rows = r h s . g e t r o w s ( ) ;
unsigned c o l s = r h s . g e t c o l s ( ) ;
QSMatrix r e s u l t ( rows , c o l s , 0 . 0 ) ;
86
return r e s u l t ;
}
The implementation of the operator= is far simpler, but only because we are building on
what already exists. The first line creates a new matrix called result which stores the result of
multiplying the dereferenced pointer to this and the right hand side matrix, rhs. The second line
then sets this to be equal to the result above. This is necessary as if it was carried out in one
step, data would be overwritten before it could be used, creating an incorrect result. Finally the
referenced pointer to this is returned. Most of the work is carried out by the operator which
is defined above. The listing is as follows:
// Cum ula tive l e f t m u l t i p l i c a t i o n o f t h i s m a t r i x and a n o t h e r
template<typename T>
QSMatrix<T>& QSMatrix<T> : : operator=(const QSMatrix<T>& r h s ) {
QSMatrix r e s u l t = ( t h i s ) r h s ;
( this ) = r e s u l t ;
return t h i s ;
}
We also wish to apply scalar element-wise operations to the matrix, in particular element-wise
scalar addition, subtraction, multiplication and division. Since they are all very similar, I will
only provide explanation for the addition operator. The first point of note is that the parameter
is now a const T&, i.e. a reference to a const type. This is the scalar value that will be added
to all matrix elements. We then create a new result matrix as before, of identical size to this.
Then we iterate over the elements of the result matrix and set their values equal to the sum of
the individual elements of this and our type value, rhs. Finally, we return the result matrix:
// Matrix / s c a l a r a d d i t i o n
template<typename T>
87
return r e s u l t ;
}
We also wish to allow (right) matrix vector multiplication. It is not too different from the
implementation of matrix-matrix multiplication. In this instance we are returning a std :: vector
<T> and also providing a separate vector as a parameter. Upon invocation of the method we
create a new result vector that has the same size as the right hand side, rhs. Then we perform
a double loop over the elements of the this matrix and assign the result to an element of the
result vector. Finally, we return the result vector:
// M u l t i p l y a m a t r i x w i t h a v e c t o r
template<typename T>
s t d : : v e c t o r <T> QSMatrix<T> : : operator ( const s t d : : v e c t o r <T>& r h s ) {
s t d : : v e c t o r <T> r e s u l t ( r h s . s i z e ( ) , 0 . 0 ) ;
return r e s u l t ;
}
Ive added a final matrix method, which is useful for certain numerical linear algebra techniques. Essentially it returns a vector of the diagonal elements of the matrix. Firstly we create
the result vector, then assign it the values of the diagonal elements and finally we return the
result vector:
// Obtain a v e c t o r o f t h e d i a g o n a l e l e m e n t s
88
template<typename T>
s t d : : v e c t o r <T> QSMatrix<T> : : d i a g v e c ( ) {
s t d : : v e c t o r <T> r e s u l t ( rows , 0 . 0 ) ;
return r e s u l t ;
}
The final set of methods to implement are for accessing the individual elements as well as
getting the number of rows and columns from the matrix. Theyre all quite simple in their
implementation. They dereference this and then obtain either an individual element or some
private member data:
// Access t h e i n d i v i d u a l e l e m e n t s
template<typename T>
T& QSMatrix<T> : : operator ( ) ( const unsigned& row , const unsigned& c o l ) {
return this>mat [ row ] [ c o l ] ;
}
// Access t h e i n d i v i d u a l e l e m e n t s ( c o n s t )
template<typename T>
const T& QSMatrix<T> : : operator ( ) ( const unsigned& row , const unsigned& c o l )
const {
return this>mat [ row ] [ c o l ] ;
}
89
return this>c o l s ;
}
8.1.7
Now that we have described all the methods in full, here is the full source listing for the QSMatrix
class:
#i f n d e f
QS MATRIX CPP
#define
QS MATRIX CPP
#include matrix . h
// Parameter C o n s t r u c t o r
template<typename T>
QSMatrix<T> : : QSMatrix ( unsigned
rows , unsigned
mat . r e s i z e ( r o w s ) ;
f o r ( unsigned i =0; i <mat . s i z e ( ) ; i ++) {
mat [ i ] . r e s i z e ( c o l s ,
initial );
}
rows =
rows ;
cols =
cols ;
// Copy C o n s t r u c t o r
template<typename T>
QSMatrix<T> : : QSMatrix ( const QSMatrix<T>& r h s ) {
mat = r h s . mat ;
rows = r h s . g e t r o w s ( ) ;
c o l s = rhs . g e t c o l s () ;
}
// ( V i r t u a l ) D e s t r u c t o r
template<typename T>
QSMatrix<T> : : QSMatrix ( ) {}
// Assignment Operator
template<typename T>
c o l s , const T&
initial ) {
90
return t h i s ;
}
// A d d i t i o n o f two m a t r i c e s
template<typename T>
QSMatrix<T> QSMatrix<T> : : operator+(const QSMatrix<T>& r h s ) {
QSMatrix r e s u l t ( rows , c o l s , 0 . 0 ) ;
return r e s u l t ;
}
91
// C u m u l a t ive a d d i t i o n o f t h i s m a t r i x and a n o t h e r
template<typename T>
QSMatrix<T>& QSMatrix<T> : : operator+=(const QSMatrix<T>& r h s ) {
unsigned rows = r h s . g e t r o w s ( ) ;
unsigned c o l s = r h s . g e t c o l s ( ) ;
return t h i s ;
}
// S u b t r a c t i o n o f t h i s m a t r i x and a n o t h e r
template<typename T>
QSMatrix<T> QSMatrix<T> : : operator (const QSMatrix<T>& r h s ) {
unsigned rows = r h s . g e t r o w s ( ) ;
unsigned c o l s = r h s . g e t c o l s ( ) ;
QSMatrix r e s u l t ( rows , c o l s , 0 . 0 ) ;
return r e s u l t ;
}
// C u m u l a t ive s u b t r a c t i o n o f t h i s m a t r i x and a n o t h e r
template<typename T>
QSMatrix<T>& QSMatrix<T> : : operator=(const QSMatrix<T>& r h s ) {
unsigned rows = r h s . g e t r o w s ( ) ;
unsigned c o l s = r h s . g e t c o l s ( ) ;
92
return t h i s ;
}
// L e f t m u l t i p l i c a t i o n o f t h i s m a t r i x and a n o t h e r
template<typename T>
QSMatrix<T> QSMatrix<T> : : operator ( const QSMatrix<T>& r h s ) {
unsigned rows = r h s . g e t r o w s ( ) ;
unsigned c o l s = r h s . g e t c o l s ( ) ;
QSMatrix r e s u l t ( rows , c o l s , 0 . 0 ) ;
return r e s u l t ;
}
// C a l c u l a t e a t r a n s p o s e o f t h i s m a t r i x
template<typename T>
93
QSMatrix<T> QSMatrix<T> : : t r a n s p o s e ( ) {
QSMatrix r e s u l t ( rows , c o l s , 0 . 0 ) ;
return r e s u l t ;
}
// Matrix / s c a l a r a d d i t i o n
template<typename T>
QSMatrix<T> QSMatrix<T> : : operator+(const T& r h s ) {
QSMatrix r e s u l t ( rows , c o l s , 0 . 0 ) ;
return r e s u l t ;
}
// Matrix / s c a l a r s u b t r a c t i o n
template<typename T>
QSMatrix<T> QSMatrix<T> : : operator (const T& r h s ) {
QSMatrix r e s u l t ( rows , c o l s , 0 . 0 ) ;
94
return r e s u l t ;
}
// Matrix / s c a l a r m u l t i p l i c a t i o n
template<typename T>
QSMatrix<T> QSMatrix<T> : : operator ( const T& r h s ) {
QSMatrix r e s u l t ( rows , c o l s , 0 . 0 ) ;
return r e s u l t ;
}
// Matrix / s c a l a r d i v i s i o n
template<typename T>
QSMatrix<T> QSMatrix<T> : : operator / ( const T& r h s ) {
QSMatrix r e s u l t ( rows , c o l s , 0 . 0 ) ;
return r e s u l t ;
}
// M u l t i p l y a m a t r i x w i t h a v e c t o r
template<typename T>
s t d : : v e c t o r <T> QSMatrix<T> : : operator ( const s t d : : v e c t o r <T>& r h s ) {
s t d : : v e c t o r <T> r e s u l t ( r h s . s i z e ( ) , 0 . 0 ) ;
95
return r e s u l t ;
}
// Obtain a v e c t o r o f t h e d i a g o n a l e l e m e n t s
template<typename T>
s t d : : v e c t o r <T> QSMatrix<T> : : d i a g v e c ( ) {
s t d : : v e c t o r <T> r e s u l t ( rows , 0 . 0 ) ;
return r e s u l t ;
}
// Access t h e i n d i v i d u a l e l e m e n t s
template<typename T>
T& QSMatrix<T> : : operator ( ) ( const unsigned& row , const unsigned& c o l ) {
return this>mat [ row ] [ c o l ] ;
}
// Access t h e i n d i v i d u a l e l e m e n t s ( c o n s t )
template<typename T>
const T& QSMatrix<T> : : operator ( ) ( const unsigned& row , const unsigned& c o l )
const {
return this>mat [ row ] [ c o l ] ;
}
96
#endif
8.1.8
We have the full listings for both the matrix header and source, so we can test the methods out
with some examples. Here is the main listing showing the matrix addition operator:
#include matrix . h
#include <i o s t r e a m >
return 0 ;
}
Here is the output of the code. We can see that the elements are all valued 3.0, which is
simply the element-wise addition of mat1 and mat2:
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
97
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
This concludes the section on creating a custom matrix library. Now we will consider how to
use external libraries in a production environment.
8.2
We have previously considered operator overloading and how to create our own matrix object
in C++. As a learning exercise, creating a matrix class can be extremely beneficial as it often
covers dynamic memory allocation (if not using std :: vectors) and operator overloading across
multiple object types (matrices, vectors and scalars). However, it is far from optimal to carry
this out in a production environment. This section will explain why it is better to use an external
dedicated matrix library project, such as Eigen.
Writing our own matrix libraries is likely to cause a few issues in a production environment:
Poor Performance - Unless significant time is spent optimising a custom matrix library,
it is often going to be far slower than the corresponding dedicated community projects such
as uBLAS, Eigen, MTL or Blitz.
Buggy Code - A large community surrounding an open-source project leads to a greater
number of bug fixes as edge cases are discovered and corrected. With a custom matrix
library, it is likely that it will be restricted solely for an individual use case and thus edge
cases are unlikely to be checked.
Few Algorithms - Once again, the benefit of a large community is that multiple efficient
algorithms can be generated for all aspects of numerical linear algebra. These in turn feed
optimisations back to the core library and thus increase overall efficiency.
Time Wasting - Are you in the business of quantitative finance or in the business of
building an all-singing all-dancing matrix library? By spending months optimising a custom
98
library, you are neglecting the original intent of its usage of the first place - solving quant
problems!
While many libraries exist (see above), I have chosen to use the Eigen project. Here are some
of the benefits of Eigen:
Up to date - Eigen is actively developed and releases new versions frequently
API - Eigen has a simple, straightforward and familiar API syntax
Dynamic matrices - Supports matrices with sizes determined at runtime
Well tested - Eigen has extensive battle testing and thus few bugs
Storage - Can use either row-major or column-major storage
Optimised structures - Dense and sparse matrices both available
Expression templates - Lazy evaluation, which allows for complex matrix arithmetic,
while maintaining performance
In this section we will install Eigen, look at examples of basic linear algebra usage and briefly
study some of the advanced features, which will be the subject of later chapters.
8.2.1
Installation
Eigen is extremely easy to install as there is no library that needs linking to. Instead the header
files are simply included in the code for your program. With GCC it is necessary to use the -I
flag in order for the compiler to be able to find the Eigen header files:
g++ I / your / path / t o / e i g e n / example program . cpp o example program
8.2.2
Basic Usage
The following program requires the Eigen/Dense header. It initialises a 3x3 matrix (using the
MatrixXd template) and then prints it to the console:
#include <i o s t r e a m >
#include <Eigen / Dense>
i n t main ( ) {
Eigen : : MatrixXd m( 3 , 3 ) ;
m << 1 , 2 , 3 ,
99
4, 5, 6,
7 , 8 , 9;
s t d : : c o u t << m << s t d : : e n d l ;
}
Notice that the overloaded << operator can accept comma-separated lists of values in order
to initialise the matrix. This is an extremely useful part of the API syntax. In addition, we can
also pass the MatrixXd to std :: cout and have the numbers output in a human-readable fashion.
8.2.3
Eigen is a large library and has many features. We will be exploring many of them over subsequent
chapters. In this section I want to describe basic matrix and vector operations, including the
matrix-vector and matrix-matrix multiplication facilities provided with the library.
8.2.4
Expression Templates
One of the most attractive features of the Eigen library is that it includes expression objects and
lazy evaluation. This means that any arithmetic operation actually returns such an expression
object, which is actually a description of the final computation to be performed rather than
the actual computation itself. The benefit of such an approach is that most compilers are able
to heavily optimise the expression such that additional loops are completely minimised. As an
example, an expression such as:
VectorXd p ( 1 0 ) , q ( 1 0 ) , r ( 1 0 ) , s ( 1 0 ) ;
...
p = 5 q + 11 r 7 s ;
This means that the underlying storage arrays are only looped over once. As such Eigen can
optimise relatively complicated arithmetic expressions.
100
8.2.5
Eigen allows for straightforward addition and subtraction of vectors and matrices. However, it
is necessary for the operations to be mathematically well-defined: The two operands must have
the same number of rows and columns. In addition, they must also possess the same scalar type.
Here is an example of usage:
#include <i o s t r e a m >
#include <Eigen / Dense>
i n t main ( ) {
// D e f i n e two m a t r i c e s , b o t h 3 x3
Eigen : : Matrix3d p ;
Eigen : : Matrix3d q ;
// D e f i n e two t h r e e d i m e n s i o n a l v e c t o r s
// The c o n s t r u c t o r p r o v i d e s i n i t i a l i s a t i o n
Eigen : : Vector3d r ( 1 , 2 , 3 ) ;
Eigen : : Vector3d s ( 4 , 5 , 6 ) ;
// Use t h e << o p e r a t o r t o
fill
the matrices
p << 1 , 2 , 3 ,
4, 5, 6,
7 , 8 , 9;
q << 1 0 , 1 1 , 1 2 ,
13 , 14 , 15 ,
16 , 17 , 18;
// Output a r i t h m e t i c o p e r a t i o n s f o r m a t r i c e s
s t d : : c o u t << p+q=\n << p + q << s t d : : e n d l ;
s t d : : c o u t << pq=\n << p q << s t d : : e n d l ;
// Output a r i t h m e t i c o p e r a t i o n s f o r v e c t o r s
s t d : : c o u t << r+s=\n << r + s << s t d : : e n d l ;
s t d : : c o u t << rs=\n << r s << s t d : : e n d l ;
}
101
p+q=
11 13 15
17 19 21
23 25 27
pq=
9 9 9
9 9 9
9 9 9
r+s=
5
7
9
rs=
3
3
3
i n t main ( ) {
// D e f i n e a 3 x3 m a t r i x and i n i t i a l i s e
Eigen : : Matrix3d p ;
p << 1 , 2 , 3 ,
4, 5, 6,
7 , 8 , 9;
// M u l t i p l y and d i v i d e by a s c a l a r
s t d : : c o u t << p 3 . 1 4 1 5 9 =\n << p 3 . 1 4 1 5 9 << s t d : : e n d l ;
s t d : : c o u t << p / 2 . 7 1 8 2 8 =\n << p / 2 . 7 1 8 2 8 << s t d : : e n d l ;
}
15.708 18.8495
102
1.10364
1.47152
1.8394
2.20728
2.57516
2.94304
3.31092
8.2.6
Matrix Transposition
Eigen also has operations for the transpose, conjugate and the adjoint of a matrix. For quantitative
finance work we are really only interested in the transpose, as we will not often be utilising complex
numbers!
It is necessary to be careful when using the tranpose operation for in-place assignment (this
applies to the conjugate and the adjoint too) as the transpose operation will write into the
original matrix before finalising the calculation, which can lead to unexpected behaviour. Thus
it is necessary to use Eigens transposeInPlace method on matrix objects when carrying out inplace transposition.
#include <i o s t r e a m >
#include <Eigen / Dense>
i n t main ( ) {
// D e c l a r e a 3 x3 m a t r i x w i t h random e n t r i e s
Eigen : : Matrix3d p = Eigen : : Matrix3d : : Random ( 3 , 3 ) ;
// Output t h e t r a n s p o s e o f p
s t d : : c o u t << pT =\n << p . t r a n s p o s e ( ) << s t d : : e n d l ;
// Inp l a c e t r a n s p o s i t i o n
p . transposeInPlace () ;
// Output t h e inp l a c e t r a n s p o s e o f p
s t d : : c o u t << pT =\n << p << s t d : : e n d l ;
}
0.511211
103
0.0826997 0 . 0 6 5 5 3 4 5 0.562082
0.905911
0.357729
0.358593
0.999984 0.736924
0.511211
pT =
0.0826997 0 . 0 6 5 5 3 4 5 0.562082
0.905911
8.2.7
0.357729
0.358593
Eigen handles matrix/matrix and matrix/vector multiplication with a simple API. Vectors are
matrices of a particular type (and defined that way in Eigen) so all operations simply overload
the operator. Here is an example of usage for matrices, vectors and transpose operations:
#include <i o s t r e a m >
#include <Eigen / Dense>
i n t main ( ) {
// D e f i n e a 3 x3 m a t r i x and two 3d i m e n s i o n a l v e c t o r s
Eigen : : Matrix3d p ;
p << 1 , 2 , 3 ,
4, 5, 6,
7 , 8 , 9;
Eigen : : Vector3d r ( 1 0 , 1 1 , 1 2 ) ;
Eigen : : Vector3d s ( 1 3 , 1 4 , 1 5 ) ;
// Matrix / m a t r i x m u l t i p l i c a t i o n
s t d : : c o u t << pp : \ n << pp << s t d : : e n d l ;
// Matrix / v e c t o r m u l t i p l i c a t i o n
s t d : : c o u t << p r : \ n << p r << s t d : : e n d l ;
s t d : : c o u t << r Tp : \ n << r . t r a n s p o s e ( ) p << s t d : : e n d l ;
// V ector / v e c t o r m u l t i p l i c a t i o n ( i n n e r p r o d u c t )
s t d : : c o u t << r T s : \ n << r . t r a n s p o s e ( ) s << s t d : : e n d l ;
}
104
30
36
42
66
81
96
8.2.8
Vector Operations
Eigen also supports common vector operations, such as the inner product (dot product) and
the vector product (cross product). Note that the sizes of the operand vectors are restricted by
the mathematical definitions of each operator. The dot product must be applied to two vectors
of equal dimension, while the cross product is only defined for three-dimensional vectors:
#include <i o s t r e a m >
#include <Eigen / Dense>
i n t main ( ) {
// D e c l a r e and i n i t i a l i s e two 3D v e c t o r s
Eigen : : Vector3d r ( 1 0 , 2 0 , 3 0 ) ;
Eigen : : Vector3d s ( 4 0 , 5 0 , 6 0 ) ;
// Apply t h e d o t and c r o s s p r o d u c t s
s t d : : c o u t << r . s =\n << r . dot ( s ) << s t d : : e n d l ;
s t d : : c o u t << r x s =\n << r . c r o s s ( s ) << s t d : : e n d l ;
}
105
300
8.2.9
Reduction
Reduction in this context means to take a matrix or vector as an argument and obtain a scalar
as a result. There are six reduction operations which interest us:
sum - Calculates the sum of all elements in a vector or matrix
prod - Calculates the product of all elements in a vector or matrix
mean - Calculates the mean average of all elements in a vector or matrix
minCoeff - Calculates the minimum element in a vector or matrix
maxCoeff - Calculates the maximum element in a vector or matrix
trace - Calculates the trace of a matrix, i.e. the sum of the diagonal elements
Here is an example of usage:
#include <i o s t r e a m >
#include <Eigen / Dense>
i n t main ( ) {
// D e c l a r e and i n i t i a l i s e a 3D m a t r i x
Eigen : : Matrix3d p ;
p << 1 , 2 , 3 ,
4, 5, 6,
7 , 8 , 9;
// Output t h e r e d u c t i o n o p e r a t i o n s
s t d : : c o u t << p . sum ( ) : << p . sum ( ) << s t d : : e n d l ;
s t d : : c o u t << p . prod ( ) : << p . prod ( ) << s t d : : e n d l ;
s t d : : c o u t << p . mean ( ) : << p . mean ( ) << s t d : : e n d l ;
s t d : : c o u t << p . minCoeff ( ) : << p . minCoeff ( ) << s t d : : e n d l ;
s t d : : c o u t << p . maxCoeff ( ) : << p . maxCoeff ( ) << s t d : : e n d l ;
s t d : : c o u t << p . t r a c e ( ) : << p . t r a c e ( ) << s t d : : e n d l ;
}
106
p . sum ( ) : 45
p . prod ( ) : 362880
p . mean ( ) : 5
p . minCoeff ( ) : 1
p . maxCoeff ( ) : 9
p . t r a c e ( ) : 15
8.2.10
Useful Features
Eigen contains many more features than I have listed here. In particular, it supports multiple data structures for efficient matrix storage, depending on structural sparsity of values via
the Sparse namespace. Further, Eigen has support for LR, Cholesky, SVD and QR decomposition. Eigenvalues can also be calculated in an optimised manner. The Geometry module
allows calculation of geometric quantities, including transforms, translations, scaling, rotations
and quaternions.
8.2.11
Next Steps
Now that we have explored the basic usage of the Eigen library, as well as glimpsed at the higher
functionality, we are ready to carry out Numerical Linear Algebra (NLA) with Eigen as the basis
(excuse the pun) for our code. This will help us solve more advanced problems in quantitative
trading and derivatives pricing.
Chapter 9
9.1
Overview
While the Finite Difference Method motivates the use of NLA techniques, such as LU Decomposition and the Thomas Algorithm, the subject is applicable to a wide range of tasks within
quantitative finance. The Cholesky Decomposition finds application among correlation matrices. QR Decomposition is an essential component of the linear least square problem, used in
regression analysis. Singular Value Decomposition (SVD) and Eigenvalue Decomposition are
two other techniques commonly used in finance for creating predictive models or carrying out
expiatory data analysis.
In this section we will study the LU Decomposition, the Thomas Algorithm, the Cholesky
Decomposition and the QR Decomposition. We will apply these algorithms to quantitative
problems later in the book.
107
108
9.2
LU Decomposition
In this section we will discuss the LU Decomposition method, which is used in certain quantitative finance algorithms.
One of the key methods for solving the Black-Scholes Partial Differential Equation (PDE)
model of options pricing is using Finite Difference Methods (FDM) to discretise the PDE and
evaluate the solution numerically. Certain implicit Finite Difference Methods eventually lead to
a system of linear equations.
This system of linear equations can be formulated as a matrix equation, involving the matrix
A and the vectors x and b, of which x is the solution to be determined. Often these matrices are
banded (their non-zero elements are confined to a subset of diagonals) and specialist algorithms
(such as the Thomas Algorithm, see below) are used to solve them. LU Decomposition will aid
us in solving the following matrix equation, without the direct need to invert the matrix A.
Ax = b
Although suboptimal from a performance point of view, we are going to implement our own
version of the LU Decomposition in order to see how the algorithm is translated from the
mathematical realm into C++.
We will make use of the Doolittles LUP decomposition with partial pivoting to decompose
our matrix A into P A = LU , where L is a lower triangular matrix, U is an upper triangular
matrix and P is a permutation matrix. P is needed to resolve certain singularity issues.
9.2.1
Algorithm
To calculate the upper triangular section we use the following formula for elements of U :
uij = aij
i1
X
ukj lik
k=1
The formula for elements of the lower triangular matrix L is similar, except that we need to
divide each term by the corresponding diagonal element of U . To ensure that the algorithm is
numerically stable when ujj 0, a pivoting matrix P is used to re-order A so that the largest
element of each column of A gets shifted to the diagonal of A. The formula for elements of L
follows:
109
j1
lij =
X
1
(aij
ukj lik )
ujj
k=1
9.2.2
Creating a custom numerical linear algebra library, while a worthwhile learning exercise, is
suboptimal from an implementation point of view. We have discussed this issue in the previous
chapter and came to the conclusion that an external, high-performance matrix library was more
appropriate. We will continue to make use of Eigen (although it isnt technically a library!) for
our NLA work.
Here is a listing that shows how to make use of Eigen in order to create a LU Decomposition
matrix. The code outputs the lower and upper triangular matrices separately.
Firstly, a typedef is created to allow us to use a 4 4 matrix in all subsequent lines. Then
we create and populate a 4 4 matrix, outputting it to the terminal. Subsequently, we use the
PartialPivLU template to create a LU Decomposition object, which is then used to provide lower
and upper triangular views for the matrix. The key method is triangularView, which allows us
to pass StrictlyLower or Upper into the template parameter in order to allow us to output the
correct matrix.
Notice how straightforward the API syntax is:
#include <i o s t r e a m >
#include <Eigen / Dense>
#include <Eigen /LU>
i n t main ( ) {
typedef Eigen : : Matrix<double , 4 , 4> Matrix4x4 ;
// D e c l a r e a 4 x4 m a t r i x w i t h d e f i n e d e n t r i e s
Matrix4x4 p ;
p << 7 , 3 , 1, 2 ,
3 , 8 , 1 , 4,
1, 1 , 4 , 1,
2 , 4, 1, 6 ;
s t d : : c o u t << Matrix P : \ n << p << s t d : : e n d l << s t d : : e n d l ;
// C r e a t e LU Decomposition t e m p l a t e o b j e c t f o r p
110
// Output L , t h e l o w e r t r i a n g u l a r m a t r i x
Matrix4x4 l = Eigen : : MatrixXd : : I d e n t i t y ( 4 , 4 ) ;
l . b l o c k <4 ,4 >(0 ,0) . t r i a n g u l a r V i e w <Eigen : : S t r i c t l y L o w e r >() = l u . matrixLU ( ) ;
s t d : : c o u t << L Matrix : \ n << l << s t d : : e n d l << s t d : : e n d l ;
// Output U, t h e upper t r i a n g u l a r m a t r i x
Matrix4x4 u = l u . matrixLU ( ) . t r i a n g u l a r V i e w <Eigen : : Upper >() ;
s t d : : c o u t << R Matrix : \ n << u << s t d : : e n d l ;
return 0 ;
}
3 1
1 4
4 1
2 4 1
LU Matrix :
7
0.428571
6.71429
1.42857
4.85714
0.142857
0.212766
3.55319
0.319149
0 . 2 8 5 7 1 4 0.723404 0 . 0 8 9 8 2 0 4
1.88623
L Matrix :
1
0.428571
0.142857
0.212766
0 . 2 8 5 7 1 4 0.723404 0 . 0 8 9 8 2 0 4
R Matrix :
7
6.71429
1 . 4 2 8 5 7 4.85714
111
9.3
3.55319 0.319149
0
1.88623
The Tridiagonal Matrix Algorithm, also known as the Thomas Algorithm (due to Llewellyn
Thomas), is an application of gaussian elimination to a tri-banded matrix. The algorithm itself
requires five parameters, each vectors. The first three parameters a, b and c represent the
elements in the tridiagonal bands. Since b represents the diagonal elements it is one element
longer than a and c, which represent the off-diagonal bands. The latter two parameters represent
the solution vector f and d, the right-hand column vector.
The original system is written as:
b1
a2
c1
...
b2
c2
...
a3
b3
c3
ak1
.
.
.
0
d
f
1 1
0
f2 d 2
0
f3 d 3
=
.
. .
.
. .
ck1 . .
dk
bk
fk
0
ci =
di
c1
b1
ci
bi c
i1 ai
d1
b1
di d
i1 ai
bi c
i1 ai
;i = 1
; i = 2, 3, ..., k 1
;i = 1
; i = 2, 3, ..., k 1
With these new coefficients, the matrix equation can be rewritten as:
112
c1
c2
c3
.
.
.
0
d
f
1 1
...
0
f2 d2
0
0
f3 d3
=
.
. .
.
. .
ck1 . .
dk
fk
0
1
...
The algorithm for the solution of these equations is now straightforward and works in reverse:
fk = dk ,
9.3.1
fi = dk ci xi+1 ,
i = k 1, k 2, ..., 2, 1
C++ Implementation
Notice that f is a non-const vector, which means it is the only vector to be modified within
the function.
It is possible to write the Thomas Algorithm function in a more efficient manner to override
the c and d vectors. Finite difference methods require the vectors to be re-used for each time
step, so the following implementation utilises two additional temporary vectors, c and d . The
memory for the vectors has been allocated within the function. Every time the function is called
these vectors are allocated and deallocated, which is suboptimal from an efficiency point of view.
A proper production implementation would pass references to these vectors from an external function that only requires a single allocation and deallocation. However, in order to keep
the function straightforward to understand Ive not included this aspect:
s t d : : v e c t o r <double> c s t a r (N, 0 . 0 ) ;
s t d : : v e c t o r <double> d s t a r (N, 0 . 0 ) ;
The first step in the algorithm is to initialise the beginning elements of the c and d vectors:
c star [0] = c [0] / b [ 0 ] ;
113
The next step, known as the forward sweep is to fill the c and d vectors such that we
form the second matrix equation A f = d :
f o r ( i n t i =1; i <N; i ++) {
double m = 1 . 0 / ( b [ i ] a [ i ] c s t a r [ i 1]) ;
c s t a r [ i ] = c [ i ] m;
d s t a r [ i ] = ( d [ i ] a [ i ] d s t a r [ i 1]) m;
}
Once the forward sweep is carried out the final step is to carry out the reverse sweep.
Notice that the vector f is actually being assigned here. The function itself is void, so we dont
return any values.
f o r ( i n t i=N1; i > 0 ; ) {
f [ i ] = d star [ i ] c s t a r [ i ] d [ i +1];
}
Ive included a main function, which sets up the Thomas Algorithm to solve one time-step
of the Crank-Nicolson finite difference method discretised diffusion equation. The details of the
algorithm are not so important here, as I will be elucidating on the method in further articles
on QuantStart.com when we come to solve the Black-Scholes equation. This is only provided in
order to show you how the function works in a real world situation:
#include <cmath>
#include <i o s t r e a m >
#include <v e c t o r >
// V e c t o r s a , b , c and d a r e c o n s t . They w i l l n o t be m o d i f i e d
// by t h e f u n c t i o n . Vector f ( t h e s o l u t i o n v e c t o r ) i s nonc o n s t
// and t h u s w i l l be c a l c u l a t e d and u p d a t e d by t h e f u n c t i o n .
void t h o m a s a l g o r i t h m ( const s t d : : v e c t o r <double>& a ,
const s t d : : v e c t o r <double>& b ,
const s t d : : v e c t o r <double>& c ,
const s t d : : v e c t o r <double>& d ,
s t d : : v e c t o r <double>& f ) {
s i z e t N = d . size () ;
// C r e a t e t h e temporary v e c t o r s
114
// Note t h a t t h i s i s i n e f f i c i e n t as i t i s p o s s i b l e t o c a l l
// t h i s f u n c t i o n many t i m e s . A b e t t e r i m p l e m e n t a t i o n would
// p a s s t h e s e temporary m a t r i c e s by nonc o n s t r e f e r e n c e t o
// s a v e e x c e s s a l l o c a t i o n and d e a l l o c a t i o n
s t d : : v e c t o r <double> c s t a r (N, 0 . 0 ) ;
s t d : : v e c t o r <double> d s t a r (N, 0 . 0 ) ;
// This u p d a t e s t h e c o e f f i c i e n t s i n t h e f i r s t row
// Note t h a t we s h o u l d be c h e c k i n g f o r d i v i s i o n by z e r o h e r e
c star [0] = c [0] / b [ 0 ] ;
d star [0] = d [0] / b [ 0 ] ;
// C r e a t e t h e c s t a r and d s t a r c o e f f i c i e n t s i n t h e f o r w a r d sweep
f o r ( i n t i =1; i <N; i ++) {
double m = 1 . 0 / ( b [ i ] a [ i ] c s t a r [ i 1]) ;
c s t a r [ i ] = c [ i ] m;
d s t a r [ i ] = ( d [ i ] a [ i ] d s t a r [ i 1]) m;
}
// A l t h o u g h t h o m a s a l g o r i t h m p r o v i d e s e v e r y t h i n g n e c e s s a r y t o s o l v e
// a t r i d i a g o n a l system , i t i s h e l p f u l t o wrap i t up i n a r e a l w o r l d
// example . The main f u n c t i o n b e l o w u s e s a t r i d i a g o n a l s y s t e m from
// a Boundary Value Problem (BVP) . This i s t h e d i s c r e t i s a t i o n o f t h e
// 1D h e a t e q u a t i o n .
i n t main ( i n t argc , char argv ) {
115
double d e l t a t = 0 . 0 0 1 ;
double r = d e l t a t / ( d e l t a x d e l t a x ) ;
// F i r s t we c r e a t e t h e v e c t o r s t o s t o r e t h e c o e f f i c i e n t s
s t d : : v e c t o r <double> a (N1, r / 2 . 0 ) ;
s t d : : v e c t o r <double> b (N, 1.0+ r ) ;
s t d : : v e c t o r <double> c (N1, r / 2 . 0 ) ;
s t d : : v e c t o r <double> d (N, 0 . 0 ) ;
s t d : : v e c t o r <double> f (N, 0 . 0 ) ;
// F i l l i n t h e c u r r e n t time s t e p i n i t i a l v a l u e
// v e c t o r u s i n g t h r e e p e a k s w i t h v a r i o u s a m p l i t u d e s
f [ 5 ] = 1; f [ 6 ] = 2; f [ 7 ] = 1;
// We o u t p u t t h e s o l u t i o n v e c t o r f , p r i o r t o a
// new times t e p
s t d : : c o u t << f = ( ;
f o r ( i n t i =0; i <N; i ++) {
s t d : : c o u t << f [ i ] ;
i f ( i < N1) {
s t d : : c o u t << , ;
}
}
s t d : : c o u t << ) << s t d : : e n d l << s t d : : e n d l ;
// F i l l i n t h e c u r r e n t time s t e p v e c t o r d
f o r ( i n t i =1; i <N1; i ++) {
d [ i ] = r 0 . 5 f [ i +1] + (1.0 r ) f [ i ] + r 0 . 5 f [ i 1 ] ;
}
// Now we s o l v e t h e t r i d i a g o n a l s y s t e m
thomas algorithm (a , b , c , d , f ) ;
// F i n a l l y we o u t p u t t h e s o l u t i o n v e c t o r f
s t d : : c o u t << f = ( ;
f o r ( i n t i =0; i <N; i ++) {
s t d : : c o u t << f [ i ] ;
116
i f ( i < N1) {
s t d : : c o u t << , ;
}
}
s t d : : c o u t << ) << s t d : : e n d l ;
return 0 ;
}
You can see the the initial vector and then the solution vector after one time-step. Since
we are modelling the diffusion/heat equation, you can see how the initial heat has spread
out. Later on we will see how the diffusion equation represents the Black-Scholes equation with
modified boundary conditions. One can think of solving the Black-Scholes equation as solving
the heat equation in reverse. In that process the diffusion happens against the flow of time.
9.4
Cholesky Decomposition
The Cholesky decomposition method makes an appearance in Monte Carlo Methods where it
is used in simulating systems with correlated variables (we make use of it in the later chapter
on Stochastic Volatility models). Cholesky decomposition is applied to the correlation matrix,
providing a lower triangular matrix L, which when applied to a vector of uncorrelated samples, u,
produces the covariance vector of the system. Thus it is highly relevant for quantitative trading.
Cholesky decomposition assumes that the matrix being decomposed is Hermitian and positivedefinite. Since we are only interested in real-valued matrices, we can replace the property of
Hermitian with that of symmetric (i.e. the matrix equals its own transpose). Cholesky decomposition is approximately 2x faster than LU Decomposition, where it applies.
For a Hermitian, positive-definite matrix P , the algorithm creates a lower-triangular matrix
L, which when multiplied by its (conjugate) transpose, LT (which is of course upper triangular),
provides the original Hermitian, positive-definite matrix:
117
P = LLT
9.4.1
(9.1)
Algorithm
In order to solve for the lower triangular matrix, we will make use of the Cholesky-Banachiewicz
Algorithm. First, we calculate the values for L on the main diagonal. Subsequently, we calculate
the off-diagonals for the elements below the diagonal:
lkk
v
u
k1
X
u
2
t
=
akk
lkj
j=1
lik
9.4.2
k1
X
1
aik
lij lkj , i > k
lkk
j=1
Eigen Implementation
As with the LU Decomposition, we will make use of the Eigen headers to calculate the Cholesky
Decomposition. Firstly, we declare a typedef for a 4 4 matrix, as with the LU Decomposition.
We create and populate such a matrix and use the Eigen::LLT template to create the actual
Cholesky Decomposition. LLT here refers to the matrix expression LLT . Once we have the
Eigen::LLT object, we call the matrixL method to obtain the lower triangular matrix L. Finally,
we obtain its transpose and then check that the multiplication of both successfully reproduces
the matrix P .
#include <i o s t r e a m >
#include <Eigen / Dense>
i n t main ( ) {
typedef Eigen : : Matrix<double , 4 , 4> Matrix4x4 ;
// D e c l a r e a 4 x4 m a t r i x w i t h d e f i n e d e n t r i e s
Matrix4x4 p ;
p << 6 , 3 , 4 , 8 ,
3, 6, 5, 1,
4 , 5 , 10 , 7 ,
8 , 1 , 7 , 25;
118
// C r e a t e t h e L and LT m a t r i c e s (LLT)
Eigen : : LLT<Matrix4x4> l l t ( p ) ;
// Output L , t h e l o w e r t r i a n g u l a r m a t r i x
Matrix4x4 l = l l t . matrixL ( ) ;
s t d : : c o u t << L Matrix : \ n << l << s t d : : e n d l << s t d : : e n d l ;
// Check t h a t LLT = P
s t d : : c o u t << LLT Matrix : \ n << l u << s t d : : e n d l ;
return 0 ;
}
5 10
7 25
L Matrix :
2.44949
1.22474
2.12132
1.63299
1.41421
2.3094
3 . 2 6 5 9 9 1.41421
1.58771
3.13249
3.26599
LT Matrix :
2.44949
1.22474
1.63299
2.12132
1 . 4 1 4 2 1 1.41421
2.3094
1.58771
3.13249
119
LLT Matrix :
6
5 10
9.5
7 25
QR Decomposition
QR Decomposition is widely used in quantitative finance as the basis for the solution of the
linear least squares problem, which itself is used for statistical regression analysis.
One of the key benefits of using QR Decomposition over other methods for solving linear least
squares is that it is more numerically stable, albeit at the expense of being slower to execute.
Hence if you are performing a large quantity of regressions as part of a trading backtest, for
instance, you will need to consider very extensively whether QR Decomposition is the best fit
(excuse the pun).
For a square matrix A the QR Decomposition converts A into the product of an orthogonal
matrix Q (i.e. QT Q = I) and an upper triangular matrix R. Hence:
A = QR
There are a few different algorithms for calculating the matrices Q and R. We will outline
the method of Householder Reflections, which is known to be more numerically stable the the
alternative Gramm-Schmidt method. Ive outlined the Householder Reflections method below.
Note, the following explanation is an expansion of the extremely detailed article on QR Decomposition using Householder Reflections over at Wikipedia.
9.5.1
Algorithm
120
The first step is to create the vector x, which is the k-th column of the matrix A, for step k.
We define = sgn(xk )(||x||). The norm || || used here is the Euclidean norm. Given the first
column vector of the identity matrix, I of equal size to A,
u:
u = x + e1
Once we have the vector u, we need to convert it to a unit vector, which we denote as v:
v = u/||u||
Now we form the matrix Q out of the identity matrix I and the vector multiplication of v:
Q = I 2vvT
Q1 A = .
.
.
...
A0
The whole process is now repeated for the minor matrix A0 , which will give a second Householder matrix Q02 . Now we have to pad out this minor matrix with elements from the identity
matrix such that we can consistently multiply the Householder matrices together. Hence, we
define Qk as the block matrix:
Ik1
Qk =
0
Q0k
121
Once we have carried out t iterations of this process we have R as an upper triangular matrix:
R = Qt ...Q2 Q1 A
9.5.2
Eigen Implementation
As with the prior examples making use of the Eigen headers, calculation of a QR decomposition
is extremely straightforward. Firstly, we create a 3 3 matrix and populate it with values.
We then use the HouseholderQR template class to create a QR object. It supports a method
householderQ that returns the matrix Q described above.
#include <i o s t r e a m >
#include <Eigen / Dense>
i n t main ( ) {
// D e c l a r e a 3 x3 m a t r i x w i t h d e f i n e d e n t r i e s
Eigen : : MatrixXf p ( 3 , 3 ) ;
p << 1 2 , 51 , 4 ,
6 , 1 6 7 , 68 ,
4, 2 4 , 41;
s t d : : c o u t << Matrix P : \ n << p << s t d : : e n d l << s t d : : e n d l ;
// C r e a t e t h e H o u s e h o l d e r QR Matrix o b j e c t
Eigen : : HouseholderQR<Eigen : : MatrixXf> qr ( p ) ;
Eigen : : MatrixXf q = qr . householderQ ( ) ;
// Output Q, t h e H o u s e h o l d e r m a t r i x
s t d : : c o u t << Q Matrix : \ n << q << s t d : : e n d l << s t d : : e n d l ;
return 0 ;
122
We have now described four separate numerical linear algebra algorithms used extensively
in quantitative finance. In subsequent chapters we will apply these algorithms to specific quant
problems.
Chapter 10
10.1
The first stage in implementation is to briefly discuss the Black-Scholes analytic solution for the
price of a vanilla call or put option. Consider the price of a European Vanilla Call, C(S, t). S
is the underlying asset price, K is the strike price, r is the interest rate (or the risk-free rate),
T is the time to maturity and is the (constant) volatility of the underlying asset S. N is a
function which will be described in detail below. The analytical formula for C(S, t) is given by:
d1
d2
log(S/K) + (r +
=
T
= d1 T
123
2
2 )T
124
Making use of put-call parity, we can also price a European vanilla put, P (S, t), with the
following formula:
All that remains is to describe the function N , which is the cumulative distribution function
of the standard normal distribution. The formula for N is given by:
1
N (x) =
2
et
/2
dt
It would also help to have closed form solutions for the Greeks. These are the sensitivities
of the option price to the various underlying parameters. They will be calculated in the next
chapter. However, in order to calculate these sensitivities we need the formula for the probability
density function of the standard normal distribution which is given below:
2
1
f (x) = ex /2
2
10.2
C++ Implementation
This code will not consist of any object oriented or generic programming techniques at this
stage. Right now the goal is to help you understand a basic C++ implementation of a pricing
engine, without all of the additional object machinery to encapsulate away the mathematical
functions. Ive written out the program in full and then below Ill explain how each component
works subsequently:
#define
// S ta n da r d normal p r o b a b i l i t y d e n s i t y f u n c t i o n
double norm pdf ( const double& x ) {
return ( 1 . 0 / ( pow ( 2 M PI , 0 . 5 ) ) ) exp ( 0.5 xx ) ;
125
// An a p p r o x i m a t i o n t o t h e c u m u l a t i v e d i s t r i b u t i o n f u n c t i o n
// f o r t h e s t a n d a r d normal d i s t r i b u t i o n
// Note : This i s a r e c u r s i v e f u n c t i o n
double norm cdf ( const double& x ) {
double k = 1 . 0 / ( 1 . 0 + 0 . 2 3 1 6 4 1 9 x ) ;
double k sum = k ( 0 . 3 1 9 3 8 1 5 3 0 + k ( 0.356563782 + k ( 1 . 7 8 1 4 7 7 9 3 7 +
k ( 1.821255978 + 1 . 3 3 0 2 7 4 4 2 9 k ) ) ) ) ;
i f ( x >= 0 . 0 ) {
return ( 1 . 0 ( 1 . 0 / ( pow ( 2 M PI , 0 . 5 ) ) ) exp ( 0.5 xx ) k sum ) ;
} else {
return 1 . 0 norm cdf (x ) ;
}
}
// C a l c u l a t e t h e European v a n i l l a c a l l p r i c e b a s e d on
// u n d e r l y i n g S , s t r i k e K, r i s k f r e e r a t e r , v o l a t i l i t y o f
// u n d e r l y i n g sigma and time t o m a t u r i t y T
double c a l l p r i c e ( const double& S , const double& K, const double& r ,
const double& v , const double& T) {
return S norm cdf ( d j ( 1 , S , K, r , v , T) )K exp( r T)
norm cdf ( d j ( 2 , S , K, r , v , T) ) ;
}
// C a l c u l a t e t h e European v a n i l l a p u t p r i c e b a s e d on
// u n d e r l y i n g S , s t r i k e K, r i s k f r e e r a t e r , v o l a t i l i t y o f
// u n d e r l y i n g sigma and time t o m a t u r i t y T
double p u t p r i c e ( const double& S , const double& K, const double& r ,
126
// Option p r i c e
double K = 1 0 0 . 0 ;
// S t r i k e p r i c e
double r = 0 . 0 5 ;
// Riskf r e e r a t e (5%)
double v = 0 . 2 ;
// V o l a t i l i t y o f t h e u n d e r l y i n g (20%)
double T = 1 . 0 ;
// One y e a r u n t i l e x p i r y
// Then we c a l c u l a t e t h e c a l l / p u t v a l u e s
double c a l l = c a l l p r i c e ( S , K, r , v , T) ;
double put = p u t p r i c e ( S , K, r , v , T) ;
// F i n a l l y we o u t p u t t h e p a r a m e t e r s and p r i c e s
s t d : : c o u t << U n d e r l y i n g :
<< S << s t d : : e n d l ;
s t d : : c o u t << S t r i k e :
<< K << s t d : : e n d l ;
<< r << s t d : : e n d l ;
s t d : : c o u t << V o l a t i l i t y :
<< v << s t d : : e n d l ;
s t d : : c o u t << Maturity :
<< T << s t d : : e n d l ;
s t d : : c o u t << C a l l P r i c e :
<< c a l l << s t d : : e n d l ;
s t d : : c o u t << Put P r i c e :
return 0 ;
}
127
The next section imports the iostream and cmath libraries. This allows us to use the std :: cout
command to output code. In addition we now have access to the C mathematical functions such
as exp, pow, log and sqrt :
#include <i o s t r e a m >
#include <cmath>
Once we have the correct libraries imported we need to create the core statistics functions
which make up most of the computation for the prices. Here is the standard normal probability
density function. M PI is the C-standard mathematical constant for :
// S t a n d a r d normal p r o b a b i l i t y d e n s i t y f u n c t i o n
double norm pdf ( const double& x ) {
return ( 1 . 0 / ( pow ( 2 M PI , 0 . 5 ) ) ) exp ( 0.5 xx ) ;
}
The next statistics function is the approximation to the cumulative distribution function for
the standard normal distribution. This approximation is found in Joshi. Note that this is a
recursive function (i.e. it calls itself!):
// An a p p r o x i m a t i o n t o t h e c u m u l a t i v e d i s t r i b u t i o n f u n c t i o n
// f o r t h e s t a n d a r d normal d i s t r i b u t i o n
// Note : This i s a r e c u r s i v e f u n c t i o n
double norm cdf ( const double& x ) {
double k = 1 . 0 / ( 1 . 0 + 0 . 2 3 1 6 4 1 9 x ) ;
double k sum = k ( 0 . 3 1 9 3 8 1 5 3 0 + k ( 0.356563782 + k ( 1 . 7 8 1 4 7 7 9 3 7 +
k ( 1.821255978 + 1 . 3 3 0 2 7 4 4 2 9 k ) ) ) ) ;
i f ( x >= 0 . 0 ) {
return ( 1 . 0 ( 1 . 0 / ( pow ( 2 M PI , 0 . 5 ) ) ) exp ( 0.5 xx ) k sum ) ;
} else {
return 1 . 0 norm cdf (x ) ;
}
}
The last remaining set of functions are directly related to the pricing of European vanilla calls
and puts. We need to calculate the dj values first, otherwise we would be repeating ourselves by
adding the function code directly to each of call price and put price :
// This c a l c u l a t e s d j , f o r j i n { 1 , 2 } . This term a p p e a r s i n t h e c l o s e d
// form s o l u t i o n f o r t h e European c a l l or p u t p r i c e
128
Now that we have the cumulative distribution function for the standard normal distribution
(norm cdf) coded up, as well as the dj function, we can calculate the closed-form solution for the
European vanilla call price. You can see that it is a fairly simple formula, assuming that the
aforementioned functions have already been implemented:
// C a l c u l a t e t h e European v a n i l l a c a l l p r i c e b a s e d on
// u n d e r l y i n g S , s t r i k e K, r i s k f r e e r a t e r , v o l a t i l i t y o f
// u n d e r l y i n g sigma and time t o m a t u r i t y T
double c a l l p r i c e ( const double& S , const double& K, const double& r ,
const double& v , const double& T) {
return S norm cdf ( d j ( 1 , S , K, r , v , T) )K exp( r T)
norm cdf ( d j ( 2 , S , K, r , v , T) ) ;
}
And similarly for the vanilla put (this formula can be derived easily via put-call parity):
// C a l c u l a t e t h e European v a n i l l a p u t p r i c e b a s e d on
// u n d e r l y i n g S , s t r i k e K, r i s k f r e e r a t e r , v o l a t i l i t y o f
// u n d e r l y i n g sigma and time t o m a t u r i t y T
double p u t p r i c e ( const double& S , const double& K, const double& r ,
const double& v , const double& T) {
return S norm cdf ( d j ( 1 , S , K, r , v , T) )+K exp( r T)
norm cdf ( d j ( 2 , S , K, r , v , T) ) ;
}
Every C++ program must have a main program. This is where the functions described above
are actually called and the values derived are output to the console. We make use of the iostream
library to provide us with the std :: cout and std :: endl output handlers. Finally, we exit the
program:
i n t main ( i n t argc , char argv ) {
// F i r s t we c r e a t e t h e parameter l i s t
double S = 1 0 0 . 0 ;
// Option p r i c e
double K = 1 0 0 . 0 ;
// S t r i k e p r i c e
double r = 0 . 0 5 ;
// Riskf r e e r a t e (5%)
129
double v = 0 . 2 ;
// V o l a t i l i t y o f t h e u n d e r l y i n g (20%)
double T = 1 . 0 ;
// One y e a r u n t i l e x p i r y
// Then we c a l c u l a t e t h e c a l l / p u t v a l u e s
double c a l l = c a l l p r i c e ( S , K, r , v , T) ;
double put = p u t p r i c e ( S , K, r , v , T) ;
// F i n a l l y we o u t p u t t h e p a r a m e t e r s and p r i c e s
s t d : : c o u t << U n d e r l y i n g :
<< S << s t d : : e n d l ;
s t d : : c o u t << S t r i k e :
<< K << s t d : : e n d l ;
<< r << s t d : : e n d l ;
s t d : : c o u t << V o l a t i l i t y :
<< v << s t d : : e n d l ;
s t d : : c o u t << Maturity :
<< T << s t d : : e n d l ;
s t d : : c o u t << C a l l P r i c e :
<< c a l l << s t d : : e n d l ;
s t d : : c o u t << Put P r i c e :
return 0 ;
}
100
Strike :
100
RiskFree Rate :
0.05
Volatility :
0.2
Maturity :
Call Price :
10.4506
Put P r i c e :
5.57352
This code should give you a good idea of how closed form solutions to the Black-Scholes
equations can be coded up in a procedural manner with C++. The next steps are to calculate
the Greeks in the same vein, as closed form solutions exist, solutions for digital and power
options, as well as a basic Monte Carlo pricer with which to validate against.
We have now shown how to price a European option with analytic solutions. We were able
to take the closed-form solution of the Black-Scholes equation for a European vanilla call or put
and provide a price.
130
This is possible because the boundary conditions generated by the pay-off function of the
European vanilla option allow us to easily calculate a closed-form solution. Many option pay-off
functions lead to boundary conditions which are much harder to solve analytically and some are
impossible to solve this way. Thus in these situations we need to rely on numerical approximation.
We will now price the same European vanilla option with a very basic Monte Carlo solver in
C++ and then compare our numerical values to the analytical case. We wont be concentrating
on an extremely efficient or optimised implementation at this stage. Right now I just want to
show you how to get up and running to give you an understanding of how risk neutral pricing
works numerically. We will also see how our values differ from those generated analytically.
10.3
The first stage in implementation is to briefly discuss how risk neutral pricing works for a vanilla
call or put option. We havent yet discussed on risk neutral pricing in depth, so in the meantime,
it might help to have a look at the article on Geometric Brownian Motion on QuantStart.com,
which is the underlying model of stock price evolution that we will be using. It is given by the
following stochastic differential equation:
where S is the asset price, is the drift of the stock, is the volatility of the stock and B is
a Brownian motion (or Wiener process).
You can think of dB as being a normally distributed random variable with zero mean and
variance dt. We are going to use the fact that the price of a European vanilla option is given as
the discounted expectation of the option pay-off of the final spot price (at maturity T ):
This expectation is taken under the appropriate risk-neutral measure, which sets the drift
equal to the risk-free rate r:
131
We can make use of Itos Lemma to give us:
1 2
d log S(t) = r dt + dB(t)
2
This is a constant coefficient SDE and therefore the solution is given by:
1 2
log S(t) = log S(0) + r t + tN (0, 1)
2
Here Ive used the fact that since B(t) is a Brownian motion, it has the distribution of a
normal distribution with variance t and mean zero so it can be written as B(t) = tN (0, 1).
Rewriting the above equation in terms of S(t) by taking the exponential gives:
S(t) = S(0)e(r 2
)t+ tN (0,1)
Using the risk-neutral pricing method above leads to an expression for the option price as
follows:
)T + T N (0,1)
))
The key to the Monte Carlo method is to make use of the law of large numbers in order to
approximate the expectation. Thus the essence of the method is to compute many draws from
the normal distribution N (0, 1), as the variable x, and then compute the pay-off via the following
formula:
f (S(0)e(r 2
)T + T x
In this case the value of f is the pay-off for a call or put. By averaging the sum of these
pay-offs and then taking the risk-free discount we obtain the approximate price for the option.
132
10.4
C++ Implementation
For this implementation we wont be utilising any object oriented or generic programming techniques right now. At this stage the goal is to generate understanding of a basic C++ Monte
Carlo implementation. Ive written out the program in full and then below Ill explain how each
component works subsequently:
#include <a l g o r i t h m >
// Needed f o r t h e max f u n c t i o n
#include <cmath>
#include <i o s t r e a m >
// A s i m p l e i m p l e m e n t a t i o n o f t h e BoxM u l l e r a l g o r i t h m , used t o g e n e r a t e
// g a u s s i a n random numbers n e c e s s a r y f o r t h e Monte C a rl o method b e l o w
// Note t h a t C++11 a c t u a l l y p r o v i d e s s t d : : n o r m a l d i s t r i b u t i o n <> i n
// t h e <random> l i b r a r y , which can be used i n s t e a d o f t h i s f u n c t i o n
double g a u s s i a n b o x m u l l e r ( ) {
double x = 0 . 0 ;
double y = 0 . 0 ;
double e u c l i d s q = 0 . 0 ;
return x s q r t (2 l o g ( e u c l i d s q ) / e u c l i d s q ) ;
}
133
double p a y o f f s u m = 0 . 0 ;
// Number o f s i m u l a t e d a s s e t p a t h s
double S = 1 0 0 . 0 ;
// Option p r i c e
double K = 1 0 0 . 0 ;
// S t r i k e p r i c e
double r = 0 . 0 5 ;
// Riskf r e e r a t e (5%)
double v = 0 . 2 ;
// V o l a t i l i t y o f t h e u n d e r l y i n g (20%)
double T = 1 . 0 ;
// One y e a r u n t i l e x p i r y
134
// Then we c a l c u l a t e t h e c a l l / p u t v a l u e s v i a Monte C ar l o
double c a l l = m o n t e c a r l o c a l l p r i c e ( num sims , S , K, r , v , T) ;
double put = m o n t e c a r l o p u t p r i c e ( num sims , S , K, r , v , T) ;
// F i n a l l y we o u t p u t t h e p a r a m e t e r s and p r i c e s
s t d : : c o u t << Number o f Paths : << num sims << s t d : : e n d l ;
s t d : : c o u t << U n d e r l y i n g :
<< S << s t d : : e n d l ;
s t d : : c o u t << S t r i k e :
<< K << s t d : : e n d l ;
<< r << s t d : : e n d l ;
s t d : : c o u t << V o l a t i l i t y :
<< v << s t d : : e n d l ;
s t d : : c o u t << Maturity :
<< T << s t d : : e n d l ;
s t d : : c o u t << C a l l P r i c e :
<< c a l l << s t d : : e n d l ;
s t d : : c o u t << Put P r i c e :
return 0 ;
}
The first function to implement is the Box-Muller algorithm. As stated above, it is designed
to convert two uniform random variables into a standard Gaussian random variable. Box-Muller
is a good choice for a random number generator if your compiler doesnt support the C++11
standard. However, if you do have a compiler that supports it, you can make use of the std ::
normal distribution<> template class found in the <random> library.
// A s i m p l e i m p l e m e n t a t i o n o f t h e BoxM u l l e r a l g o r i t h m , used t o g e n e r a t e
// g a u s s i a n random numbers n e c e s s a r y f o r t h e Monte C a rl o method b e l o w
// Note t h a t C++11 a c t u a l l y p r o v i d e s s t d : : n o r m a l d i s t r i b u t i o n <> i n
// t h e <random> l i b r a r y , which can be used i n s t e a d o f t h i s f u n c t i o n
double g a u s s i a n b o x m u l l e r ( ) {
135
double x = 0 . 0 ;
double y = 0 . 0 ;
double e u c l i d s q = 0 . 0 ;
return x s q r t (2 l o g ( e u c l i d s q ) / e u c l i d s q ) ;
}
The next two functions are used to price a European vanilla call or put via the Monte Carlo
method. Weve outlined above how the theory works, so lets study the implementation itself.
The comments will give you the best overview. Ive neglected to include the function for the
put, as it is almost identical to the call and only differs in the nature of the pay-off. In fact, this
is a hint telling us that our code is possibly repeating itself (a violation of the Do-Not-RepeatYourself, DRY, principle):
// P r i c i n g a European v a n i l l a c a l l o p t i o n w i t h a Monte C ar l o method
double m o n t e c a r l o c a l l p r i c e ( const i n t& num sims , const double& S ,
const double& K, const double& r ,
const double& v , const double& T) {
double S a d j u s t = S exp (T ( r 0.5 vv ) ) ;
// The a d j u s t m e n t t o t h e s p o t
price
double S c u r = 0 . 0 ;
// Our c u r r e n t a s s e t p r i c e ( s p o t )
double p a y o f f s u m = 0 . 0 ;
o f f s
// A d j u s t t h e s p o t p r i c e v i a t h e Brownian motion f i n a l d i s t r i b u t i o n
136
The main function is extremely similar to that found in European vanilla option pricing
with C++ and analytic formulae. The exception is the addition of the num sims variable which
stores the number of calculated asset paths. The larger this value, the more accurate the option
price will be. Hence an inevitable tradeoff between execution time and price accuracy exists.
Unfortunately, this is not a problem limited to this book! Another modification is that the call
and put values are now derived from the Monte Carlo function calls:
i n t main ( i n t argc , char argv ) {
// F i r s t we c r e a t e t h e parameter l i s t
i n t num sims = 1 0 0 0 0 0 0 0 ;
// Number o f s i m u l a t e d a s s e t p a t h s
double S = 1 0 0 . 0 ;
// Option p r i c e
double K = 1 0 0 . 0 ;
// S t r i k e p r i c e
double r = 0 . 0 5 ;
// Riskf r e e r a t e (5%)
double v = 0 . 2 ;
// V o l a t i l i t y o f t h e u n d e r l y i n g (20%)
double T = 1 . 0 ;
// One y e a r u n t i l e x p i r y
// Then we c a l c u l a t e t h e c a l l / p u t v a l u e s v i a Monte C ar l o
double c a l l = m o n t e c a r l o c a l l p r i c e ( num sims , S , K, r , v , T) ;
double put = m o n t e c a r l o p u t p r i c e ( num sims , S , K, r , v , T) ;
// F i n a l l y we o u t p u t t h e p a r a m e t e r s and p r i c e s
s t d : : c o u t << Number o f Paths : << num sims << s t d : : e n d l ;
s t d : : c o u t << U n d e r l y i n g :
<< S << s t d : : e n d l ;
s t d : : c o u t << S t r i k e :
<< K << s t d : : e n d l ;
<< r << s t d : : e n d l ;
s t d : : c o u t << V o l a t i l i t y :
<< v << s t d : : e n d l ;
137
s t d : : c o u t << Maturity :
<< T << s t d : : e n d l ;
s t d : : c o u t << C a l l P r i c e :
<< c a l l << s t d : : e n d l ;
s t d : : c o u t << Put P r i c e :
return 0 ;
}
100
Strike :
100
RiskFree Rate :
0.05
Volatility :
0.2
Maturity :
Call Price :
10.4553
Put P r i c e :
5.57388
We can compare this with the output from the analytical formulae generated in European
vanilla option pricing with C++ and analytic formulae, which are given below for convenience.
This serves two purposes. Firstly, we can see that the values are almost identical, which provides
an extra level of validation that the code in both instances is pricing correctly. Secondly, we can
compare accuracy:
Call Price :
10.4506
Put P r i c e :
5.57352
As can be seen the prices are relatively accurate for 107 simulation paths. On my MacBook
Air this program took a few seconds to calculate. Adding a couple of orders of magnitude on
to the number of paths would quickly make the program prohibitive to execute. Thus we have
run into the first problem - that of optimising execution speed. The second is that of making
the above code maintainable. Notice that there is a significant duplication of code in both the
monte carlo call price and monte carlo put price functions.
138
Chapter 11
2C
S 2
C
C
S
C
t
Since all of the sensitivities are commonly denoted by letters of the Greek alphabet (except
Vega!) they have come to be known colloquially as the Greeks.
In this chapter we will calculate the Greeks using three separate methods. In the first instance
we will utilise formula derived directly from the analytic formulae for European vanilla call and
put options on a single asset. This will provide us with a baseline to determine the accuracy of
subsequent numerical methods.
139
140
The first numerical approach utilised will be based on a Finite Difference Method (FDM)
and the original analytical formulae. The second numerical method will use a combination of the
FDM technique and Monte Carlo for pricing. The latter approach is readily applicable to a wider
range of contingent claims as it is not dependent upon the existence of an analytic solution.
11.1
Analytic Formulae
The formulae of the Greeks for a European vanilla call and put option on a single asset are
tabulated below:
Calls
C
S
Delta,
Gamma,
Vega,
Theta,
Rho,
N (d1 ) 1
N (d1 )
0
2C
S 2
N (d1 )
S T t
SN 0 (d1 ) T t
C
t
Puts
(d )
1
rKer(T t) N (d2 )
SN
2 T t
(d )
1
SN
+ rKer(T t) N (d2 )
2 T t
C
r
2
2 )T )/( T ) and d2 = d1 T .
The following listing implements these methods in C++ making use of the formulae for the
probability functions and also includes a basic Monte Carlo pricer, both taken from the chapter
on European Options Pricing. Here is the listing for black scholes .h:
#define
// Needed f o r t h e max f u n c t i o n
// =================
// ANALYTIC FORMULAE
// =================
// S ta n da r d normal p r o b a b i l i t y d e n s i t y f u n c t i o n
141
// An a p p r o x i m a t i o n t o t h e c u m u l a t i v e d i s t r i b u t i o n f u n c t i o n
// f o r t h e s t a n d a r d normal d i s t r i b u t i o n
// Note : This i s a r e c u r s i v e f u n c t i o n
double norm cdf ( const double x ) {
double k = 1 . 0 / ( 1 . 0 + 0 . 2 3 1 6 4 1 9 x ) ;
double k sum = k ( 0 . 3 1 9 3 8 1 5 3 0 + k ( 0.356563782 + k ( 1 . 7 8 1 4 7 7 9 3 7 + k
( 1.821255978 + 1 . 3 3 0 2 7 4 4 2 9 k ) ) ) ) ;
i f ( x >= 0 . 0 ) {
return ( 1 . 0 ( 1 . 0 / ( pow ( 2 M PI , 0 . 5 ) ) ) exp ( 0.5 xx ) k sum ) ;
} else {
return 1 . 0 norm cdf (x ) ;
}
}
// C a l c u l a t e t h e European v a n i l l a c a l l p r i c e b a s e d on
// u n d e r l y i n g S , s t r i k e K, r i s k f r e e r a t e r , v o l a t i l i t y o f
// u n d e r l y i n g sigma and time t o m a t u r i t y T
double c a l l p r i c e ( const double S , const double K, const double r , const
double v , const double T) {
return S norm cdf ( d j ( 1 , S , K, r , v , T) )K exp( r T) norm cdf ( d j ( 2 ,
S , K, r , v , T) ) ;
}
// C a l c u l a t e t h e European v a n i l l a c a l l D e l t a
double c a l l d e l t a ( const double S , const double K, const double r , const
142
// C a l c u l a t e t h e European v a n i l l a c a l l Gamma
double call gamma ( const double S , const double K, const double r , const
double v , const double T) {
return norm pdf ( d j ( 1 , S , K, r , v , T) ) / ( Sv s q r t (T) ) ;
}
// C a l c u l a t e t h e European v a n i l l a c a l l Vega
double c a l l v e g a ( const double S , const double K, const double r , const
double v , const double T) {
return S norm pdf ( d j ( 1 , S , K, r , v , T) ) s q r t (T) ;
}
// C a l c u l a t e t h e European v a n i l l a c a l l Theta
double c a l l t h e t a ( const double S , const double K, const double r , const
double v , const double T) {
return (S norm pdf ( d j ( 1 , S , K, r , v , T) ) v ) / ( 2 s q r t (T) )
r K exp( r T) norm cdf ( d j ( 2 , S , K, r , v , T) ) ;
}
// C a l c u l a t e t h e European v a n i l l a c a l l Rho
double c a l l r h o ( const double S , const double K, const double r , const
double v , const double T) {
return KT exp( r T) norm cdf ( d j ( 2 , S , K, r , v , T) ) ;
}
// C a l c u l a t e t h e European v a n i l l a p u t p r i c e b a s e d on
// u n d e r l y i n g S , s t r i k e K, r i s k f r e e r a t e r , v o l a t i l i t y o f
// u n d e r l y i n g sigma and time t o m a t u r i t y T
double p u t p r i c e ( const double S , const double K, const double r , const
double v , const double T) {
return S norm cdf ( d j ( 1 , S , K, r , v , T) )+K exp( r T) norm cdf ( d j ( 2 ,
S , K, r , v , T) ) ;
}
143
// C a l c u l a t e t h e European v a n i l l a p u t D e l t a
double p u t d e l t a ( const double S , const double K, const double r , const
double v , const double T) {
return norm cdf ( d j ( 1 , S , K, r , v , T) ) 1 ;
}
// C a l c u l a t e t h e European v a n i l l a p u t Gamma
double put gamma ( const double S , const double K, const double r , const
double v , const double T) {
return call gamma ( S , K, r , v , T) ; // I d e n t i c a l t o c a l l by putc a l l p a r i t y
}
// C a l c u l a t e t h e European v a n i l l a p u t Vega
double p u t v e g a ( const double S , const double K, const double r , const
double v , const double T) {
return c a l l v e g a ( S , K, r , v , T) ; // I d e n t i c a l t o c a l l by putc a l l p a r i t y
}
// C a l c u l a t e t h e European v a n i l l a p u t Theta
double p u t t h e t a ( const double S , const double K, const double r , const
double v , const double T) {
return (S norm pdf ( d j ( 1 , S , K, r , v , T) ) v ) / ( 2 s q r t (T) )
+ r K exp( r T) norm cdf ( d j ( 2 , S , K, r , v , T) ) ;
}
// C a l c u l a t e t h e European v a n i l l a p u t Rho
double p u t r h o ( const double S , const double K, const double r , const double
v , const double T) {
return TK exp( r T) norm cdf ( d j ( 2 , S , K, r , v , T) ) ;
}
// ===========
// MONTE CARLO
// ===========
// A s i m p l e i m p l e m e n t a t i o n o f t h e BoxM u l l e r a l g o r i t h m , used t o g e n e r a t e
144
return x s q r t (2 l o g ( e u c l i d s q ) / e u c l i d s q ) ;
}
Here is the listing for the main.cpp file making use of the above header:
#include b l a c k s c h o l e s . h
// Option p r i c e
double K = 1 0 0 . 0 ;
// S t r i k e p r i c e
double r = 0 . 0 5 ;
// Riskf r e e r a t e (5%)
double v = 0 . 2 ;
// V o l a t i l i t y o f t h e u n d e r l y i n g (20%)
double T = 1 . 0 ;
// One y e a r u n t i l e x p i r y
145
double c a l l r h o v = c a l l r h o ( S , K, r , v , T) ;
double put = p u t p r i c e ( S , K, r , v , T) ;
double p u t d e l t a v = p u t d e l t a ( S , K, r , v , T) ;
double put gamma v = put gamma ( S , K, r , v , T) ;
double p u t v e g a v = p u t v e g a ( S , K, r , v , T) ;
double p u t t h e t a v = p u t t h e t a ( S , K, r , v , T) ;
double p u t r h o v = p u t r h o ( S , K, r , v , T) ;
// F i n a l l y we o u t p u t t h e p a r a m e t e r s and p r i c e s
s t d : : c o u t << U n d e r l y i n g :
<< S << s t d : : e n d l ;
s t d : : c o u t << S t r i k e :
<< K << s t d : : e n d l ;
<< r << s t d : : e n d l ;
s t d : : c o u t << V o l a t i l i t y :
<< v << s t d : : e n d l ;
s t d : : c o u t << Maturity :
s t d : : c o u t << C a l l P r i c e :
<< c a l l << s t d : : e n d l ;
s t d : : c o u t << C a l l D e l t a :
<< c a l l d e l t a v << s t d : : e n d l ;
s t d : : c o u t << C a l l Gamma:
<< c a l l g a m m a v << s t d : : e n d l ;
s t d : : c o u t << C a l l Vega :
<< c a l l v e g a v << s t d : : e n d l ;
s t d : : c o u t << C a l l Theta :
<< c a l l t h e t a v << s t d : : e n d l ;
s t d : : c o u t << C a l l Rho :
s t d : : c o u t << Put P r i c e :
s t d : : c o u t << Put D e l t a :
<< p u t d e l t a v << s t d : : e n d l ;
<< p u t v e g a v << s t d : : e n d l ;
<< p u t t h e t a v << s t d : : e n d l ;
<< p u t r h o v << s t d : : e n d l ;
return 0 ;
}
100
Strike :
100
RiskFree Rate :
0.05
146
Volatility :
0.2
Maturity :
Call Price :
10.4506
Call Delta :
0.636831
C a l l Gamma:
0.018762
C a l l Vega :
37.524
C a l l Theta :
6.41403
C a l l Rho :
53.2325
Put P r i c e :
5.57352
Put D e l t a :
0.363169
Put Gamma:
0.018762
Put Vega :
37.524
Put Theta :
1.65788
Put Rho :
41.8905
We are now going to compare the analytical prices with those derived from a Finite Difference
Method.
11.2
FDM is widely used in derivatives pricing (as well as engineering/physics in general) to solve
partial differential equations (PDE). In fact, we will see in a later chapter how to use FDM to
solve the Black-Scholes equation in a numerical manner. In this section, however, we are going to
apply the same technique, namely the discretisation of the partial derivatives, to create a simple
approximation to the Greek sensitivities with which we can compare to the analytical solution.
The essence of the method is that we will approximate the partial derivative representing
the particular sensitivity of interest. To do this we make use of the analytical formulae for the
Black-Scholes prices of the call and puts. These formulae are covered in the previous chapter on
European Options pricing.
As an example, lets assume we want to calculate the Delta of a call option. The Delta is
given by
C
S (S, T, , r, K).
If we calculate two call prices, one at spot S and the other at spot
147
C
C(S + S, T, , r, K) C(S, T, , r, K)
S
S
(11.1)
Each of the additional first order sensitivities (Vega, Rho and Theta) can be calculated in this
manner by simply incrementing the correct parameter dimension. Gamma on the other hand is
a second order derivative and so must be approximated in a different way. The usual approach
in FDM is to use a central difference approximation to produce the following formula:
2
S
(S)2
(11.2)
At this stage we will keep the code procedural as we wish to emphasise the mathematical
formulae. We are now able to implement the FDM numerical approximations in C++. For the
sake of brevity we will restrict ourselves to the calculation of the call Delta and Gamma, as the
remaining sensitivities are similar:
#include b l a c k s c h o l e s . h
// This u s e s t h e f o r w a r d d i f f e r e n c e a p p r o x i m a t i o n t o c a l c u l a t e t h e D e l t a o f
a c a l l option
double c a l l d e l t a f d m ( const double S , const double K, const double r , const
double v , const double T, const double d e l t a S ) {
return ( c a l l p r i c e ( S + d e l t a S , K, r , v , T) c a l l p r i c e ( S , K, r , v , T) ) /
delta S ;
}
// This u s e s t h e c e n t r e d d i f f e r e n c e a p p r o x i m a t i o n t o c a l c u l a t e t h e Gamma o f
a c a l l option
double call gamma fdm ( const double S , const double K, const double r , const
double v , const double T, const double d e l t a S ) {
return ( c a l l p r i c e ( S + d e l t a S , K, r , v , T) 2 c a l l p r i c e ( S , K, r , v , T)
+ c a l l p r i c e ( S d e l t a S , K, r , v , T) ) / ( d e l t a S d e l t a S ) ;
}
148
double S = 1 0 0 . 0 ;
// Option p r i c e
double d e l t a S = 0 . 0 0 1 ;
// Option p r i c e i n c r e m e n t
double K = 1 0 0 . 0 ;
// S t r i k e p r i c e
double r = 0 . 0 5 ;
// Riskf r e e r a t e (5%)
double v = 0 . 2 ;
// V o l a t i l i t y o f t h e u n d e r l y i n g (20%)
double T = 1 . 0 ;
// One y e a r u n t i l e x p i r y
// F i n a l l y we o u t p u t t h e p a r a m e t e r s and g r e e k s
s t d : : c o u t << U n d e r l y i n g :
<< S << s t d : : e n d l ;
s t d : : c o u t << D e l t a u n d e r l y i n g :
<< d e l t a S << s t d : : e n d l ;
s t d : : c o u t << S t r i k e :
<< K << s t d : : e n d l ;
<< r << s t d : : e n d l ;
s t d : : c o u t << V o l a t i l i t y :
<< v << s t d : : e n d l ;
s t d : : c o u t << Maturity :
s t d : : c o u t << C a l l D e l t a :
<< c a l l d e l t a f << s t d : : e n d l ;
s t d : : c o u t << C a l l Gamma:
<< c a l l g a m m a f << s t d : : e n d l ;
Underlying :
100
Delta underlying :
0.001
Strike :
100
RiskFree Rate :
0.05
Volatility :
0.2
Maturity :
Call Delta :
0.636845
C a l l Gamma:
0.0187633
149
11.3
The final method of calculating the Greeks is to use a combination of the FDM and Monte Carlo.
The overall method is the same as above, with the exception that we will replace the analytical
prices of the call/puts in the Finite Difference approximation and use a Monte Carlo engine
instead to calculate the prices. This method is significantly more versatile as it can be extended
to many differing types of contingent claim prices.
It is extremely important to re-use the random draws from the initial Monte Carlo simulation in calculating the incremented/decremented parameter paths. Thus, when calculating
C(S, K, r, v, T ) and C(S + S, K, r, v, T ) it is necessary to use the same random draws. Further,
it is significantly more optimal as there is no need to calculate additional asset paths for each
incremented parameter instance.
The following code calculates the Monte Carlo price for the Delta and the Gamma, making
use of separate Monte Carlo prices for each instance. The essence of the Monte Carlo method
is to calculate three separate stock paths, all based on the same Gaussian draws. Each of these
draws will represent an increment (or not) to the asset path parameter, S. Once those paths
have been generated a price for each can be calculated. This price is then substituted into the
FDM derivative approximations, in exactly the same way as with the analytical formulae.
The final prices are passed to the monte carlo call price function by reference and the function
itself is void. This provides a simple mechanism for returning multiple doubles from the function.
An alternative would be to pass a vector of values by reference, to be set.
It is straightforward to modify the code to calculate the Vega, Rho or Theta (based on the
Delta). A more production ready implementation would utilise an object-oriented framework.
However, for the purposes of this chapter an OOP-based implementation would likely obscure
the algorithm:
#include b l a c k s c h o l e s . h
150
// These w i l l s t o r e a l l t h r e e c u r r e n t p r i c e s as t h e Monte C a rl o
// a l g o r i t h m i s c a r r i e d o u t
double S p c u r = 0 . 0 ;
double S c u r = 0 . 0 ;
double Sm cur = 0 . 0 ;
// Loop o v e r t h e number o f s i m u l a t i o n s
f o r ( i n t i =0; i <num sims ; i ++) {
double gauss bm = g a u s s i a n b o x m u l l e r ( ) ; // Random g a u s s i a n draw
// A d j u s t t h r e e s t o c k p a t h s
double e x p g a u s s = exp ( s q r t ( vvT) gauss bm ) ;
// P r e c a l c u l a t e
151
// There a r e t h r e e s e p a r a t e p r i c e s
p r i c e S p = ( p a y o f f s u m p / s t a t i c c a s t <double>(num sims ) ) exp( r T) ;
p r i c e S = ( p a y o f f s u m / s t a t i c c a s t <double>(num sims ) ) exp( r T) ;
p r i c e S m = ( payoff sum m / s t a t i c c a s t <double>(num sims ) ) exp( r T) ;
}
// C a l l t h e Monte Ca r lo p r i c e r f o r each o f t h e t h r e e s t o c k p a t h s
// (We o n l y need two f o r t h e D e l t a )
m o n t e c a r l o c a l l p r i c e ( num sims , S , K, r , v , T, d e l t a S , p r i c e S p ,
price S , price Sm ) ;
return ( p r i c e S p p r i c e S ) / d e l t a S ;
}
double call gamma mc ( const i n t num sims , const double S , const double K,
const double r , const double v , const double T, const double d e l t a S ) {
// These v a l u e s w i l l be p o p u l a t e d v i a t h e m o n t e c a r l o c a l l p r i c e f u n c t i o n
.
// They r e p r e s e n t t h e i n c r e m e n t e d Sp ( S+d e l t a S ) , noni n c r e m e n t e d S ( S )
and
// decremented Sm ( Sd e l t a S ) p r i c e s .
double p r i c e S p = 0 . 0 ;
double p r i c e S = 0 . 0 ;
double p r i c e S m = 0 . 0 ;
// C a l l t h e Monte Ca r lo p r i c e r f o r each o f t h e t h r e e s t o c k p a t h s
152
// Option p r i c e
double d e l t a S = 0 . 0 0 1 ;
// Option p r i c e i n c r e m e n t
double K = 1 0 0 . 0 ;
// S t r i k e p r i c e
double r = 0 . 0 5 ;
// Riskf r e e r a t e (5%)
double v = 0 . 2 ;
// V o l a t i l i t y o f t h e u n d e r l y i n g (20%)
double T = 1 . 0 ;
// One y e a r u n t i l e x p i r y
i n t num sims = 1 0 0 0 0 0 0 0 0 ;
// Number o f s i m u l a t i o n s t o c a r r y o u t f o r
Monte Ca r l o
// F i n a l l y we o u t p u t t h e p a r a m e t e r s and g r e e k s
s t d : : c o u t << Number o f s i m s :
s t d : : c o u t << U n d e r l y i n g :
<< S << s t d : : e n d l ;
s t d : : c o u t << D e l t a u n d e r l y i n g :
<< d e l t a S << s t d : : e n d l ;
s t d : : c o u t << S t r i k e :
<< K << s t d : : e n d l ;
<< r << s t d : : e n d l ;
s t d : : c o u t << V o l a t i l i t y :
<< v << s t d : : e n d l ;
s t d : : c o u t << Maturity :
s t d : : c o u t << C a l l D e l t a :
<< c a l l d e l t a m << s t d : : e n d l ;
s t d : : c o u t << C a l l Gamma:
100000000
Underlying :
100
153
Analytic
FDM/Analytic
FDM/MC
Delta underlying :
0.001
Strike :
100
RiskFree Rate :
0.05
Volatility :
0.2
Maturity :
Call Delta :
0.636894
C a l l Gamma:
0.0188733
Delta
Gamma
0.636831
0.636845
0.636894
0.018762
0.0187633
0.0188733
Here is a summary table with the calculation of the Delta and Gamma for a European vanilla
call option across all of the methods listed above:
The values are extremely similar, even for the Monte Carlo based approach, albeit at the
computational expense of generating the random draws. The FDM/MC approach can be applied
to other contingent claims where an analytical solution is not forthcoming and this is often the
method used in practice.
154
Chapter 12
Asian/Path-Dependent Options
with Monte Carlo
In this chapter Im going to discuss how to price a certain type of Exotic option known as a
Path-Dependent Asian in C++ using Monte Carlo Methods. It is considered exotic in the
sense that the pay-off is a function of the underlying asset at multiple points throughout its
lifetime, rather than just the value at expiry. It is an example of a multi look option. An Asian
option utilises the mean of the underlying asset price sampled at appropriate intervals along its
lifetime as the basis for its pay-off, which is where the path-dependency of the asset comes
from. The name actually arises because they were first devised in 1987 in Tokyo as options on
crude oil futures.
There are two types of Asian option that we will be pricing. They are the arithmetic Asian and
the geometric Asian. They differ only in how the mean of the underlying values is calculated. We
will be studying discrete Asian options in this chapter, as opposed to the theoretical continuous
Asian options. The first step will be to break down the components of the program and construct
a set of classes that represent the various aspects of the pricer. Before that however, I will brief
explain how Asian options work (and are priced by Monte Carlo).
12.1
An Asian option is a type of exotic option. Unlike a vanilla European option where the price of
the option is dependent upon the price of the underlying asset at expiry, an Asian option pay-off
is a function of multiple points up to and including the price at expiry. Thus it is path-dependent
155
156
as the price relies on knowing how the underlying behaved at certain points before expiry. Asian
options in particular base their price off the mean average price of these sampled points. To
simplify this chapter we will consider N equally distributed sample points beginning at time
t = 0 and ending at maturity, T .
Unlike in the vanilla European option Monte Carlo case, where we only needed to generate
multiple spot values at expiry, we now need to generate multiple asset paths, each sampled at
multiple equally-spaced points in time. Thus instead of providing a double value representing
spot to our option, we now need to provide a std :: vector<double> (i.e. a vector of double
values), each element of which represents a sample of the asset price on a particular path. We
will still be modelling our asset price path via a Geometric Brownian Motion (GBM), and we
will create each path by adding the correct drift and variance at each step in order to maintain
the properties of GBM.
In order to calculate the arithmetic mean A of the asset prices we use the following formula:
A(0, T ) =
N
1 X
S(ti )
N i=1
where S(ti ) is the underlying asset price at the i-th time sample. Similarly for the geometric
mean:
A(0, T ) = exp
N
1 X
log(S(ti ))
N i=1
These two formulae will then form the basis of our pay off price method, which is attached
to our AsianOption class.
12.2
In order to increase maintainability we will separate the components of the Asian options pricer.
As mentioned in the chapter on option pay-off hierarchies we are able to create an abstract base
class called PayOff, which defines an interface that all subsequent inherited pay-off classes will
implement. The major benefit of this approach is that we can encapsulate multiple various
types of pay-off functionality without the need to modify the remaining classes, such as our
AsianOption class (to be discussed below). We make use of the operator() to turn our pay-off
classes into a functor (i.e. a function object). This means that we can call the object just
157
as we would a function. Calling a PayOff object has the effect of calculating the pay-off and
returning it.
Here is the declaration for the PayOff base class:
c l a s s PayOff {
public :
PayOff ( ) ; // D e f a u l t ( no parameter ) c o n s t r u c t o r
v i r t u a l PayOff ( ) { } ; // V i r t u a l d e s t r u c t o r
// O v e r l o a d e d ( ) o p e r a t o r , t u r n s t h e PayOff i n t o an a b s t r a c t f u n c t i o n
object
v i r t u a l double operator ( ) ( const double& S ) const = 0 ;
};
The second class will represent many aspects of the exotic path-dependent Asian option.
This class will be called AsianOption. It will once again be an abstract base class, which means
that it contains at least one pure virtual function. Since there are many types of Asian option
- arithmetic, geometric, continuous, discrete - we will once again make use of an inheritance
hierarchy. In particular, we will override the pay off price function, which determines how the
mean pay-off is to be calculated, once the appropriate PayOff object has been provided.
Here is the declaration for the AsianOption base class:
c l a s s AsianOption {
protected :
PayOff p a y o f f ;
// P o i n t e r t o payo f f c l a s s ( i n t h i s i n s t a n c e c a l l or
put )
public :
AsianOption ( PayOff
pay off ) ;
v i r t u a l AsianOption ( ) { } ;
// Pure v i r t u a l payo f f o p e r a t o r ( t h i s w i l l d e t e r m i n e a r i t h m e t i c or
geometric )
v i r t u a l double p a y o f f p r i c e ( const s t d : : v e c t o r <double>& s p o t p r i c e s )
const = 0 ;
};
In C++ there is usually a trade-off between simplicity and extensibility. More often than
not a program must become more complex if it is to be extendable elsewhere. Thus we have
158
the choice of creating a separate object to handle the path generation used by the Asian option
or write it procedurally. In this instance I have elected to use a procedural approach because I
dont feel that delving into the complexities of random number generation classes has a beneficial
effect on learning how Asian options are priced at this stage.
12.3
Pay-Off Classes
The first class we will consider is the PayOff. As stated above this is an abstract base class and
so can never be instantiated directly. Instead it provides an interface through which all inherited
classes will be bound to. The destructor is virtual to avoid memory leaks when the inherited
and base classes are destroyed. Well take a look at the full listing for the payoff .h header file
and then step through the important sections:
#i f n d e f
PAY OFF H
#define
PAY OFF H
c l a s s PayOff {
public :
PayOff ( ) ; // D e f a u l t ( no parameter ) c o n s t r u c t o r
v i r t u a l PayOff ( ) { } ; // V i r t u a l d e s t r u c t o r
// O v e r l o a d e d ( ) o p e r a t o r , t u r n s t h e PayOff i n t o an a b s t r a c t f u n c t i o n
object
v i r t u a l double operator ( ) ( const double& S ) const = 0 ;
};
c l a s s P a y O f f C a l l : public PayOff {
private :
double K; // S t r i k e p r i c e
public :
P a y O f f C a l l ( const double& K ) ;
virtual PayOffCall ( ) {};
159
public :
PayOffPut ( const double& K ) ;
v i r t u a l PayOffPut ( ) { } ;
v i r t u a l double operator ( ) ( const double& S ) const ;
};
#endif
The declaration for the PayOff class is straightforward except for the overload of operator().
The syntax says that this method cannot be implemented within this class (= 0) and that it
should be overridden by an inherited class (virtual). It is also a const method as it does not
modify anything. It simply takes a spot price S and returns the option pay-off, for a given strike,
K:
// O v e r l o a d e d ( ) o p e r a t o r , t u r n s t h e PayOff i n t o an a b s t r a c t f u n c t i o n
object
v i r t u a l double operator ( ) ( const double& S ) const = 0 ;
Beyond the PayOff class we implement the PayOffCall and PayOffPut classes which implement
call and put functionality. We could also introduce digital and double digital pay-off classes here,
but for the purposes of demonstrating Asian options, I will leave that as an exercise! Notice now
that in each of the declarations of the operator() the = 0 syntax of the pure virtual declaration
has been removed. This states that the methods should be implemented by these classes:
// V i r t u a l f u n c t i o n i s now overr i d d e n ( n o t purev i r t u a l anymore )
v i r t u a l double operator ( ) ( const double& S ) const ;
That sums up the header file for the PayOff class hierarchy. We will now look at the source
file (below) and then step through the important sections:
#i f n d e f
#define
160
#include p a y o f f . h
PayOff : : PayOff ( ) {}
// ==========
// P a y O f f C a l l
// ==========
// C o n s t r u c t o r w i t h s i n g l e s t r i k e parameter
P a y O f f C a l l : : P a y O f f C a l l ( const double& K ) { K = K ; }
// =========
// PayOffPut
// =========
// C o n s t r u c t o r w i t h s i n g l e s t r i k e parameter
PayOffPut : : PayOffPut ( const double& K ) {
K = K;
}
#endif
The only major point of note within the source file is the implementation of the call and
put operator() methods. They make use of the std :: max function found within the <algorithm>
standard library:
161
..
The concludes the PayOff class hierarchy. It is quite straightforward and were not using any
real advanced features beyond abstract base classes and pure virtual functions. However, we
have written quite a lot of code to encapsulate something seemingly as simple as a simple pay-off
function. The benefits of such an approach will become clear later on, when or if we decide we
wish to create more complex pay-offs as we will not need to modify our AsianOption class to do
so.
12.4
To generate the paths necessary for an Asian option we will use a procedural approach. Instead of
encapsulating the random number generator and path generator in a set of objects, we will make
use of two functions, one of which generates the random Gaussian numbers (via the Box-Muller
method) and the other which takes these numbers and generates sampled Geometric Brownian
Motion asset paths for use in the AsianOption object. The chapter on European option pricing
via Monte Carlo explains the concept of risk-neutral pricing, Monte Carlo techniques and the
Box-Muller method. I have included the full function in the listing below for completeness.
The second function, calc path spot prices is more relevant. It takes a reference to a vector
of doubles (std :: vector<double>&) and updates it to reflect a random asset path based on a set
of random Gaussian increments of the asset price. I will show the full listing for the header file
which contains these functions, then I will step through calc path spot prices in detail:
#i f n d e f
PATH GENERATION H
#define
PATH GENERATION H
162
do {
x = 2 . 0 rand ( ) / s t a t i c c a s t <double>(RAND MAX) 1;
y = 2 . 0 rand ( ) / s t a t i c c a s t <double>(RAND MAX) 1;
e u c l i d s q = xx + yy ;
} while ( e u c l i d s q >= 1 . 0 ) ;
return x s q r t (2 l o g ( e u c l i d s q ) / e u c l i d s q ) ;
}
// This p r o v i d e s a v e c t o r c o n t a i n i n g sampled p o i n t s o f a
// Geometric Brownian Motion s t o c k p r i c e p a t h
void c a l c p a t h s p o t p r i c e s ( s t d : : v e c t o r <double>& s p o t p r i c e s ,
// A s s e t p a t h
const double& r ,
// R i s k f r e e i n t e r e s t r a t e
const double& v ,
// V o l a t i l i t y o f u n d e r l y i n g
const double& T) { // E x p i r y
// S i n c e t h e d r i f t and v o l a t i l i t y o f t h e a s s e t a r e c o n s t a n t
// we w i l l p r e c a l c u l a t e as much as p o s s i b l e f o r maximum e f f i c i e n c y
double dt = T/ s t a t i c c a s t <double>( s p o t p r i c e s . s i z e ( ) ) ;
double d r i f t = exp ( dt ( r 0.5 vv ) ) ;
double v o l = s q r t ( vv dt ) ;
163
#endif
Turning our attention to calc path spot prices we can see that the function requires a vector
of doubles, the risk-free rate r, the volatility of the underlying and the time at expiry, T :
void c a l c p a t h s p o t p r i c e s ( s t d : : v e c t o r <double>& s p o t p r i c e s ,
const double& r ,
// A s s e t p a t h
// R i s k f r e e i n t e r e s t r a t e (
constant )
const double& v ,
// V o l a t i l i t y o f u n d e r l y i n g (
constant )
const double& T) { // E x p i r y
Since we are dealing with constant increments of time for our path sampling frequency we
need to calculate this increment. These increments are always identical so in actual fact it can be
pre-calculated outside of the loop for the spot price path generation. Similarly, via the properties
of Geometric Brownian Motion, we know that we can increment the drift and variance of the
asset in a manner which can be pre-computed. The only difference between this increment and
the European case is that we are replacing T with dt for each subsequent increment of the path.
See the European option pricing chapter for a comparison:
double dt = T/ s t a t i c c a s t <double>( s p o t p r i c e s . s i z e ( ) ) ;
double d r i f t = exp ( dt ( r 0.5 vv ) ) ;
double v o l = s q r t ( vv dt ) ;
The final part of the function calculates the new spot prices by iterating over the spot price
vector and adding the drift and variance to each piece. We are using the arithmetic of logarithms
here, and thus can multiply by our drift and variance terms, since it is the log of the asset price
that is subject to normally distributed increments in Geometric Brownian Motion. Notice that
the loop runs from i = 1, not i = 0. This is because the spot price vector is already pre-filled
with S, the initial asset price S(0), elsewhere in the program:
f o r ( i n t i =1; i <s p o t p r i c e s . s i z e ( ) ; i ++) {
double gauss bm = g a u s s i a n b o x m u l l e r ( ) ;
s p o t p r i c e s [ i ] = s p o t p r i c e s [ i 1] d r i f t exp ( v o l gauss bm ) ;
}
That concludes the path-generation header file. Now well take a look at the AsianOption
classes themselves.
164
12.5
The final component of our program (besides the main file of course!) is the Asian option
inheritance hierarchy. We wish to price multiple types of Asian option, including geometric Asian
options and arithmetic Asian options. One way to achieve this is to have separate methods on an
AsianOption class. However this comes at the price of having to continually add more methods if
we wish to make more granular changes to our AsianOption class. In a production environment
this would become unwieldy. Instead we can use the abstract base class approach and generate
an abstract AsianOption class with a pure virtual method for pay off price . This method is
implemented in subclasses and determines how the averaging procedure for the asset prices over
the asset path lifetime will occur. Two publicly-inherited subclasses AsianOptionArithmetic and
AsianOptionGeometric implement this method.
Lets take a look at the full listing for the header declaration file and then well step through
the interesting sections:
#i f n d e f
ASIAN H
#define
ASIAN H
c l a s s AsianOption {
protected :
PayOff p a y o f f ;
// Payo f f c l a s s ( i n t h i s i n s t a n c e c a l l or p u t )
public :
AsianOption ( PayOff
pay off ) ;
v i r t u a l AsianOption ( ) { } ;
// Pure v i r t u a l payo f f o p e r a t o r ( t h i s w i l l d e t e r m i n e a r i t h m e t i c or
geometric )
v i r t u a l double p a y o f f p r i c e ( const s t d : : v e c t o r <double>& s p o t p r i c e s )
const = 0 ;
};
c l a s s A s i a n O p t i o n A r i t h m e t i c : public AsianOption {
public :
165
A s i a n O p t i o n A r i t h m e t i c ( PayOff
pay off ) ;
// O v e r r i d e t h e pure v i r t u a l f u n c t i o n t o p r o d u c e a r i t h m e t i c Asian O p t i o n s
v i r t u a l double p a y o f f p r i c e ( const s t d : : v e c t o r <double>& s p o t p r i c e s )
const ;
};
pay off ) ;
v i r t u a l AsianOptionGeometric ( ) { } ;
// O v e r i d e t h e pure v i r t u a l f u n c t i o n t o p r o d u c e g e o m e t r i c Asian O p t i o n s
v i r t u a l double p a y o f f p r i c e ( const s t d : : v e c t o r <double>& s p o t p r i c e s )
const ;
};
#endif
The first thing to notice about the abstract AsianOption class is that it has a pointer to a
PayOff class as a protected member:
protected :
PayOff p a y o f f ;
// Payo f f c l a s s ( i n t h i s i n s t a n c e c a l l or p u t )
This is an example of polymorphism. The object will not know what type of PayOff class
will be passed in. It could be a PayOffCall or a PayOffPut. Thus we can use a pointer to the
PayOff abstract class to represent storage of this as-yet-unknown PayOff class. Note also that
pay off ) ;
Finally we have the declaration for the pure virtual pay off price method. This takes a vector
of spot prices, but notice that it is passed as a reference to const, so this vector will not be
modified within the method:
// Pure v i r t u a l payo f f o p e r a t o r ( t h i s w i l l d e t e r m i n e a r i t h m e t i c or
geometric )
166
The listings for the AsianOptionArithmetic and AsianOptionGeometric classes are analogous to
those in the PayOff hierarchy, with the exception that their constructors take a pointer to a
PayOff object. That concludes the header file.
The source file essentially implements the two pay off price methods for the inherited subclasses of AsianOption:
#i f n d e f
ASIAN CPP
#define
ASIAN CPP
#include <numeric>
#include <cmath>
// N e c e s s a r y f o r s t d : : a c c u m u l a t e
// For l o g / exp f u n c t i o n s
#include a s i a n . h
// =====================
// A s i a n O p t i o n A r i t h m e t i c
// =====================
p a y o f f ) : p a y o f f ( p a y o f f ) {}
// =====================
// A s i a n O p t i o n A r i t h m e t i c
// =====================
A s i a n O p t i o n A r i t h m e t i c : : A s i a n O p t i o n A r i t h m e t i c ( PayOff
pay off )
: AsianOption ( p a y o f f ) {}
// A r i t h m e t i c mean payo f f p r i c e
double A s i a n O p t i o n A r i t h m e t i c : : p a y o f f p r i c e (
const s t d : : v e c t o r <double>& s p o t p r i c e s ) const {
unsigned num times = s p o t p r i c e s . s i z e ( ) ;
double sum = s t d : : accumulate ( s p o t p r i c e s . b e g i n ( ) , s p o t p r i c e s . end ( ) , 0 ) ;
double a r i t h m e a n = sum / s t a t i c c a s t <double>(num times ) ;
return ( p a y o f f ) ( a r i t h m e a n ) ;
}
167
// ====================
// AsianOptionGeometric
// ====================
pay off )
: AsianOption ( p a y o f f ) {}
#endif
Lets take a look at the arithmetic option version. First of all we determine the number of
sample points via the size of the spot price vector. Then we use the std :: accumulate algorithm
and iterator syntax to sum the spot values in the vector. Finally we take the arithmetic mean of
those values and use pointer dereferencing to call the operator() for the PayOff object. For this
program it will provide a call or a put pay-off function for the average of the spot prices:
double A s i a n O p t i o n A r i t h m e t i c : : p a y o f f p r i c e (
const s t d : : v e c t o r <double>& s p o t p r i c e s ) const {
unsigned num times = s p o t p r i c e s . s i z e ( ) ;
double sum = s t d : : accumulate ( s p o t p r i c e s . b e g i n ( ) , s p o t p r i c e s . end ( ) , 0 ) ;
double a r i t h m e a n = sum / s t a t i c c a s t <double>(num times ) ;
return ( p a y o f f ) ( a r i t h m e a n ) ;
}
The geometric Asian is similar. We once again determine the number of spot prices. Then
we loop over the spot prices, summing the logarithm of each of them and adding it to the grand
total. Then we take the geometric mean of these values and finally use pointer dereferencing
168
once again to determine the correct call/put pay-off value:
double AsianOptionGeometric : : p a y o f f p r i c e (
const s t d : : v e c t o r <double>& s p o t p r i c e s ) const {
unsigned num times = s p o t p r i c e s . s i z e ( ) ;
double log sum = 0 . 0 ;
f o r ( i n t i =0; i <s p o t p r i c e s . s i z e ( ) ; i ++) {
log sum += l o g ( s p o t p r i c e s [ i ] ) ;
}
double geom mean = exp ( log sum / s t a t i c c a s t <double>(num times ) ) ;
return ( p a y o f f ) ( geom mean ) ;
}
Note here what the AsianOption classes do not require. Firstly, they dont require information
about the underlying (i.e. vol). They also dont require time to expiry or the interest rate. Thus
we are really trying to encapsulate the term sheet of the option in this object, i.e. all of the
parameters that would appear on the contract when the option is made. However, for simplicity
we have neglected to include the actual sample times, which would also be written on the contract.
This instead is moved to the path-generator. However, later code will amend this, particularly
as we can re-use the AsianOption objects in more sophisticated programs, where interest rates
and volatility are subject to stochastic models.
12.6
The final part of the program is the main.cpp file. It brings all of the previous components
together to produce an output for the option price based on some default parameters. The full
listing is below:
#include <i o s t r e a m >
#include p a y o f f . h
#include a s i a n . h
#include p a t h g e n e r a t e . h
169
// Number o f s i m u l a t e d a s s e t p a t h s
// Number o f a s s e t sample i n t e r v a l s
double S = 3 0 . 0 ;
// Option p r i c e
double K = 2 9 . 0 ;
// S t r i k e p r i c e
double r = 0 . 0 8 ;
// Riskf r e e r a t e (8%)
double v = 0 . 3 ;
// V o l a t i l i t y o f t h e u n d e r l y i n g (30%)
double T = 1 . 0 0 ;
// One y e a r u n t i l e x p i r y
s t d : : v e c t o r <double> s p o t p r i c e s ( n u m i n t e r v a l s , S ) ;
// Vector o f s p o t
prices
// C r e a t e t h e PayOff o b j e c t s
PayOff p a y o f f c a l l = new P a y O f f C a l l (K) ;
// C r e a t e t h e AsianOption o b j e c t s
AsianOptionArithmetic asian ( p a y o f f c a l l ) ;
// Update t h e s p o t p r i c e v e c t o r w i t h c o r r e c t
// s p o t p r i c e p a t h s a t c o n s t a n t i n t e r v a l s
double p a y o f f s u m = 0 . 0 ;
f o r ( i n t i =0; i <num sims ; i ++) {
c a l c p a t h s p o t p r i c e s ( s p o t p r i c e s , r , v , T) ;
p a y o f f s u m += a s i a n . p a y o f f p r i c e ( s p o t p r i c e s ) ;
}
double d i s c o u n t p a y o f f a v g = ( p a y o f f s u m / s t a t i c c a s t <double>(num sims ) )
exp( r T) ;
delete p a y o f f c a l l ;
// F i n a l l y we o u t p u t t h e p a r a m e t e r s and p r i c e s
s t d : : c o u t << Number o f Paths : << num sims << s t d : : e n d l ;
s t d : : c o u t << Number o f I n t s :
<< n u m i n t e r v a l s << s t d : : e n d l ;
s t d : : c o u t << U n d e r l y i n g :
<< S << s t d : : e n d l ;
s t d : : c o u t << S t r i k e :
<< K << s t d : : e n d l ;
<< r << s t d : : e n d l ;
s t d : : c o u t << V o l a t i l i t y :
<< v << s t d : : e n d l ;
s t d : : c o u t << Maturity :
<< T << s t d : : e n d l ;
170
s t d : : c o u t << Asian P r i c e :
<< d i s c o u n t p a y o f f a v g << s t d : : e n d l ;
return 0 ;
}
The first interesting aspect of the main.cpp program is that we have now added a
unsigned num intervals = 250; line which determines how frequently the spot price will be sampled
in the Asian option. As stated above, this would usually be incorporated into the option term
sheet, but I have included it here instead to help make the pricer easier to understand without
too much object communication overhead. We have also created the vector of spot prices, which
are pre-filled with the default spot price, S:
unsigned n u m i n t e r v a l s = 2 5 0 ;
// Number o f a s s e t sample i n t e r v a l s
..
s t d : : v e c t o r <double> s p o t p r i c e s ( n u m i n t e r v a l s , S ) ;
// The v e c t o r o f s p o t
prices
Then we create a PayOffCall object and assign it to a PayOff pointer. This allows us to leverage
polymorphism and pass that object through to the AsianOption, without the option needing to
know the actual type of PayOff. Note that whenever we use the new operator, we must make use
of the corresponding delete operator.
PayOff p a y o f f c a l l = new P a y O f f C a l l (K) ;
..
delete p a y o f f c a l l ;
The next step is to create the AsianOptionArithmetic object. We could have as easily chosen
the AsianOptionGeometric and the program would be trivial to modify to do so. It takes in the
pointer to the PayOff as its lone constructor argument:
AsianOptionArithmetic asian ( p a y o f f c a l l ) ;
Then we create a loop for the total number of path simulations. In the loop we recalculate
a new spot price path and then add that pay-off to a running sum of all pay-offs. The final
step is to discount the average of this pay-off via the risk-free rate across the lifetime of the
option (T 0 = T ). This discounted price is then the final price of the option, with the above
parameters:
double p a y o f f s u m = 0 . 0 ;
f o r ( i n t i =0; i <num sims ; i ++) {
c a l c p a t h s p o t p r i c e s ( s p o t p r i c e s , r , v , T) ;
171
p a y o f f s u m += a s i a n . p a y o f f p r i c e ( s p o t p r i c e s ) ;
}
double d i s c o u n t p a y o f f a v g = ( p a y o f f s u m / s t a t i c c a s t <double>(num sims ) )
exp( r T) ;
The final stage of the main program is to output the parameters and the options price:
Number o f Paths : 100000
Number o f I n t s :
250
Underlying :
30
Strike :
29
RiskFree Rate :
0.08
Volatility :
0.3
Maturity :
Asian P r i c e :
2.85425
The benefits of such an object-oriented approach are now clear. We can easily add more
PayOff or AsianOption classes without needing to extensively modify any of the remaining code.
These objects are said to have a separation of concerns, which is exactly what is needed for
large-scale software projects.
172
Chapter 13
Implied Volatility
In this chapter we will discuss a practical application of the Black-Scholes model, design patterns
and function objects in C++. In particular, we are going to consider the concept of Implied
Volatility. In derivatives pricing, the implied volatility of an option is the value of the underlyings volatility (usually denoted by ), which when input into an derivatives pricing model (such
as with the Black-Scholes equation) provides a pricing value for the option which is equal to the
currently observed market price of that option.
13.1
Motivation
The use of implied volatility is motivated by the fact that it is often more useful as a measure of
relative value between options than the actual price of those options. This is because the option
price is heavily dependent upon the price of the underlying asset. Options are often held in a
delta neutral portfolio, which means that the portfolio is hedged for small moves in the price of
the underlying asset. Given the need to continuously rebalance these portfolios, an option with
a higher actual price can be cheaper in terms of its volatility if the underlying itself rises, as the
underlying asset can be sold at a higher price.
Implied volatilities can in fact be considered a form of prices, as their values have been
determined by actual transactions in the marketplace, not on the basis of statistical estimates.
Professional traders actually tend to quote values of options in terms of their vol, rather than
their actual market prices.
Our task for this chapter is to attempt to calculate implied volatilities using the Black-Scholes
equation. If we let the market price of an option be given by CM and the Black-Scholes function
by B(S, K, r, T, ), where S is the underlying price, K is the option strike, r is the risk-free
173
174
interest rate, T is the time to maturity, then we are trying to find the value of such that:
B(S, K, r, T, ) = CM
13.2
(13.1)
Root-Finding Algorithms
The first place to start is to consider whether an analytical inverse exists. Unfortunately, the
Black-Scholes model does not lend itself to an analytical inverse and thus we are forced to
carry out the computation using numerical methods. The common methods for achieving these
are known as root finding algorithms. In this chapter we will look at two techniques: Interval
Bisection and Newton-Raphson.
13.3
The first numerical algorithm considered is Interval Bisection. It makes use of a real analysis
concept known as the intermediate value theorem. Since the Black-Scholes formula is continuous (and well behaved, which for us means sufficiently smooth), we can make use of the
theorem to help us find a volatility.
The theorem states, mathematically, that if m, n R, and g(x) : R R, continuous, such
that g(m) < y, g(n) > y, then p (m, n) R such that g(p) = y. For us, g is the Black-Scholes
function, y is the current market price and p is the volatility.
Heuristically, the theorem states that if a function is continuous and we know two (differing)
values in the domain that generate an image interval, then there must exist a value in the domain
between these two values that provides a mapped value lying within the image domain. This is
exactly what we need in order to determine the implied volatility from the market prices.
Interval bisection is quite straightforward to understand. It is a trial and error algorithm.
We pick the mid-point value, c, of an interval, and then either g(c) = y, g(c) < y or g(c) > y.
In the first instance the algorithm terminates. In the latter two cases, we subdivide the interval
(c, n) (respectively (m, c)) and find the corresponding midpoints. At each level of the recursion,
the interval size is halved. In practice the algorithm is terminated using a tolerance value, ,
once |g(c) y| < .
Interval bisection is not an efficient method for root-finding, but it is straightforward to
implement, especially when we make use of functors and function templates.
175
13.3.1
So far weve made extensive use of object-orientation and, in particular, inheritance hierarchies
as one of the main design patterns for solving quantitative finance problems. Were now going
to use a very different set of tools, that of functors and function templates. Weve discussed
how functors work before and have used them to create PayOff classes. Now we are going
to supplement their use by including function templates, which are a means of applying the
template generic programming paradigm that we have previously applied to classes, to functions
themselves.
The motivation for using functors and function templates is that we wish to create our interval
bisection code in a reusable fashion. Ideally, the interval bisection code should be able to cope
with a generic function, including those with their own set of (fixed) parameters. This is the
situation we are in, because the Black-Scholes formula requires not only the volatility, but the
strike, spot price, maturity time and interest rate.
Creating the Black-Scholes call option as a functor allows us to pass it to the interval bisection
function with attached state, which in this case is the collection of extra arguments representing
the option parameters. In fact, the interval bisection method doesnt even need to know about
these parameters, as the only interface exposed to it is an option pricing operator() method,
which accepts only a volatility.
Another reason to use function templatisation over inheritance is that we would be at the
mercy of virtual functions. Virtual functions are relatively slow, as each time a function is called
the vtable has to be queried, which is extra overhead. Further, virtual functions cannot be
inlined, which is a major disadvantage. This leads to slower code. These problems also apply to
function pointers as well, which is why we arent making use of them either.
The approach we will follow is to make the interval bisection function a template function,
such that it can accept a generic function object, which in our case will be the Black-Scholes call
pricing method. Well now discuss the C++ implementation.
13.3.2
C++ Implementation
Weve previously considered analytical pricing of European call options in some depth. However,
Ive reproduced and modified the code here for completeness ( bs prices .h):
#i f n d e f
BS PRICES H
#define
BS PRICES H
176
#define
// S ta n da r d normal p r o b a b i l i t y d e n s i t y f u n c t i o n
double norm pdf ( const double x ) {
return ( 1 . 0 / ( pow ( 2 M PI , 0 . 5 ) ) ) exp ( 0.5 xx ) ;
}
// An a p p r o x i m a t i o n t o t h e c u m u l a t i v e d i s t r i b u t i o n f u n c t i o n
// f o r t h e s t a n d a r d normal d i s t r i b u t i o n
// Note : This i s a r e c u r s i v e f u n c t i o n
double norm cdf ( const double x ) {
double k = 1 . 0 / ( 1 . 0 + 0 . 2 3 1 6 4 1 9 x ) ;
double k sum = k ( 0 . 3 1 9 3 8 1 5 3 0 + k ( 0.356563782 + k ( 1 . 7 8 1 4 7 7 9 3 7 +
k ( 1.821255978 + 1 . 3 3 0 2 7 4 4 2 9 k ) ) ) ) ;
i f ( x >= 0 . 0 ) {
return ( 1 . 0 ( 1 . 0 / ( pow ( 2 M PI , 0 . 5 ) ) ) exp ( 0.5 xx ) k sum ) ;
} else {
return 1 . 0 norm cdf (x ) ;
}
}
// C a l c u l a t e t h e European v a n i l l a c a l l p r i c e b a s e d on
// u n d e r l y i n g S , s t r i k e K, r i s k f r e e r a t e r , v o l a t i l i t y o f
// u n d e r l y i n g sigma and time t o m a t u r i t y T
double c a l l p r i c e ( const double S , const double K, const double r ,
177
#endif
The following is a listing for black scholes .h, which contains a basic function object (functor)
for handling calculation of options prices when provided with a volatility, . Notice that all of
the Black-Scholes model option paramaters are stored as private variables, with the exception
of the volatility . This is because the function call operator() method takes it as a parameter.
This method will eventually be (repeatedly) called by the interval bisection function:
#i f n d e f
BLACK SCHOLES H
#define
BLACK SCHOLES H
class BlackScholesCall {
private :
double S ;
// U n d e r l y i n g a s s e t p r i c e
double K;
// S t r i k e p r i c e
double r ;
// Riskf r e e r a t e
double T ;
// Time t o m a t u r i t y
public :
B l a c k S c h o l e s C a l l ( double
S , double
K,
double
r , double
T) ;
The following is a listing for black scholes .cpp, which contains the source implementation for
the Black-Scholes options class. This class simply provides a wrapper around the analytical
price for the call option, but crucially specifies the needed parameters, in such a way that the
interval bisection function avoids having to concern itself with them. Notice that S, K, r and T
are referencing private member data, while is being passed from the method call as a parameter:
#i f n d e f
#define
178
#include b l a c k s c h o l e s . h
#include b s p r i c e s . h
B l a c k S c h o l e s C a l l : : B l a c k S c h o l e s C a l l ( double
S , double
K,
double
r , double
T) :
S ( S ) , K( K ) , r ( r ) , T( T ) {}
#endif
The following is a listing for interval bisection .h, which contains the function template for
carrying out interval bisection. As with classes, we need to add the template<typename T>
syntax to the signature of the function in order to tell the compiler it should be expecting
a generic type as one of its parameters. The function body implicitly calls operator() of the
function object g, so any object passed to it must define operator() for a sole parameter.
The remainder of the code carries out the Interval Bisection algorithm for finding a root of
the generic function g:
#i f n d e f
INTERVAL BISECTION H
#define
INTERVAL BISECTION H
#include <cmath>
// C r e a t i n g a f u n c t i o n t e m p l a t e
// Trying t o f i n d an x such t h a t | g ( x ) y | < e p s i l o n ,
// s t a r t i n g w i t h t h e i n t e r v a l (m, n ) .
template<typename T>
double i n t e r v a l b i s e c t i o n ( double y t a r g e t ,
// T a r g e t y v a l u e
double m,
// L e f t i n t e r v a l v a l u e
double n ,
// R i g h t i n t e r v a l v a l u e
double e p s i l o n ,
// T o l e r a n c e
T g) {
// Fu n ct io n o b j e c t o f t y p e T,
named g
179
// C r e a t e t h e i n i t i a l x midp o i n t v a l u e
// Find t h e mapped y v a l u e o f g ( x )
double x = 0 . 5 (m + n ) ;
double y = g ( x ) ;
// While t h e d i f f e r e n c e b e t w e e n y and t h e y t a r g e t
// v a l u e i s g r e a t e r than e p s i l o n , k e e p s u b d i v i d i n g
// t h e i n t e r v a l i n t o s u c c e s s i v e l y s m a l l e r h a l v e s
// and ree v a l u a t e t h e new y .
do {
if (y < y target ) {
m = x;
}
if (y > y target ) {
n = x;
}
x = 0 . 5 (m + n ) ;
y = g(x) ;
} while ( f a b s ( yy t a r g e t ) > e p s i l o n ) ;
return x ;
}
#endif
The final listing is for the main implementation (main.cpp). Ive hardcoded some option
parameters (but you could easily modify this to input them from the command line), so that
the implied volatility can be calculated. Firstly we create a BlackScholesCall instance and pass it
the required parameters (without the volatility, at this stage). Then we define the parameters
for the interval bisection itself, and finally pass the interval bisection the bsc function object as
a generic parameter. This then calculates the implied volatility of the option for us:
#i f n d e f
MAIN CPP
#define
MAIN CPP
180
#include b l a c k s c h o l e s . h
#include i n t e r v a l b i s e c t i o n . h
#include <i o s t r e a m >
// U n d e r l y i n g s p o t p r i c e
double K = 1 0 0 . 0 ;
// S t r i k e p r i c e
double r = 0 . 0 5 ;
// Riskf r e e r a t e (5%)
double T = 1 . 0 ;
// One y e a r u n t i l e x p i r y
// C r e a t e t h e BlackS c h o l e s C a l l f u n c t o r
B l a c k S c h o l e s C a l l b s c ( S , K, r , T) ;
// I n t e r v a l B i s e c t i o n p a r a m e t e r s
double l o w v o l = 0 . 0 5 ;
double h i g h v o l = 0 . 3 5 ;
double e p i s i l o n = 0 . 0 0 1 ;
// C a l c u l a t e t h e i m p l i e d v o l a t i l i t y
double sigma = i n t e r v a l b i s e c t i o n (C M, l o w v o l , h i g h v o l , e p i s i l o n , b s c ) ;
// Output t h e v a l u e s
s t d : : c o u t << I m p l i e d Vol : << sigma << s t d : : e n d l ;
return 0 ;
}
#endif
I m p l i e d Vol : 0 . 2 0 1 3 1 8
Thus we obtain an implied volatility of 20.1% for this particular call option. The objectoriented and generic aspects of the above code lend themselves naturally to extension and re-use.
181
13.4
In the above section we made use of interval bisection to numerically solve for the implied
volatility. In this section we are going to modify our code to make use of the Newton-Raphson
process, which is more optimal for this problem domain than interval bisection.
For the previous calculation we made use of a function template to carry out the interval
bisection. The template function accepted a function object (functor) of type T, g, which itself
accepted a volatility parameter () to provide an option price. The main design issue we have to
contend with is that to use Newton-Raphson, we require a second function to represent g 0 , the
derivative of g (the vega of the option). There are multiple approaches to deal with this issue:
Two functors - We can create a separate function object to represent the derivative of
the function stored in the first functor. However, this method is not scalable if we wish to
create second (or higher order) derivatives, or partial derivatives for any other variables.
A derivative is also fundamentally a component of the original function, rather than a
separate entity. Thus we wont be using this approach.
Derivative method - It is possible to create a derivative method for the function object
g. However, we still suffer from the same scaling issue in that we would need to create
derivative methods for all orders or variables. We could create a generalised derivative
function, but this would be a substantial amount of work for very little initial gain.
Pointer to member function - To avoid the ugliness of the previous method, we can
make use of a pointer to a member function. This method allows us to specify which
function is to be called at the point of compilation, using templates. It is the approach
taken in Joshi and we will follow it here.
Before considering the C++ implementation, we will briefly discuss how the Newton-Raphson
root-finding algorithm works.
13.4.1
Newton-Raphson Method
Newton-Raphson is a more efficient algorithm for finding roots provided that some assumptions
are met. In particular, g must possess an easily calculated derivative. If the derivative is
analytically calculable, then this aids efficiency even further. g must also be well behaved, that
is it cannot vary too wildly, else the derivative approximation made use of in the method becomes
more innacurate.
182
In the case of the Black-Scholes formula, the derivative we seek is the derivative of the option
price with respect to the volatility,
B
.
call option an analytical function exists for the vega, so we are in luck.
The idea of Newton-Raphson is to use the analytic derivative to make a linear estimate of
where the solution should occur, which is much more accurate than the mid-point approach
taken by Interval Bisection. Thus the starting approximation to g, g0 , is given by (where x0 is
our initial guess):
(13.2)
x=
y g(x0 )
+ x0
g 0 (x0 )
(13.3)
xn+1 =
y g(xn )
+ xn
g 0 (xn )
(13.4)
As with interval bisection, the algorithm is terminated when |g(xn ) y| < , for some prespecified tolerance .
Rewritten with our financial notation (where I have suppressed the other parameters for the
Black-Scholes formula, B), the relation becomes:
n+1 =
CM B(n )
+ n
B
(n )
(13.5)
We can use this relation in order to find the implied volatility via a terminating criterion.
13.4.2
The idea of a pointer to a member function is to restrict the usage of a function pointer to
methods of a particular class. The scope resolution operator :: combined with the pointer
dereference operator is used to carry this out. In order to define such a function pointer we
183
use the following syntax (where g prime represents g 0 , the derivative of g):
double (T : : g p r i m e ) ( double ) const
Where root func is an object of type T, containing two methods g and g prime, representing
the value of the function and the derivative value of the function respectively that will be tested
for roots.
One of the major benefits of this approach is that the calls to these function pointers can be
inlined as they are treated as template parameters and so are evaluated at compile-time.
13.4.3
Implementing Newton-Raphson
In order to make use of the new approach, we need to add the explicit formula for a call option
vega to our bs prices .h header file and modify the original BlackScholesCall object we created in
the previous tutorial to make use of it. Here is the added function to calculate the option vega:
..
..
// C a l c u l a t e t h e European v a n i l l a c a l l v e g a Greek b a s e d on
// u n d e r l y i n g S , s t r i k e K, r i s k f r e e r a t e r , v o l a t i l i t y o f
// u n d e r l y i n g sigma and time t o m a t u r i t y T
double c a l l v e g a ( const double S , const double K, const double r ,
const double sigma , const double T) {
return S s q r t (T) norm pdf ( d j ( 1 , S , K, r , sigma , T) ) ;
}
..
..
BLACK SCHOLES H
#define
BLACK SCHOLES H
class BlackScholesCall {
private :
184
double S ;
// U n d e r l y i n g a s s e t p r i c e
double K;
// S t r i k e p r i c e
double r ;
// Riskf r e e r a t e
double T ;
// Time t o m a t u r i t y
public :
B l a c k S c h o l e s C a l l ( double
S , double
K,
double
r , double
T) ;
// This i s t h e m o d i f i e d s e c t i o n . o p e r a t o r ( )
// has been r e p l a c e d w i t h o p t i o n p r i c e and
// we have added o p t i o n v e g a ( b o t h c o n s t )
double o p t i o n p r i c e ( double sigma ) const ;
double o p t i o n v e g a ( double sigma ) const ;
};
#endif
The source listing has also been changed to include the implementation of option vega, given
in black scholes .cpp:
#i f n d e f
#define
#include b l a c k s c h o l e s . h
#include b s p r i c e s . h
B l a c k S c h o l e s C a l l : : B l a c k S c h o l e s C a l l ( double
S , double
K,
double
r , double
T) :
S ( S ) , K( K ) , r ( r ) , T( T ) {}
// Renamed from o p e r a t o r ( ) t o o p t i o n p r i c e ( )
double B l a c k S c h o l e s C a l l : : o p t i o n p r i c e ( double sigma ) const {
return c a l l p r i c e ( S , K, r , sigma , T) ;
}
185
return c a l l v e g a ( S , K, r , sigma , T) ;
}
#endif
The next stage is to create the Newton-Raphson solver itself. The function template will
accept an object of type T (the functor) and two pointers to member functions (methods) of T,
g and g prime. Here is the listing for newton raphson.h:
#i f n d e f
NEWTON RAPHSON H
#define
NEWTON RAPHSON H
#include <cmath>
template<typename T,
double (T : : g ) ( double ) const ,
double (T : : g p r i m e ) ( double ) const>
double newton raphson ( double y t a r g e t ,
// T a r g e t y v a l u e
double i n i t ,
// I n i t i a l x v a l u e
double e p s i l o n ,
// T o l e r a n c e
const T& r o o t f u n c ) {
// Fu n ct io n o b j e c
// S e t t h e i n i t i a l o p t i o n p r i c e s and v o l a t i l i t y
double y = ( r o o t f u n c . g ) ( i n i t ) ;
// I n i t i a l o p t i o n p r i c e s
double x = i n i t ;
// I n i t i a l
volatility
#endif
186
Now we can create the main() function to wrap all of our code together:
#i f n d e f
MAIN CPP
#define
MAIN CPP
#include b l a c k s c h o l e s . h
#include newton raphson . h
#include <i o s t r e a m >
// U n d e r l y i n g s p o t p r i c e
double K = 1 0 0 . 0 ;
// S t r i k e p r i c e
double r = 0 . 0 5 ;
// Riskf r e e r a t e (5%)
double T = 1 . 0 ;
// One y e a r u n t i l e x p i r y
// C r e a t e t h e BlackS c h o l e s C a l l f u n c t o r
B l a c k S c h o l e s C a l l b s c ( S , K, r , T) ;
// Newton Raphson p a r a m e t e r s
double i n i t = 0 . 3 ;
// Our g u e s s i m p l . v o l o f 30%
double e p s i l o n = 0 . 0 0 1 ;
// C a l c u l a t e t h e i m p l i e d v o l a t i l i t y
double sigma = newton raphson<B l a c k S c h o l e s C a l l ,
&B l a c k S c h o l e s C a l l : : o p t i o n p r i c e ,
&B l a c k S c h o l e s C a l l : : o p t i o n v e g a >
(C M, i n i t , e p s i l o n , b s c ) ;
// Output t h e v a l u e s
s t d : : c o u t << I m p l i e d Vol : << sigma << s t d : : e n d l ;
return 0 ;
}
#endif
187
The output of the code is given by:
I m p l i e d Vol : 0 . 2 0 1 3 1 7
This matches the implied volatility given in the previous section, although the calculation
was far more optimal.
188
Chapter 14
14.1
Overview
We will now show how to construct a random number generator class hierarchy. This allows
us to separate the generation of random numbers from the Monte Carlo solvers that make use
of them. It helps us reduce the amount of code we will need to write in the future, increases
extensibility by allowing easy creation of additional random number generators and makes the
code more maintainable.
There are further reasons to write our own random number generators:
It allows us to make use of pseudo-random numbers. These are sequences of numbers
that possess the correct statistical properties to emulate random numbers in order to improve the convergence rates of Monte Carlo simulations. The interface for random numbers
and pseudo-random numbers is identical and we can hide away the details in the specific
classes. In particular we can implement low-discrepancy numbers and anti-thetic sampling
189
190
in this manner.
Relying on the rand function provided with the C++ standard is unreliable. Not only is
rand implementation specific, because it varies across multiple vendor compilers, but we are
14.2
Our random number generators will be formed from an inheritance hierarchy. We have already
used this method when constructing PayOff classes for option pay-off functions. To form the
hierarchy we will create an abstract base class that specifies the interface to the random number
generator. All subsequent generators will inherit the interface from this class.
The primary considerations of this interface are as follows:
Quantity or dimension of the generator: Many of the options pricers we have already
created require more than a single random number in order to be accurately priced. This
is the case for path-dependent options such as Asians, Barriers and Lookbacks. Thus our
first consideration is to make sure that the generator provides a vector of random numbers,
with the dimension specified at the creation of the instance.
The supported statistical distributions from which to draw random variables: For options pricing, the two main statistical distributions of interest will be the uniform distribution and the standard normal distribution (i.e. the Gaussian distribution). Gaussian
random draws are calculated from uniform random draws. We can use the statistical
classes in order to obtain random draws from any particular distribution we wish, without
modifying the RNG.
We will need methods to support obtaining and setting the random seed, so that we can
control which random numbers are generated and to ensure reproducibility across separate
runs and platforms.
191
With those considerations in mind, lets create a simple abstract base class for our random
number generator, in the file random.h:
#i f n d e f
RANDOM H
#define
RANDOM H
c l a s s RandomNumberGenerator {
protected :
unsigned long i n i t s e e d ;
// I n i t i a l random s e e d v a l u e
unsigned long c u r s e e d ;
// Current random s e e d v a l u e
// D i m e n s i o n a l i t y o f t h e RNG
public :
RandomNumberGenerator ( unsigned long
init seed )
seed ; }
v i r t u a l void r e s e t r a n d o m s e e d ( ) { c u r s e e d = i n i t s e e d ; }
v i r t u a l void set num draws ( unsigned long
num draws ; }
#endif
Lets run through the code. Firstly, note that we have three protected member variables
(which are all large unsigned long integers). cur seed is the RNG current seed value. init seed is
192
the initial seed value, which does not change once the RNG has been instantiated. The current
seed can only be reset to the initial seed. num draws represents the dimensionality of the random
number generator (i.e. how many random draws to create):
protected :
unsigned long i n i t s e e d ;
// I n i t i a l random s e e d v a l u e
unsigned long c u r s e e d ;
// Current random s e e d v a l u e
// D i m e n s i o n a l i t y o f t h e RNG
Since were creating an abstract base class is it a good idea to use protected data?
This is actually a contentious issue. Sometimes protected variables are frowned upon. Instead,
it is argued that all data should be private and that accessor methods should be used. However,
inherited classes -are- clients of the base class, just as public clients of the classes are. The
alternative argument is that it is extremely convenient to use protected member data because it
reduces the amount of cluttered accessor and modifier methods. For brevity I have used protected
member data here.
Although the class will never be instantiated directly, it still has a constructor which must be
called to populate the protected members. We use a member initialisation list to carry this out.
We also create an empty method implementation for the constructor ({}), avoiding the need to
create a random.cpp source file. Notice that were setting the current seed to the initial seed as
well.
RandomNumberGenerator ( unsigned long
init seed )
We then have four separate access and reset methods (all virtual), which get, set and reset
the random seed and another which resets the number of random draws. They are all directly
implemented in the header file, once again stopping us from needing to create a random.cpp
source file:
v i r t u a l unsigned long g e t r a n d o m s e e d ( ) const { return c u r s e e d ; }
v i r t u a l void s e t r a n d o m s e e d ( unsigned long
seed ; }
v i r t u a l void r e s e t r a n d o m s e e d ( ) { c u r s e e d = i n i t s e e d ; }
v i r t u a l void set num draws ( unsigned long
num draws ; }
We now need a method to create a random integer. This is because subsequent random
number generators will rely on transforming random unsigned longs into uniform variables on
193
the open interval (0, 1). The method is declared pure virtual as different RNGs will implement
this differently. We dont want to force an approach on future clients of our code:
// Obtain a random i n t e g e r ( needed f o r c r e a t i n g random u n i f o r m s )
v i r t u a l unsigned long g e t r a n d o m i n t e g e r ( ) = 0 ;
Finally we fill a supplied vector with uniform random draws. This vector will then be passed
to a statistical distribution class in order to generate random draws from any chosen distribution
that we implement. In this way we are completely separating the generation of the uniform
random variables (on the open interval (0, 1)) and the draws from various statistical distributions.
This maximises code re-use and aids testing:
// F i l l s a v e c t o r w i t h uniform random v a r i a b l e s on t h e open i n t e r v a l ( 0 , 1 )
v i r t u a l void g e t u n i f o r m d r a w s ( s t d : : v e c t o r <double>& draws ) = 0 ;
Our next task is to implement a linear congruential generator algorithm as a means for
creating our uniform random draws.
14.2.1
Linear congruential generators (LCG) are a form of random number generator based on the
following general recurrence relation:
xk+1 = g xk modn
Where n is a prime number (or power of a prime number), g has high multiplicative order
modulo n and x0 (the initial seed) is co-prime to n. Essentially, if g is chosen correctly, all
integers from 1 to n 1 will eventually appear in a periodic fashion. This is why LCGs are
termed pseudo-random. Although they possess enough randomness for our needs (as n can be
large), they are far from truly random. We wont dwell on the details of the mathematics behind
LCGs, as we will not be making strong use of them going forward in our studies. However,
most system-supplied RNGs make use of LCGs, so it is worth being aware of the algorithm.
The listing below ( lin con gen .cpp) contains the implementation of the algorithm. If you want to
learn more about how LCGs work, take a look at Numerical Recipes[20].
194
14.2.2
With the mathematical algorithm described, it is straightforward to create the header file listing
( lin con gen .h) for the Linear Congruential Generator. The LCG simply inherits from the RNG
abstract base class, adds a private member variable called max multiplier (used for pre-computing
a specific ratio required in the uniform draw implementation) and implements the two pure virtual
methods that were part of the RNG abstract base class:
#i f n d e f
#define
#include random . h
c l a s s L i n e a r C o n g r u e n t i a l G e n e r a t o r : public RandomNumberGenerator {
private :
double m a x m u l t i p l i e r ;
public :
L i n e a r C o n g r u e n t i a l G e n e r a t o r ( unsigned long
unsigned long
num draws ,
i n i t s e e d = 1) ;
v i r t u a l unsigned long g e t r a n d o m i n t e g e r ( ) ;
v i r t u a l void g e t u n i f o r m d r a w s ( s t d : : v e c t o r <double> draws ) ;
};
#endif
The source file ( lin con gen .cpp) contains the implementation of the linear congruential generator algorithm. We make heavy use of Numerical Recipes in C [20], the famed numerical
algorithms cookbook. The book itself is freely available online. I would strongly suggest reading
the chapter on random number generator (Chapter 7) as it describes many of the pitfalls with
using a basic linear congruential generator, which I do not have time to elucidate on in this
chapter. Here is the listing in full:
#i f n d e f
#define
#include l i n c o n g e n . h
195
// 75
// S c h r a g e s a l g o r i t h m c o n s t a n t s
// Parameter c o n s t r u c t o r
LinearCongruentialGenerator : : LinearCongruentialGenerator (
unsigned long
num draws ,
unsigned long
init seed
init seed ) {
i f ( i n i t s e e d == 0 ) {
init seed = 1;
cur seed = 1;
}
m a x m u l t i p l i e r = 1 . 0 / ( 1 . 0 + (m1) ) ;
}
// O b t a i n s a random u n s i g n e d l o n g i n t e g e r
unsigned long L i n e a r C o n g r u e n t i a l G e n e r a t o r : : g e t r a n d o m i n t e g e r ( ) {
unsigned long k = 0 ;
k = cur seed / q ;
cur seed = a ( cur seed k q) r k ;
return c u r s e e d ;
196
// C r e a t e a v e c t o r o f uniform draws b e t w e e n ( 0 , 1 )
void L i n e a r C o n g r u e n t i a l G e n e r a t o r : : g e t u n i f o r m d r a w s ( s t d : : v e c t o r <double>&
draws ) {
f o r ( unsigned long i =0; i <num draws ; i ++) {
draws [ i ] = g e t r a n d o m i n t e g e r ( ) m a x m u l t i p l i e r ;
}
}
#endif
Firstly, we set all of the necessary constants (see [20] for the explanation of the chosen values).
Note that if we created another LCG we could inherit from the RNG base class and use different
constants:
// D e f i n e t h e c o n s t a n t s f o r t h e Park & M i l l e r a l g o r i t h m
// 75
// 232 1
// S c h r a g e s a l g o r i t h m c o n s t a n t s
Secondly we implement the constructor for the LCG. If the seed is set to zero by the client,
we set it to unity, as the LCG algorithm does not work with a seed of zero. The max mutliplier is
a pre-computed scaling factor necessary for converting a random unsigned long into a uniform
value on on the open interval (0, 1) R:
// Parameter c o n s t r u c t o r
LinearCongruentialGenerator : : LinearCongruentialGenerator (
unsigned long
num draws ,
unsigned long
init seed
i f ( i n i t s e e d == 0 ) {
init seed = 1;
init seed ) {
197
cur seed = 1;
}
m a x m u l t i p l i e r = 1 . 0 / ( 1 . 0 + (m1) ) ;
}
We now concretely implement the two pure virtual functions of the RNG base class, namely
get random integer and get uniform draws. get random integer applies the LCG modulus algorithm
and modifies the current seed (as described in the algorithm above):
// O b t a i n s a random u n s i g n e d l o n g i n t e g e r
unsigned long L i n e a r C o n g r u e n t i a l G e n e r a t o r : : g e t r a n d o m i n t e g e r ( ) {
unsigned long k = 0 ;
k = cur seed / q ;
cur seed = a ( cur seed k q) r k ;
return c u r s e e d ;
}
get uniform draws takes in a vector of the correct length (num draws) and loops over it con-
verting random integers generated by the LCG into uniform random variables on the interval
(0, 1):
// C r e a t e a v e c t o r o f uniform draws b e t w e e n ( 0 , 1 )
void L i n e a r C o n g r u e n t i a l G e n e r a t o r : : g e t u n i f o r m d r a w s ( s t d : : v e c t o r <double>&
draws ) {
f o r ( unsigned long i =0; i <num draws ; i ++) {
draws [ i ] = g e t r a n d o m i n t e g e r ( ) m a x m u l t i p l i e r ;
}
}
The concludes the implementation of the linear congruential generator. The final component
is to tie it all together with a main.cpp program.
198
14.2.3
Because we have already carried out most of the hard work in random.h, lin con gen .h, lin con gen
.cpp, the main implementation (main.cpp) is straightforward:
#include <i o s t r e a m >
#include l i n c o n g e n . h
return 0 ;
}
Firstly, we set up the initial seed and the dimensionality of the random number generator. Then we pre-initialise the vector, which will ultimately contain the uniform draws. Then
we instantiate the linear congruential generator and pass the random draws vector into the
get uniform draws method. Finally we output the uniform variables. The output of the code
is as follows:
7 . 8 2 6 3 7 e 06
0.131538
0.755605
0.45865
0.532767
0.218959
199
0.0470446
0.678865
0.679296
0.934693
0.383502
0.519416
0.830965
0.0345721
0.0534616
0.5297
0.671149
0.00769819
0.383416
0.0668422
As can be seen, all of the values lie between (0, 1). We are now in a position to utilise
statistical distributions with the uniform random number generator to obtain random draws.
14.3
Statistical Distributions
One of the commonest concepts in quantitative finance is that of a statistical distribution. Random variables play a huge part in quantitative financial modelling. Derivatives pricing, cash-flow
forecasting and quantitative trading all make use of statistical methods in some fashion. Hence,
modelling statistical distributions is extremely important in C++.
Many of the chapters within this book have made use of random number generators in order
to carry out pricing tasks. So far this has been carried out in a procedural manner. Functions
have been called to provide random numbers without any data encapsulation of those random
number generators. The goal of this chapter is to show you that it is beneficial to create a class
hierarchy both for statistical distributions and random number generators, separating them out
in order to gain the most leverage from code reuse.
In a nutshell, we are splitting the generation of (uniform integer) random numbers from
draws of specific statistical distributions, such that we can use the statistics classes elsewhere
without bringing along the heavy random number generation functions. Equally useful is the
fact that we will be able swap out different random number generators for our statistics classes
for reasons of reliability, extensibility and efficiency.
200
14.3.1
The inheritance hierarchy for modelling of statistical distributions is relatively simple. Each
distribution of interest will share the same interface, so we will create an abstract base class, as
was carried out for the PayOff hierarchy. We are primarily interested in modelling continuous
probability distributions for the time being.
Each (continuous) statistical distribution contains the following properties to be modelled:
Domain Interval - The interval subset of R with which the distribution is defined for
Probability Density Function - Describes the frequency for any particular value in our
domain
Cumulative Density Function - The function describing the probability that a value is
less than or equal to a particular value
Expectation - The expected value (the mean) of the distribution
Variance - Characterisation of the spread of values around the expected value
Standard Deviation - The square root of the variance, used because it possesses the
same units as the expected value, unlike the variance
We also wish to produce a sequence of random draws from this distribution, assuming a
sequence of random numbers is available to provide the randomness. We can achieve this in
two ways. We can either use the inverse cumulative distribution function (also known as the
quantile function), which is a property of the distribution itself, or we can use a custom method
(such as Box-Muller). Some of the distributions do not possess an analytical inverse to the CDF
and hence they will need to be approximated numerically, via an appropriate algorithm. This
calculation will be encapsulated into the class of the relevant inherited distribution.
Here is the partial header file for the StatisticalDistribution abstract base class (we will add
extra distributions later):
#i f n d e f
STATISTICS H
#define
STATISTICS H
#include <cmath>
#include <v e c t o r >
class S t a t i s t i c a l D i s t r i b u t i o n {
201
public :
StatisticalDistribution () ;
virtual S t a t i s t i c a l D i s t r i b u t i o n ( ) ;
// D i s t r i b u t i o n f u n c t i o n s
v i r t u a l double pdf ( const double& x ) const = 0 ;
v i r t u a l double c d f ( const double& x ) const = 0 ;
// I n v e r s e c u m u l a t i v e d i s t r i b u t i o n f u n c t i o n s ( aka t h e q u a n t i l e f u n c t i o n )
v i r t u a l double i n v c d f ( const double& q u a n t i l e ) const = 0 ;
// D e s c r i p t i v e s t a t s
v i r t u a l double mean ( ) const = 0 ;
v i r t u a l double var ( ) const = 0 ;
v i r t u a l double s t d e v ( ) const = 0 ;
#endif
Weve specified pure virtual methods for the probability density function (pdf), cumulative
density function (cdf), inverse cdf ( inv cdf ), as well as descriptive statistics functions such as
mean, var (variance) and stdev (standard deviation). Finally we have a method that takes in
a vector of uniform random variables on the open interval (0, 1), then fills a vector of identical
length with draws from the distribution.
Since all of the methods are pure virtual, we only need a very simple implementation of a
source file for this class, since we are simply specifying an interface. However, we would like
to see a concrete implementation of a particular class. We will consider, arguably, the most
important distribution in quantitative finance, namely the standard normal distribution.
14.3.2
Firstly well briefly review the formulae for the various methods we need to implement for the
standard normal distribution. The probability density function of the standard normal distribu-
202
tion is given by:
1 2
1
(x) = e 2 x
2
1
(x) =
2
t2
e 2 dt
The inverse cumulative density function of the standard normal distribution (also known as
the probit function) is somewhat more involved. No analytical formula exists for this particular
function and so it must be approximated by numerical methods. We will utilise the BeasleySpringer-Moro algorithm, found in Korn[15].
Given that we are dealing with the standard normal distribution, the mean is simply = 0,
variance 2 = 1 and standard deviation, = 1. The implementation for the header file (which
is a continuation of statistics .h above) is as follows:
c l a s s S t a n d a r d N o r m a l D i s t r i b u t i o n : public S t a t i s t i c a l D i s t r i b u t i o n {
public :
StandardNormalDistribution () ;
virtual StandardNormalDistribution ( ) ;
// D i s t r i b u t i o n f u n c t i o n s
v i r t u a l double pdf ( const double& x ) const ;
v i r t u a l double c d f ( const double& x ) const ;
// I n v e r s e c u m u l a t i v e d i s t r i b u t i o n f u n c t i o n ( aka t h e p r o b i t f u n c t i o n )
v i r t u a l double i n v c d f ( const double& q u a n t i l e ) const ;
// D e s c r i p t i v e s t a t s
v i r t u a l double mean ( ) const ;
// e q u a l t o 0
// e q u a l t o 1
v i r t u a l double s t d e v ( ) const ;
// e q u a l t o 1
203
STATISTICS CPP
#define
STATISTICS CPP
#define
#include s t a t i s t i c s . h
#include <i o s t r e a m >
S t a t i s t i c a l D i s t r i b u t i o n : : S t a t i s t i c a l D i s t r i b u t i o n ( ) {}
S t a t i s t i c a l D i s t r i b u t i o n : : S t a t i s t i c a l D i s t r i b u t i o n ( ) {}
// C o n s t r u c t o r / d e s t r u c t o r
S t a n d a r d N o r m a l D i s t r i b u t i o n : : S t a n d a r d N o r m a l D i s t r i b u t i o n ( ) {}
S t a n d a r d N o r m a l D i s t r i b u t i o n : : S t a n d a r d N o r m a l D i s t r i b u t i o n ( ) {}
// P r o b a b i l i t y d e n s i t y f u n c t i o n
double S t a n d a r d N o r m a l D i s t r i b u t i o n : : pdf ( const double& x ) const {
return ( 1 . 0 / s q r t ( 2 . 0 M PI ) ) exp ( 0.5 xx ) ;
}
// C u m u l a t ive d e n s i t y f u n c t i o n
double S t a n d a r d N o r m a l D i s t r i b u t i o n : : c d f ( const double& x ) const {
double k = 1 . 0 / ( 1 . 0 + 0 . 2 3 1 6 4 1 9 x ) ;
double k sum = k ( 0 . 3 1 9 3 8 1 5 3 0 + k ( 0.356563782 + k ( 1 . 7 8 1 4 7 7 9 3 7 +
k ( 1.821255978 + 1 . 3 3 0 2 7 4 4 2 9 k ) ) ) ) ;
i f ( x >= 0 . 0 ) {
return ( 1 . 0 ( 1 . 0 / ( pow ( 2 M PI , 0 . 5 ) ) ) exp ( 0.5 xx ) k sum ) ;
} else {
return 1 . 0 c d f (x ) ;
}
}
204
// I n v e r s e c u m u l a t i v e d i s t r i b u t i o n f u n c t i o n ( aka t h e p r o b i t f u n c t i o n )
double S t a n d a r d N o r m a l D i s t r i b u t i o n : : i n v c d f ( const double& q u a n t i l e ) const {
// This i s t h e B e a s l e y S p r i n g e r Moro a l g o r i t h m which can
// be found i n Glasserman [ 2 0 0 4 ] . We won t go i n t o t h e
// d e t a i l s here , so have a l o o k a t t h e r e f e r e n c e f o r more i n f o
s t a t i c double a [ 4 ] = {
2.50662823884 ,
18.61500062529 ,
41.39119773534 ,
25.44106049637};
s t a t i c double b [ 4 ] = {
8.47351093090 ,
23.08336743743 ,
21.06224101826 ,
3.13082909833};
s t a t i c double c [ 9 ] = { 0 . 3 3 7 4 7 5 4 8 2 2 7 2 6 1 4 7 ,
0.9761690190917186 ,
0.1607979714918209 ,
0.0276438810333863 ,
0.0038405729373609 ,
0.0003951896511919 ,
0.0000321767881768 ,
0.0000002888167364 ,
0.0000003960315187};
205
double num = 0 . 0 ;
} else {
return 1.0 i n v c d f (1 q u a n t i l e ) ;
}
}
// E x p e c t a t i o n /mean
double S t a n d a r d N o r m a l D i s t r i b u t i o n : : mean ( ) const { return 0 . 0 ; }
// Variance
double S t a n d a r d N o r m a l D i s t r i b u t i o n : : var ( ) const { return 1 . 0 ; }
// S t a n d a r d D e v i a t i o n
double S t a n d a r d N o r m a l D i s t r i b u t i o n : : s t d e v ( ) const { return 1 . 0 ; }
206
f o r BM)
i f ( u n i f o r m d r a w s . s i z e ( ) % 2 != 0 ) {
s t d : : c o u t << Uniform draw v e c t o r s i z e not an even number . << s t d : :
endl ;
return ;
}
// Slow , b u t e a s y t o implement
f o r ( i n t i =0; i <u n i f o r m d r a w s . s i z e ( ) / 2 ; i ++) {
d i s t d r a w s [ 2 i ] = s q r t ( 2.0 l o g ( u n i f o r m d r a w s [ 2 i ] ) )
s i n ( 2 M PI u n i f o r m d r a w s [ 2 i +1]) ;
d i s t d r a w s [ 2 i +1] = s q r t ( 2.0 l o g ( u n i f o r m d r a w s [ 2 i ] ) )
c o s ( 2 M PI u n i f o r m d r a w s [ 2 i +1]) ;
}
return ;
}
#endif
Ill discuss briefly some of the implementations here. The cumulative distribution function
(cdf) is referenced from Joshi[12]. It is an approximation, rather than closed-form solution. The
inverse CDF ( inv cdf ) makes use of the Beasley-Springer-Moro algorithm, which was implemented
via the algorithm given in Korn[15]. A similar method can be found in Joshi[11]. Once again
the algorithm is an approximation to the real function, rather than a closed form solution. The
final method is random draws. In this instance we are using the Box-Muller algorithm. However,
we could instead utilise the more efficient Ziggurat algorithm, although we wont do so here.
14.3.3
We will now utilise the new statistical distribution classes with a simple random number generator
in order to output statistical values:
#include s t a t i s t i c s . h
#include <i o s t r e a m >
#include <v e c t o r >
207
return 0 ;
}
The output from the program is as follows (a sequence of normally distributed random variables):
3.56692
3.28529
0.192324
0.723522
1.10093
0.217484
2.22963
1.06868
0.35082
0.806425
208
0.168485
1.3742
0.131154
0.59425
0.449029
2.37823
0.0431789
0.891999
0.564585
1.26432
Now that we have set up the inheritance hierarchy, we could construct additional (continuous)
statistical distributions, such as the log-normal distribution, the gamma distribution and the chisquare distribution.
Chapter 15
Jump-Diffusion Models
This chapter will deal with a particular assumption of the Black-Scholes model and how to refine
it to produce more accurate pricing models. In the Black-Scholes model the stock price evolves
as a geometric Brownian motion. Crucially, this allows continuous Delta hedging and thus a
fixed no-arbitrage price for any option on the stock.
If we relax the assumption of GBM and introduce the concept of discontinuous jumps in the
stock price then it becomes impossible to perfectly hedge and leads to market incompleteness.
This means that options prices are only bounded rather than fixed. In this article we will consider
the effect on options prices when such jumps occur and implement a semi-closed form pricer in
C++ based on the analytical formula derived by Merton[16].
15.1
This section closely follows the chapter on Jump Diffusions in Joshi[12], where more theoretical
details are provided.
In order to model such stock jumps we require certain properties. Firstly, the jumps
should occur in an instantaneous fashion, neglecting the possibility of a Delta hedge. Secondly,
we require that the probability of any jump occuring in a particular interval of time should
be approximately proportional to the length of that time interval. The statistical method that
models such a situation is given by the Poisson process[12].
A Poisson process states the probability of an event occuring in a given time interval t is
given by t+, where is the intensity of the process and is an error term. The integer-valued
number of events that have occured at time t is given by N (t). A necessary property is that the
probability of a jump occuring is independent of the number of jumps already occured, i.e. all
209
210
future jumps should have no memory of past jumps. The probability of j jumps occuring by
time t is given by:
P(N (t) = j) =
(t)j t
e
j!
(15.1)
Thus, we simply need to modify our underlying GBM model for the stock via the addition of
jumps. This is achieved by allowing the stock price to be multiplied by a random factor J:
(15.2)
15.2
After an appropriate application of risk neutrality[12] we have that log ST , the log of the final
price of the stock at option expiry, is given by:
N (T )
X
1
log(ST ) = log(S0 ) + + 2 T + T N (0, 1) +
logJj
2
j=1
(15.3)
This is all we need to price European options under a Monte Carlo framework. To carry
this out we simply generate multiple final spot prices by drawing from a normal distribution
and a Poisson distribution, and then selecting the Jj values to form the jumps. However, this is
somewhat unsatisfactory as we are specifically choosing the Jj values for the jumps. Shouldnt
they themselves also be random variables distributed in some manner?
In 1976, Robert Merton[16] was able to derive a semi-closed form solution for the price of
European options where the jump values are themselves normally distributed. If the price of an
option priced under Black-Scholes is given by BS(S0 , , r, T, K) with S0 initial spot, constant
volatility, r constant risk-free rate, T time to maturity and K strike price, then in the jumpdiffusion framework the price is given by[12]:
X
e T (0 T )n
BS(S0 , n , rn , T, K)
n!
n=0
(15.4)
211
Where
p
2 + n 2 /T
(15.5)
rn
r (m 1) + n log m/T
(15.6)
(15.7)
The extra parameters and m represent the standard deviation of the lognormal jump process
and the scale factor for jump intensity, respectively.
15.3
C++ Implementation
We will avoid a fully-fledged object-oriented approach for this model as it extends simply the
analytical pricing of European call options quite straightforwardly. Below is the code in its
entirety. The major change includes the calculation of the factorial within the summation loop
and the weighted sum of the Black-Scholes prices:
#define
// S t a n d a r d normal p r o b a b i l i t y d e n s i t y f u n c t i o n
double norm pdf ( const double x ) {
return ( 1 . 0 / ( pow ( 2 M PI , 0 . 5 ) ) ) exp ( 0.5 xx ) ;
}
// An a p p r o x i m a t i o n t o t h e c u m u l a t i v e d i s t r i b u t i o n f u n c t i o n
// f o r t h e s t a n d a r d normal d i s t r i b u t i o n
// Note : This i s a r e c u r s i v e f u n c t i o n
double norm cdf ( const double x ) {
double k = 1 . 0 / ( 1 . 0 + 0 . 2 3 1 6 4 1 9 x ) ;
double k sum = k ( 0 . 3 1 9 3 8 1 5 3 0 + k ( 0.356563782 + k ( 1 . 7 8 1 4 7 7 9 3 7 + k
( 1.821255978 + 1 . 3 3 0 2 7 4 4 2 9 k ) ) ) ) ;
i f ( x >= 0 . 0 ) {
212
// C a l c u l a t e t h e European v a n i l l a c a l l p r i c e b a s e d on
// u n d e r l y i n g S , s t r i k e K, r i s k f r e e r a t e r , v o l a t i l i t y o f
// u n d e r l y i n g sigma and time t o m a t u r i t y T
double b s c a l l p r i c e ( const double S , const double K, const double r ,
const double sigma , const double T) {
return S norm cdf ( d j ( 1 , S , K, r , sigma , T) )K exp( r T)
norm cdf ( d j ( 2 , S , K, r , sigma , T) ) ;
}
// C a l c u l a t e t h e Merton jumpd i f f u s i o n p r i c e b a s e d on
// a f i n i t e sum a p p r o x i m a t i o n t o t h e i n f i n i t e s e r i e s
// s o l u t i o n , making u s e o f t h e BS c a l l p r i c e .
double b s j d c a l l p r i c e ( const double S , const double K, const double r ,
const double sigma , const double T, const i n t N, const double m,
const double lambda , const double nu ) {
double p r i c e = 0 . 0 ;
// S t o r e s t h e f i n a l c a l l p r i c e
double f a c t o r i a l = 1 . 0 ;
// Prec a l c u l a t e as much as p o s s i b l e
double lambda p = lambda m;
double lambda p T = lambda p T ;
// C a l c u l a t e t h e f i n i t e sum o v e r N terms
f o r ( i n t n=0; n<N; n++) {
213
// C a l c u l a t e n !
i f ( n == 0 ) {
f a c t o r i a l = 1 ;
} else {
f a c t o r i a l = n ;
}
// R e f i n e t h e jump p r i c e o v e r t h e l o o p
p r i c e += ( ( exp(lambda p T ) pow ( lambda p T , n ) ) / f a c t o r i a l )
b s c a l l p r i c e ( S , K, r n , sigma n , T) ;
}
return p r i c e ;
}
// Option p r i c e
double K = 1 0 0 . 0 ;
// S t r i k e p r i c e
double r = 0 . 0 5 ;
// Riskf r e e r a t e (5%)
double v = 0 . 2 ;
// V o l a t i l i t y o f t h e u n d e r l y i n g (20%)
double T = 1 . 0 ;
// One y e a r u n t i l e x p i r y
int N = 5 0 ;
// Terms i n t h e f i n i t e sum a p p r o x i m a t i o n
double m = 1 . 0 8 3 2 8 7 ;
// S c a l e f a c t o r f o r J
double lambda = 1 . 0 ;
// I n t e n s i t y o f jumps
double nu = 0 . 4 ;
// S t d e v o f l o g n o r m a l jump p r o c e s s
// Then we c a l c u l a t e t h e c a l l jumpd i f f u s i o n v a l u e
double c a l l j d = b s j d c a l l p r i c e ( S , K, r , v , T, N, m, lambda , nu ) ;
s t d : : c o u t << C a l l P r i c e under JD :
return 0 ;
}
<< c a l l j d << s t d : : e n d l ;
214
The output from the code is:
C a l l P r i c e under JD :
18.7336
We can clearly see that in comparison to the Black-Scholes price of 10.4506 given in the prior
chapter the value of the call under the jump diffusion process is much higher. This is to be
expected since the jumps introduce extra volatility into the model.
Chapter 16
Stochastic Volatility
We have spent a good deal of time looking at vanilla and path-dependent options on QuantStart
so far. We have created separate classes for random number generation and sampling from a
standard normal distribution. Were now going to build on this by generating correlated time
series paths.
Correlated asset paths crop up in many areas of quantitative finance and options pricing. In
particular, the Heston Stochastic Volatility Model requires two correlated GBM asset paths as
a basis for modelling volatility.
16.1
Motivation
Lets motivate the generation of correlated asset paths via the Heston Model. The original BlackScholes model assumes that volatility, , is constant over the lifetime of the option, leading to
the stochastic differential equation (SDE) for GBM:
dSt = St dt + St dWt
(16.1)
The basic Heston model now replaces the constant coefficient with the square root of the
t St dWtS
dSt
St dt +
dt
( t )dt + t dWt
215
(16.2)
(16.3)
216
Where dWtS and dWt are Brownian motions with correlation . Hence we have two correlated
stochastic processes. In order to price path-dependent options in the Heston framework by Monte
Carlo, it is necessary to generate these two asset paths.
16.2
Rather than considering the case of two correlated assets, we will look at N seperate assets and
then reduce the general procedure to N = 2 for the case of our Heston motivating example.
At each time step in the path generation we require N correlated random numbers, where
ij denotes the correlation coefficient between the ith and jth asset, xi will be the uncorrelated
random number (which we will sample from the standard normal distribution), i will be a
correlated random number, used in the asset path generation and ij will be a matrix coefficient
necessary for obtaining i .
To calculate i we use the following process for each of the path generation time-steps:
i =
i
X
ik xk , 1 i N
(16.4)
2
ik
= 1, 1 i N
(16.5)
ik jk = ij , j < i
(16.6)
k=1
i
X
k=1
i
X
k=1
Thankfully, for our Heston model, we have N = 2 and this reduces the above equation set to
the far simpler relations:
1
2
x1
(16.7)
p
x1 + x2 1 2
(16.8)
This motivates a potential C++ implementation. We already have the capability to generate
paths of standard normal distributions. If we inherit a new class CorrelatedSND (SND for Standard Normal Distribution), we can provide it with a correlation coefficient and an original time
series of random standard normal variables draws to generate a new correlated asset path.
217
16.3
Cholesky Decomposition
It can be seen that the process used to generate N correlated i values is in fact a matrix equation.
It turns out that it is actually a Cholesky Decomposition, which we have discussed in the chapter
on Numerical Linear Algebra. Thus a far more efficient implementation than I am constructing
here would make use of an optimised matrix class and a pre-computed Cholesky decomposition
matrix.
The reason for this link is that the correlation matrix, , is symmetric positive definite.
Thus it can be decomposed into = RR , although R , the conjugate-transpose matrix simply
reduces to the transpose in the case of real-valued entries, with R a lower-triangular matrix.
Hence it is possible to calculate the correlated random variable vector via:
= Rx
(16.9)
16.4
C++ Implementation
As we pointed out above the procedure for obtaining the second path will involve calculating an
uncorrelated set of standard normal draws, which are then recalculated via an inherited subclass
to generate a new, correlated set of random variables. For this we will make use of statistics .h
and statistics .cpp, which can be found in the chapter on Statistical Distributions.
Our next task is to write the header and source files for CorrelatedSND. The listing for
correlated snd .h follows:
#i f n d e f
CORRELATED SND H
#define
CORRELATED SND H
#include s t a t i s t i c s . h
c l a s s CorrelatedSND : public S t a n d a r d N o r m a l D i s t r i b u t i o n {
protected :
double rho ;
218
const s t d : : v e c t o r <double> u n c o r r d r a w s ;
// Modify an u n c o r r e l a t e d s e t o f d i s t r i b u t i o n draws t o be c o r r e l a t e d
v i r t u a l void c o r r e l a t i o n c a l c ( s t d : : v e c t o r <double>& d i s t d r a w s ) ;
public :
CorrelatedSND ( const double
rho ,
const s t d : : v e c t o r <double>
uncorr draws ) ;
v i r t u a l CorrelatedSND ( ) ;
#endif
The class inherits from StandardNormalDistribution, provided in statistcs .h. We are adding
two protected members, rho (the correlation coefficient) and uncorr draws, a pointer to a const
vector of doubles. We also create an additional virtual method, correlation calc , that actually
performs the correlation calculation. The only additional modification is to add the parameters,
which will ultimately become stored as protected member data, to the constructor.
Next up is the source file, correlated snd .cpp:
#i f n d e f
#define
#include c o r r e l a t e d s n d . h
#include <i o s t r e a m >
#include <cmath>
rho ,
const s t d : : v e c t o r <double>
: rho ( r h o ) , u n c o r r d r a w s ( u n c o r r d r a w s ) {}
CorrelatedSND : : CorrelatedSND ( ) {}
uncorr draws )
219
// This c a r r i e s o u t t h e a c t u a l c o r r e l a t i o n m o d i f i c a t i o n . I t i s e a s y t o s e e
that i f
// rho = 0 . 0 , t h e n d i s t d r a w s i s u nm o di f i ed , whereas i f rho = 1 . 0 , t h e n
dist draws
// i s s i m p l y s e t e q u a l t o u n c o r r d r a w s . Thus w i t h 0 < rho < 1 we have a
// w e i g h t e d a v e r a g e o f each s e t .
void CorrelatedSND : : c o r r e l a t i o n c a l c ( s t d : : v e c t o r <double>& d i s t d r a w s ) {
f o r ( i n t i =0; i <d i s t d r a w s . s i z e ( ) ; i ++) {
d i s t d r a w s [ i ] = rho ( u n c o r r d r a w s ) [ i ] + d i s t d r a w s [ i ] s q r t (1 rho
rho ) ;
}
}
d i r e c t l y from
// s t a t i s t i c s . h , which i s f u l l y commented !
i f ( u n i f o r m d r a w s . s i z e ( ) != d i s t d r a w s . s i z e ( ) ) {
s t d : : c o u t << Draws v e c t o r s a r e o f u ne qu al s i z e i n s t a n d a r d normal d i s t
.
<< s t d : : e n d l ;
return ;
}
i f ( u n i f o r m d r a w s . s i z e ( ) % 2 != 0 ) {
s t d : : c o u t << Uniform draw v e c t o r s i z e not an even number . << s t d : :
endl ;
return ;
}
220
return ;
}
#endif
The work is carried out in correlation calc . It is easy to see that if = 0, then dist draws
is unmodified, whereas if = 1, then dist draws is simply equated to uncorr draws. Thus with
0 < < 1 we have a weighted average of each set of random draws. Note that I have reproduced
the Box-Muller functionality here so that you dont have to look it up in statistics .cpp. In a
production code this would be centralised elsewhere (such as with a random number generator
class).
Now we can tie it all together. Here is the listing of main.cpp:
#include s t a t i s t i c s . h
#include c o r r e l a t e d s n d . h
#include <i o s t r e a m >
#include <v e c t o r >
// Number o f v a l u e s
int v a l s = 3 0 ;
/ UNCORRELATED SND /
/ ================ /
221
// random number g e n e r a t i o n !
f o r ( i n t i =0; i <s n d u n i f o r m d r a w s . s i z e ( ) ; i ++) {
s n d u n i f o r m d r a w s [ i ] = rand ( ) / s t a t i c c a s t <double>(RAND MAX) ;
}
/ CORRELATION SND /
/ =============== /
// C o r r e l a t i o n c o e f f i c i e n t
double rho = 0 . 5 ;
// C r e a t e t h e c o r r e l a t e d s t a n d a r d normal d i s t r i b u t i o n
CorrelatedSND csnd ( rho , &s n d n o r m a l d r a w s ) ;
s t d : : v e c t o r <double> c s n d u n i f o r m d r a w s ( v a l s , 0 . 0 ) ;
s t d : : v e c t o r <double> c s n d n o r m a l d r a w s ( v a l s , 0 . 0 ) ;
// Uniform g e n e r a t i o n f o r t h e c o r r e l a t e d SND
f o r ( i n t i =0; i <c s n d u n i f o r m d r a w s . s i z e ( ) ; i ++) {
c s n d u n i f o r m d r a w s [ i ] = rand ( ) / s t a t i c c a s t <double>(RAND MAX) ;
}
return 0 ;
}
The above code is somewhat verbose, but that is simply a consequence of not encapsulating
222
the random number generation capability. Once we have created an initial set of standard normal
draws, we simply have to pass that to an instance of CorrelatedSND (in this line: CorrelatedSND
csnd(rho, &snd normal draws);) and then call random draws(..) to create the correlated stream.
There are plenty of extensions we could make to this code. The obvious two are encapsulating
the random number generation and converting it to use an efficient Cholesky Decomposition
223
implementation. Now that we have correlated streams, we can also implement the Heston Model
in Monte Carlo.
Up until this point we have priced all of our options under the assumption that the volatility,
, of the underlying asset has been constant over the lifetime of the option. In reality financial
markets do not behave this way. Assets exist under market regimes where their volatility can
vary signficantly during different time periods. The 2007-2008 financial crisis and the May Flash
Crash of 2010 are good examples of periods of intense market volatility.
Thus a natural extension of the Black Scholes model is to consider a non-constant volatility.
Steven Heston formulated a model that not only considered a time-dependent volatility, but also
introduced a stochastic (i.e. non-deterministic) component as well. This is the famous Heston
model for stochastic volatility.
In this chapter we will outline the mathematical model and use a discretisation technique
known as Full Truncation Euler Discretisation, coupled with Monte Carlo simulation, in order to
price a European vanilla call option with C++. As with the majority of the models implemented
on QuantStart, the code is object-oriented, allowing us to plug-in other option types (such as
Path-Dependent Asians) with minimal changes.
16.5
Mathematical Model
The Black Scholes model uses a stochastic differential equation with a geometric Brownian motion
to model the dynamics of the asset path. It is given by:
dSt = St dt + St dWtS
(16.10)
St is the price of the underlying asset at time t, is the (constant) drift of the asset, is the
(constant) volatility of the underlying and dWtS is a Weiner process (i.e. a random walk).
The Heston model extends this by introducing a second stochastic differential equation to
represent the path of the volatility of the underlying over the lifetime of the option. The SDE
for the variance is given by a Cox-Ingersoll-Ross process:
dSt
= St dt +
dt
t St dWtS
( t )dt + t dWt
(16.11)
(16.12)
224
Where:
is the drift of the asset
is the expected value of t , i.e. the long run average price variance
is the rate of mean reversion of t to the long run average
is the vol of vol, i.e. the variance of t
Note that none of the parameters have any time-dependence. Extensions of the Heston model
generally allow the values to become piecewise constant.
In order for t > 0, the Feller condition must be satisfied:
2 > 2
(16.13)
In addition, the model enforces that the two separate Weiner processes making up the randomness are in fact correlated, with instantaneous constant correlation :
dWtS dWt
16.6
= dt
(16.14)
Euler Discretisation
Given that the SDE for the asset path is now dependent (in a temporal manner) upon the
solution of the second volatility SDE, it is necessary to simulate the volatility process first and
then utilise this volatility path in order to simulate the asset path. In the case of the original
Black Scholes SDE it is possible to use Itos Lemma to directly solve for St . However, we are
unable to utilise that procedure here and must use a numerical approximation in order to obtain
both paths. The method utilised is known as Euler Discretisation.
The volatility path will be discretised into constant-increment time steps of t, with the
updated volatility, i+1 given as an explicit function of i :
i+1
i + ( i )t + i Wi+1
(16.15)
225
discretisation errors where i+1 can become negative. This is not a physical situation and so is
a direct consequence of the numerical approximation. In order to handle negative values, we need
to modify the above formula to include methods of eliminating negative values for subsequent
iterations of the volatility path. Thus we introduce three new functions f1 , f2 , f3 , which lead to
three separate schemes for how to handle the negative volatility values:
i+1
f1 (i ) + ( f2 (i ))t +
f3 (i )Wi+1
(16.16)
Scheme
f1
f2
f3
Reflection
|x|
|x|
|x|
Partial Truncation
Full Truncation
x+
x+
i+1
= i + (
i+ )t
+ i+ Wi+1
(16.17)
Wi+1
Wi is normally distributed with variance t and that the distribution of Wi+1
Wi is
independent of i. This means it can be replaced with tN (0, 1), where N (0, 1) is a random
q
1 +
S
= Si exp vi t + vi+ tWi+1
2
Si+1
(16.18)
226
As with the Full Truncation mechanism outlined above, the volatility term appearing in the
asset SDE discretisation has also been truncated and so i is replaced by i+ .
16.6.1
The next major issue that we need to look at is how to generate the Wi and WiS terms for the
volatility path and the asset path respectively, such that they remain correlated with correlation
, as prescribed via the mathematical model. This is exactly what is necessary here.
Once we have two uniform random draw vectors it is possible to use the StandardNormalDistribution
class outlined in the chapter on statistical distributions to create two new vectors containing
standard normal random draws - exactly what we need for the volatility and asset path simulation!
16.6.2
In order to price a European vanilla call option under the Heston stochastic volatility model,
we will need to generate many asset paths and then calculate the risk-free discounted average
pay-off. This will be our option price.
The algorithm that we will follow to calculate the full options price is as follows:
1. Choose number of asset simulations for Monte Carlo and number of intervals to discretise
asset/volatility paths over
2. For each Monte Carlo simulation, generate two uniform random number vectors, with the
second correlated to the first
3. Use the statistics distribution class to convert these vectors into two new vectors containing
standard normal draws
4. For each time-step in the discretisation of the vol path, calculate the next volatility value
from the normal draw vector
5. For each time-step in the discretisation of the asset path, calculate the next asset value
from the vol path vector and normal draw vector
6. For each Monte Carlo simulation, store the pay-off of the European call option
7. Take the mean of these pay-offs and then discount via the risk-free rate to produce an
option price, under risk-neutral pricing.
227
We will now present a C++ implementation of this algorithm using a mixture of new code
and prior classes written in prior chapters.
16.7
C++ Implementation
We are going to take an object-oriented approach and break the calculation domain into various
re-usable classes. In particular we will split the calculation into the following objects:
PayOff - This class represents an option pay-off object. We have discussed it at length on
QuantStart.
Option - This class holds the parameters associated with the term sheet of the European
option, as well as the risk-free rate. It requires a PayOff instance.
StandardNormalDistribution - This class allows us to create standard normal random draw
values from a uniform distribution or random draws.
CorrelatedSND - This class takes two standard normal random draws and correlates the
second with the first by a correlation factor .
HestonEuler - This class accepts Heston model parameters and then performs a Full Truncation of the Heston model, generating both a volatility path and a subequent asset path.
We will now discuss the classes individually.
16.7.1
PayOff Class
The PayOff class wont be discussed in any great detail within this chapter as it is described
fully in previous chapters. The PayOff class is a functor and as such is callable.
16.7.2
Option Class
The Option class is straightforward. It simply contains a set of public members for the option
term sheet parameters (strike K, time to maturity T ) as well as the (constant) risk-free rate
r. The class also takes a pointer to a PayOff object, making it straightforward to swap out
another pay-off (such as that for a Put option).
The listing for option.h follows:
#i f n d e f
OPTION H
#define
OPTION H
228
#include p a y o f f . h
c l a s s Option {
public :
PayOff p a y o f f ;
double K;
double r ;
double T ;
Option ( double
double
K , double
r,
T , PayOff
pay off ) ;
v i r t u a l Option ( ) ;
};
#endif
#i f n d e f
OPTION CPP
#define
OPTION CPP
#include o p t i o n . h
K , double
T , PayOff
r,
pay off ) :
K( K ) , r ( r ) , T( T ) , p a y o f f ( p a y o f f ) {}
Option : : Option ( ) {}
#endif
As can be seen from the above listings, the class doesnt do much beyond storing some data
members and exposing them.
229
16.7.3
The StandardNormalDistribution and CorrelatedSND classes are described in detail within the
chapter on statistical distributions and in the sections above so we will not go into detail here.
16.7.4
HestonEuler Class
The HestonEuler class is designed to accept the parameters of the Heston Model - in this case ,
, and - and then calculate both the volatility and asset price paths. As such there are private
data members for these parameters, as well as a pointer member representing the option itself.
There are two calculation methods designed to accept the normal draw vectors and produce the
respective volatility or asset spot paths.
The listing for heston mc.h follows:
#i f n d e f
HESTON MC H
#define
HESTON MC H
#include <cmath>
#include <v e c t o r >
#include o p t i o n . h
// The H e s t o n E u l e r c l a s s s t o r e s t h e n e c e s s a r y i n f o r m a t i o n
// f o r c r e a t i n g t h e v o l a t i l i t y and s p o t p a t h s b a s e d on t h e
// Heston S t o c h a s t i c V o l a t i l i t y model .
class HestonEuler {
private :
Option pOption ;
double kappa ;
double t h e t a ;
double x i ;
double rho ;
public :
H e s t o n E u l e r ( Option
pOption ,
double
kappa , double
double
x i , double
virtual HestonEuler ( ) ;
theta ,
rho ) ;
230
// C a l c u l a t e t h e v o l a t i l i t y p a t h
void c a l c v o l p a t h ( const s t d : : v e c t o r <double>& v o l d r a w s ,
s t d : : v e c t o r <double>& v o l p a t h ) ;
// C a l c u l a t e t h e a s s e t p r i c e p a t h
void c a l c s p o t p a t h ( const s t d : : v e c t o r <double>& s p o t d r a w s ,
const s t d : : v e c t o r <double>& v o l p a t h ,
s t d : : v e c t o r <double>& s p o t p a t h ) ;
};
#endif
HESTON MC CPP
#define
HESTON MC CPP
#include heston mc . h
// H e s t o n E u l e r
// ===========
H e s t o n E u l e r : : H e s t o n E u l e r ( Option
pOption ,
double
kappa , double
double
x i , double
theta ,
rho ) :
H e s t o n E u l e r : : H e s t o n E u l e r ( ) {}
231
v o l p a t h [ i ] = v o l p a t h [ i 1] + kappa dt ( t h e t a v max ) +
x i s q r t ( v max dt ) v o l d r a w s [ i 1 ] ;
}
}
// C r e a t e t h e s p o t p r i c e p a t h making u s e o f t h e v o l a t i l i t y
// p a t h . Uses a s i m i l a r E u l e r T r u n c a t i o n method t o t h e v o l p a t h .
f o r ( i n t i =1; i <v e c s i z e ; i ++) {
double v max = s t d : : max( v o l p a t h [ i 1] , 0 . 0 ) ;
s p o t p a t h [ i ] = s p o t p a t h [ i 1] exp ( ( pOption>r 0 . 5 v max ) dt +
s q r t ( v max dt ) s p o t d r a w s [ i 1]) ;
}
}
#endif
The calc vol path method takes references to a const vector of normal draws and a vector
to store the volatility path. It calculates the t value (as dt), based on the option maturity
time. Then, the stochastic simulation of the volatility path is carried out by means of the Full
Truncation Euler Discretisation, outlined in the mathematical treatment above. Notice that i+
is precalculated, for efficiency reasons.
The calc spot path method is similar to the calc vol path method, with the exception that it
accepts another vector, vol path that contains the volatility path values at each time increment.
The risk-free rate r is obtained from the option pointer and, once again, i+ is precalculated.
Note that all vectors are passed by reference in order to reduce unnecessary copying.
16.7.5
Main Program
This is where it all comes together. There are two components to this listing: The generate normal correlation paths
function and the main function. The former is designed to handle the boilerplate code of generating the necessary uniform random draw vectors and then utilising the CorrelatedSND object
232
to produce correlated standard normal distribution random draw vectors.
I wanted to keep this entire example of the Heston model tractable, so I have simply used
the C++ built-in rand function to produce the uniform standard draws. However, in a production environment a Mersenne Twister uniform number generator (or something even more
sophisticated) would be used to produce high-quality pseudo-random numbers. The output of
the function is to calculate the values for the spot normals and cor normals vectors, which are
used by the asset spot path and the volatility path respectively.
The main function begins by defining the parameters of the simulation, including the Monte
Carlo values and those necessary for the option and Heston model. The actual parameter values
are those give in the paper by Broadie and Kaya[3]. The next task is to create the pointers to
the PayOff and Option classes, as well as the HestonEuler instance itself.
After declaration of the various vectors used to hold the path values, a basic Monte Carlo
loop is created. For each asset simulation, the new correlated values are generated, leading to
the calculation of the vol path and the asset spot path. The option pay-off is calculated for
each path and added to the total, which is then subsequently averaged and discounted via the
risk-free rate. The option price is output to the terminal and finally the pointers are deleted.
Here is the listing for main.cpp:
#include <i o s t r e a m >
#include p a y o f f . h
#include o p t i o n . h
#include c o r r e l a t e d s n d . h
#include heston mc . h
233
// C r e a t e t h e c o r r e l a t e d s t a n d a r d normal d i s t r i b u t i o n
CorrelatedSND csnd ( rho , &s p o t n o r m a l s ) ;
s t d : : v e c t o r <double> c s n d u n i f o r m d r a w s ( v a l s , 0 . 0 ) ;
// Uniform g e n e r a t i o n f o r t h e c o r r e l a t e d SND
f o r ( i n t i =0; i <c s n d u n i f o r m d r a w s . s i z e ( ) ; i ++) {
c s n d u n i f o r m d r a w s [ i ] = rand ( ) / s t a t i c c a s t <double>(RAND MAX) ;
}
// Number o f s i m u l a t e d a s s e t p a t h s
// Number o f i n t e r v a l s f o r t h e a s s e t p a t h
t o be sampled
double S 0 = 1 0 0 . 0 ;
// I n i t i a l s p o t p r i c e
double K = 1 0 0 . 0 ;
// S t r i k e p r i c e
double r = 0 . 0 3 1 9 ;
// Riskf r e e r a t e
double v 0 = 0 . 0 1 0 2 0 1 ; // I n i t i a l
volatility
double T = 1 . 0 0 ;
// One y e a r u n t i l e x p i r y
// C o r r e l a t i o n o f a s s e t and v o l a t i l i t y
double kappa = 6 . 2 1 ;
// Meanr e v e r s i o n r a t e
double t h e t a = 0 . 0 1 9 ;
// Long run a v e r a g e v o l a t i l i t y
double x i = 0 . 6 1 ;
// Vol o f v o l
234
// Vector o f i n i t i a l
s p o t normal draws
s t d : : v e c t o r <double> v o l d r a w s ( n u m i n t e r v a l s , 0 . 0 ) ;
// Vector o f i n i t i a l
c o r r e l a t e d v o l normal draws
s t d : : v e c t o r <double> s p o t p r i c e s ( n u m i n t e r v a l s , S 0 ) ;
// Vector o f
i n i t i a l spot prices
s t d : : v e c t o r <double> v o l p r i c e s ( n u m i n t e r v a l s , v 0 ) ;
// Vector o f
i n i t i a l vol prices
// Monte C a rl o o p t i o n s p r i c i n g
double p a y o f f s u m = 0 . 0 ;
f o r ( unsigned i =0; i <num sims ; i ++) {
s t d : : c o u t << C a l c u l a t i n g path << i +1 << o f << num sims << s t d : :
endl ;
g e n e r a t e n o r m a l c o r r e l a t i o n p a t h s ( rho , s p o t d r a w s , v o l d r a w s ) ;
h e s t e u l e r . c a l c v o l p a t h ( vol draws , v o l p r i c e s ) ;
h e s t e u l e r . c a l c s p o t p a t h ( spot draws , v o l p r i c e s , s p o t p r i c e s ) ;
p a y o f f s u m += pOption>p a y o f f >operator ( ) ( s p o t p r i c e s [ n u m i n t e r v a l s
1]) ;
}
double o p t i o n p r i c e = ( p a y o f f s u m / s t a t i c c a s t <double>(num sims ) ) exp
( r T) ;
s t d : : c o u t << Option P r i c e : << o p t i o n p r i c e << s t d : : e n d l ;
// Free memory
delete pOption ;
delete pPa yOffCa ll ;
return 0 ;
}
235
For completeness, I have included the makefile utilised on my MacBook Air, running Mac
OSX 10.7.4:
h e s t o n : main . cpp heston mc . o c o r r e l a t e d s n d . o s t a t i s t i c s . o o p t i o n . o p a y o f f .
o
c l a n g++ o h e s t o n main . cpp heston mc . o c o r r e l a t e d s n d . o s t a t i s t i c s . o
o p t i o n . o p a y o f f . o a r c h x 8 6 6 4
c o r r e l a t e d s n d . o : c o r r e l a t e d s n d . cpp s t a t i s t i c s . o
c l a n g++ c c o r r e l a t e d s n d . cpp s t a t i s t i c s . o a r c h x 8 6 6 4
s t a t i s t i c s . o : s t a t i s t i c s . cpp
c l a n g++ c s t a t i s t i c s . cpp a r c h x 8 6 6 4
o p t i o n . o : o p t i o n . cpp p a y o f f . o
c l a n g++ c o p t i o n . cpp p a y o f f . o a r c h x 8 6 6 4
p a y o f f . o : p a y o f f . cpp
c l a n g++ c p a y o f f . cpp a r c h x 8 6 6 4
The exact option price is 6.8061, as reported by Broadie and Kaya[3]. It can be made
somewhat more accurate by increasing the number of asset paths and discretisation intervals.
There are a few extensions that could be made at this stage. One is to allow the various
schemes to be implemented, rather than hard-coded as above. Another is to introduce timedependence into the parameters. The next step after creating a model of this type is to actually
calibrate to a set of market data such that the parameters may be determined.
236
Chapter 17
238
point to the initial time, t = 0. This is analogous to the diffusion of heat in the heat equation. In
fact, the Black-Scholes can be transformed into the heat equation by a suitable coordinate change
and solved analytically, although this is beyond the scope of this chapter!
In order to obtain a solution to the Black-Scholes PDE for a European vanilla call option, we
will carry out the following steps:
Describe the PDE - Our first task is to outline the mathematical formalism by describing
the Black-Scholes equation itself along with any initial and boundary conditions that apply
as well as the domain over which the solution will be calculated.
Discretisation - We will then discretise the Black-Scholes PDE using suitable approximations to the derivative terms.
Object Orientation - Once we have the discretisation in place we will decide how to
define the objects representing our finite difference method in C++ code.
Execution and Output - After we have created all of the C++ code for the implementation, and executed it, we will plot the resulting option pricing surface using Python and
matplotlib.
C
C
1
2C
+ rS
+ 2 S 2 2 rC = 0
t
S
2
S
Our goal is to find a stable discretisation for this formula that we can implement. It will
produce an option pricing surface, C(S, t) as a function of spot S and time t that we can plot.
17.1
In this chapter we are going to start simply by approximating the solution to a European vanilla
call option. In fact an analytic solution exists for such an option. However, our task here is to
outline the Finite Difference Method, not to solve the most exotic option we can find right away!
In order to carry out the procedure we must specify the Black-Scholes PDE, the domain on
which the solution will exist and the constraints - namely the initial and boundary conditions that apply. Here is the full mathematical formalism of the problem:
239
C
t
= rS
C
1
2C
+ 2 S 2 2 rC
S
2
S
C(0, t)
C(S, T )
max(S K, 0),
0tT
0 S Smax
Lets step through the problem. The first line is simply the Black-Scholes equation.
The second line describes the boundary conditions for the case of a European vanilla call
option. The left-hand boundary equation states that on the left-boundary, for all times up to
expiry, the value of the call will equal 0. The right-hand equation for the right-boundary states
that, for all times up to expiry, the value of the call will equal the pay-off function, albeit slightly
adjusted to take into account discounting due to the risk-free rate, r. This is a consequence of
Put-Call Parity. Both of these boundary conditions are of the Dirichlet type.
The final line is the initial condition, which describes how the solution should behave at
the start of the time-marching procedure. Since we are marching backwards, this is actually
the final pay-off of the option at expiry, which is the familiar expression for a vanilla call option
pay-off.
17.2
Now that we have specified the continuous problem mathematically, we have to take this and
create a finite set of discretised algebraic equations, which are able to be solved by the computer.
We will make use of forward differencing for the time partial derivative, centred differencing for
the first order spatial derivative and a centred second difference for the diffusion term:
C n+1 C n
1
+ 2 Sj2
t
2
n
n
Cj+1
2Cjn + Cj1
x2
+ rSj
n
n
Cj+1
Cj1
2x
rCjn = 0
This can be rearranged so that the solution at time level N + 1 is given in terms of the
solution at time level N . This is what gives the method its explicit name. The solution is
explicitly dependent upon previous time levels. In later FDM methods we will see that this does
not have to be the case. Here is the rearranged equation:
240
n
n
Cjn+1 = j Cj1
+ j Cjn + j Cj+1
In order to describe these coefficients succinctly we can use the fact that the spot values,
Sj , increase linearly with x. Therefore Sj = jx. After some algebraic rearrangement, the
coefficients are given by:
2 j 2 t rjt
2
2
1 2 j 2 t rt
2 j 2 t rjt
+
2
2
This provides us with everything we need to begin our Finite Difference implementation.
Note that I have not discussed some extremely important topics with regards to finite difference
methods - particularly consistency, stability and convergence. These are all deep areas of
numerical analysis in their own right. However, I will be going into more detail about these
topics later!
17.3
Implementation
Were now ready to begin turning our mathematical algorithm into a C++ implementation. At
this stage it isnt immediately obvious how we will go about doing this. One could create a
monolithic procedural code to calculate the entire solution. However, frequent QuantStarters
will know that this is a suboptimal approach for many reasons. Instead we will make use of the
object-oriented paradigm, as well as previous code that we have already written, in order to save
development time.
Lets discuss the classes which will form the basis of our FDM solver:
PayOff - This class represents the pay-off functionality of an option. It is also a functor.
We have already made extensive use of it in our Monte Carlo options pricing chapters. I
have added the code listing below for completeness.
VanillaOption - This is a simple class that encapsulates the option parameters. We are
using it as well as PayOff as we will want to extend the FDM solver to allow more exotic
241
options in the future.
ConvectionDiffusionPDE - This is an abstract base class designed to provide an interface
to all subsequent derived classes. It consists of pure virtual functions representing the
various coefficients of the PDE as well as boundary and initial conditions.
BlackScholesPDE - This inherits from ConvectionDiffusionPDE and provides concrete
implementations of the coefficients and boundary/initial conditions specific to the BlackScholes equation.
FDMBase - This is another abstract base class that provides discretisation parameters and result storage for the Finite Difference scheme.
It possesses a pointer to a
ConvectionDiffusionPDE.
FDMEulerExplicit - This inherits from FDMBase and provides concrete methods for the
Finite Difference scheme methods for the particular case of the Explicit Euler Method,
which we described above.
Lets now describe each class in detail.
17.3.1
PayOff Class
I dont want elaborate too much on the PayOff class as it has been discussed before in earlier
chapters, such as when pricing Asian options by Monte Carlo. However, I have included the
listings for the header and source files for completeness.
Here is the listing for payoff .h:
#i f n d e f
#define
c l a s s PayOff {
public :
PayOff ( ) ; // D e f a u l t ( no parameter ) c o n s t r u c t o r
v i r t u a l PayOff ( ) { } ; // V i r t u a l d e s t r u c t o r
242
// O v e r l o a d e d ( ) o p e r a t o r , t u r n s t h e PayOff i n t o an a b s t r a c t f u n c t i o n
object
v i r t u a l double operator ( ) ( const double& S ) const = 0 ;
};
c l a s s P a y O f f C a l l : public PayOff {
private :
double K; // S t r i k e p r i c e
public :
P a y O f f C a l l ( const double& K ) ;
virtual PayOffCall ( ) {};
public :
PayOffPut ( const double& K ) ;
v i r t u a l PayOffPut ( ) { } ;
v i r t u a l double operator ( ) ( const double& S ) const ;
};
#endif
#define
#include p a y o f f . h
PayOff : : PayOff ( ) {}
243
// ==========
// P a y O f f C a l l
// ==========
// C o n s t r u c t o r w i t h s i n g l e s t r i k e parameter
P a y O f f C a l l : : P a y O f f C a l l ( const double& K ) { K = K ; }
// =========
// PayOffPut
// =========
// C o n s t r u c t o r w i t h s i n g l e s t r i k e parameter
PayOffPut : : PayOffPut ( const double& K ) {
K = K;
}
#endif
17.3.2
VanillaOption Class
In the future we may wish to price many differing types of exotic options via Finite Difference
Methods. Thus it is sensible to create a VanillaOption class to encapsulate this functionality.
In particular, we are going to encapsulate the storage of the parameters of a European vanilla
option. Despite the fact that the interest rate, r, and the volatility, , are not part of an option
term sheet, we will include them as parameters for simplicity.
244
The notable component of the option is the pointer to a PayOff class. This allows us to use
a call, put or some other form of pay-off without needing to expose this to the outside world,
which in this instance refers to the FDM solver.
Here is the listing for option.h:
#i f n d e f
VANILLA OPTION H
#define
VANILLA OPTION H
#include p a y o f f . h
class VanillaOption {
public :
PayOff p a y o f f ;
double K;
double r ;
double T ;
double sigma ;
VanillaOption () ;
V a n i l l a O p t i o n ( double
double
K , double
r , double
sigma , PayOff
T,
pay off ) ;
};
#endif
The source file only really provides implementations for the constructors, both of which are
blank as the member initialisation list takes care of member initialisation.
Here is the listing for option.cpp:
#i f n d e f
#define
#include o p t i o n . h
V a n i l l a O p t i o n : : V a n i l l a O p t i o n ( ) {}
V a n i l l a O p t i o n : : V a n i l l a O p t i o n ( double
double
K , double
r , double
sigma , PayOff
T,
pay off ) :
245
K( K ) , r ( r ) , T( T ) , sigma ( s i g m a ) , p a y o f f ( p a y o f f ) {}
#endif
17.3.3
PDE Classes
Separating the mathematical formalism of the PDE with the finite difference method that solves it
leads to the creation of the ConvectionDiffusionPDE and BlackScholesPDE classes. ConvectionDiffusionPDE
is simply an abstract base class, providing an interface for all subsequent inherited classes.
The pure virtual methods consist of all of the coefficients found in a second-order convectiondiffusion PDE. In addition, pure virtual methods are provided for left and right boundary
conditions. Thus we are defining a one-dimensional (in space) PDE here, as is evident by the
parameters for each method - they only require a single spatial value x, along with the temporal
parameter, t. The final method is init cond , which allows an initial condition to be applied to
the PDE.
The big difference between BlackScholesPDE and ConvectionDiffusionPDE, apart from the abstractness of the latter, is that BlackScholesPDE contains a public pointer member to a VanillaOption
class, which is where it obtains the parameters necessary for the calculation of the coefficients.
Here is the listing for pde.h:
#i f n d e f
PDE H
#define
PDE H
#include o p t i o n . h
// C o n v e c t i o n D i f f u s i o n E q u a t i o n Secondo r d e r PDE
c l a s s Co nvectionDiffusionPDE {
public :
// PDE C o e f f i c i e n t s
v i r t u a l double d i f f c o e f f ( double t , double x ) const = 0 ;
v i r t u a l double c o n v c o e f f ( double t , double x ) const = 0 ;
v i r t u a l double z e r o c o e f f ( double t , double x ) const = 0 ;
v i r t u a l double s o u r c e c o e f f ( double t , double x ) const = 0 ;
// Boundary and i n i t i a l c o n d i t i o n s
v i r t u a l double b o u n d a r y l e f t ( double t , double x ) const = 0 ;
v i r t u a l double b o u n d a r y r i g h t ( double t , double x ) const = 0 ;
246
// BlackS c h o l e s PDE
c l a s s BlackScholesPDE : public ConvectionDiffusionPDE {
public :
VanillaOption option ;
BlackScholesPDE ( V a n i l l a O p t i o n
option ) ;
#endif
The source file pde.cpp implements the virtual methods for the BlackScholesPDE class. In
particular, the diffusion, convection, zero-term and source coefficients are provided, based on the
Black-Scholes equation. In this instance, x is the spot price.
diff coeff , conv coeff , zero coeff and source coeff are self-explanatory from the Black-Scholes
PDE. However, note that the way to access the parameters from the option is through the
dereferenced pointer member access syntax. option>r is equivalent to (option). r.
The right boundary condition makes use of Put-Call Parity and is actually a Dirichlet specification. The left boundary is also Dirichlet and set to 0.
The syntax for init cond may require some explanation. Essentially the option and subsequent
pay-off are being dereferenced by the pointer member access syntax at which point the function
call operator operator() is called on the pay-off. This is possible because it is a functor.
Here is a listing for pde.cpp:
#i f n d e f
PDE CPP
#define
PDE CPP
247
#include pde . h
#include <math . h>
BlackScholesPDE : : BlackScholesPDE ( V a n i l l a O p t i o n
{}
// D i f f u s i o n c o e f f i c i e n t
double BlackScholesPDE : : d i f f c o e f f ( double t , double x ) const {
double v o l = o p t i o n >sigma ;
return 0 . 5 v o l v o l xx ;
// \ f r a c {1}{2} \ sigma 2 S2
// C o n v e c t i o n c o e f f i c i e n t
double BlackScholesPDE : : c o n v c o e f f ( double t , double x ) const {
return ( o p t i o n >)x ;
// rS
// Zeroterm c o e f f i c i e n t
double BlackScholesPDE : : z e r o c o e f f ( double t , double x ) const {
return ( o p t i o n >r ) ;
// r
// Source c o e f f i c i e n t
double BlackScholesPDE : : s o u r c e c o e f f ( double t , double x ) const {
return 0 . 0 ;
}
// L e f t boundaryc o n d i t i o n ( v a n i l l a c a l l o p t i o n )
double BlackScholesPDE : : b o u n d a r y l e f t ( double t , double x ) const {
return 0 . 0 ;
// S p e c i f i c a l l y f o r a CALL o p t i o n
// R i g h t boundaryc o n d i t i o n ( v a n i l l a c a l l o p t i o n )
double BlackScholesPDE : : b o u n d a r y r i g h t ( double t , double x ) const {
// This i s v i a PutC a l l P a r i t y and works f o r a c a l l o p t i o n
return ( x( o p t i o n >K) exp ( ( o p t i o n >r ) ( ( o p t i o n >T)t ) ) ) ;
}
248
// I n i t i a l c o n d i t i o n ( v a n i l l a c a l l o p t i o n )
double BlackScholesPDE : : i n i t c o n d ( double x ) const {
return o p t i o n >p a y o f f >operator ( ) ( x ) ;
}
#endif
17.3.4
FDM Class
The separation of the PDE from the Finite Difference Method to solve it means that we need a
separate inheritance hierarchy for FDM discretisation. FDMBase constitutes the abstract base
class for a FDM solver specific to a convection-diffusion PDE. It contains members for parameters
related to spatial discretisation, temporal discretisation, time-marching, the coefficients for the
actual derivative approximation as well as storage for the current and previous solutions. The
comments in the listing should be explanatory for each of the values.
There are five pure virtual methods to implement in the derived classes. calculate step sizes is
called on construction and populates all of the spatial and temporal step sizes. set initial conditions
makes use of the PDE itself, and subsequently the option pay-off, to create the solution pay-off
profile at expiry, i.e. the initial condition. calculate boundary conditions is called on every timestep to set the boundary conditions. In the case of FDMEulerExplicit, with the Black-Scholes
European vanilla call, these are Dirichlet conditions.
calculate inner domain updates all solution discretisation points which do not lie on the bound-
ary. The bulk of the work is carried out in this method. The client interacts with an FDM solver
via the public method step march, which performs the actual time looping across the temporal
domain.
Here is the listing for fdm.h:
#i f n d e f
FDM H
#define
FDM H
#include pde . h
#include <v e c t o r >
// F i n i t e D i f f e r e n c e Method A b s t r a c t Base C l a s s
c l a s s FDMBase {
249
protected :
Co nvec tionDiffusionPDE pde ;
// Space d i s c r e t i s a t i o n
double x dom ;
// S p a t i a l e x t e n t [ 0 . 0 , x dom ]
unsigned long J ;
// Number o f s p a t i a l d i f f e r e n c i n g p o i n t s
double dx ;
// S p a t i a l s t e p s i z e ( c a l c u l a t e d from a b o v e )
s t d : : v e c t o r <double> x v a l u e s ;
// S t o r e s t h e c o o r d i n a t e s o f t h e x
dimension
// Time d i s c r e t i s a t i o n
double t dom ;
// Temporal e x t e n t [ 0 . 0 , t dom ]
unsigned long N;
// Number o f t e m p o r a l d i f f e r e n c i n g p o i n t s
double dt ;
// Temporal s t e p s i z e ( c a l c u l a t e d from a b o v e )
// Timemarching
double p r e v t , c u r t ;
// Current and p r e v i o u s t i m e s
// D i f f e r e n c i n g c o e f f i c i e n t s
double alpha , beta , gamma ;
// S t o r a g e
s t d : : v e c t o r <double> n e w r e s u l t ;
s t d : : v e c t o r <double> o l d r e s u l t ;
// Old s o l u t i o n ( becomes N)
// C o n s t r u c t o r
FDMBase( double
J,
double
N,
ConvectionDiffusionPDE
pde ) ;
// O v e r r i d e t h e s e v i r t u a l methods i n d e r i v e d c l a s s e s f o r
// s p e c i f i c FDM t e c h n i q u e s , such as e x p l i c i t Euler , CrankN i c o l s o n , e t c .
v i r t u a l void c a l c u l a t e s t e p s i z e s ( ) = 0 ;
v i r t u a l void s e t i n i t i a l c o n d i t i o n s ( ) = 0 ;
v i r t u a l void c a l c u l a t e b o u n d a r y c o n d i t i o n s ( ) = 0 ;
v i r t u a l void c a l c u l a t e i n n e r d o m a i n ( ) = 0 ;
250
public :
// Carry o u t t h e a c t u a l times t e p p i n g
v i r t u a l void s t e p m a r c h ( ) = 0 ;
};
public :
FDMEulerExplicit ( double
J,
double
N,
ConvectionDiffusionPDE
pde ) ;
void s t e p m a r c h ( ) ;
};
#endif
The source listing for the FDM hierarchy, fdm.cpp contains some tricky code. Well run
through each part separately below. Heres the full listing before we get started:
#i f n d e f
FDM CPP
#define
FDM CPP
J,
double
N,
ConvectionDiffusionPDE
pde )
J,
double
N,
251
ConvectionDiffusionPDE
: FDMBase( x dom ,
J,
t dom ,
N,
pde )
pde ) {
void FDMEulerExplicit : : c a l c u l a t e s t e p s i z e s ( ) {
dx = x dom/ s t a t i c c a s t <double>(J1) ;
dt = t dom / s t a t i c c a s t <double>(N1) ;
}
void FDMEulerExplicit : : s e t i n i t i a l c o n d i t i o n s ( ) {
// S p a t i a l s e t t i n g s
double c u r s p o t = 0 . 0 ;
// Temporal s e t t i n g s
prev t = 0.0;
cur t = 0.0;
}
void FDMEulerExplicit : : c a l c u l a t e b o u n d a r y c o n d i t i o n s ( ) {
n e w r e s u l t [ 0 ] = pde>b o u n d a r y l e f t ( p r e v t , x v a l u e s [ 0 ] ) ;
n e w r e s u l t [ J 1] = pde>b o u n d a r y r i g h t ( p r e v t , x v a l u e s [ J 1]) ;
}
void FDMEulerExplicit : : c a l c u l a t e i n n e r d o m a i n ( ) {
// Only u s e i n n e r r e s u l t i n d i c e s (1 t o J2)
252
// D i f f e r e n c i n g c o e f f i c i e n t s ( s e e \ a l p h a , \ b e t a and \gamma i n t e x t )
alpha = d t s i g d t s i g 2 ;
b e t a = dx dx ( 2 . 0 d t s i g ) + ( dt dx dx
( pde>z e r o c o e f f ( p r e v t , x v a l u e s [ j ] ) ) ) ;
gamma = d t s i g + d t s i g 2 ;
// Update i n n e r v a l u e s o f s p a t i a l d i s c r e t i s a t i o n g r i d ( E x p l i c i t E u l e r )
n e w r e s u l t [ j ] = ( ( a l p h a o l d r e s u l t [ j 1]) +
( beta o l d r e s u l t [ j ] ) +
(gamma o l d r e s u l t [ j +1]) ) / ( dxdx )
( dt ( pde>s o u r c e c o e f f ( p r e v t , x v a l u e s [ j ] ) ) ) ;
}
}
void FDMEulerExplicit : : s t e p m a r c h ( ) {
s t d : : o f s t r e a m fdm out ( fdm . c s v ) ;
fdm out . c l o s e ( ) ;
253
#endif
Lets go through each part in turn. The first thing to note is that were including the fstream
library. This is necessary to output the solution surface to disk:
#include <f s t r e a m >
Youll notice that we actually need to implement a constructor for the abstract base class
FDMBase. This is because it is actually storing member data and so we need to call this con-
J,
double
N,
ConvectionDiffusionPDE
pde )
The constructor for FDMEulerExplicit initialises the parent class members, as well as calls the
methods to fill in the step sizes and initial conditions:
FDMEulerExplicit : : FDMEulerExplicit ( double
J,
double
N,
ConvectionDiffusionPDE
: FDMBase( x dom ,
J,
t dom ,
N,
pde )
pde ) {
For N temporal discretisation points we have N 1 intervals. Similarly for the spatial
discretisation. This method calculates these steps values for later use:
Note that in more sophisticated finite difference solvers the step size need not be constant.
One can create stretched grids where areas of interest (such as boundaries) are given greater
resolution at the expense of poorer resolution where it is less important.
void FDMEulerExplicit : : c a l c u l a t e s t e p s i z e s ( ) {
dx = x dom/ s t a t i c c a s t <double>(J1) ;
dt = t dom / s t a t i c c a s t <double>(N1) ;
}
Now that the step sizes are set it is time to pre-fill the initial condition. All of the spatial
arrays are set to have J points and are zeroed. Then the method loops these arrays and uses
254
the pointer to the PDE to obtain the initial condition as a function of spot. We also have a
useful helper array, x values which stores the spot value at each discretisation point to save us
calculating it every time step. Finally, we set the current and previous times to zero:
void FDMEulerExplicit : : s e t i n i t i a l c o n d i t i o n s ( ) {
// S p a t i a l s e t t i n g s
double c u r s p o t = 0 . 0 ;
// Temporal s e t t i n g s
prev t = 0.0;
cur t = 0.0;
}
Now that the initial conditions are set we can begin the time-marching. However, in each
time step we must first set the boundary conditions. In this instance the edge points (at index
0 and index J 1) are set using the PDE boundary left and boundary right method. This is
an example of a Dirichlet condition. More sophisticated boundary conditions approximate the
derivative at these points (Neumann conditions), although we wont be utilising these conditions
here:
void FDMEulerExplicit : : c a l c u l a t e b o u n d a r y c o n d i t i o n s ( ) {
n e w r e s u l t [ 0 ] = pde>b o u n d a r y l e f t ( p r e v t , x v a l u e s [ 0 ] ) ;
n e w r e s u l t [ J 1] = pde>b o u n d a r y r i g h t ( p r e v t , x v a l u e s [ J 1]) ;
}
The meat of the FDM solver occurs in the calculate inner domain method. Lets run through
how it works. A loop is carried out over the spatial cells - but only those away from the boundary,
hence the index 1 to J 2. In order to save excessive repetitive calculation (and enhance
readability), we create two helper variables called dt sig and dt sig 2 . If you refer back to the
255
mathematical formalism at the start of the chapter, it will be apparent where these variables
arise from.
The next step is to calculate the , and coefficients which represent the algebraic rearrangement of the derivative discretisation. Again, these terms will be clear from the above
mathematical formalism. In fact, Ive tried to make the C++ implementation as close as possible to the mathematical algorithm in order to make it easier to follow! Notice that we need to
obtain certain coefficients via pointer dereferencing of the underlying PDE.
Once , and are defined we can use the finite differencing to update the solution into the
new result vector. This formula will also be clear from the mathematical algorithm above:
void FDMEulerExplicit : : c a l c u l a t e i n n e r d o m a i n ( ) {
// Only u s e i n n e r r e s u l t i n d i c e s (1 t o J2)
f o r ( unsigned long j =1; j <J 1; j ++) {
// Temporary v a r i a b l e s used t h r o u g h o u t
double d t s i g = dt ( pde> d i f f c o e f f ( p r e v t , x v a l u e s [ j ] ) ) ;
double d t s i g 2 = dt dx 0 . 5 ( pde>c o n v c o e f f ( p r e v t , x v a l u e s [ j ] )
);
// D i f f e r e n c i n g c o e f f i c i e n t s ( s e e \ a l p h a , \ b e t a and \gamma i n t e x t )
alpha = d t s i g d t s i g 2 ;
b e t a = dx dx ( 2 . 0 d t s i g ) + ( dt dx dx
( pde>z e r o c o e f f ( p r e v t , x v a l u e s [ j ] ) ) ) ;
gamma = d t s i g + d t s i g 2 ;
// Update i n n e r v a l u e s o f s p a t i a l d i s c r e t i s a t i o n g r i d ( E x p l i c i t E u l e r )
n e w r e s u l t [ j ] = ( ( a l p h a o l d r e s u l t [ j 1]) +
( beta o l d r e s u l t [ j ] ) +
(gamma o l d r e s u l t [ j +1]) ) / ( dxdx )
( dt ( pde>s o u r c e c o e f f ( p r e v t , x v a l u e s [ j ] ) ) ) ;
}
}
Now that all of the prior pure virtual methods have been given an implementation in the
derived FDMEulerExplicit class it is possible to write the step march method to wrap everything
together.
The method opens a file stream to disk and then carries out a while loop in time, only exiting
when the current time exceeds the maximum domain time. Within the loop time is advanced
256
by the time-step dt. In each step the boundary conditions are reapplied, the inner domain new
solution values are calculated and then this new result is output to disk sequentially. The old
solution vector is then set to the new solution vector and the looping continues. Finally, we close
the file stream. This concludes the implementation of FDMEulerExplicit:
void FDMEulerExplicit : : s t e p m a r c h ( ) {
s t d : : o f s t r e a m fdm out ( fdm . c s v ) ;
fdm out . c l o s e ( ) ;
}
17.3.5
Main Implementation
It is now time to tie all of the previous components together. The main implementation is actually
quite straightforward, which is a consequence of the design choices we made above. Firstly, we
include the relevant header files. Then we define the parameters of our European vanilla call
option. The next stage is to define the discretisation parameters for the finite difference solver.
In this instance were using a mesh 20x20 representing a domain [0.0, 1.0] [0.0, 1.0] in size.
Once the option and FDM parameters have been declared, the next step is to create the
pay-off, call option, Black-Scholes PDE and the FDM solver objects. Since were dynamically
allocating some of our objects we need to make sure we call delete prior to the pointers going
out of scope.
After the declaration of all the relevant solver machinery, we call the public step march method
of the FDMEulerExplicit instance, which then carries out the solution, outputting it to fdm.csv.
257
#include p a y o f f . h
#include o p t i o n . h
#include pde . h
#include fdm . h
// S t r i k e p r i c e
double r = 0 . 0 5 ;
// Riskf r e e r a t e (5%)
double v = 0 . 2 ;
// V o l a t i l i t y o f t h e u n d e r l y i n g (20%)
double T = 1 . 0 0 ;
// One y e a r u n t i l e x p i r y
// FDM d i s c r e t i s a t i o n p a r a m e t e r s
double x dom = 1 . 0 ;
// Spot g o e s from [ 0 . 0 , 1 . 0 ]
unsigned long J = 2 0 ;
double t dom = T ;
// Time p e r i o d as f o r t h e o p t i o n
unsigned long N = 2 0 ;
// Run t h e FDM s o l v e r
fdm euler . step march ( ) ;
return 0 ;
}
258
17.4
Upon execution of the code a file fdm.csv is generated in the same directory as main.cpp. Ive
written a simple Python script (using the matplotlib library) to plot the option price surface
C(S, t) as a function of time to expiry, t, and spot price, S.
from m p l t o o l k i t s . mplot3d import Axes3D
import m a t p l o t l i b
import numpy a s np
from m a t p l o t l i b import cm
from m a t p l o t l i b import p y p l o t a s p l t
x , y , z = np . l o a d t x t ( fdm . c s v , unpack=True )
X = np . r e s h a p e ( x , ( 2 0 , 2 0 ) )
Y = np . r e s h a p e ( y , ( 2 0 , 2 0 ) )
Z = np . r e s h a p e ( z , ( 2 0 , 2 0 ) )
step = 0.04
maxval = 1 . 0
fig = plt . figure ()
ax = f i g . a d d s u b p l o t ( 1 1 1 , p r o j e c t i o n= 3d )
259
260
Bibliography
[1] A. Alexandrescu. Modern C++ Design: Generic Programming and Design Patterns Applied.
Addison-Wesley, 2001.
[2] M. Baxter and A. Rennie. Financial Calculus. Cambridge University Press, 1999.
[3] M. Broadie and O. Kaya. Exact simulation of stochastic volatility and other affine jump
diffusion processes. Journal of Operations Research, 54:217231, 2006.
[4] D.J. Duffy. Financial instrument pricing using C++. Wiley, 2004.
[5] D.J. Duffy. Introduction to C++ for financial engineers. Wiley, 2006.
[6] D.J. Duffy and J. Kienitz. Monte Carlo frameworks. Wiley, 2009.
[7] E. Gamma, R. Helm, R. Johnson, and J. Vlissides. Design Patterns: Elements of Reusable
Object-Oriented Software. Addison-Wesley, 1994.
[8] P. Glasserman. Monte Carlo Methods in Financial Engineering. Springer, 2003.
[9] G. H. Golub and C. F. van Loan. Matrix Computations (4th Ed). Johns Hopkins University
Press, 2012.
[10] J. Hull. Options, Futures and Other Derivatives, 8th Ed. Pearson, 2011.
[11] M. S. Joshi. C++ Design Patterns and Derivatives Pricing, 2nd Ed. Cambridge University
Press, 2008.
[12] M. S. Joshi. The Concepts and Practice of Mathematical Finance, 2nd Ed. Cambridge
University Press, 2008.
[13] N. M. Josuttis. The C++ Standard Library: A Tutorial and Reference (2nd Ed). AddisonWesley, 2012.
[14] A. Koenig and B. E. Moo. Accelerated C++. Addison-Wesley, 2000.
261
262
[15] R. Korn. Monte Carlo Methods and Models in Finance and Insurance. CRC Press, 2010.
[16] R. Merton. Option pricing when the underlying stock returns are discontinuous. Journal of
Financial Economics, 3:125144, 1976.
[17] S. Meyers. More Effective C++: 35 New Ways to Improve Your Programs and Designs.
Addison Wesley, 1995.
[18] S. Meyers. Effective STL: 50 Specific Ways to Improve the Use of the Standard Template
Library. Addison Wesley, 2001.
[19] S. Meyers. Effective C++: 55 Specific Ways to Improve Your Programs and Designs, 3rd
Ed. Addison Wesley, 2005.
[20] W. H. Press. Numerical Recipes in C, 2nd Ed. Cambridge University Press, 1992.
[21] S. E. Shreve. Stochastic Calculus for Finance I - The Binomial Asset Pricing Model.
Springer, 2004.
[22] S. E. Shreve. Stochastic Calculus for Finance II - Continuous Time Models. Springer, 2004.
[23] B. Stroustrup. The C++ Programming Language, 4th Ed. Addison-Wesley, 2013.
[24] H. Sutter. Exceptional C++. Addison-Wesley, 2000.
[25] L. N. Trefethen and D. Bau III. Numerical Linear Algebra. Society for Industrial and
Applied Mathematics, 1997.
[26] P. Wilmott. Paul Wilmott Introduces Quantitative Finance, 2nd Ed. John Wiley & Sons,
2007.