"#$%&'()$*&# 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 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); }
//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;
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