This post is also available in: en, en and en
C++ Fundamentals and My First Option Class¶
Introduction and objectives¶
In this chapter we design and implement our first real working C++ code. Our goal is to model a European option by a C++ class. We know that a class has a member data and member functions in general and in this case we model the following option attributes:
- Strike price
- Volatility
- Risk-free interest rate
- Expiry date as member data. Furthermore, we are interested in designing call and put options as well as modelling some of their interesting properties, namely option price and option sensitivities, for example:
- Option delta
- Option gamma
- Other option sensitivities (for more options, see Haug, 1998)
We set up basic infraestructure by implementing the software in two files. First, we define the
EuropeanOption
class in a so-called header file. This means that we declare the option's member data and member functions in this file. It can be seen as a specification of the class. Second, we implement the functions that were declared in the header file and this is done in a so-called code file. Together, these two files describe the class. It is possible to use just one file but we prefer using two files because the code is easier to maintain in this case.
We take a well-known example, in this case a European Option. We implement it in C++ and in this way the reader will become familiar with C++ syntax and the object-oriented way of thinking as soon as possible. In later chapters we shall add more functionality to this class. This is the best way to learn, namely step-by-step.
The following topics will be discussed in this chapter:
- Creating my first C++ class: separating class design issues from class implementation.
- Member data and member functions in C++; the different categories of member functions.
- Determining accessibility levels in a class.
- Using the
EuropeanOption
class in test programs and applications.
After having studied this chapter you will have gained a good understanding of a basic C++ class. Having understood the topics in this chapter we then proceed to more advanced functionality in chapter four. In particular, we discuss a number of C++ features in more detail.
The C++ class that we introduce in this chapter implements the closed form solution for the Black-Scholes equation and it will be used in later chapters to test the accuracy of approximate methods such as the binomial method and finite difference schemes.
Class ==
member data +
member functions¶
In general, programming any class involves -- grosso modo -- determining its member functions and member data. We are working in Financial Engineering and in order to reduce the scope here we examine European options.
C++ is an example of a class-based object-oriented language. A class is a description of a group of related attributes and operations. In C++ we use the synonyms member data for attributes and member functions* for operations. The member data and member functions are closely related. This feature is called encapsulation*. In short, the class's functions know which attributes to use. Let us take an example of a class implementing European options for stocks. The defining parameters for the European option will be designed in C++ as the following member data:
- The risk-free interest rate: $r$.
- The volatility of the relative price change: $\sigma$.
- The strike price: $K$.
- The time to expiration (in years): $T$.
- The stock price: $S$ (or $U$ depending on the underlying).
- The cost-of-carry: $b$.
The cost-of-carry for the Black-Scholes model has the same value as $r$ but will have different values depending on the type of underlying asset (for example, $b=0$ for a future option), see Haug, 1998). We must define the data types of the member data. In this case we usually design them as
double
precision number although C++ allows us to design classes with so-called generic data types. This means that the member data can be customized with different specific data types depending on the current requirements. Having defined the member data we now must decide what to do with the data. To this end, we introduce the concept of object (or instance of a class). A class is abstract in the sense that is member data have not been instantiated (or instance of a class). A class is abstract in the sense that is member data have not been instantiated (they are just abstract descriptions of data) while an object is tangible and all its member data have been initialised. For example, the following assignments describe a European option on an index (Haug, 1998, p. 15): - Underlying value (stock price index) $U=500$.
- Volatility $\sigma = 0.15$.
- Strike Price $K=490$.
- Time to expiry $T=0.25$ ($3$ months).
- Risk-free interest rate $r=0.08$.
- Cost-of-carry $b=0.03$. Having discussed member data we now describe the functionality of classes and objects. In general, a class has member functions that model the lifecycle of an object. The main categories in general are:
- Member functions (constructors) for creation of objetcs.
- Member functions that modify the member data (modifiers).
- Member functions that perform calculations on the member data (selectors).
- A member function (destructor) that deletes an object when no longer needed. There are various ways to create an object using constructors. For example, it is possible to create an instance of a European option class by initialising its member data. Two other constructors deserve mention: first, the default constructor creates an object with default member data while the copy constructor creates an object as a deep copy of some other object. The destructor is the other extreme; it removes the object from memory when the object is no longer needed. We note that the names of constructors and of the destructor are the same as the name of their corresponding class.
We now discuss the member functions that operate on an object after it has been constructed and before it is destructed. Again, we concentrate on the class for European options.
C++ is based on the message-passing paradigm. This means that client code sends a message to an object by calling its member functions. For example, here is a piece of code that calculates the price of the index put option that we introduced earlier (we assume that the member data have been initialised):
The header file (function prototypes)¶
In general, the code that is needed for a complete description of a class in C++ is contained in two files: first, the header file (this section) and this contains the formal descriptions of the member data and member functions in the class. Second, the code file contains the body of each declared member function. In other words, each member function declaration in the header file must have a corresponding entry in the code file.
We now discuss the details of the header file. First, there are two regions or parts called private and public, respectively. Both parts may contain member data and member functions. Members that are declared in the private part are not accessible from outside the class and may only be accessed by members in the class itself while public members may be accessed by any C++ code. In general, all data should be declared in the private part because this tends to change; however, in this chapter we place the data that represents the structure of an option in the public area. This is for convenience only.
The public member functions in the options class can be categorised as follows (see the code below):
- Constructors: the different ways of creating instances of option class.
- Destructors: deleting an object when it is no longer needed (automatically taken care of by the runtime system).
- Assignment operator: the ability to assign one object to another object (this is a 'deepy' copy).
- 'Core business' functions: these are the functions that calculate the price and the delta for the option, for example.
- Other functions: for example, it is possible to switch a call option to a put option (and viceversa). Of course, the price and delta will be different!
!cat EuropeanOption.hpp
The class body (code file)¶
Having discussed the function prototypes for the option class, we need to describe how to fill in the body of the code for these functions. To this end, there are two major issues to be addresed. First, we must include the header file and other headers of libraries that are needed by the code. In this case, this leads to:
#include "EuropeanOption.hpp" //Declarations of functions
#include <cmath> // For mathematical functions, e.g. exp()
double EuropeanOption::PutPrice() const
{
double tmp = sig * std::sqrt(T);
double d1 = (std::log(U/K) + (b + (sig*sig)*0.5)*T)/tmp;
double d2 = d1 - tmp;
return (K * std::exp(-r*T)* N(-d2)) - (U*std::exp((b - r)*T)* N(-d1));
}
!cat EuropeanOption.cpp
Using the class¶
The code file is compiled and syntax errors should be resolved. We then need to write a program to test the class. The corresponding file is then compiled and linked with the other code to form an executable unit.
In this section we give an example of a test program. The object-oriented paradigm is based on the message-passing metaphor. Here we mean that client software sends messages to an object (by means of member function calls) by using the so-called dot notation. For example, to calculate the price of an existing option instance we code as follows:
double option_price = myOption.Price();
!cat TestEuropeanOption.cpp
#include <iostream>
#include "EuropeanOption.cpp"
{
EuropeanOption callOption;
std::cout << "Call option on a stock: " << callOption.Price() << std::endl;
// Put option on a stock index
EuropeanOption indexOption;
indexOption.optType = "P";
indexOption.U = 100.0;
indexOption.K = 95.0;
indexOption.T = 0.5;
indexOption.r = 0.10;
indexOption.sig = 0.20;
double q = 0.05; // Dividend yield
indexOption.b = indexOption.r - q;
std::cout << "Put option on an index: " << indexOption.Price() << std::endl;
// Call and put options on a future
EuropeanOption futureOption;
futureOption.optType = "P";
futureOption.U = 19.0;
futureOption.K = 19.0;
futureOption.T = 0.75;
futureOption.r = 0.10;
futureOption.sig = 0.28;
futureOption.b = 0.0;
std::cout << "Put option on a future: " << futureOption.Price() << std::endl;
// Now change over to a call on the option
futureOption.toggle();
std::cout << "Call option on a future: " << futureOption.Price() << std::endl;
// Call option on currency
EuropeanOption currencyOption;
currencyOption.optType = "C";
currencyOption.U = 1.56;
currencyOption.K = 1.60;
currencyOption.T = 0.5;
currencyOption.r = 0.06;
currencyOption.sig = 0.12;
double rf = 0.08; // risk-free rate of foreign currency
currencyOption.b = currencyOption.r - rf;
std::cout << std::endl << "** Other pricing examples **" << std::endl << std::endl;
std::cout << "Call option on a currency: " << currencyOption.Price() << std::endl;
//////// NOW CALCULATIONS OF SENSITIVITIES //////////////////////////////////
// Call and put options on a future: Delta and Elasticity
EuropeanOption futureOption2;
futureOption2.optType = "P";
futureOption2.U = 105.0;
futureOption2.K = 100.0;
futureOption2.T = 0.5;
futureOption2.r = 0.10;
futureOption2.sig = 0.36;
futureOption2.b = 0.0;
std::cout << "Delta on a put future: " << futureOption2.Delta() << std::endl;
// Now change over to a call on the option
futureOption2.toggle();
std::cout << "Delta on a call future: " << futureOption2.Delta() << std::endl;
// Stock Option: Gamma
EuropeanOption stockOption;
stockOption.optType = "C";
stockOption.U = 55.0;
stockOption.K = 60.0;
stockOption.T = 0.75;
stockOption.r = 0.10;
stockOption.sig = 0.30;
stockOption.b = stockOption.r;
stockOption.toggle();
// Calculating theta of a European stock index
EuropeanOption indexOption2;
indexOption2.optType = "P";
indexOption2.U = 430.0;
indexOption2.K = 405.0;
indexOption2.T = 0.0833; // One month expiration
indexOption2.r = 0.07;
indexOption2.sig = 0.20;
double divYield = 0.05; // Dividend yield, 5% per annum
indexOption2.b = indexOption2.r - divYield;
// Stock Option: Rho
EuropeanOption stockOption2;
stockOption2.optType = "C";
stockOption2.U = 72.0;
stockOption2.K = 75.0;
stockOption2.T = 1.0;
stockOption2.r = 0.09;
stockOption2.sig = 0.19;
stockOption2.b = stockOption2.r;
// Calculating Cost of Carry of a European stock index
EuropeanOption indexOption3;
indexOption3.optType = "P";
indexOption3.U = 500.0;
indexOption3.K = 490.0;
indexOption3.T = 0.222225;
indexOption3.r = 0.08;
indexOption3.sig = 0.15;
double divYield3 = 0.05; // Dividend yield, 5% per annum
indexOption3.b = indexOption3.r - divYield3 ;
}
Examining the class in detail¶
The code implements the class for a plain one-factor option has been discussed in some detail in the previous section. We have not done justice to all the detaills but our objective was to create working
code as soon as possible in the book. Furthermore, we shall discuss the syntax in greater detail in later chapters, especially Chapter 4.
Nonetheless, it is advisable at this stage to say something about the syntax that we use here. In this sense we avoid forward references.
Accessibility issues¶
A class contains of members in general. A member is either a member data or a member function. All members in a class are accessible from any other members of the class. However, a class can decide to expose
certain members to outside clients just as it can decide to keep some members hidden
from the outside world. To this end, we can define private and public member areas:
- Public member: any client can access it.
- Private member: not accessible to clients, only to member of the class.
In general, data and functions are tightly coupled and this principle is called encapsulation. In general, it is advisable to define data to be private because this feature seems to be the most volatile part of a class interface. In this chapter the member data are public but this is for convenience only.
Using standard libraries¶
In later chapters we shall introduce the Standard Template Library (STL), a library of template classes or containers, algorithms that operate on those containers and so-called iterators that allow us to navigate in the containers.
Some important data containers are:
- Vectors.
- Lists.
- Maps (or dictionaries).
It is important to note at this stage that it is not necessary to create your own data containers and corresponding algorithms.
The scope resolution ::
¶
Contrary to C and other procedural languages, C++ allows us to define member functions as elements of a class. To make this relationship explicit we use the so-called scope resolution operator ::
, for example:
double EuropeanOption::Price() const
{
if (optType == "C")
{
return CallPrice();
}
else
return PutPrice();
}
In this case the pricing function belongs to the given option class.
Virtual destructor: better safe than sorry¶
We start with a conclusion:
Declare all destructors to be virtual.
The reason why this is so will discussed in a later chapter. Failing to declare a destructor to be virtual may result in memory problems, so we play safe.
Other paradigms¶
The first example in this chapter was a C++ class that models plain one-factor options. This is a good application of the object-oriented paradigm: we encapsulate tightly-coupled data representing an option's parameter and we then create member functions that act on that data. Before we go overboad by thinking that everything in sight must be a class or object we mention that there are other paradigms that are just as effective and that can be used in conjunction with, or as a competitor to, the object-oriented paradigm. To this end, we show how modular programming techniques can be used in Quantitative Finance by taking some simple examples of interest rate calculations (see Fabozzi 1993; Hull, 2006). The examples are not difficult but we use them because they elaborate on a number of coding issues that will be needed in this book.
We have created a number of functions for the following kinds of calculations:
- Calculating the future value of a sum of money (paid once per year, $m$ times a year and continous compounding).
- Future value of an ordinary annuity.
- Simple present value calculations.
- Present value of a series of future values.
- Present value of an ordinary annuity. As usual, we create two files, one (the header) containing function declarations and the other one containing code. The header file is given by:
!cat SimpleBondPricing.hpp
!cat SimpleBondPricing.cpp
!cat TestSimpleBondPricing.cpp
#include "SimpleBondPricing.cpp"
#include <iostream>
{
// Future value of a sum of money invested today
long nPeriods = 6; // 6 years
double P = 10000000; // Amount invested now
double r = 0.092; // 9.2% interest
double fv = Chapter3CPPBook::FutureValue(P, nPeriods, r);
std::cout.setf(std::ios_base::fixed, std::ios_base::floatfield);
std::cout << "Future Value: " << fv << std::endl;
long frequency = 2;
double fv2 = Chapter3CPPBook::FutureValue(P, nPeriods, r, frequency);
// cout.setf(0, ios_base::floatfield);
std::cout << "Future Value, 2 periods: " << fv2 << std::endl;
// Future value of a sum of money invested today, m periods
// per year. r is annual interest rate
// Using: means that we search in NS for the functions
using namespace Chapter3CPPBook;
double P0 = 10000000; // 10 million
r = 0.092;
long m = 2; // Twice per year
nPeriods = 6;
std::cout << "**Future with " << m << " periods: " << FutureValue(P0, nPeriods, r, m) << std::endl;
// Future value of an ordinary annuity
double A = 2000000;
r = 0.08;
nPeriods = 15; // 15 years
std::cout << "**Ordinary Annuity; " << OrdinaryAnnuity(A, nPeriods, r) << std::endl;
// Present Value
double Pn = 5000000;
r = 0.10;
nPeriods = 7;
std::cout << "**Present value: " << PresentValue(Pn, nPeriods, r) << std::endl;
// Present Value of a series of future values
Vector futureValues(5); // For five years
for (long j = 0; j < 4; j++)
{ // The first 4 years
futureValues[j] = 100.0;
}
futureValues[4] = 1100.0;
nPeriods = 5; // Redundant in a sense since this is in the vector
r = 0.0625;
std::cout << "**Present value, series: " << PresentValue(futureValues, nPeriods, r) << std::endl;
// Present Value of an ordinary annuity
A = 100.0;
r = 0.09;
nPeriods = 8;
std::cout << "**PV, ordinary annuity: " << PresentValueOrdinaryAnnuity(A, nPeriods, r) << std::endl;
// Now test periodic testing with continuous compounding
P0 = 10000000;
r = 0.092;
nPeriods = 6;
for (long mm = 1; mm <= 10000000; mm *=12)
{
std::cout << "Periodic: " << mm << ", " << FutureValue(P0, nPeriods, r, mm) << std::endl;
}
std::cout << "Continuous Compounding: " << FutureValueContinuous(P0, nPeriods, r);
}