Accounting for Employee Stock Options

Accounting for Employee Stock Options: accelerating convergence.

Employee Stock Options (ESOs) have greatly expanded the way corporations can remunerate employees. The popularity of these exotic instruments, in part, can be explained by a desire of firms to more deftly craft incentives to maximize shareholder value and retain staff with strategic skills and know-how. Employee Stock Options moreover provide a useful resonance in terms of compensation: they permit staff to enjoy windfall gains when corporations are best placed to share the spoils of stock price increases. As the corporation’s stock price declines, this largess can be removed without overtly spiting personnel: reducing pay where the key catalyst is a market response rather than a vindictive boss. This expanded remuneration toolbox however brings with it, the necessity to determine value and communicate contingency costs that are likely to be incurred by shareholders. Valuing the potential costs to corporations is complicated because ESOs are highly exotic with a blend of European, American and barrier exercise features that alter during different phases of vesting and post vesting. (See Hull and White (2004) Lattice Design below). A large number of Regulatory and Accounting actors effectively agree that fair value is a fundamental norm that should be adhered to and conveyed in Financial Reports. The standard techniques endorsed by the SAB 107, SAB 110 and FAS 123R include Black Scholes (1973) and lattice frameworks. Black Scholes (1973) was developed, in principle, to value only European Options. Employee Stock Options however feature a highly idiosyncratic blend of exercise privileges. All else being equal, one might expect a lattice approach would be relied upon because trees putatively model the many nuances embedded in the granting and vesting of ESOs. Hull and White (2004) have expounded a lattice pricing model, (they referred to it as “our Enhanced FASB 123 model”) that makes explicit reference to parameters omitted from Black Scholes (1973). In this paper, we examined a key weakness identified by Cvitanic, Wiener and Zapatero (2008) in relation to the lattice approach when applied to ESOs.

Lattice models when applied to ESO Hull and White (2004) valuation can be extremely sluggish in terms of convergence. If trees converge at a slow pace, then estimation can be pushed beyond a practicable threshold consistent with the available computing resources to most professionals. Hull and White (2004) have developed a lattice pricing model that makes explicit reference to parameters that are not available in Black Scholes (1973) yet are important for the valuation of Employee Stock Options (ESOs). Cvitanic, Wiener and Zapatero (2008) point out that a key weakness of the lattice approach, when applied to valuing ESOs, is the sluggish convergence not generally experienced in trees configured to estimate plain vanilla options. See Simon Benninga's code below for a standard implementation of Hull and White (2004). Cvitanic, Wiener and Zapatero (2008) propose a very useful closed form solution which may or may not be endorsed by a whole host of regulatory and professional bodies. Here, we propose a small refinement to Hull and White (2004), based on insights developed by Boyle and Lau (1994), see paper, which ensures faster convergence in lattice estimation when barriers occur. Our model provides estimates consistent with Cvitanic, Wiener and Zapatero (2008). The proposed model should also neatly fit into the rubric currently prescribed by the American Financial Accounting Standards Board (FASB) and endorsed by the Securities and Exchange Commission (SEC). We also apply a number of truncation techniques to the lattice which removes the zero region of the tree. We truncate the ESO lattice above the early exercise boundary, peculiar to Hull White (2004), and dynamically specify the lattice array to spare memory and reduce estimation time.

The VBA code immediately below is a standard textbook implementation of Hull and White (2004) ESO valuation. The major weakness with this VBA implementation is closely linked to oscillation in model outputs and sluggish convergence.

VBA Code for the standard Hull White (2004) ESO model

Function ESO(Stock As Double, X As Double, T As Double, Vest As Double, _

Interest As Double, sigma As Double, Divrate As Double, _

Exitrate As Double, Multiple As Double, n As Single)

Dim Up As Double, Down As Double, r As Double, Div As Double, _

piUp As Double, piDown As Double, Delta As Double, _

i As Integer, j As Integer

ReDim Opt(n, n)

ReDim S(n, n)

Up = Exp(sigma * Sqr(T / n))

Down = Exp(-sigma * Sqr(T / n))

r = Exp(Interest * T / n)

Div = Exp(-Divrate * T / n)

piUp = (r * Div - Down) / (Up - Down) 'Risk-neutral up probability

piDown = (Up - r * Div) / (Up - Down) 'Risk-neutral down probability

'Defining the stock price

For i = 0 To n

For j = 0 To i ' j is the number of Up steps

S(i, j) = Stock * Up ^ j * Down ^ (i - j)

Next j

Next i

'Defining the option value on the last nodes of tree

For i = 0 To n

Opt(n, i) = Application.Max(S(n, i) - X, 0)

Next i

'Early exercise when stock price > multiple * exercise after vesting

For i = n - 1 To 0 Step -1

For j = 0 To i

If i > Vest * n / T And S(i, j) >= Multiple * X Then Opt(i, j) = Application.Max(S(i, j) - X, 0)

If i > Vest * n / T And S(i, j) < Multiple * X Then Opt(i, j) = ((1 - Exitrate * T / n) * _

(piUp * Opt(i + 1, j + 1) + piDown * Opt(i + 1, j)) / r + Exitrate * T / n * _

Application.Max(S(i, j) - X, 0))

If i <= Vest * n / T Then Opt(i, j) = (1 - Exitrate * T / n) * (piUp * Opt(i + 1, j + 1) + _

piDown * Opt(i + 1, j)) / r

Next j

Next i

ESO = Opt(0, 0)

End Function

Hull and White (2004) with Boyle and Lau (1994) tuning

The next tranche of VBA code below is optimised to use less computer memory. We identify a zero region and the barrier region of the Hull White ESO lattice that can be used to leverage some efficiency. The zero region can be excluded from the tree and memory freed up to expedite estimation. Likewise, above the barrier defined by the Multiple, (M in Hull and White (2004)), can also be truncated from the lattice to produce some additional efficiencies. Also, we implement a technique suggested by Broadie and Detemple (1996) to optimise array or lattice mapping. Most implementations of binomial trees (including Benninga (2014) above) define a two-dimensional lattice. This approach is the most intuitive and can be easily expounded because it follows the tree mapping and grid references prominent in text books. Broadie and Detemple (1996) , Appendices B.1 and B.2, state that it is not necessary to store the entire tree in memory and use can be made of dynamic memory. The dynamic binomial tree only needs 𝑛+1 storage spaces which reduces by decrements of one with each successive backward recursion. This optimization effectively reduces the unnecessary data storage. We develop that approach in the tree below. Please see paper.

The Hull and White (2004) Lattice Design

The Hull and White (2004) model nests condition (i) – (iv) into a Cox, Ross and Rubinstein (1979) framework. These conditions divide the binomial tree into four sections in which different option pricing regimes are applied. Hull and White (2004) specified that:

(i) the option can be exercised in the post vesting period.

(ii) the vested option is exercised when the stock price S ≥ M*K. M*K is by default a barrier

(iii) the product of the time step 𝛿𝑡 and e can be used to estimate the probability of forfeiture in the vesting period moving from node to node.

(iv) in the post vesting period the probability 𝑒𝛿𝑡 captures the probability that the option is forfeited if S < K or exercised early to realize S – K if S ≥ K. K here denotes the strike or exercise.

We use the same notation as Hull White (2004). 𝑒−𝑟𝛿𝑡 denotes a continuous discount factor whereas 𝑒𝛿𝑡 denotes the product of the exit rate and the time step interval.

VBA code for Hull and White (2004) with refinements based on insights developed by Boyle and Lau (1994)

Function Dyn_Trun_HWBL(Stock As Double, X As Double, T As Double, Vest As Double, Interest As Double, _

Sigma As Double, Divrate As Double, Exitrate As Double, Multiple As Double, n As Integer)

Dim dt As Double, r As Double, u As Double, d As Double, p As Double, StoppingOpt As Double, _

i As Integer, j As Integer, VestInt As Integer, Condition As Integer, NumZero As Integer, NumStopping As Integer

'Boyle and Lau

For i = 1 To n

If n < Int((i ^ 2 * Sigma ^ 2 * T) / (Log(Multiple * X / Stock)) ^ 2) Then

n = Int((i ^ 2 * Sigma ^ 2 * T) / (Log(Multiple * X / Stock)) ^ 2)

Exit For

End If

Next

ReDim Opt(n)

dt = T / n

r = Exp(Interest * dt)

u = Exp(Sigma * Sqr(dt))

d = 1 / u

p = (Exp((Interest - Divrate) * dt) - d) / (u - d)

VestInt = Int(Vest / dt) 'The last column in the vesting period

NumZero = Int((Log(X / Stock) / Log(u) + n) / 2) 'The last zero node from the bottom at the maturity

NumStopping = Application.Ceiling((Log(X * Multiple / Stock) / Log(u) + n) / 2, 1) 'The first early exercise node at the maturity

Finish = NumStopping - 1

'Distinguish two conditions:

StoppingOpt = Stock * u ^ (2 * NumStopping - n) - X

Condition = 0 'The horizontal layer to which (n, NumStopping) belongs is the boundary of proactive early exercise region

If Stock * u ^ (2 * (NumStopping - 1) - (n - 1)) >= X * Multiple Then

StoppingOpt = Stock * u ^ (2 * (NumStopping - 1) - (n - 1)) - X

Condition = 1 'The horizontal layer to which (n-1,NumStopping-1) belongs is the boundary of proactive early exercise region

End If

'Defining the option value of nodes at the maturity

For i = 0 To NumStopping - 1

If i <= NumZero Then

Opt(i) = 0

Else

Opt(i) = Stock * u ^ (2 * i - n) - X

End If

Next i

'Option Pricing Loop

For j = n - 1 To 0 Step -1

'Start: Truncate "zero" region

If j >= n - NumZero Then

Start = NumZero - (n - 1 - j)

Else

Start = 0

End If

'Option pricing after vesting period

If j > VestInt Then

'Finish: Truncate "redundant proactive early exercise" region (St>=Multiple*X)

If Finish <= j Then

If (n - j) Mod 2 = 0 Then

If Condition = 0 Then Finish = Finish - 1

If Condition = 1 Then Opt(Finish + 1) = StoppingOpt

Else

If Condition = 0 Then Opt(Finish + 1) = StoppingOpt

If Condition = 1 Then Finish = Finish - 1

End If

ElseIf Finish > j Then 'No proactive early exercise at j column

Finish = j

End If

'No proactive early exercise, only passive early exercise (St<Multiple*X)

For i = Start To Finish

Opt(i) = ((1 - Exitrate * dt) * (p * Opt(i + 1) + (1 - p) * Opt(i))) / r + _

Exitrate * dt * Application.Max(Stock * u ^ (2 * i - j) - X, 0)

Next

'Option values of proactive early exercise nodes at (Vest/dt+1) columnn

If j = VestInt + 1 Then

For i = Finish + 1 To j

Opt(i) = Stock * u ^ (2 * i - j) - X

Next

End If

'Option pricing during vesting period

ElseIf j <= VestInt Then

For i = Start To j

Opt(i) = ((1 - Exitrate * dt) * (p * Opt(i + 1) + (1 - p) * Opt(i))) / r

Next

End If

Next

Dyn_Trun_HWBL = Opt(0)

End Function


C++ Code for the standard Hull White (2004) ESO model

C++ is heavily relied upon in computational finance to generate improved performance. The latter is necessary to minimize the response time thereby secure system reliability. Improved performance allows for the implementation of a more replete menu of features without compromising the integrity of estimation. Equally, elevated performance might create opportunities to make use of low end microprocessors or even reduce consumption of power. When a 100,000 step Hull and White (2004) static lattice was estimated on my stellar but small laptop - an overnight run managed to raise room temperature to a balmy 22 degree Celsius in mid winter. The fan which normally never so much as squeaked before became quite torpid and made a groaning noise and the end of life seemed to beckon. Good data structures and algorithms can mitigate many of these problems characteristic of computationally intense estimation. We can demonstrate below the effect of optimizing C++ code that make better use of hardware and prolong the life of equipment. Much to my chagrin, I have actually lost a mac mini to a reckless inordinate uber step size lattice estimation that naively ran until convergence of a static Hull and White (2004) could be attained.

The first snippet of C++ code below is less efficient than the snippet further below. To examine the efficiencies that can be obtained by moving from static to dynamic memory usage and truncation see video below for explanation. We improve the Hull and White (2004) algorithm by incorporating a Boyle and Lau (1994) specification for the barrier and by making better use of computer memory. The software implementation can be made more efficient by defining the array mapping differently for the lattice. This will involve truncating both the zero region and barrier region. We minimize the runtime required for binomial pricing by taking a two-dimensional tree and storing it as a one-dimensional array. This software implementation in the second C++ snippet below dispenses with the requirement to store the whole binomial tree at once. Again, see video below.

// Standard Implementation of static Hull and White (2004) ESO model in C++ without optimization

#include <iostream>

#include <vector>

#include <algorithm>

#include <math.h>

#include <time.h>

using namespace std;


double ESOs_Binomial(void)

{

double Spot = 100; // Spot Price

double K = 100; // Strike Price

double Vest = 2; // Vesting period (in years)

double T = 10; // Maturity in Years

double Sigma = 0.2; // Volatility

double DivRate = 0.0; // Dividend rate

double ExitRate = 0.04; // Exit rate

double Multiple = 1.5; // Multiple: Stock/strike ratio

double InterestR = 0.06; // Interest Rate

double n = 100; // no longer subdivision

double R = exp(InterestR *T/ n); // Interest rate in one period.(a)

double dt = T/ n; // Time interval.

double Up = exp(Sigma * sqrt(dt));

double Down = exp(-Sigma * sqrt(dt));

double PiUp = (exp((InterestR - DivRate) * dt) - Down) / (Up - Down); //'Risk-neutral up probability

double PiDown = 1 - PiUp; //'Risk-neutral down probability

int i, j;

int NT = (int)(n); //the depth of binomial tree, also means the dimension of the array.

vector<vector<double> > S(NT + 1, vector<double>(NT + 1));

vector<vector<double> > OptN(NT + 1, vector<double>(NT + 1));

// Build the Static Two-Dimensional binomial tree

for (i = 0; i <= NT; i++) {

for (j = 0; j <= i; j++) {

S[i][j] = Spot*pow(Up, j)*pow(Down, i - j);

}

}

for (i = 0; i <= NT; i++) {

OptN[NT][i] = max(S[NT][i] - K, 0.0);

}

//Early exercise when: stock price > (multiple * exercise after vesting)

//Calculate the option price from second last period

for (i = (NT - 1); i >= 0; i--)

{

for (j = 0; j <= i; j++)

{

if ((i > Vest*n/T) && (S[i][j] >= (Multiple*K)))

{

OptN[i][j] = max(S[i][j] - K, 0.0);

}

if ((i > Vest*n/T) && (S[i][j] < (Multiple*K)))

{

OptN[i][j] = ((1 - ExitRate*T / n) * (PiUp * OptN[i + 1][j + 1] + PiDown * OptN[i + 1][j]) / R + ExitRate *T/ n * max(S[i][j] - K, 0.0));

}

if (i <= Vest*n/T)

{

OptN[i][j] = (1 - ExitRate*T / n) * (PiUp * OptN[i + 1][j + 1] + PiDown * OptN[i + 1][j]) / R;

}

}

}

return OptN[0][0];

}

// Main program

int main()

{

clock_t start_time, end_time;

start_time = clock();

cout << "The ESO value of Binomial is: " << ESOs_Binomial() << 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");

}



Optimized C++ code for Hull and White (2004) with refinements based on insights developed by Boyle and Lau (1994)

//C++ Code for Hull White (2004) model amended for Boyle Lau barrier and optimized using truncation and dynamic // memory

#include <stdio.h>

#include <math.h>

#include <iostream>

#include <algorithm>

#include <iostream>

#include <vector>

#include<iomanip>

#include <string>

#include <cmath>

#include <time.h>

//using namespace System;

using namespace std;

// Function for binomial tree

double DynTrunHWBL(double S, double K, double T, double Vest, double Interest, double Sigma, double Divrate, double Exitrate, double Multiple, int n) {

double dt, r, u, d, p, StoppingOpt;

int i, j, VestInt, Condition, NumZero, NumStopping, Finish, Start;

for (i = 1; i <= n; i++)

{

if (n < int((pow(i, 2) * pow(Sigma, 2) * T) / pow(log(Multiple * K / S), 2)))

{

n = int((pow(i, 2) * pow(Sigma, 2) * T) / pow(log(Multiple * K / S), 2));

break;

}

}

dt = T / n;

r = exp(Interest * dt);

u = exp(Sigma * sqrt(dt));

d = 1 / u;

p = (exp((Interest - Divrate) * dt) - d) / (u - d);

VestInt = int(Vest / dt); //The last column in the vesting period

NumZero = int((log(K / S) / log(u) + n) / 2); //The last zero node from the bottom at the maturity

NumStopping = ceil((log(K * Multiple / S) / log(u) + n) / 2); //The first early exercise node at the maturity

Finish = NumStopping - 1;

vector<double> Opt;

//resize the column

Opt.resize(n + 1);

//Distinguish two conditions:

StoppingOpt = S * pow(u, 2 * NumStopping - n) - K;

Condition = 0;

if (S * pow(u, 2 * (NumStopping - 1) - (n - 1)) >= K * Multiple)

{

StoppingOpt = S * pow(u, 2 * (NumStopping - 1) - (n - 1)) - K;

Condition = 1;

}

//Defining the option value of nodes at the maturity

for (i = 0; i < NumStopping; i++)

{

if (i <= NumZero)

{

Opt[i] = 0;

}

else

{

Opt[i] = S * pow(u, 2 * i - n) - K;

}

}

//Option Pricing Loop

for (j = n - 1; j >= 0; j--)

{

//Start: Truncate "zero" region

if (j >= n - NumZero)

{

Start = NumZero - (n - 1 - j);

}

else

{

Start = 0;

}

//Option pricing after vesting period

if (j > VestInt)

{

//Finish: Truncate "redundant proactive early exercise" region (St>=Multiple*K)

if (Finish <= j)

{

if ((n - j) % 2 == 0)

{

if (Condition == 0)

{

Finish = Finish - 1;

}

if (Condition == 1)

{

Opt[Finish + 1] = StoppingOpt;

}

}

else

{

if (Condition == 0)

{

Opt[Finish + 1] = StoppingOpt;

}

if (Condition == 1)

{

Finish = Finish - 1;

}

}

}

else if (Finish > j) //No proactive early exercise at j column

{

Finish = j;

}

//No proactive early exercise, only passive early exercise (St<Multiple*K)

for (i = Start; i <= Finish; i++)

{

Opt[i] = ((1 - Exitrate * dt) * (p * Opt[i + 1] + (1 - p) * Opt[i])) / r + Exitrate * dt * max(S * pow(u, 2 * i - j) - K, 0.0);

}

//Option values of proactive early exercise nodes at (Vest/dt+1) columnn

if (j == VestInt + 1)

{

for (i = Finish + 1; i <= j; i++)

{

Opt[i] = S * pow(u, 2 * i - j) - K;

}

}

}

//Option pricing during vesting period

else if (j <= VestInt)

{

for (i = Start; i <= j; i++)

{

Opt[i] = ((1 - Exitrate * dt) * (p * Opt[i + 1] + (1 - p) * Opt[i])) / r;

}

}

}

// Return the option price

return Opt[0];

}

int main() {

clock_t start_time, end_time;

start_time = clock();

double S = 100.0; // Spot Price

double K = 100.0; // Strike Price

double T = 10.0; // Maturity in Years

double Vest = 2; // Vesting period (in years

double Interest = 0.06; // Interest Rate

double Sigma = 0.20; // Volatility

double Divrate = 0.0; // Dividend rate

double Exitrate = 0.04; // Exit rate

double Multiple = 1.5; // Multiple: Stock/strike ratio

int n = 100; // Number of Steps Specified for Tree

cout << "The ESO price is " << DynTrunHWBL(S, K, T, Vest, Interest, Sigma, Divrate, Exitrate, Multiple, n) << 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");

// https://www.youtube.com/c/BrianByrneFinance

}