Options 2: The Binomial Option Pricing Model

This notebook implements binomial option pricing models for european and american style options.

See also https://github.com/JuliaQuant/FinancialDerivatives.jl for a more ambitious derivatives package (not used here).

Load Packages and Extra Functions


In [1]:
using Dates

include("jlFiles/printmat.jl")
include("jlFiles/printTable.jl")


Out[1]:
printTable2

In [2]:
using Plots

#pyplot(size=(600,400))
gr(size=(480,320))
default(fmt = :svg)

The Binomial Model for One Time Step

In a binomial model the underlying can change from $S$ today to either $Su$ or $Sd$ in the next period (which is $h$ years from now).

Let $f_u$ and $f_d$ be the values of the derivative in the up- and down-state next period. Then, the value today is

$f =e^{-yh}\left[ pf_{u}+\left( 1-p\right) f_{d}\right] \: \text{ with } \: p=\frac{e^{yh}-d}{u-d}$

For a call option that expires next period, the payoffs in the two states are

$ f_u = \max(Su-K,0) $

$ f_d = \max(Sd-K,0) $


In [3]:
S = 10                          #underlying price today
u = 1.1
d = 0.95
K = 10                          #strike price: try different values
y = 0                           #interest rate
h = 1/12                        #time to expiration

fu = max(S*u-K,0)               #value of call option in up node 
fd = max(S*d-K,0)               #in down node

p = (exp(y*h) - d)/(u-d)
C = exp(-y*h)*(p*fu+(1-p)*fd)

printblue("Pricing of a call option with strike $K, one time step:")
xx = [fu,fd,p,C]
printTable(xx,[""],["Payoff up","Payoff down","p","call price"])


Pricing of a call option with strike 10, one time step:
                     
Payoff up       1.000
Payoff down     0.000
p               0.333
call price      0.333

A CRR Tree for Many (Short) Time Steps

The CRR approach to construct $(u,d)$ for a small time step of length $h$ is

$u=e^{\sigma\sqrt{h}} \: \text{ and }\: d=e^{-\sigma\sqrt{h}}$,

where $\sigma$ is the annualized standard deviation of the underlying. Notice that $p$ depends on the choice of $(u,d)$

$ p=\frac{e^{yh}-d}{u-d}. $


In [4]:
m = 0.5                 #time to expiration (in years)
y = 0.05                #interest rate (annualized) 

σ = 0.2                 #annualized std of underlying asset
n = 50                  #number of time steps of size h
h = m/n                 #time step size (in years)

u = exp(σ*sqrt(h))
d = exp(-σ*sqrt(h))
p = (exp(y*h) - d)/(u-d)  #recalibrate p, it depends on u and d

printblue("CRR parameters in tree when m=$m, y=$y, σ= and n=$n:")
xx = [h,u,d,p,exp(y*h)]
printTable(xx,[""],["h","u","d","p","exp(yh)"])

printblue("Checking if u > exp(y*h) > d: $(u > exp(y*h) > d)")


CRR parameters in tree when m=0.5, y=0.05, σ=0.2 and n=50:
                 
h           0.010
u           1.020
d           0.980
p           0.508
exp(yh)     1.001

Checking if u > exp(y*h) > d: true

Build a Tree for the Underlying Asset

The next cell illustrates how we can create a vector of vectors of different lengths. In this example, x[1] is a vector [1], while x[2] is a vector [2;2], etc.


In [5]:
x = [ones(i)*i for i = 1:3]      #vector or vectors (of different lengths)

printblue("Illustrating a vector of vectors:\n")
for i = 1:length(x)
    printblue("i=$i:")
    printmat(x[i])
end


Illustrating a vector of vectors:

i=1:
     1.000

i=2:
     2.000
     2.000

i=3:
     3.000
     3.000
     3.000

We create a tree by starting at the current spot price $S$. The future nodes are then created by multiplying by $u$ or $d$

$\text{step 0}: (S)$

$\text{step 1}: (Su,Sd)$

$\text{step 2}: (Suu,Sud,Sdd)$ (since $Sud=Sdu$)

...

Clearly, the vectors are of different lengths at the different time steps.

For step 2, the code in the next cell creates the vector $(Suu,Sud,Sdd)$ by first multiplying the step 1 vector by the up factor ($u(Su,Sd)$) and then attaching (as the last element) the last element of the step 1 vector times the down factor ($dSd$).

Notice that the first index of a traditional vector is 1, so STree[1] corresponds to time step 0, and STree[n+1] to time step $n$.


In [6]:
"""
    BuildSTree(S,n,u,d)

Build binomial tree, starting at `S` and having `n` steps with up move `u` and down move `d`

# Output
- `STree:: Vector or vectors`: each (sub-)vector is for a time step 

"""
function BuildSTree(S,n,u,d)
    STree = [fill(NaN,i) for i = 1:n+1]  #vector of vectors (of different lengths)
    STree[1][1] = S                      #step 0 is in STree[1], element 1 
    for i = 2:n+1                        #move forward in time
        STree[i][1:end-1] = u*STree[i-1]   #up move from STree[i-1][1:end]
        STree[i][end] = d*STree[i-1][end]  #down move from STree[i-1][end] 
    end
    return STree
end


Out[6]:
BuildSTree

Showing the Tree for the Underlying Asset

The next few cells illustrate how these nodes are put into an array of vectors and then shows the entire tree.


In [7]:
S = 42.0                             #current price of underlying asset

STree = BuildSTree(S,n,u,d)
printblue("printing the first 3 time steps in the tree")
for i = 1:3
    printblue("i=$i:")
    printmat(STree[i])
end


printing the first 3 time steps in the tree
i=1:
    42.000

i=2:
    42.848
    41.168

i=3:
    43.714
    42.000
    40.353


In [8]:
p1 = plot( legend = false,
      xlim = (-0.1,2.5),
      ylim = (40,44),
      xticks = 0:2,
      title = "The first few nodes in the tree",
      xlabel = "time step",
      ylabel = "value of asset",
      annotatation = (0,43.8,text("Structure of STree[timestep][node]",8,:left)) )

for i = 1:3, j = 1:length(STree[i])
    scatter!( [i-1],[STree[i][j]],             #[] to make a 1-element vector
              annotation = (i-1+0.1,STree[i][j],text("STree[$i][$j]",8,:left)) )
end
display(p1)           #needed since plot() does not have data points to plot


0 1 2 40 41 42 43 44 The first few nodes in the tree time step value of asset STree[1][1] STree[2][1] STree[2][2] STree[3][1] STree[3][2] STree[3][3]

In [9]:
p1 = plot( legend = false,
           xlim = (-0.1,2.5),
           ylim = (40,44),
           xticks = 0:2,
           title = "The first few nodes in STree",
           xlabel = "time step",
           ylabel = "value of asset" )

for i = 1:3, j = 1:length(STree[i])
    annotate!(i-1,STree[i][j],text(string(round(STree[i][j],digits=2)),8))
end
display(p1)


0 1 2 40 41 42 43 44 The first few nodes in STree time step value of asset 42.0 42.85 41.17 43.71 42.0 40.35

In [10]:
p1 = scatter( [0.0],STree[1],
              markercolor = :blue,
              xlim = (-2,n+1.5),
              ylim = (0,115),
              legend = false,
              title = "All nodes in STree",
              xlabel = "time step",
              ylabel = "value of asset" )

for i = 1:n+1
    scatter!(fill(i-1,i),STree[i],color=:blue)
end
display(p1)


0 10 20 30 40 50 0 25 50 75 100 All nodes in STree time step value of asset

Calculating the Option Price

Let $f_{ij}$ be the option price at time step $i$ when the underlying price is $S_{ij}$. (We use $S_{ij}$ as a shorthand notation, to avoid writing things like $Sudd$.) In the code below, $S_{i1}$ is the highest node in time step $i$, $S_{i2}$ is the second highest, etc.

For a European call option, the call price at the last time step $n$ is $f_{nj} = \max(0,S_{nj}-K)$.

Instead, the put price is $f_{nj} = \max(0,K-S_{nj})$.

For all earlier time steps, the value is

$f_{ij} = e^{-yh}[p f_{i+1,j} + (1-p) f_{i+1,j+1}]$

For instance,

$f_{i1} = e^{-yh}[p f_{i+1,1} + (1-p) f_{i+1,2}]$

$f_{i2} = e^{-yh}[p f_{i+1,2} + (1-p) f_{i+1,3}]$

European Options


In [11]:
"""
    EuOptionPrice(STree,K,y,h,p,isPut=false)

Calculate price of European option from binomial model

# Output
- `Value:: Vector of vectors`: option values at different nodes, same structure as STree

"""
function EuOptionPrice(STree,K,y,h,p,isPut=false)     #price of European option
    Value = deepcopy(STree)                           #tree for derivative, to fill
    n     = length(STree) - 1                         #number of steps in STree
    if isPut
        Value[n+1] = max.(0,K.-STree[n+1])            #put, at last time node
    else
        Value[n+1] = max.(0,STree[n+1].-K)            #call, at last time node
    end
    for i = n:-1:1                                   #move backward in time
        Value[i] = exp(-y*h)*(p*Value[i+1][1:end-1] + (1-p)*Value[i+1][2:end])
    end                                           #p*up + (1-p)*down, discount
    return Value
end


Out[11]:
EuOptionPrice

In [12]:
K = 42.0                                  #strike price 

Pe = EuOptionPrice(STree,K,y,h,p,true)        #Pe[1] is a 1x1 vector with the put price (node 0)
Ce = EuOptionPrice(STree,K,y,h,p,false)       #use Pe[1][1] or Pe[1][]

Ce_parity = Pe[1][] + S - exp(-m*y)*K              #put-call parity, Pe[1][] is a scalar

printlnPs("European put and call prices at K=$K and S=$S: ",[Pe[1] Ce[1]])

printlnPs("\nCall price according to put-call parity:                    ",Ce_parity)


European put and call prices at K=42.0 and S=42.0:      1.844     2.881

Call price according to put-call parity:                         2.881

American Options

The option values are calculated as for the European option, except that that the option value is

$f_{ij} = \max(\text{value if exercised now},\text{continuation value})$

The continuation value has the same form as in the European case, and thus assumes that the option has not been exercised before the next period.

The value of exercising now is $S_{ij}-K$ for a call and $K-S_{ij}$ for a put.


In [13]:
"""
    AmOptionPrice(STree,K,y,h,p,isPut=false)

Calculate price of American option from binomial model

# Output
- `Value:: Vector of vectors`: option values at different nodes, same structure as STree
- `Exerc::` Vector of vectors`: true if early exercise at the node, same structure as STree

"""
function AmOptionPrice(STree,K,y,h,p,isPut=false)     #price of American option
    Value = deepcopy(STree)                           #tree for derivative, to fill
    n     = length(STree) - 1
    Exerc = [falses(i) for i = 1:length(STree)]
    if isPut
        Value[n+1] = max.(0,K.-STree[n+1])            #put, at last time node
    else
        Value[n+1] = max.(0,STree[n+1].-K)            #call, at last time node
    end
    Exerc[n+1] = Value[n+1] .> 0                      #exercise
    for i = n:-1:1                                    #move backward in time
        fa  = exp(-y*h)*(p*Value[i+1][1:end-1] + (1-p)*Value[i+1][2:end])
        if isPut 
            Value[i] = max.(K.-STree[i],fa)         #put
        else
            Value[i] = max.(STree[i].-K,fa)         #call
        end
        Exerc[i] = Value[i] .> fa                   #early exercise
    end
    return Value, Exerc
end


Out[13]:
AmOptionPrice

In [14]:
K = 42.0                                  #strike price

(Pa,Exerc) = AmOptionPrice(STree,K,y,h,p,true)
Ca, = AmOptionPrice(STree,K,y,h,p,false)

printlnPs("American put and call prices at K=$K and S=$S: ",[Pa[1] Ca[1]])

printlnPs("\nCompare with European put and call prices:         ",[Pe[1] Ce[1]])


American put and call prices at K=42.0 and S=42.0:      1.950     2.881

Compare with European put and call prices:              1.844     2.881

Plotting the Tree of American Option Values


In [15]:
p1 = plot( xlim = (-0.5,11),
           ylim = (33,53), 
           xticks = 0:10,
           legend = false,
           title = "American put values at different nodes",
           xlabel = "time step",
           ylabel = "value of asset",
           annotation = (0,50,text("Some nodes may not be\nreached due to early exercise",8,:left)) )

for i = 1:11, j = 1:length(STree[i])
    annotate!(i-1,STree[i][j],text(string(round(Pa[i][j],digits=2)),8))
end
display(p1)


0 1 2 3 4 5 6 7 8 9 10 35 40 45 50 American put values at different nodes time step value of asset Some nodes may not be reached due to early exercise 1.95 1.59 2.32 1.28 1.92 2.74 1.01 1.56 2.29 3.2 0.78 1.24 1.88 2.71 3.72 0.59 0.97 1.52 2.26 3.18 4.28 0.43 0.75 1.21 1.85 2.68 3.69 4.88 0.31 0.56 0.94 1.49 2.22 3.15 4.26 5.53 0.22 0.41 0.72 1.18 1.81 2.65 3.67 4.87 6.21 0.14 0.29 0.53 0.91 1.45 2.19 3.12 4.24 5.52 6.92 0.09 0.2 0.38 0.68 1.14 1.78 2.61 3.65 4.86 6.21 7.61

Plotting where Exercise Happens


In [16]:
p1 = plot( xlim = (-2,n+1),
           ylim = (0,115),
           legend = false,
           title = "Exercise nodes",
           xlabel = "time step",
           ylabel = "value of asset",
           annotation = (0,100,text("Some nodes may not be\nreached due to early exercise",8,:left)) )

for i = 1:n+1, j = 1:length(STree[i])
    if Exerc[i][j]
        scatter!([i-1],[STree[i][j]],markercolor=:blue) 
    end
end
display(p1)


0 10 20 30 40 50 0 25 50 75 100 Exercise nodes time step value of asset Some nodes may not be reached due to early exercise

In [ ]: