Thursday, February 7, 2013

C++ - Coding Black Scholes Formula

Question: Write a C++ code to calculate the option values for a call C(t = 0,S), and a put P(t = 0,S) using the Black-Scholes formula:
C(t = 0,S ) = SN (d1) − Xe −r(T−t)N (d2),
(2)

P (t = 0,S ) = Xe − r(T− t)N (− d2) − SN (− d1 )
(3)

with

                       2
d =  log(S∕X-)-+-(√r +-σ-∕2)(T-−-t)
 1             σ  T  − t
(4)

     log(S∕X-)-+-(r −-σ2∕2)(T-−-t)
d2 =           σ √T  − t
(5)

You can use the cumulative distribution function N(x) from earlier on. So rather than starting with an empty project copy and paste the code from the previous project into your new project, you can find a version of the code on my website (click here). Now we need to create a function for the call option, so we need to think about what arguments need to be supplied and what are the local variables to the function. Obviously we need to supply the asset price, current time both of which may vary, and the parameters for the function are the strike price, interest rate, volatility and maturity, and we want to return a real number as the answer. Inside the function, we will need to calculate the value of d1 and d2. This leads to a function definition of the form

double callOptionPrice(double S,double t,double X,double r,double sigma,double T)
{
  double d1;
  double d2;
  return 0;
}

You must place this function header in between the main function and the normalDistribution function, since we will be calling ”normalDistribution” inside ”callOptionPrice” and then ”callOptionPrice” inside ”main”. Next we can simply fill in the mathematical formulas as follows

double callOptionPrice(double S,double t,double X,double r,double sigma,double T)
{
  double d1=(log(S/X) + (r+sigma*sigma/2.)*(T-t))/(sigma*sqrt(T-t));
  double d2=(log(S/X) + (r-sigma*sigma/2.)*(T-t))/(sigma*sqrt(T-t));
  return normalDistribution(d1)*S - normalDistribution(d2)*X*exp(-r*(T-t));
}

int main()
{
  cout << "Call Option Price = " << callOptionPrice(1,0,1,0.05,0.2,1) << endl;
  return 0;
}

Now we need to test the function under different settings. There are obviously going to be problem in each of the following cases:

  • S=0
  • sigma=0
  • t=T

since all will result in undefined mathematical values given the way that d1 and d2 are calculated. However, returning a value of plus or minus infinity for d1 and d2 does not result in an undefined value for the cumulative normal distribution since the function returns finite values from infinite limits. Using our knowledge of what happens to this function we can go through each case and decide what are the appropriate values to return.

Case S=0

In this situation we must make use of the boundary condition of the problem, which is that the option value is worthless if the asset value is zero. The only slight caveat here is that you should check whether S is smaller than a very small number rather than comparing it with zero. In code you add the following line to your function before the calculations.

if(S<1.e-14)return 0.;

By executing a return the function will stop and return the value without executing any more code.

Case sigma=0

This case is very similar to when t=T. Depending on the sign of the numerator in the calculation for d1 and d2 the function will either return zero or the asset minus the discounted strike price. In code this looks like

if(sigma<1.e-14)
  {
    if(S<X*exp(-r*(T-t)))return 0.;
    else return S-X*exp(-r*(T-t));
  }

Case t=T

Finally if t and T are almost equal then we are at maturity and we return the payoff. The code might look like

  if(fabs(T-t)<1.e-14)
  {
    if(S<X)return 0.;
    else return S-X;
  }

On adding each of these parts to the code you should test and validate each part using a range of parameters. Note here that another case that could cause problems is if t > T. There is no sensible value that can be returned in this case so if you add in a check for it you would be looking to exit the program if this happened or at least print a warning to the screen. Adding this in is left as an exercise. Your final code should look like

#include <iostream>
#include <cmath>
using namespace std;

double normalDistribution(double x)
{
  static const double RT2PI = sqrt(4.0*acos(0.0));
  static const double SPLIT = 10./sqrt(2);
  static const double a[] = {220.206867912376,221.213596169931,112.079291497871,33.912866078383,6.37396220353165,0.700383064443688,3.52624965998911e-02};
  static const double b[] = {440.413735824752,793.826512519948,637.333633378831,296.564248779674,86.7807322029461,16.064177579207,1.75566716318264,8.83883476483184e-02};
  
  const double z = fabs(x);
  double Nz = 0.0;
  
  // if z outside these limits then value effectively 0 or 1 for machine precision
  if(z<=37.0)
  {
    // NDash = N'(z) * sqrt{2\pi}
    const double NDash = exp(-z*z/2.0)/RT2PI;
    if(z<SPLIT)
    {
      const double Pz = (((((a[6]*z + a[5])*z + a[4])*z + a[3])*z + a[2])*z + a[1])*z + a[0];
      const double Qz = ((((((b[7]*z + b[6])*z + b[5])*z + b[4])*z + b[3])*z + b[2])*z + b[1])*z + b[0];
      Nz = RT2PI*NDash*Pz/Qz;
    }
    else
    {
      const double F4z = z + 1.0/(z + 2.0/(z + 3.0/(z + 4.0/(z + 13.0/20.0))));
      Nz = NDash/F4z;
    }
  }
  return x>=0.0 ? 1-Nz : Nz;
}

// return the value of a call option using the black scholes formula
double callOptionPrice(double S,double t,double X,double r,double sigma,double T)
{
  if(S<1.e-14)return 0.; // check if asset worthless
  if(sigma<1.e-14) // check if sigma zero
  {
    if(S<X*exp(-r*(T-t)))return 0.;
    else return S-X*exp(-r*(T-t));
  }
  if(fabs(T-t)<1.e-14) // check if we are at maturity
  {
    if(S<X)return 0.;
    else return S-X;
  }
  // calculate option price
  double d1=(log(S/X) + (r+sigma*sigma/2.)*(T-t))/(sigma*sqrt(T-t));
  double d2=(log(S/X) + (r-sigma*sigma/2.)*(T-t))/(sigma*sqrt(T-t));
  return normalDistribution(d1)*S - normalDistribution(d2)*X*exp(-r*(T-t));
}

int main()
{
  cout << "Call Option Price = " << callOptionPrice(1,0,1,0.05,0.2,1) << endl;
  return 0;
}

No comments:

Post a Comment