import numpy as np
import pandas as pd
#timing
from time import time
from functools import wraps
def timing(f):
@wraps(f)
def wrap(*args, **kw):
ts=time()
result=f(*args, **kw)
te=time()
print('func:%r args:[%r, %r] took: %2.4f sec'% \
(f.__name__, args, kw, te-ts))
return result
return wrap
S0 = 100
K = 100
T = 1
r = 0.06
N = 100
u = 1.1
d = 1/u
opttype = 'C' # option type is call or put. C or P
@timing
def binomial_slow(S0, K, T, r, N, u, d, opttype):
# 0. Pre-compute Constants
dt = T/N
prob_u = (np.exp(r*dt)-d)/(u-d)
df = np.exp(-r*dt)
# 1. initialise asset price at maturity - Time step = N
S = np.zeros(N+1) # create a N+1 empty array
S[0] = S0 * d**N # the very bottom the right column
for j in range(1,N+1):
S[j]=S[j-1]*u/d
# 2. initialise the option value at maturity
C=np.zeros(N+1)
for j in range(0, N+1):
C[j]=max(0, S[j]-K )
# 3. Step backward through trees
for i in np.arange(N,0,-1): #work backward (Inital, End, Step)
for j in range(0, i):
C[j] = df * (C[j]*(1-prob_u) + C[j+1]*prob_u)
#Collapse to the bottom until arriving C[0]
return C[0]
binomial_slow(S0, K, T, r, N, u, d, opttype)
func:'binomial_slow' args:[(100, 100, 1, 0.06, 100, 1.1, 0.9090909090909091, 'C'), {}] took: 0.0050 sec
38.45357509478931
@timing
def binomial_fast(S0, K, T, r, N, u, d, opttype):
# 0. Pre-compute Constants
dt = T/N
prob_u = (np.exp(r*dt)-d)/(u-d)
df = np.exp(-r*dt)
# The time-saving part is we apply np.array instead of looping.
# 1. We directly apply, ST = S0 * u^i * d^(T-i)
S = S0 * d**(np.arange(N,-1,-1)) * u**np.arange(0,N+1,1)
# 2. We also use np.array to find the max of option in the tree
# c = max (S-K, 0) in an array
C=np.maximum(S - K, np.zeros(N+1))
# 3. Step backward through trees
for i in np.arange(N,0,-1):
C = df * (prob_u*C[1:i+1] + (1-prob_u)*C[0:i])
return C
binomial_fast(S0, K, T, r, N, u, d, opttype)
func:'binomial_fast' args:[(100, 100, 1, 0.06, 100, 1.1, 0.9090909090909091, 'C'), {}] took: 0.0010 sec
array([38.45357509])
for N in [3,5,10,100,5000]:
binomial_slow(S0, K, T, r, N, u, d, opttype)
binomial_fast(S0, K, T, r, N, u, d, opttype)
func:'binomial_slow' args:[(100, 100, 1, 0.06, 3, 1.1, 0.9090909090909091, 'C'), {}] took: 0.0001 sec func:'binomial_fast' args:[(100, 100, 1, 0.06, 3, 1.1, 0.9090909090909091, 'C'), {}] took: 0.0001 sec func:'binomial_slow' args:[(100, 100, 1, 0.06, 5, 1.1, 0.9090909090909091, 'C'), {}] took: 0.0001 sec func:'binomial_fast' args:[(100, 100, 1, 0.06, 5, 1.1, 0.9090909090909091, 'C'), {}] took: 0.0003 sec func:'binomial_slow' args:[(100, 100, 1, 0.06, 10, 1.1, 0.9090909090909091, 'C'), {}] took: 0.0001 sec func:'binomial_fast' args:[(100, 100, 1, 0.06, 10, 1.1, 0.9090909090909091, 'C'), {}] took: 0.0005 sec func:'binomial_slow' args:[(100, 100, 1, 0.06, 100, 1.1, 0.9090909090909091, 'C'), {}] took: 0.0047 sec func:'binomial_fast' args:[(100, 100, 1, 0.06, 100, 1.1, 0.9090909090909091, 'C'), {}] took: 0.0010 sec func:'binomial_slow' args:[(100, 100, 1, 0.06, 5000, 1.1, 0.9090909090909091, 'C'), {}] took: 7.5409 sec func:'binomial_fast' args:[(100, 100, 1, 0.06, 5000, 1.1, 0.9090909090909091, 'C'), {}] took: 0.0453 sec
and, $$ p\approx \frac{1}{2}+\frac{(\mu-\frac{1}{2}\sigma^2)\sqrt{\delta t}}{2\sigma} $$
S0=100
N=4
T=1/3
r=0.1
sigma=0.2
def binomial_fast_volatility(S0, K, T, r, N, sigma, opttype):
# 0. Pre-compute Constants
# P.S. Different Methods are applied to get those constants, but similar logic
dt = T/N
df = np.exp(-r*dt)
temp1 = np.exp((r + sigma**2)*dt)
temp2 = 0.5 * (df + temp1)
u = temp2 + np.sqrt(temp2**2 - 1)
d = 1/u
prob_u = ( np.exp(r*dt) - d )/( u - d)
# The time-saving part is we apply np.array instead of looping.
# 1. We directly apply, ST = S0 * u^i * d^(T-i)
S = S0 * d**(np.arange(N,-1,-1)) * u**np.arange(0,N+1,1)
# 2. We also use np.array to find the max of option in the tree
# c = max (S-K, 0) in an array
C=np.maximum(S - K, np.zeros(N+1))
# 3. Step backward through trees
for i in np.arange(N,0,-1):
C = df * (prob_u*C[1:i+1] + (1-prob_u)*C[0:i])
return C
binomial_fast_volatility(S0, K, T, r, N, sigma, opttype)
array([6.13447247])
U=50
a=np.zeros(U)
for i in range(1,U+1):
a[i-1]=binomial_fast_volatility(S0, K, T, r, i, sigma, opttype)
pd.Series(a).plot()
<AxesSubplot:>
S0=100
K=100
N=4
T=1/3
r=0.1
sigma=0.2
def binomial_fast_volatility_American(S0, K, T, r, N, sigma, opttype):
# 0. Pre-compute Constants
# P.S. Different Methods are applied to get those constants, but similar logic
dt = T/N
df = np.exp(-r*dt)
temp1 = np.exp((r + sigma**2)*dt)
temp2 = 0.5 * (df + temp1)
u = temp2 + np.sqrt(temp2**2 - 1)
d = 1/u
prob_u = ( np.exp(r*dt) - d )/( u - d)
# The time-saving part is we apply np.array instead of looping.
# 1. We directly apply, ST = S0 * u^i * d^(T-i)
S = S0 * d**(np.arange(N,-1,-1)) * u**np.arange(0,N+1,1)
# 2. We also use np.array to find the max of option in the tree
# c = max (S-K, 0) in an array
C=np.maximum(S - K, np.zeros(N+1)) #len(C)=N+1
# 3. Step backward through trees
for i in np.arange(N,0,-1):
C = df * (prob_u*C[1:i+1] + (1-prob_u)*C[0:i])
# for american
#S = S0 * d**(np.arange(i-1,-1,-1)) * u**np.arange(0,i,1)
# the above row has same meaning as the row below, but much more time consuming
payoff = S[:i]/d - K
C=np.maximum(C, payoff) # the second term is the payoff
return C
binomial_fast_volatility_American(S0, K, T, r, N, sigma, opttype)
array([13.26244768])
S0=100
K=100
N=120
T=1
r=0.1
sigma=0.2
print(binomial_fast_volatility(S0, K, T, r, N, sigma, opttype),binomial_fast_volatility_American(S0, K, T, r, N, sigma, opttype))
[13.26244768] [13.26244768]
by Fanyu Zhao