• No results found

A function to simulate stock prices

Monte Carlo Pricing in C++

9.1 A function to simulate stock prices

We wish to write a function generatePricePath which generates a sim-ulated stock price path. It will take as parameters a final date toDate and a number of steps nSteps.

Because we want to generate a price path according to a discrete-time version of the Black–Scholes model, we choose to make the function generate-PricePath a member function of the class called BlackScholesModel. This is logical since the key data that we need to generate the price path are the drift, µ, and volatility, σ. These are already member variables of Model. In addition, by adding the function as a member of BlackScholes-Model it becomes immediately clear that the generatePricePath function will generate prices that follow geometric Brownian motion, as required by the Black–Scholes model.

This specification for the function effectively tells us what we need to write in the header file.

class␣BlackScholesModel␣{

public:

␣␣␣␣...␣other␣members␣of␣BlackScholesModel␣...

␣␣␣␣std::vector<double>␣generatePricePath(

␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣double␣toDate,

␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣int␣nSteps)␣const;

};

As this example shows, the class declaration describes the functionality of the class without describing in detail how it is implemented. If you choose good function and variable names, you won’t need too many comments. A well designed class will almost document itself.

We will also want a function generateRiskNeutralPricePath which be-haves the same, except it uses the Q-measure to compute the path. Therefore we add the following declaration to BlackScholesModel

class␣BlackScholesModel␣{

public:

␣␣␣␣...␣other␣members␣of␣BlackScholesModel␣...

␣␣␣␣std::vector<double>␣generateRiskNeutralPricePath(

␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣double␣toDate,

␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣int␣nSteps)␣const;

};

To implement these functions, we introduce a private function that allows you to choose the drift in the simulation of the price path.

class␣BlackScholesModel␣{

␣␣␣␣...␣other␣members␣of␣BlackScholesModel␣...

private:

␣␣␣␣std::vector<double>␣generateRiskNeutralPricePath(

␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣double␣toDate,

␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣int␣nSteps,

␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣␣double␣drift)␣const;

};

This function is private because we’ve only created it to make the implemen-tation easier. Users of the class don’t need (or even want) to know about it.

To implement the function we need to know what algorithm to use. The relevant mathematics is given below (and is motivated in Appendix A).

Algorithm 2. To simulate a stock price that follows discrete-time geometric Brownian motion with drift µ and volatility σ at discrete times t0, t1, . . . tn

one should proceed as follows:

(i) Define

δti= ti− ti−1.

(ii) Choose independent normally distributed i with mean 0 and standard deviation 1.

(iii) Define

The generated stock prices at Sti will have the same joint probability distribution as those given by the Black–Scholes model.

Given this mathematical formulation of the algorithm, it is straightforward to implement our helper function. You can find the code in BlackScholes-Model.cpp.

We’ve only implemented the algorithm for evenly spaced time points. It is easy to generalise to arbitrary time points.

Having implemented the private function, the public functions are very simple. Again these implementations are placed in BlackScholesModel.cpp vector < double >␣B l a c k S c h o l e s M o d e l :: g e n e r a t e P r i c e P a t h (

toDate ,␣nSteps ,␣r i s k F r e e R a t e␣);

}

The point to notice is that by using a design with a private helper function, we’ve avoided writing the same complex code twice.

It is important that we test this code. The first test is a visual one, we want to check that this really looks like a price path. We can use the LineChart class to do this.

v o i d␣t e s t V i s u a l l y ()␣{

B l a c k S c h o l e s M o d e l␣bsm ; bsm . r i s k F r e e R a t e␣=␣0 . 0 5 ; bsm . v o l a t i l i t y␣=␣0 . 1 ; bsm . s t o c k P r i c e␣=␣1 0 0 . 0 ; bsm . d a t e␣=␣2 . 0 ;

int␣n S t e p s␣=␣1 0 0 0 ; d o u b l e␣m a t u r i t y␣=␣4 . 0 ; vector < double >␣p a t h␣=

bsm . g e n e r a t e P r i c e P a t h (␣m a t u r i t y ,␣n S t e p s␣);

d o u b l e␣dt␣=␣( m a t u r i t y - bsm . d a t e )/ n S t e p s ; vector < double >␣t i m e s␣=

l i n s p a c e ( dt , m a t u r i t y , n S t e p s );

L i n e C h a r t␣l i n e C h a r t ;

l i n e C h a r t . s e t T i t l e ( " S t o c k␣p r i c e␣p a t h " );

l i n e C h a r t . s e t S e r i e s ( times ,␣p a t h );

l i n e C h a r t . w r i t e A s H T M L ( " e x a m p l e P r i c e P a t h . h t m l " );

}

This function makes use of a helper function linspace that has been added to the matlib library. The call linspace(a,b,n) generates n evenly spaced numbers between the values a and b. The output graph is shown in Figure 9.1. The visual check is simply to observe that this looks reasonably like a real stock price’s history.

You can run this test using FMLib9 to obtain the relevant code. I hope you agree that producing such a sophisticated chart in C++ is strong evidence of how far we have come in our understanding of C++.

Visual tests are useful, but we should always make sure that we have fully automated tests of our code. The reason is that once one has a large amount of code, it will all need regular testing in case we have accidentally written a bug. If our tests are fully automated, we can run them every time we make any change to the code.

One simple test is that, if our risk-neutral price path function is correct, then the discounted mean of the final stock price should equal the initial price.

We test that in the code below:

Stock price path

0.8 1.6 2.4 3.2 4.0

95 100 105 110 115

FIGURE 9.1: A simulated stock price path. The bottom axis is the time in years, the vertical axis is the price in dollars.

v o i d␣t e s t R i s k N e u t r a l P r i c e P a t h ()␣{

rng ( " d e f a u l t " );

B l a c k S c h o l e s M o d e l␣bsm ; bsm . r i s k F r e e R a t e␣=␣0 . 0 5 ; bsm . v o l a t i l i t y␣=␣0 . 1 ; bsm . s t o c k P r i c e␣=␣1 0 0 . 0 ; bsm . d a t e␣=␣2 . 0 ;

int␣n P a t h s␣=␣1 0 0 0 0 ; int␣n s t e p s␣=␣5;

d o u b l e␣m a t u r i t y␣=␣4 . 0 ;

vector < double >␣f i n a l P r i c e s ( nPaths , 0 . 0 ) ; for␣( int␣i =0;␣i < n P a t h s ;␣i ++)␣{

vector < double >␣p a t h␣=

bsm . g e n e r a t e R i s k N e u t r a l P r i c e P a t h ( m a t u r i t y ,␣n s t e p s␣);

f i n a l P r i c e s [ i ]␣=␣p a t h . b a c k ();

}

A S S E R T _ A P P R O X _ E Q U A L (␣m e a n (␣f i n a l P r i c e s␣) , exp (␣bsm . r i s k F r e e R a t e * 2 . 0 ) * bsm . s t o c k P r i c e ,

0 . 5 ) ; }

We have introduced one new feature here in the line:

rng ( " d e f a u l t " );

This is not a call to a standard C++ function, it is a function in the FM-Lib library that resets the state of the random number generator back to its original “default” state. Calls to randUniform and randn will always generate exactly the same sequence of “random numbers” each time the random num-ber generator is reset to its default state. We have put the phrase “random numbers” in quotes because the random numbers generated are never truly random. Computer-generated random numbers merely look random in much the same way as the digits of the number π look random. They are more correctly called pseudo random numbers.

Resetting the state of the random number generator is very useful for testing because it guarantees that each time the test is run, we will use the same pseudo random numbers. This means that if the test passes once, it will always pass and similarly if it fails once, it will always fail. It is very frustrating when debugging code to have a test that sometimes passes and sometimes fails—especially if you run all your tests every time you change your code.

Tip: Tests depending on random numbers

Always reset the random number generator to a known state before running tests that involve random numbers.