Leisen Reimer C++ Optimization and Spreadsheet Modeling
Some virtues of Leisen Reimer (1996)
Below, we present three C++ implementations for the Leisen-Reimer binomial tree. The Leisen Reimer (1996) model is noted for its rapid convergence. In the video presented here we implement the VBA code found in the preceding page and the C++ static code found in the first snippet below. It is quite clear Leisen Reimer (1996) converges more quickly to true relative to Cox, Ross and Rubinstein.
C++ Implementation with three level of Optimization
Below, we present three C++ implementations for the Leisen-Reimer binomial tree. The Leisen Reimer (1996) model is noted for its relatively rapid convergence. Below we present three snippets of C++ code: (1) Static, (2) Dynamic and (3) Dynamic- zero region Truncated. The latter being the most optimized. In the video just below, we look at the Leisen Reimer binomial option pricing model. I consider three versions of the model set up in C++ (1) - (3). We go to Table 1 of the original Leisen Reimer (1996) paper. It is clear that the (3) is the fastest for computation and this secured by making use of Dynamic memory and making redundant the zero region. The static model is the slowest and cannot reach 25,000 steps. I also illustrate that the Leisen Reimer tree has a different shape to the Cox Ross and Rubinstein tree. This can be illustrated in excel.
(1) Leisen Reimer Static (C++ Code)
Static Memory storage tends to produce less efficient outcomes. If the entire lattice has to be saved at once this imposes a cost when running any estimation. We include the static code for completeness but would advise making use of the Dynamic approach proposed by Haug
//Static Leisen Reimer from Volopta
//Changed to include static Leisen Reimer
#include <vector>
#include <stdio.h>
#include <math.h>
#include <iostream>
#include <string>
#include <algorithm>
#include<iomanip>
#include <time.h>
using namespace std;
// Returns the sign of a number
double Sgn(double x) {
if (x<0) return -1;
else if (x>0) return 1;
else if (x == 0) return 0;
}
// Function for binomial tree
double Binomial(int n, double Spot, double K, double r, double q, double v, double T, char PutCall, char OpStyle)
{
int i, j; // Counters
if (n % 2 == 0) // Use only odd number of steps
n = n + 1;
//double dt, u, d, p;
int z;
int Method =2;
// Quantities for the tree
double dt = T / n;
double d1 = (log(Spot / K) + (r - q + v*v / 2) * T) / v / sqrt(T); // D Plus
double d2 = (log(Spot / K) + (r - q - v*v / 2) * T) / v / sqrt(T); // D Minus
double rq_n = exp((r - q) * dt);
double Term1 = pow((d1 / (n + 1.0 / 3.0 - (1 - Method) * 0.1 / (n + 1))), 2) * (n + 1.0 / 6.0);
double pp = 0.5 + Sgn(d1) * 0.5 * sqrt(1 - exp(-Term1));
double Term2 = pow((d2 / (n + 1.0 / 3.0 - (1 - Method) * 0.1 / (n + 1))), 2) * (n + 1.0 / 6.0);
double p = 0.5 + Sgn(d2) * 0.5 * sqrt(1 - exp(-Term2));
//changed
double u = rq_n * pp / p;
double d = (rq_n - p * u) / (1 - p);
//Original Static CRR
// int i, j;
vector<vector<double> > S(n + 1, vector<double>(n + 1));
vector<vector<double> > Op(n + 1, vector<double>(n + 1));
// double dt, u, d, p;
// dt = T / n;
// u = exp(v*sqrt(dt));
// d = 1 / u;
// p = (exp((r- q)*dt) - d) / (u - d);
// Build the binomial tree
for (j = 0; j <= n; j++) {
for (i = 0; i <= j; i++) {
S[i][j] = Spot*pow(u, j - i)*pow(d, i);
}
}
// Compute terminal payoffs
for (i = 0; i <= n; i++) {
if (PutCall == 'C')
Op[i][n] = max(S[i][n] - K, 0.0);
else
Op[i][n] = max(K - S[i][n], 0.0);
}
// Backward recursion through the tree
for (j = n - 1; j >= 0; j--) {
for (i = 0; i <= j; i++) {
if (OpStyle == 'E')
Op[i][j] = exp(-r*dt)*(p*(Op[i][j + 1]) + (1 - p)*(Op[i + 1][j + 1]));
else {
if (PutCall == 'C')
Op[i][j] = max(S[i][j] - K, exp(-r*dt)*(p*(Op[i][j + 1]) + (1 - p)*(Op[i + 1][j + 1])));
else
Op[i][j] = max(K - S[i][j], exp(-r*dt)*(p*(Op[i][j + 1]) + (1 - p)*(Op[i + 1][j + 1])));
}
}
}
return Op[0][0];
}
int main()
{
clock_t start_time, end_time;
start_time = clock();
//for (double ii = 0; ii <= 20; ii++) {
int n = 15;
//double Spot = ii * 10;
double Spot = 100;
double K = 100;
double r = 0.07;
double q = 0.0;
double v = 0.3;
double T = 0.5;
char PutCall = 'C';
char OpStyle = 'A';
cout << setprecision(10);
cout << Spot << " " << "The binomial price is " << Binomial(n, Spot, K, r, q, v, T, PutCall, OpStyle) << endl;
end_time = clock();
cout << " " << start_time << " " << end_time << " " << (end_time - start_time) << endl;
cout << " " << start_time << " " << end_time << " " << (end_time - start_time) / (double)CLOCKS_PER_SEC << " seconds" << endl;
system("PAUSE");
}
(2) Leisen Reimer Dynamic (C++ Code)
//This is code for Leisen Reimer but based on dynamic programming
//The code does not truncate the zero region
//Make sure both methods are set to Method 1 to get same results
#include <stdio.h>
#include <math.h>
#include <iostream>
#include <algorithm>
#include <iostream>
#include <vector>
#include<iomanip>
#include <string>
//using namespace System;
using namespace std;
// Returns the sign of a number
double Sgn(double x) {
if (x<0) return -1;
else if (x>0) return 1;
else if (x == 0) return 0;
}
// Function for binomial tree
double Binomial(int n, double S, double K, double r, double q, double v, double T, char PutCall, char OpStyle) {
int i, j; // Counters
if (n % 2 == 0) // Use only odd number of steps
n = n + 1;
//double dt, u, d, p;
int z;
int Method =2;
// Quantities for the tree
double dt = T / n;
double d1 = (log(S / K) + (r - q + v*v / 2) * T) / v / sqrt(T); // D Plus
double d2 = (log(S / K) + (r - q - v*v / 2) * T) / v / sqrt(T); // D Minus
double rq_n = exp((r - q) * dt);
double Term1 = pow((d1 / (n + 1.0 / 3.0 - (1 - Method) * 0.1 / (n + 1))), 2) * (n + 1.0 / 6.0);
double pp = 0.5 + Sgn(d1) * 0.5 * sqrt(1 - exp(-Term1));
double Term2 = pow((d2 / (n + 1.0 / 3.0 - (1 - Method) * 0.1 / (n + 1))), 2) * (n + 1.0 / 6.0);
double p = 0.5 + Sgn(d2) * 0.5 * sqrt(1 - exp(-Term2));
//changed
double u = rq_n * pp / p;
double d = (rq_n - p * u) / (1 - p);
//u = exp(v*sqrt(dt));
//d = 1.0 / u;
//p = (exp((r - q)*dt) - d) / (u - d);
if (PutCall == 'C')
{
z = 1;
}
else if (PutCall == 'P')
{
z = -1;
}
vector<double> OptionValue;
//resize the row of the sub-vector: (1,2,3..)
OptionValue.resize(n + 1);
// Calculating Intrinsic Value
for (i = 0; i <= n; i++) {
OptionValue[i] = max(z*(S*pow(u, i)*pow(d, n - i) - K), 0.0);
}
// Backward recursion through the tree
for (j = n - 1; j >= 0; j--)
for (i = 0; i <= j; i++) {
if (OpStyle == 'E')
OptionValue[i] = exp(-r*dt)*(p*(OptionValue[i + 1]) + (1.0 - p)*(OptionValue[i]));
else {
OptionValue[i] = max(z*(S*pow(u, i)*pow(d, j - i) - K), exp(-r*dt)*(p*(OptionValue[i + 1]) + (1.0 - p)*(OptionValue[i])));
}
}
// Return the option price
return OptionValue[0];
}
int main() {
clock_t start_time, end_time;
start_time = clock();
//double S, K, T, v, r;
//char PutCall, OpStyle;
//int n;
int n = 25000; // Number of steps
double S = 100; // Spot Price
double K = 110.0; // Strike Price
double T = 0.5; // Years to maturity
double r = 0.07; // Risk Free Rate
double q = 0.0; // q added
double v = 0.30;
char PutCall = 'C';
char OpStyle = 'A';
cout << setprecision(10);
cout << "The binomial price is " << Binomial(n, S, K, r, q, v, T, PutCall, OpStyle) << endl;
end_time = clock();
cout << " " << start_time << " " << end_time << " " << (end_time - start_time) << endl;
cout << " " << start_time << " " << end_time << " " << (end_time - start_time) / (double)CLOCKS_PER_SEC << " seconds" << endl;
//system("PAUSE");
}
(3) Leisen Reimer Dynamic and Truncated (C++ Code)
// Leisen Reimer dynamic and truncated zeros
// Seems to tally with Leisen Reimer paper
#include <stdio.h>
#include <math.h>
#include <iostream>
#include <algorithm>
#include <iostream>
#include <vector>
#include<iomanip>
#include <string>
//using namespace System;
using namespace std;
// Returns the sign of a number
double Sgn(double x) {
if (x<0) return -1;
else if (x>0) return 1;
else if (x == 0) return 0;
}
// Function for binomial tree
double Binomial(int n, double S, double K, double r, double q, double v, double T, char PutCall, char OpStyle) {
int i, j; // Counters
if (n % 2 == 0) // Use only odd number of steps
n = n + 1;
//double dt, u, d, p;
int z;
int Method =2;
// Quantities for the tree
double dt = T / n;
double d1 = (log(S / K) + (r - q + v*v / 2) * T) / v / sqrt(T); // D Plus
double d2 = (log(S / K) + (r - q - v*v / 2) * T) / v / sqrt(T); // D Minus
double rq_n = exp((r - q) * dt);
double Term1 = pow((d1 / (n + 1.0 / 3.0 - (1 - Method) * 0.1 / (n + 1))), 2) * (n + 1.0 / 6.0);
double pp = 0.5 + Sgn(d1) * 0.5 * sqrt(1 - exp(-Term1));
double Term2 = pow((d2 / (n + 1.0 / 3.0 - (1 - Method) * 0.1 / (n + 1))), 2) * (n + 1.0 / 6.0);
double p = 0.5 + Sgn(d2) * 0.5 * sqrt(1 - exp(-Term2));
//changed
double u = rq_n * pp / p;
double d = (rq_n - p * u) / (1 - p);
//CRR
//u = exp(v*sqrt(dt));
//d = 1.0 / u;
//p = (exp((r - q)*dt) - d) / (u - d);
if (PutCall == 'C')
{
z = 1;
}
else if (PutCall == 'P')
{
z = -1;
}
vector<double> OptionValue;
//resize the row of the sub-vector: (1,2,3..)
OptionValue.resize(n + 1);
// Calculating Intrinsic Value
for (i = 0; i <= n; i++) {
OptionValue[i] = max(z*(S*pow(u, i)*pow(d, n - i) - K), 0.0);
}
if (z ==1)
// Backward recursion through the tree
// The tree is segmented for truncating zeros
for (j = n - 1; j >= (n+1)/2; j--)
for (i = (j - ((n+1)/2)); i <= j; i++) {
if (OpStyle == 'E')
OptionValue[i] = exp(-r*dt)*(p*(OptionValue[i + 1]) + (1.0 - p)*(OptionValue[i]));
else {
OptionValue[i] = max(z*(S*pow(u, i)*pow(d, j - i) - K), exp(-r*dt)*(p*(OptionValue[i + 1]) + (1.0 - p)*(OptionValue[i])));
}
}
else if (z == -1)
for (j = n - 1; j >= (n+1)/2; j--)
for (i = 0; i <= (n+1)/2; i++) {
OptionValue[(n+1)/2]=0;
if (OpStyle == 'E')
OptionValue[i] = exp(-r*dt)*(p*(OptionValue[i + 1]) + (1.0 - p)*(OptionValue[i]));
else {
OptionValue[i] = max(z*(S*pow(u, i)*pow(d, j - i) - K), exp(-r*dt)*(p*(OptionValue[i + 1]) + (1.0 - p)*(OptionValue[i])));
}
}
for (j = ((n+1)/2)-1; j >= 0; j--)
for (i =0; i <= j + 1; i++) {
if (OpStyle == 'E')
OptionValue[i] = exp(-r*dt)*(p*(OptionValue[i + 1]) + (1.0 - p)*(OptionValue[i]));
else {
OptionValue[i] = max(z*(S*pow(u, i)*pow(d, j - i) - K), exp(-r*dt)*(p*(OptionValue[i + 1]) + (1.0 - p)*(OptionValue[i])));
}
}
// Return the option price
return OptionValue[0];
}
int main() {
clock_t start_time, end_time;
start_time = clock();
//double S, K, T, v, r;
//char PutCall, OpStyle;
//int n;
int n = 10000; // Number of steps
double S = 100; // Spot Price
double K = 110.0; // Strike Price
double T = 0.5; // Years to maturity
double r = 0.07; // Risk Free Rate
double q = 0.0; // q added
double v = 0.30;
char PutCall = 'C';
char OpStyle = 'E';
cout << setprecision(10);
cout << "The binomial price is " << Binomial(n, S, K, r, q, v, T, PutCall, OpStyle) << endl;
end_time = clock();
cout << " " << start_time << " " << end_time << " " << (end_time - start_time) << endl;
cout << " " << start_time << " " << end_time << " " << (end_time - start_time) / (double)CLOCKS_PER_SEC << " seconds" << endl;
//system("PAUSE");
}
A Deeper Dive in Leisen Reimer (1996) Modeling
In the video below, we develop a little bit more the main features that distinguish Leisen Reimer from Cox, Ross and Rubinstein. The zero region in the Leisen Reimer tree is fixed. It does not adjust when we adjust the parameter values. This is different to Cox, Ross and Rubinstein. I use the manual trees set out in excel to capture the main differences. Making the zero region redundant through truncation will all else being equal speed up lattice estimation. In the exposition here it is possible to unfurl some properties linked to the early exercise boundaries for both Leisen reimer and Cox Ross and Rubinstein. This is relevant for understanding Intelligent lattice Search.