IFRS 2.1 explicitly defines the objective of the Standard as the specification of the “expenses associated with transactions in which share options are granted to employees”. IFRS 2.2 requires the expensing of “equity-settled share-based payment transactions, in which the entity receives…services as consideration for equity instruments of the entity (including shares or share options)”. IFRS 2.10 requires the determination and recognition of “fair value” of this expense: “For equity-settled share-based payment transactions, the entity shall measure…the fair value of the…services received”. IFRS 2.11 provides further clarification on the definition of “fair value” : “To apply the requirements of paragraph 10 to transactions with employees and others providing similar services, the entity shall granted…The fair value of those equity instruments shall be measured at grant date.”
An option-pricing approach to the assignment of fair value is stipulated in IFRS 2.33: “The liability shall be measured, initially and at each reporting date until settled, at the fair value of the share appreciation rights, by applying an option pricing model, taking into account the terms and conditions on which the share appreciation rights were granted, and the extent to which the employees have rendered service to date.”
The Standard is not prescriptive regarding which valuation models should be used though it does describe the factors that should be taken into account when estimating the fair value. In IFRS 2.47, detailing disclosure requirement, assumes that “inputs to the model” will comprise at least “the weighted average share price, exercise price, expected volatility, option life, expected dividends, the risk-free interest rate…the method used and the assumptions made to incorporate the effects of expected early exercise”.
Appendix B offers more insight into the suitability or otherwise of certain models. IFRS 2 B.5 notes that the Black-Scholes-Merton may prove insufficiently flexible: “[M]any employee options have long lives, are usually exercisable during the period between vesting date and the end of the options’ life, and are often exercised early. These factors should be considered when estimating the grant date fair value of the options. For many entities, this might preclude the use of the Black-Scholes-Merton formula, which does not allow for the possibility of exercise before the end of the option’s life and may not adequately reflect the effects of expected early exercise. It also does not allow for the possibility that expected volatility and other model inputs might vary over the option’s life.” IFRS 2 B.17 highlights the advantages of a more flexible lattice-based model: “[E]xpected early exercise could be modelled in a binomial or similar option pricing model that uses contractual life as an input”.
The model framework should be sufficiently flexible to account for:
The restrictiveness of the assumptions inherent in Black-Scholes-Merton option pricing model mean that it is generally unsuitable for valuing Employee Stock Option Plans (ESOP). Moreover, the failure to account for employee behaviour and volatility of volatility in the application of BSM could lead to inaccurately high or low option values.
If we begin by allowing for early exercise, and the non-constant volatility and dividend payments of the underlying whilst ignoring the existence of a vesting period and the possibility of option rights forfeiture, one can view the ESOP as a long-dated American-style call option granted by the Company to the employees. Traditional closed-form analytical1 or Monte-Carlo simulation2 solutions fail to incorporate the possibility of early exercise. Lattice-based models are a favoured approach to value those path-dependent options such as American options, where the timing of exercise is at the holder’s discretion. We refer to the binomial model for valuing American Options as the Basic Model, which we employ for largely pedagogical purposes en route to building the Final Model.
1 A 2008 paper gives an analytical derivation of the value of employee stock options. The formula stretches over 16 pages of typescript. Due to its complexity, the approach has failed to supplant binomial models as the preferred approach for ESO valuation. See Cvitanić, Jakša, Z. Wiener and F. Zapatero. “Analytic Pricing of Employee Stock Options.” Review of Financial Studies 21 (2008): 683-724.
2 A 2001 paper by Longstaff & Schwartz presents a new approach for approximating the value of American options by simulation. To date the model has not been adapted to incorporate the more complex characteristics particular to Employee Stock Option Plans. See Longstaff, Francis & Schwartz, Eduardo. (2001). Valuing American Options by Simulation: A Simple Least-Squares Approach. Review of Financial Studies. 14. 113-47.
Variable | Notation | Description | Value |
---|---|---|---|
Stock | Underlying asset. Stock price of the Sponsoring Company | €100 | |
Strike Price | The price at which option holders can exercise the right to buy the underlying. The difference between stock and strike is the intrinsic value (IV). IV is floored at zero as rational actors will only exercise if | €115 | |
Volatility | The volatility of the underlying. An input in the Binomial model which determines the magnitude of the up and down moves. May be based on historic or implied volatility. | 30% | |
Timestep | The fractions of time into which the Tree is segmented from the initial node at to terminal nodes at . | 50 | |
Time to Expiry | Time in years to maturity of option. | 10 | |
Risk Free Rate | The riskless interest rate. An important input to computing risk neutral probabilities | 5% | |
Dividend Yield | The dividend yield on the underlying. Also an important input to computing risk neutral probabilities | 2.5% |
3 Cox, J., S. Ross, and M. Rubinstein (1979): “Option Pricing: A Simplified Approach.” Journal of Financial Economics, Volume 7, Issue 3, pp. 229–263
The probability of reaching a particular node in the binomial tree depends on the numbers of distinct paths to that node and the probabilities of the up and down moves.
The binomial grid of 4 time steps below shows all asset price paths to each node. "u" represents an up-move and "d" a down-move.
The probability of reaching a particular node in the binomial tree depends on the numbers of distinct paths to that node and the probabilities of the up and down moves. The tree below displays the probability of reaching each node. "p" denotes the probability of an up-move and 1-p the probability of a down-move.
Recall that the binomial tree model is formed by building forward the “tree” of interim and terminal asset prices then performing backward induction, which values the option at each node as the probability-adjusted, discounted value of its child nodes. The option price is the sum of these discounted expected values.
The underlying asset price path is determined by the number of time steps and the magnitude of “up-moves” and “down-moves” at each node.
The magnitude of these moves is an exponential function of volatility scaled by square root of time:
Risk neutral valuation allows us to infer the probability of an “up-move”. Risk neutral probabilities are the implied probabilities, which equate the current market price of the option to the discounted expected payoff. They are the outputs of a function which takes the market value, future expected value and the discount rate as the inputs. The fundamental assumption is that the call option seller can form a riskless portfolio by going long a fraction of the share - determined by delta - and writing a call option ():
The payoff at each up and down node - the sum of (delta*stock) PLUS the intrinsic value of option - will be the same.
Delta is the fraction which equates the payoff at up and down nodes.
The seller of the option has delta hedged his exposure. The portfolio is riskless and, for there to be no arbitrage opportunities, it must earn the risk-free interest rate. The payoff at each node - the future portfolio value, composed of option and fraction of share - is discounted to the present value at the risk-free rate:
Rearranging, we find the option value at each node to be the discounted value of the fractional share position and the discounted intrinsic value of the option:
Or, equivalently:
Which, upon removing the terms, simplifies to:
Which can be expressed in terms of a risk neutral probability:
Where:
The Financial Accounting Standards Board4 proposed a two-step adjustment procedure to the classic American Binomial Model in order to better reflect early exercise and option rights forfeiture.
We can represent the adjusted price of an American Options by the so-called FASB 123 method in a simple formula:
4 Financial Accounting Standards Board. 1995. FASB 123: Accounting for Stock-Based
Compensation. Appendix B
In their 2004 paper5, Hull and White made a number of criticisms of the FASB 123 model.
The Hull-White Enhanced model, segments the Binomial Tree into 4 regions:
REGION 1: At the terminal nodes of a tree comprising N time steps, the value of the option is the option’s intrinsic value:
REGION 2: During the vesting period , the option prices at the at the j node of the tree at time it (where t v) are computed as:
Note the similarity to pricing of a European style option within a Binomial model framework.
REGION 3: After the vesting period (where it V), if the stock price is greater than or equal to the exercise criterion (,) then the option will be exercised:
REGION 4: After the vesting period (where it v), if the stock price is less than the exercise criterion, (,), then the option will be held:
Note the effect of weighting achieved with the combination of the and terms.
We can visualize this model in the following image which preserves the original notation of the Hull White 2004 paper.
denotes a continuous discount factor whereas 𝑒𝛿𝑡 denotes the product of the exit rate and the time step interval.The double use of to denote both exponential and exit rate tends to confuse for which reason I opt for less ambiguous notation above.
5 Hull, J, and White, A: How to Value Employee Stock Options Financial Analysts Journal, Vol. 60, No. 1, January/February 2004, 114-119
6 Huddart S, Lang M (1996) Employee stock option exercises: an empirical analysis. J Account Econ 21:5–43
7 Carpenter JN (1998) The exercise and valuation of executive stock options. J Financ Econ 48:127–158
import numpy as np
def Binomial(n, S, K, r,q, v, t, PutCall):
# Solved Parameters
At = t/n #Timestep
u = np.exp(v*np.sqrt(At)) # Upmove
d = 1./u # Downmove
p = (np.exp((r-q)*At)-d) / (u-d) # Risk-Neutral Probability
#Binomial price tree
stockvalue = np.zeros((n+1,n+1)) #Create symmetrical zero-filled array with dimensions of Timesteps + 1
stockvalue[0,0] = S #Location of current stock price in matrix (1st element in 1st column)
# Note the rows express time progression and columns tree level
for i in range(1,n+1): #Outer loop: For each timestep...
stockvalue[i,0] = stockvalue[i-1,0]*u # Compute future stockprice as product of prev. price and upmoves
for j in range(1,i+1): # Inner Loop: For each time step at each tree level...
stockvalue[i,j] = stockvalue[i-1,j-1]*d # Compute associated downmove stock value for each upmove
# (Nested for loop."Inner loop" will be executed one time
# for each iteration of the "outer loop").
#Option value at terminal nodes
optionvalue = np.zeros((n+1,n+1)) #Create symmetrical zero-filled matrix with dimensions of Timesteps + 1
for j in range(n+1): # For each node at the terminal time step...
# Compute instrinsic value based on put or call position
if PutCall=="C": # Call
optionvalue[n,j] = max(0, stockvalue[n,j]-K)
elif PutCall=="P": #Put
optionvalue[n,j] = max(0, K-stockvalue[n,j])
#Backward induction process for option values at interim nodes
for i in range(n-1,-1,-1): # For each time step, working backwards, from penultimate to first...
for j in range(i+1): # For each node at each of above time step...
# Compute the maximum of the expected value and the current intrinsic val.
if PutCall=="P":
optionvalue[i,j] = max(0,
K-stockvalue[i,j],np.exp(-r*At)*(p*optionvalue[i+1,j]+(1-p)*optionvalue[i+1,j+1]))
elif PutCall=="C":
optionvalue[i,j] = max(0,
stockvalue[i,j]-K,np.exp(-r*At)*(p*optionvalue[i+1,j]+(1-p)*optionvalue[i+1,j+1]))
#return optionvalue[0,0]
return optionvalue[0,0], stockvalue, optionvalue, n, S, K, r, q, v,t
from tabulate import tabulate
# Inputs
n = 25 #number of steps
S = 100 #initial underlying asset price
r = 0.05 #risk-free interest rate
q = 0.025 #risk-free interest rate
K = 115 #strike price
v = 0.3 #volatility
t = 10 #time to expiry
result = Binomial(n, S, K, r, q, v, t, PutCall="C")
header3 = ['Opt. Val.(Basic Model)','Spot', 'Strike', 'Rfr','Div. Yld.', 'Vol.',
'Time to Exp.', 'Time Steps']
table3 = [[result[0], result[4],result[5],result[6],result[7],result[8],result[9],result[3]]]
print(tabulate(table3,header3))
As we shall in our extended discussion of the Final Model, the optimal number of tree steps, in terms of computational expense versus results accuracy/convergence, is around 2000. It is impractical to view node results as a tree when the time steps exceed 25-30, as can be apreciated below. It is therefore more instructive to shift to generating node values in the form of list.
# Import required libraries
import matplotlib.pyplot as plt
import numpy as np
import networkx as nx
# Plot Binomial Grid
def binomial_grid(n):
G=nx.Graph() # Empty Grid
for i in range(0,n+1): # Outer Loop: For each time fraction of time Initial to Terminal
for j in range(1,i+2): # Inner Loop: For each node at each time step first to final
if i<n: # Add connectors between all pre-terminal nodes
G.add_edge((i,j),(i+1,j))
G.add_edge((i,j),(i+1,j+1))
posG={} # Situate Nodes
for node in G.nodes():
posG[node]=(node[0],n+2+node[0]-2*node[1])
nx.draw(G,pos=posG)
# Plot Price and Option Tree
def plot_binomial_tree(initial_spot, asset_array, option_array):
s = initial_spot
px = asset_array
opx = option_array
# displaying the title
plt.figure(figsize=(14, 12))
plt.suptitle('Binomial Tree: 30 Time Steps', fontsize=20)
# Start
plt.figtext(-0.07,0.50, 'S = '+str(s))
plt.figtext(-0.07,0.47, 'V = '+"{:.2f}".format(opx[0,0]))
binomial_grid(25)
px = result[1]
opx = result[2]
# Plot a 30-Step Binomial Tree
# displaying the title
plot_binomial_tree(px[0,0], px, opx)
The tree nodes are represented as coordinates with first co-oordinate as time step and the second as first node level. So, for example, the co-oordinates [1,1] and [1,0] refer to the the up and down moves in the first step pf the binomial tree. The following code generates this "Co-ordinate Dictionary" to hold these values.
.# Tree Visualization
# Create empty dictionaries to hold all node values at all time steps
S_vals = {}
O_vals = {}
# Assigns keys and values to S_Vals dictionary
for j in range(0, n+1):
S_vals[(n, j)] = round(px[n,j], 3) #Terminal Stock Values
for i in range(n-1,-1,-1):
for j in range(i+1):
S_vals[(i, j)] = round(px[i,j],3) #Interim Stock Values
# Assigns keys and values to O_Vals dictionary
for j in range(0, n+1):
O_vals[(n, j)] = round(opx[n,j], 3) #Terminal Option Values
for i in range(n-1,-1,-1):
for j in range(i+1):
O_vals[(i, j)] = round(opx[i,j],3) #Interim Option Values
for (i, j), value in S_vals.items():
print(f" At Node [{i:<3}, {i-j:<2}] The stock value is: {value:>15}")
for (i, j), value in O_vals.items():
print(f" At Node [{i:<3}, {i-j:<2}] The option value is: {value:>15}")
import numpy as np
def FASB_Binomial(n, S, K, r,q, v, t, vest_per, ee, PutCall):
# Solved Parameters
At = t/n #Timestep
u = np.exp(v*np.sqrt(At)) # Upmove
d = 1./u # Downmove
p = (np.exp((r-q)*At)-d) / (u-d) # Risk-Neutral Probability
#Binomial price tree
stockvalue = np.zeros((n+1,n+1)) #Create symmetrical zero-filled array with dimensions of Timesteps + 1
stockvalue[0,0] = S #Location of current stock price in matrix (1st element in 1st column)
# Note the rows express time progression and columns tree level
for i in range(1,n+1): #Outer loop: For each timestep...
stockvalue[i,0] = stockvalue[i-1,0]*u # Compute future stockprice as product of prev. price and upmoves
for j in range(1,i+1): # Inner Loop: For each time step at each tree level...
stockvalue[i,j] = stockvalue[i-1,j-1]*d # Compute associated downmove stock value for each upmove
# (Nested for loop."Inner loop" will be executed one time
# for each iteration of the "outer loop").
#Option value at terminal nodes
optionvalue = np.zeros((n+1,n+1)) #Create symmetrical zero-filled matrix with dimensions of Timesteps + 1
for j in range(n+1): # For each node at the terminal time step...
# Compute instrinsic value based on put or call position
if PutCall=="C": # Call
optionvalue[n,j] = max(0, stockvalue[n,j]-K)
elif PutCall=="P": #Put
optionvalue[n,j] = max(0, K-stockvalue[n,j])
#Backward induction process for option values at interim nodes
for i in range(n-1,-1,-1): # For each time step, working backwards, from penultimate to first...
for j in range(i+1): # For each node at each of above time step...
# Compute the maximum of the expected value and the current intrinsic val.
if PutCall=="P":
optionvalue[i,j] = max(0,
K-stockvalue[i,j],np.exp(-r*At)*(p*optionvalue[i+1,j]+(1-p)*optionvalue[i+1,j+1]))
elif PutCall=="C":
optionvalue[i,j] = max(0,
stockvalue[i,j]-K,np.exp(-r*At)*(p*optionvalue[i+1,j]+(1-p)*optionvalue[i+1,j+1]))
FASB = optionvalue[0,0]*((1-ee)**(vest_per))
#return optionvalue[0,0]
return FASB, stockvalue, optionvalue, n, S, K, r, q, v, t, vest_per, ee
# Inputs
n = 25 #number of steps
S = 100 #initial underlying asset price
r = 0.05 #risk-free interest rate
q = 0.025 #risk-free interest rate
K = 115 #strike price
v = 0.3 #volatility
t = 8.5 #expected life
vest_per = 3 #volatility
ee = 0.03 #expected forefeiture through employee exit
result1 = FASB_Binomial(n, S, K, r,q, v, t, vest_per, ee, PutCall="C")
header1 = ['Opt. Val.(Interm. Model)','Spot', 'Strike', 'Rfr','Div. Yld.', 'Vol.',
'EL', 'Time Steps','Vest.','Forfeit.']
table1 = [[result1[0], result1[4],result1[5],result1[6],result1[7],result1[8],result1[9],result1[3],result1[10],result1[11]]]
print(tabulate(table1,header1))
from numpy import exp,sqrt
def HW_binomial_call(S0, K, T, r, div, sigma, Nsteps, Tvest, ee1,ee2, M):
#Solved Parameters
dt = T / Nsteps
u = exp(sigma * sqrt(dt))
d = 1. / u
a = exp(r*dt) * exp(-div*dt)
p = (a - d) / (u - d)
#vector of payoff and option price in the tree
S = np.zeros([Nsteps + 1, Nsteps + 1])
f = np.zeros([Nsteps + 1, Nsteps + 1])
# compute the S value at time t
for i in range(0,Nsteps+1):
for j in range(0,i+1):
S[i,j] = S0 * u ** (j) * d ** (i - j)
for j in range(0,Nsteps+1):
f[Nsteps , j] = max(S[Nsteps, j] - K, 0)
# the backward recursion
for i in range(Nsteps-1,-1,-1):
for j in range(0,i+1):
if i*dt > Tvest and S[i,j]>= M * K:
f[i, j]= max(S[i, j] - K,0)
elif i*dt > Tvest and S[i,j]< M * K:
f[i, j]=(ee2*dt)*(max(S[i, j] - K, 0))+(1-ee2*dt)*exp(-r*dt)*(p*f[i + 1, j + 1] + (1 - p) * f[i + 1, j])
else:
f[i, j] = (1-ee1*dt)*exp(-r * dt) * (p * f[i + 1, j + 1] + (1 - p) * f[i + 1, j])
return f[0, 0], S, f, Nsteps, S0, K, r, div, sigma, T, Tvest, ee1, ee2, M
#Inputs
S0 = 100
K = 115
T = 10
r = 0.05
div = 0.025
sigma = 0.30
Nsteps = 25
Tvest=2 #vesting time
ee1 = 3/100 #forfeiture rate pre-vest
ee2 = 8/100 #forfeiture rate post-vest
M = 3
result2 = HW_binomial_call(S0, K, T, r, div, sigma, Nsteps,Tvest, ee1,ee2, M)
header2 = ['Opt Val (Fin)','Spot', 'Strike', 'Rfr','Div Yld', 'Vol','TTE', 'T Steps',
'Vest','Forf (<V)','Forf (>V)', 'M']
table2 = [[result2[0], result2[4],result2[5],result2[6],result2[7],result2[8],result2[9],result2[3],
result2[10],result2[11],result2[12],result2[13]]]
print(tabulate(table2,header2))
import pandas as pd
header_res = ['Opt Value (Final HW Model)','Spot', 'Strike', 'Risk Free Rate','Dividend Yld', 'Volaltility','Time to Exp.', 'Time Steps',
'Time to Vest','Pre-Vest Forf.','Post-Vest Forf', 'Exercise Multiple']
df = pd.DataFrame()
df['Output/Input Variable'] = ['Option Value (Final Model)','Spot', 'Strike', 'Risk Free Rate','Dividend Yld',
'Volaltility','Time to Expiry', 'Time Steps', 'Time to Vest',
'Pre-Vest Forfeit.','Post-Vest Forfeit. ', 'Exercise Mult.']
df['Value'] = [result2[0], result2[4],result2[5],result2[6],result2[7],result2[8],result2[9],result2[3],
result2[10],result2[11],result2[12],result2[13]]
df['Value'] = df['Value'].round(2)
df
# Tree Visualization
px_HW = result2[1]
opx_HW = result2[2]
# Create empty dictionaries to hold all node values at all time steps
S_vals_HW = {}
O_vals_HW = {}
# Assigns keys and values to S_Vals dictionary
for j in range(0, Nsteps+1):
S_vals_HW[(Nsteps, j)] = round(px_HW[Nsteps,j], 3) #Terminal Stock Values
for i in range(Nsteps-1,-1,-1):
for j in range(i+1):
S_vals_HW[(i, j)] = round(px_HW[i,j],3) #Interim Stock Values
# Assigns keys and values to O_Vals dictionary
for j in range(0, Nsteps+1):
O_vals_HW[(Nsteps, j)] = round(opx_HW[Nsteps,j], 3) #Terminal Option Values
for i in range(Nsteps-1,-1,-1):
for j in range(i+1):
O_vals_HW[(i, j)] = round(opx_HW[i,j],3) #Interim Option Values
# Stock Price Path
for (i, j), value in S_vals_HW.items():
print(f" At Node [{i:<3}, {j:<2}] The stock value is: {value:>15}")
# Option Value Path
for (i, j), value in O_vals_HW.items():
print(f" At Node [{i:<3}, {j:<2}] The option value is: {value:>15}")
It is important to determine how efficient the model is, specifically whether and with what speed it converges on a particular value. This will ensure we avoid using too many or too few time steps when running the model, resulting, respectively, in excessive computatational expense or inaccurate output.
We can see that our model converges fairly quickly. It only takes around 1000 steps for the model price to be accurate within rounding error.
def HW_binomial_call_test(S0, K, T, r, div, sigma, Nsteps, Tvest, ee1,ee2, M):
#Solved Parameters
dt = T / Nsteps
u = exp(sigma * sqrt(dt))
d = 1. / u
a = exp(r*dt) * exp(-div*dt)
p = (a - d) / (u - d)
#vector of payoff and option price in the tree
S = np.zeros([Nsteps + 1, Nsteps + 1])
f = np.zeros([Nsteps + 1, Nsteps + 1])
# compute the S value at time t
for i in range(0,Nsteps+1):
for j in range(0,i+1):
S[i,j] = S0 * u ** (j) * d ** (i - j)
for j in range(0,Nsteps+1):
f[Nsteps , j] = max(S[Nsteps, j] - K, 0)
# the backward recursion
for i in range(Nsteps-1,-1,-1):
for j in range(0,i+1):
if i*dt > Tvest and S[i,j]>= M * K:
f[i, j]= max(S[i, j] - K,0)
elif i*dt > Tvest and S[i,j]< M * K:
f[i, j]=(ee2*dt)*(max(S[i, j] - K, 0))+(1-ee2*dt)*exp(-r*dt)*(p*f[i + 1, j + 1] + (1 - p) * f[i + 1, j])
else:
f[i, j] = (1-ee1*dt)*exp(-r * dt) * (p * f[i + 1, j + 1] + (1 - p) * f[i + 1, j])
return f[0, 0]
#Inputs
S0 = 100
K = 115
T = 10
r = 0.05
div = 0.025
sigma = 0.30
Tvest=2 #vesting time
ee1 = 3/100 #forfeiture rate pre-vest
ee2 = 8/100 #forfeiture rate post-vest
M = 3
list_val = []
list_step = []
for Nsteps in range(10,1500, 100):
list_val.append(HW_binomial_call_test(S0, K, T, r, div, sigma, Nsteps, Tvest, ee1,ee2, M))
for Nsteps in range(10,1500, 100):
list_step.append(Nsteps)
# Visualize the simulated portfolio for risk and return
import matplotlib.pyplot as plt
with plt.style.context('seaborn'):
fig = plt.figure(figsize=(14,8))
ax = plt.axes()
ax.set_title('Convergence of the Final Model',fontsize= 20)
ax.plot(list_step, list_val)
ax.set_xlabel('Number of Time Steps', fontsize= 15)
ax.set_ylabel('Option Value', fontsize= 15)
ax.grid(True)
The Sponsoring Compnay will rationally seek to find an equilibrium between employee incentivization and the expense of granting the option.The Sponsor should thus undertake sensitivity analysis to evaluate the variablity of cost due to changes in company-determined variables (Strike, Time to Expiry, Time to vest), employee-determined variables(pre and post-vesting forfeiture rates, exercise multiple) and market-determined variables (risk-free rate, volatility, dividend yield). The following code produces which assist with the tasks of single-factor and dual-factor sensitivity analysis.
# Single Factor Sensitivity Analysis (1)
S0 = 100
K = 115
T = 10
r = 0.05
div = 0.025
sigma = 0.30
Nsteps = 40
Tvest=2
ee1 = 3/100 #forfeiture rate
ee2 = 8/100 #forfeiture rate
M = 3
list_val1 = []
list_fac1 = []
for K in range(90, 116, 1):
list_fac1.append(K)
list_val1.append(HW_binomial_call_test(S0, K, T, r, div, sigma, Nsteps, Tvest, ee1,ee2, M))
# Single Factor Sensitivity Analysis (2)
S0 = S0
K = K
T = T
r = r
div = div
sigma = sigma
Nsteps = Nsteps
Tvest= Tvest
ee1 = ee1
ee2 = ee2
M = M
S0 = 100
K = 115
T = 10
r = 0.05
div = 0.025
sigma = 0.30
Tvest=2 #vesting time
ee1 = 3/100 #forfeiture rate pre-vest
ee2 = 8/100 #forfeiture rate post-vest
M = 3
list_val2 = []
list_fac2 = []
for T in range(5, 16, 1):
list_fac2.append(T)
list_val2.append(HW_binomial_call_test(S0, K, T, r, div, sigma, Nsteps, Tvest, ee1,ee2, M))
# Single Factor Sensitivity Analysis (3)
S0 = S0
K = K
T = T
r = r
div = div
sigma = sigma
Nsteps = Nsteps
Tvest= Tvest
ee1 = ee1
ee2 = ee2
M = M
list_val3 = []
list_fac3 = []
for Tvest in range(2, 8, 1):
list_fac3.append(Tvest)
list_val3.append(HW_binomial_call_test(S0, K, T, r, div, sigma, Nsteps, Tvest, ee1,ee2, M))
# Single Factor Sensitivity Analysis (4)
S0 = S0
K = K
T = T
r = r
div = div
sigma = sigma
Nsteps = Nsteps
Tvest= Tvest
ee1 = ee1
ee2 = ee2
M = M
list_val4 = []
list_fac4 = []
for ee1 in np.linspace(0.005, 0.08, 50):
list_fac4.append(ee1)
list_val4.append(HW_binomial_call_test(S0, K, T, r, div, sigma, Nsteps, Tvest, ee1,ee2, M))
# Single Factor Sensitivity Analysis (5)
S0 = S0
K = K
T = T
r = r
div = div
sigma = sigma
Nsteps = Nsteps
Tvest= Tvest
ee1 = ee1
ee2 = ee2
M = M
list_val5 = []
list_fac5 = []
for ee2 in np.linspace(0.005, 0.08, 50):
list_fac5.append(ee2)
list_val5.append(HW_binomial_call_test(S0, K, T, r, div, sigma, Nsteps, Tvest, ee1,ee2, M))
# Single Factor Sensitivity Analysis (6)
S0 = S0
K = K
T = T
r = r
div = div
sigma = sigma
Nsteps = Nsteps
Tvest= Tvest
ee1 = ee1
ee2 = ee2
M = M
list_val6 = []
list_fac6 = []
for M in np.linspace(1.0, 3.5, 15):
list_fac6.append(M)
list_val6.append(HW_binomial_call_test(S0, K, T, r, div, sigma, Nsteps, Tvest, ee1,ee2, M))
# Display results
'bmh', 'classic', 'dark_background', 'fast', 'fivethirtyeight', 'ggplot'
with plt.style.context('seaborn'):
fig, ax = plt.subplots(3,2, figsize=(20,30))
fig.suptitle('Single Factor Sensitivity Analysis', fontsize=26)
ax[0,0].plot(list_fac1, list_val1)
ax[0,1].plot(list_fac2, list_val2)
ax[1,0].plot(list_fac3, list_val3)
ax[1,1].plot(list_fac4, list_val4)
ax[2,0].plot(list_fac5, list_val5)
ax[2,1].plot(list_fac6, list_val6)
# Set axis title
ax[0,0].set_title('OPT. PRICE SENSITIVITY TO STRIKE',fontsize= 20),
ax[0,1].set_title('OPT. PRICE SENSITIVITY TO TIME TO EXPIRY',fontsize= 20),
ax[1,0].set_title('OPT. PRICE SENSITIVITY TO TIME TO VEST',fontsize= 20),
ax[1,1].set_title('OPT. PRICE SENSITIVITY TO PRE-VEST FORFEITURE',fontsize= 20),
ax[2,0].set_title('OPT. PRICE SENSITIVITY TO POST-VEST FORFEITURE',fontsize= 20),
ax[2,1].set_title('OPT. PRICE SENSITIVITY TO EXERCISE MULTIPLE',fontsize= 20)
# Set axis label
ax[0,0].set_xlabel('STRIKE'),
ax[0,1].set_xlabel('TIME TO EXPIRY'),
ax[1,0].set_xlabel('TIME TO VEST'),
ax[1,1].set_xlabel('PRE-VEST FORFEITURE'),
ax[2,0].set_xlabel('POST-VEST FORFEITURE'),
ax[2,1].set_xlabel('EXERCISE MULTIPLE')
# Set axis label
ax[0,0].set_ylabel('OPTION PRICE'),
ax[0,1].set_ylabel('OPTION PRICE'),
ax[1,0].set_ylabel('TIME TO VEST'),
ax[1,1].set_ylabel('OPTION PRICE'),
ax[2,0].set_ylabel('OPTION PRICE'),
ax[2,1].set_ylabel('OPTION PRICE')
plt.show()
def f(K,T):
S0 = 100
K = 100
T = 10
r = 6 / 100
div = 2.5/100
sigma = 20 / 100
Nsteps = 40
Tvest=2 #vesting time
ee1 = 3/100 #forfeiture rate
ee2 = 8/100 #forfeiture rate
M = 3
list_valx = []
list_valy = []
list_valz = []
for K in np.linspace(90, 116, 100):
for T in np.linspace(5, 16, 100):
list_valx.append(K)
arr_x = np.array(list_valx)
list_valy.append(T)
arr_y = np.array(list_valy)
list_valz.append(HW_binomial_call_test(S0, K, T, r, div, sigma, Nsteps, Tvest, ee1,ee2, M))
arr_z = np.array(list_valz)
return arr_z, arr_y, arr_x
result_SA = f(K,T)
from mpl_toolkits import mplot3d
%matplotlib notebook
import matplotlib.pyplot as plt
# Create the figure
fig = plt.figure(figsize=(10,8))
ax = plt.axes(projection='3d')
fig.suptitle('Dual Factor Sensitivity Analysis', fontsize=16)
# Generate the values
x_vals = result_SA[2]
y_vals = result_SA[1]
z_vals = result_SA[0]
# Plot the values
t=ax.scatter(x_vals, y_vals, z_vals, c=z_vals , cmap='viridis', alpha=0.22);
ax.set_title('Sensitivity of Option Price to Strike AND Time to Expiry',fontsize= 12)
ax.set_xlabel('Strike')
ax.set_ylabel('Time To Expiry')
ax.set_zlabel('Option Price')
fig.colorbar(t)