Serving the Quantitative Finance Community

 
GaryVel2462
Topic Author
Posts: 3
Joined: March 8th, 2023, 10:04 pm

Fugit, and estimating expected lifetime in a binomial model

March 9th, 2023, 1:27 pm

Hi everyone,
 
My exposure to options is largely limited to work done in valuing employee share option plans, which is via a binomial model or Monte Carlo simulation (and so apologies in advance for my limited starting point!). The two outputs are the option value and the expected lifetime. Happy with the computed option values – these tie up with the literature, other models, general reasoning, etc.
 
The binomial model literature (Hull & White, etc.) suggests that the expected lifetime can be calculated using the fugit approach, ie. a risk neutral expected term based on the points at which exercise is expected to happen, where the term is then reset. My calculations show that this gives surprising results, such as an increase in the expected lifetime when the assumed dividend yield increases … hence my posing the following.
 
There is an old discussion on Willmott.com (archived at https://web.archive.org/web/20150704172739/http://wilmott.com/messageview.cfm?catid=34&threadid=67833) which suggests that using -rho / (option value – delta * current price) is a good approximation … and it definitely does seem to give far more accurate answers! So my questions are:
 
  1. Is there an explanation for what this alternative approach is? I’ve read and re-read this old discussion and really just don’t understand. I also can’t find it in the literature.
  2. Is there a way to approximate this approach in a binomial tree? An extract of my code (dealing with the two loops within the tree) is below. I’m battling to see how this code should be adjusted to take account of what robnavin says in his reply in the old discussion.
  3. Are there any other accepted means to calculate expected life time of a binomial tree?
 
Thanks for any pointers that you are able to provide!

double dt = T / N;
double r  = exp(risk_free * dt);
double u  = exp(sigma * sqrt(dt));
double d  = 1 / u;
double p  = (exp((risk_free - divrate) * dt) - d) / (u - d);
 
for (int i = N - 1; i >= 0; i--)
{
    for (int j = 0; j <= i; j++)
    {
        double PV_option_one_period = (p * option_value[i + 1][j + 1] + (1 - p) * option_value[i + 1][j]) / r;
 
        // this is after vesting so note the use of px (determined from exit_post_vesting)
        if (i >= VestInt) {
 
            // if option instrinsic value > risk neutral option value, then "optimal" assumes option holder will exercise
            // Alternatively if the multiple is hit then option is exercised.
            if ((instrinsic_value[i][j] > PV_option_one_period) || (share_price_matrix[i][j] >= strike_price * multiple))
            {
                option_value[i][j]  = instrinsic_value[i][j];
                expected_term[i][j] = 0.0;
            }
            else {
                option_value[i][j]   = PV_option_one_period;
                expected_term[i][j]  = (p * expected_term[i + 1][j + 1] + (1 - p) * expected_term[i + 1][j] + dt);
            }
        }
 
        else {
            option_value[i][j]  = PV_option_one_period;
            expected_term[i][j] = (p * expected_term[i + 1][j + 1] + (1 - p) * expected_term[i + 1][j] + dt);
        }
    }
}
 
User avatar
Alan
Posts: 2958
Joined: December 19th, 2001, 4:01 am
Location: California
Contact:

Re: Fugit, and estimating expected lifetime in a binomial model

April 13th, 2023, 7:10 pm

Looking at that old thread, it cited this, which I thought was clear.

Looking at your code, it seems to me 
expected_term[i][j] = 0.0
   should instead read something like: 
expected_term[i][j] = (i+1)*dt 
 
and
 
expected_term[i][j]  = (p * expected_term[i + 1][j + 1] + (1 - p) * expected_term[i + 1][j] + dt)
should instead read something like: 
 expected_term[i][j]  = (p * expected_term[i + 1][j + 1] + (1 - p) * expected_term[i + 1][j])
But I could be wrong.
Plus there are the caveats from the discussion that this is a risk-neutral expectation, so perhaps of limited utility.
 
GaryVel2462
Topic Author
Posts: 3
Joined: March 8th, 2023, 10:04 pm

Re: Fugit, and estimating expected lifetime in a binomial model

April 19th, 2023, 8:54 pm

Thanks for taking the time to reply, Alan. Replacing 
expected_term[i][j] = 0.0
with 
expected_term[i][j] = (i+1)*dt 
and resetting the expected term each time the following boundary is hit
((instrinsic_value[i][j] > PV_option_one_period) || (share_price_matrix[i][j] >= strike_price * multiple))
in the end gives the same result as my formulation above. (It does start to diverge when allowing for exits which is typically done when valuing employee share options for IFRS2 purposes, ie to provide for staff that exit the share option plan).

As mentioned my calculations show that this fugit gives surprising results (e.g. an increase in the expected lifetime when the assumed dividend yield increases).

Since posting my question I have used a weighted duration, where the weighting is the option's intrinsic value of the option at that point. This gives a much more reasonable expected duration and is similar in concept to a Macaulay duration. Specifically:

for all j:
    expected_term[N][j]  = instrinsic_value[N][j] * T
for all i, j:
    expected_term[i][j] = instrinsic_value[i][j] * i * dt   -> when boundary is hit, ie. reset the expected term
    expected_term[i][j] = (p * expected_term[i + 1][j + 1] + (1 - p) * expected_term[i + 1][j]) -> when boundary not hit.
and then divide expected_term[0][0] by a similarly calculated denominator that does not include the T or i * dt term.


As an example of the odd fugit behaviour vs. the updated approach using the intrinsic value:
dividend yield    H&W fugit    term weighted by instrinsic value
     5%               8.7                    6.1
    10%               8.3                    4.2
    15%               8.6#                   3.7

# this cannot be right, ie. an increasing fugit for an increasing dividend yield.
Assumptions for above: current price = strike price = 100; vesting years = 3, maturity = 10 years, risk free = 7.5% continuous, sigma = 25%.

So my gut feel is that the fugit approach should be cashflow weighted, where the cashflows are the intrinsic value of the option at that point.

Having said that, I haven't come across fugit other than in academic literature.
 
User avatar
Alan
Posts: 2958
Joined: December 19th, 2001, 4:01 am
Location: California
Contact:

Re: Fugit, and estimating expected lifetime in a binomial model

April 25th, 2023, 4:42 pm

I took my own advice and modified some old amer-style call option code to include fugit.  The essence of it is:
// Rubinstein 1994 fugit example parms from "On the Accounting Valuation of Employee Stock Options"
double S0 = 100.0;
double K = 100.0;
double T;
double r = 0.08;
double q = 0.035;   // cont. div yield 
double sigma = 0.30;

int main(int argc, char *argv[])
{
 clock_t clock1, clock2;
 char osdate[128], ostime[12];
 double dt, halfdt, R, Discount, u, uu, d, dd, p_up, p_down, x, S_hat;
 int64_t step, ny, nsteps, n;
 int64_t rpts;
 int64_t i, J;
 double t; // t is calendar time; t=T on expiration
 char yn;

top:
 cout << "T = years to expiration: Enter T: ";
 cin >> T;
 cout << "T=" << T << " There will be ny time-steps per year: Enter ny: ";
 cin >> ny;
 nsteps = ny*T;
 dt = T / nsteps;
 halfdt = 0.5*dt;
 cout << "There will be reports every m time-steps: Enter m: ";
 cin >> rpts;

vector<double> prices(nsteps + 1);
vector<double> call_value(nsteps + 1);
vector<double> exercise_time(nsteps + 1); 
 
 R = exp((r-q)*dt);
 Discount = exp(-r*dt);
 u = exp(sigma*sqrt(dt));
 uu = u*u;
 d = 1.0 / u;
 dd = d*d;
 p_up = (R - d) / (u - d);
 p_down = 1.0 - p_up; 
 
 t = T;
 clock1 = clock();
 
 printf("initiating..\n");
 for (i = 0; i <= nsteps; i++) prices[i] = S0*pow(d, nsteps - i)*pow(u, i);
 for (i = 1; i <= nsteps; i++) call_value[i] = max(0.0, prices[i] - K);
 for (i = 1; i <= nsteps; i++) exercise_time[i] = T;
 printf("starting with..\n");
 for (step = nsteps - 1; step >= 0; --step) { // 1

 if (step%rpts == 0)
 cout << "step=" << step << " of " << nsteps << endl;
 
 t -= dt;
 
 // American-style, cont. div yield
 for (i = 0; i <= step; ++i){
     call_value[i] = (p_up*call_value[i + 1] + p_down*call_value[i])*Discount;
     exercise_time[i] = (p_up*exercise_time[i + 1] + p_down*exercise_time[i]);
     prices[i] = d*prices[i + 1];
     if (call_value[i] < prices[i] - K){
         call_value[i] = prices[i] - K;
         exercise_time[i] = t;
           }
         }
        } // 1
 clock2 = clock();

 cout << "number of tsteps=" << nsteps << endl;
 printf("Runtime=%10.6f secs\n", (double)(clock2 - clock1) / CLOCKS_PER_SEC);
 printf("S0=%6.2f K=%6.2f T=%6.2f r=%6.3f q=%6.3f sigma=%6.2f \n", S0, K, T, r, q, sigma);
        printf("call_value=%12.10f expected_exercise_time=%12.10f\n",call_value[0], exercise_time[0]);
I compared against Rubinstein (1994, pg 9). With q = 0.035, I found E[T*]=9.14 years and with q = 0.045, I found E[T*]=8.80 years, all quite consistent with both Rubinstein and what you would expect. (Here T* is the optimal exercise time, and E[...] is a risk-neutral expectation). I used T=10 and 1000 t-steps per year. Note you may see some unused variables defined as this code was developed for something else.
 
User avatar
Alan
Posts: 2958
Joined: December 19th, 2001, 4:01 am
Location: California
Contact:

Re: Fugit, and estimating expected lifetime in a binomial model

April 25th, 2023, 6:24 pm

Sorry; it doesn't change the numbers I quoted, but the 3 for-loops below printf("initiating..\n") should each start at i=0
 
User avatar
Alan
Posts: 2958
Joined: December 19th, 2001, 4:01 am
Location: California
Contact:

Re: Fugit, and estimating expected lifetime in a binomial model

April 25th, 2023, 8:33 pm

One final thing. 

I shouldn't have called E[T*] an "expected exercise time". As in the thread title, "expected lifetime" makes more sense.
 
GaryVel2462
Topic Author
Posts: 3
Joined: March 8th, 2023, 10:04 pm

Re: Fugit, and estimating expected lifetime in a binomial model

May 1st, 2023, 8:55 pm

Thanks again Alan. I think that the anomaly that I'm seeing (increasing expected lifetime with an increase in the dividend yield after a certain point) is related to the vesting period, before which the American option can be exercised. This period is typical of employee share option (ESO) plans.

Here is example code which includes a vesting period. At zero it gives the same answers as you have above; at V=3 years it gives an increasing expected lifetime after the dividend yield exceeds 10 percent.

As mentioned above, using the Macaulay duration approach (weighted by instrinic value) gives a value that appears far more reasonable and consistent. The expected lifetime falls quite rapidly as the dividend yield increases, holding the other assumptions unchanged.
#include <vector>
#include <cmath>
#include <iostream>
#include <iomanip>

std::vector<double> binomial_option_value(double share_price, double strike_price, double T, double vesting_period, double risk_free, double sigma, double divrate, int N)
{
    std::vector<double> result(2, 0);

    double dt = T / N;
    double u = exp(sigma * sqrt(dt));
    double d = 1 / u;
    double r = exp(risk_free * dt);
    double p = (exp((risk_free - divrate) * dt) - d) / (u - d);

    int VestInt = int(vesting_period / dt + 0.001);

    std::vector <double> vec(N + 1, 0.0);
    std::vector <std::vector <double> > expected_term(N + 1, vec);
    std::vector <std::vector <double> > share_price_matrix(N + 1, vec);
    std::vector <std::vector <double> > instrinsic_value(N + 1, vec);
    std::vector <std::vector <double> > option_value(N + 1, vec);

    // define the stock price at each point on the binomial tree
    for (int i = N; i >= 0; i--)
    {
       for (int j = 0; j <= i; j++)
        {
           share_price_matrix[i][j] = share_price * pow(u, j) * pow(d, i - j);
           instrinsic_value[i][j]   = std::max(share_price_matrix[i][j] - strike_price, 0.0);
        }
    }

    for (int i = 0; i <= N; i++)
    {
        option_value[N][i]  = instrinsic_value[N][i];
        expected_term[N][i] = T;
    }

    for (int i = N - 1; i >= 0; i--)
    {
        for (int j = 0; j <= i; j++)
        {
           double PV_option_one_period = (p * option_value[i + 1][j + 1] + (1 - p) * option_value[i + 1][j]) / r;

           if (i >= VestInt) {

              if (instrinsic_value[i][j] > PV_option_one_period)
              {
                 option_value[i][j] = instrinsic_value[i][j];
                 expected_term[i][j] = i * dt;
              }
              else {
                 option_value[i][j]  = PV_option_one_period;
                 expected_term[i][j] = (p * expected_term[i + 1][j + 1] + (1 - p) * expected_term[i + 1][j]);
              }
           }

           else {
              option_value[i][j]  = PV_option_one_period;
              expected_term[i][j] = (p * expected_term[i + 1][j + 1] + (1 - p) * expected_term[i + 1][j]);
           }
        }
    }

    result[0] = option_value[0][0];
    result[1] = expected_term[0][0];

    return result;

}


int main() {
    double share_price      = 100.0;
    double strike_price     = 100.0;
    double vesting_period   =   3.0;
    double T                =  10.0;
    double risk_free        =  0.08;
    double sigma            =  0.30;
    // double divrate       = 0.045;
    int N                   = 1000;

    std::vector<double> result(2, 0);

    // std::cout << std::setprecision(4);
    std::cout << std::fixed;
    std::cout << std::setprecision(3);

    for (int divrate = 0; divrate<=250; divrate+=25)
    {
        result = binomial_option_value(share_price, strike_price, T, vesting_period, risk_free, sigma, (double)divrate / 1000, N);
        std::cout << "Div. yield:\t" << (double)divrate / 1000 << "\toptimal ESO price is " << result[0] << "\tand expected lifetime is: " << result[1] << "\n";
    }

}



Div. yield: 0.000 optimal ESO price is 61.679 and expected lifetime is: 10.000
Div. yield: 0.025 optimal ESO price is 43.266 and expected lifetime is: 9.519
Div. yield: 0.050 optimal ESO price is 32.126 and expected lifetime is: 8.741
Div. yield: 0.075 optimal ESO price is 24.596 and expected lifetime is: 8.404
Div. yield: 0.100 optimal ESO price is 18.975 and expected lifetime is: 8.329
Div. yield: 0.125 optimal ESO price is 14.612 and expected lifetime is: 8.385
Div. yield: 0.150 optimal ESO price is 11.173 and expected lifetime is: 8.522
Div. yield: 0.175 optimal ESO price is 8.465 and expected lifetime is: 8.715
Div. yield: 0.200 optimal ESO price is 6.344 and expected lifetime is: 8.865
Div. yield: 0.225 optimal ESO price is 4.695 and expected lifetime is: 9.059
Div. yield: 0.250 optimal ESO price is 3.429 and expected lifetime is: 9.231
[Finished in 1.6s]