Sei sulla pagina 1di 10

Option Pricing Modeling

Pricing European- and American-Options


Using the Trinomial Model with Memoization

Siteng Jin

"#$%&'()$*&#
The purpose of the project is to use Trinomial Model to price both European and
American Option recursively. To speed up the process, implement memoization
method on the recursions, and test the speed of the code.
For European option, also compared the result of Trinomial Model to that of explicit
Black-Scholes formula. And verify put-call parity from the result of Trinomial Model.
The parameters needed are:

Expiration Time (in years): T
Number of Stages for the trinomial tree: n
Risk Free Interest Rate: r
Stock Volatility: !
Initial Stock Price: S
0

Strike Price: k

Inputs need to be calculated:

1. The up-factor
! ! !
! !!
! !
! ! !

2. The up-tick-probability p
! ! !
! !
!
!
! !
!
!
!
!

The down-tick-probability q
! ! !
! ! !
! !
!
!
!
!

The no-tick probability ! ! !! !!, where r is the continuous interest rate of
a single stage, ! ! !
!!!
!
.
+,-(.$
! For n = 1000 stages:


! For n = 5000 stages:



/&#).(-*&#
After comparing the results to the B-S Model, it shows that they are very close to each
other. And the accuracy doesnt increase a lot as the number of stages goes up.
However, with memoization, the code can handle larger number of stages to run huge
amount of recursions very fast. It only took 0.275 seconds to run when the number of
stages is 1000. And it took 13.083 seconds to run when the number of stages is 5000.
Since the accuracy of 1000 stages is already good enough, the code shows great
efficiency and accuracy at the same time.
!""#$%&'

[Main.cpp]

//Pricing European- andAmerican-Options Using the Trinomial Model with Memoization
//Written by Siteng Jin
//Referred to black_scholes.cpp written by Prof. Sreenivas
#include <iostream>
#include <iomanip>
#include <cmath>
#include <fstream>
#include <cstdlib>
#include "./normdist.h" // this defines the normal distribution from Odegaard's files
using namespace std;

//EPSILON is defined for future use of verification of memoization
#define EPSILON 0.00001

//Define command line input parameters:
//i. Expiration: experiation_time
//ii. # Stages: no_of_stages
//iii. Risk-free rate: risk_free_rate
//iv. Volatility: volatility
//v. Initial Price of Underlying: initial_stock_price
//vi. Strike Price: strike_price
//In addition, other values to be calculated are also defined as global
//R: exp(r*(delta t))
//Up factor: up_factor
//Up-tick Probability: uptick_prob
//No-tick Probability: notick_prob
//Down-tick Probability: downtick_prob
double risk_free_rate, strike_price;
double initial_stock_price, expiration_time, volatility;
int no_of_stages;
double R, up_factor, uptick_prob, downtick_prob, notick_prob;

//These are dynamic arrays declared as global and are used for memoization
//And they will be initialized in the function initialize_memoization
double** memoization_european_call;
double** memoization_european_put;
double** memoization_american_call;
double** memoization_american_put;

//Function: initialize_memoization
//The first and second dimension of the two-dimension dynamic arrays are
//stage k and ticks i. As a result, the size of first dimension should be
//the number of stages plus one, and the size of the second dimension at
//stage k should be 2*k+1.
//In order to make it easier to verify if (k, i) is memoized, I initialize
//all values in the dynamic arrays to be -1.0.
void initialize_memoization()
{
for(int i = 0; i < no_of_stages+1; i++)
{
memoization_european_call[i] = new double[2*i+1];
memoization_european_put[i] = new double[2*i+1];
memoization_american_call[i] = new double[2*i+1];
memoization_american_put[i] = new double[2*i+1];
for(int j = 0; j < 2*i+1; j++)
{
memoization_european_call[i][j] = -1.0;
memoization_european_put[i][j] = -1.0;
memoization_american_call[i][j] = -1.0;
memoization_american_put[i][j] = -1.0;
}
}
}

//Revursive function: european_call_option
//It has three steps:
//1. Check if european_call_option(k, i) has already been calculated before, if yes, return.
//(I use memoization_european_call[k][k+i] + 1.0 < EPSILON because they are float numbers)
//2. If european_call_option(k, i) has not been calculated, check if current stage is the final
//stage. If that's the case, simply return the european call option payoff and store its value.
//3. The function will get to this step only if the previous two conditions are not satisfied.
//Then return the expected value of getting to the next stage and store it.
double european_call_option(int k, int i)
{
if(!(memoization_european_call[k][k+i] + 1.0 < EPSILON))
{
return memoization_european_call[k][k+i];
}
else if(k == no_of_stages)
{
return (memoization_european_call[k][k+i] = max(0.0, initial_stock_price*pow(up_factor, ((float) i)) -
strike_price));
}
else
{
return memoization_european_call[k][k+i]= (uptick_prob*european_call_option(k+1, i+1)
+ notick_prob*european_call_option(k+1, i)
+ downtick_prob*european_call_option(k+1, i-1))/R;
}
}

//Revursive function: european_put_option
//It has three steps:
//1. Check if european_put_option(k, i) has already been calculated before, if yes, return.
//2. If european_put_option(k, i) has not been calculated, check if current stage is the final
//stage. If that's the case, simply return the european put option payoff and store its value.
//3. The function will get to this step only if the previous two conditions are not satisfied.
//Then return the expected value of getting to the next stage and store it.
double european_put_option(int k, int i)
{
if(!(memoization_european_put[k][k+i] + 1.0 < EPSILON))
{
return memoization_european_put[k][k+i];
}
else if(k == no_of_stages)
{
return (memoization_european_put[k][k+i] = max(0.0, strike_price - initial_stock_price*pow(up_factor, ((float)
i))));
}
else
{
return memoization_european_put[k][k+i]= (uptick_prob*european_put_option(k+1, i+1)
+ notick_prob*european_put_option(k+1, i)
+ downtick_prob*european_put_option(k+1, i-1))/R;
}
}

//Revursive function: american_call_option
//This function has very similar structure as the european_call_option function.
//The only difference is the payoff when k is not the final stage. It returns the maximum value
//between the payoff of exercising the american call option immediately and getting into the next stage.
double american_call_option(int k, int i)
{
if(!(memoization_american_call[k][k+i] + 1.0 < EPSILON))
{
return memoization_american_call[k][k+i];
}
else if(k == no_of_stages)
{
return (memoization_american_call[k][k+i] = max(0.0, initial_stock_price*pow(up_factor, ((float) i)) -
strike_price));
}
else
{
return memoization_american_call[k][k+i]= max(max(0.0, initial_stock_price*pow(up_factor, ((float) i)) -
strike_price),
(uptick_prob*american_call_option(k+1, i+1)
+ notick_prob*american_call_option(k+1, i)
+ downtick_prob*american_call_option(k+1, i-1))/R);
}
}

//Revursive function: american_put_option
//This function has very similar structure as the european_put_option function.
//The only difference is the payoff when k is not the final stage. It returns the maximum value
//between the payoff of exercising the american call option immediately and getting into the next stage.
double american_put_option(int k, int i)
{
if(!(memoization_american_put[k][k+i] + 1.0 < EPSILON))
{
return memoization_american_put[k][k+i];
}
else if(k == no_of_stages)
{
return (memoization_american_put[k][k+i] = max(0.0, strike_price - initial_stock_price*pow(up_factor, ((float)
i))));
}
else
{
return memoization_american_put[k][k+i]= max(max(0.0, strike_price - initial_stock_price*pow(up_factor, ((float)
i))),
(uptick_prob*american_put_option(k+1, i+1)
+ notick_prob*american_put_option(k+1, i)
+ downtick_prob*american_put_option(k+1, i-1))/R);
}
}

//From this point to the point before the main function, is the code borrowed from black_sholes.cpp
//written by Prof. Sreenivas. It calculates the theoretical value of european call and put options
//according to theories in lecture 7 of IE523.
double option_price_put_black_scholes(const double& S, // spot price
const double& K, // Strike (exercise) price,
const double& r, // interest rate
const double& sigma, // volatility
const double& time){
double time_sqrt = sqrt(time);
double d1 = (log(S/K)+r*time)/(sigma*time_sqrt) + 0.5*sigma*time_sqrt;
double d2 = d1-(sigma*time_sqrt);
return K*exp(-r*time)*N(-d2) - S*N(-d1);
}

double option_price_call_black_scholes(const double& S, // spot (underlying) price
const double& K, // strike (exercise) price,
const double& r, // interest rate
const double& sigma, // volatility
const double& time) { // time to maturity
double time_sqrt = sqrt(time);
double d1 = (log(S/K)+r*time)/(sigma*time_sqrt)+0.5*sigma*time_sqrt;
double d2 = d1-(sigma*time_sqrt);
return S*N(d1) - K*exp(-r*time)*N(d2);
}

double N(const double& z) {
if (z > 6.0) { return 1.0; }; // this guards against overflow
if (z < -6.0) { return 0.0; };
double b1 = 0.31938153;
double b2 = -0.356563782;
double b3 = 1.781477937;
double b4 = -1.821255978;
double b5 = 1.330274429;
double p = 0.2316419;
double c2 = 0.3989423;
double a=fabs(z);
double t = 1.0/(1.0+a*p);
double b = c2*exp((-z)*(z/2.0));
double n = ((((b5*t+b4)*t+b3)*t+b2)*t+b1)*t;
n = 1.0-b*n;
if ( z < 0.0 ) n = 1.0 - n;
return n;
}

int main (int argc, char* argv[])
{
//Input parameters on command line
sscanf (argv[1], "%lf", &expiration_time);
sscanf (argv[2], "%d", &no_of_stages);
sscanf (argv[3], "%lf", &risk_free_rate);
sscanf (argv[4], "%lf", &volatility);
sscanf (argv[5], "%lf", &initial_stock_price);
sscanf (argv[6], "%lf", &strike_price);

//Calculate the values according to equations in Programming Assignment # 10
R = exp(risk_free_rate*expiration_time/((double) no_of_stages));
up_factor = exp(volatility*sqrt(2*(expiration_time/((double) no_of_stages))));
uptick_prob = pow((sqrt(R) - 1/sqrt(up_factor))/(sqrt(up_factor) - 1/sqrt(up_factor)),2);
downtick_prob = pow((sqrt(up_factor) - sqrt(R))/(sqrt(up_factor) - 1/sqrt(up_factor)),2);
notick_prob = 1 - uptick_prob - downtick_prob;

//Print out the parameters
cout << "--------------------------------------" << endl;
cout << "Recursive Trinomial European Option Pricing" << endl;
cout << "Expiration Time (Years) = " << expiration_time << endl;
cout << "Number of Stages = " << no_of_stages << endl;
cout << "Risk Free Interest Rate = " << risk_free_rate << endl;
cout << "Volatility (%age of stock value) = " << volatility*100 << endl;
cout << "Initial Stock Price = " << initial_stock_price << endl;
cout << "Strike Price = " << strike_price << endl;
cout << "--------------------------------------" << endl;

cout << "Up Factor = " << up_factor << endl;
cout << "Uptick Probability = " << uptick_prob << endl;
cout << "Downtick Probability = " << downtick_prob << endl;
cout << "Notick Probability = " << notick_prob << endl;
cout << "--------------------------------------" << endl;

//Allocate memories for arrays of memoization for european call, european put,
//american call and american put options.
memoization_european_call = new double*[no_of_stages+1];
memoization_european_put = new double*[no_of_stages+1];
memoization_american_call = new double*[no_of_stages+1];
memoization_american_put = new double*[no_of_stages+1];

//Call initialize_memoization function to initialize the arrays as described before
initialize_memoization();

//Calculate the theoretical values of european call and put option using the black sholes formula
double call_price = option_price_call_black_scholes(initial_stock_price, strike_price, risk_free_rate, volatility,
expiration_time);
double put_price = option_price_put_black_scholes(initial_stock_price, strike_price, risk_free_rate, volatility,
expiration_time);

//Print out the result of the value of options calculated through trinomial pricing models and black sholes formula
cout << "Trinomial Price of an European Call Option = " << european_call_option(0, 0) << endl;
cout << "Call Price according to Black-Scholes = " << call_price << endl;
cout << "--------------------------------------" << endl;
cout << "Trinomial Price of an European Put Option = " << european_put_option(0, 0) << endl;
cout << "Put Price according to Black-Scholes = " << put_price << endl;
cout << "--------------------------------------" << endl;
cout << "Verifying Put-Call Parity: S+P-C = Kexp(-r*T)" << endl;
cout << initial_stock_price << " + " << put_price << " - " << call_price;
cout << " = " << strike_price << "exp(-" << risk_free_rate << " * " << expiration_time << ")" << endl;
cout << initial_stock_price + put_price - call_price << " = " << strike_price*exp(-risk_free_rate*expiration_time) <<
endl;
cout << "--------------------------------------" << endl;

cout << "Trinomial Price of an American Call Option = " << american_call_option(0, 0) << endl;
cout << "Trinomial Price of an American Put Option = " << american_put_option(0, 0) << endl;
cout << "--------------------------------------" << endl;

//Release memories of memoization arrays
delete memoization_european_call;
delete memoization_european_put;
delete memoization_american_call;
delete memoization_american_put;
}

[normdist.h]
// normdist.h

#ifndef _NORMAL_DIST_H_
#define _NORMAL_DIST_H_

double n(const double& z); // normal distribution function
double N(const double& z); // cumulative probability of univariate normal
double N(const double& a, const double& b, const double& rho);// cumulative probability of bivariate normal
double N3(const double& h, const double& k, const double& j,
const double& rho12, const double& rho13, const double& rho23); // trivariate
double random_normal(); // random numbers with mean zero and variance one
double random_uniform_0_1();
#endif

Potrebbero piacerti anche