13.3 Interval Bisection Method
13.3.1 Interval Bisection with Function Templates
So far we’ve made extensive use of object-orientation and, in particular, inheritance hierarchies as one of the main design patterns for solving quantitative finance problems. We’re now going to use a very different set of tools, that of functors and function templates. We’ve 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 doesn’t 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 aren’t 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. We’ll now discuss the C++ implementation.
13.3.2
C++ Implementation
We’ve previously considered analytical pricing of European call options in some depth. However, I’ve reproduced and modified the code here for completeness (bs prices .h):
#i f n d e f BS PRICES H
#define USE MATH DEFINES
#include <i o s t r e a m >
#include <cmath>
// 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∗ x∗x ) ; }
// 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 : T h i s i s a r e c u r s i v e f u n c t i o n
double n o r m c d f (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 . 3 5 6 5 6 3 7 8 2 + k ∗ ( 1 . 7 8 1 4 7 7 9 3 7 + k ∗ ( − 1 . 8 2 1 2 5 5 9 7 8 + 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∗ x∗x ) ∗ k sum ) ; } e l s e { return 1 . 0 − n o r m c d f (−x ) ; } } // T h i s c a l c u l a t e s d j , f o r j i n { 1 , 2 } . T h i s 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 o r p u t p r i c e
double d j (const i n t j , const double S , const double K, const double r ,
const double sigma , const double T) {
return ( l o g ( S/K) + ( r + ( pow( −1 , j −1) ) ∗ 0 . 5 ∗ sigma ∗ sigma ) ∗T) / ( sigma ∗ ( pow (T , 0 . 5 ) ) ) ;
}
// 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 t i m e t o m a t u r i t y T
177
const double sigma , const double T) {
return S ∗ n o r m c d f ( d j ( 1 , S , K, r , sigma , T) )−K∗ exp(− r ∗T) ∗ n o r m c d f ( d j ( 2 , S , K, r , sigma , T) ) ;
}
#e n d i f
The following is a listing forblack 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 calloperator()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
c l a s s B l a c k S c h o l e s C a l l { 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 ; // Risk−f 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 ) ;
double operator( ) (double sigma ) const; } ;
#e n d i f
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 BLACK SCHOLES CPP
#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 ) {}
double B l a c k S c h o l e s C a l l : :operator( ) (double sigma ) const {
return c a l l p r i c e ( S , K, r , sigma , T) ; }
#e n d i f
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<typenameT>
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 defineoperator()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 // T r y i n g t o f i n d an x s u c h 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 ) { // F u n c t i o 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 mid−p 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 t h a n 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 re−e v a l u a t e t h e new y .
do { i f ( y < y t a r g e t ) { m = x ; } i f ( y > y t a r g e t ) { n = x ; } x = 0 . 5 ∗ (m + n ) ; y = g ( x ) ; } while ( f a b s ( y−y t a r g e t ) > e p s i l o n ) ; return x ; } #e n d i f
The final listing is for the main implementation (main.cpp). I’ve 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 aBlackScholesCallinstance 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 thebsc 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
#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 > i n t main (i n t a r g c , char ∗∗ a r g v ) { // F i r s t we c r e a t e t h e p a r a m e t e r l i s t double S = 1 0 0 . 0 ; // 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 ; // Risk−f 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 double C M = 1 0 . 5 ; // O p t i o n mark et p r i c e // C r e a t e t h e B l a c k−S 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 ; } #e n d i f
The output of the code is as follows:
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 object- oriented and generic aspects of the above code lend themselves naturally to extension and re-use.
181