In [4]:
%pylab inline
lecture = 13

import sys
sys.path.append("lib")
import fmt
import sympy as sp
import pandas as pd
from IPython.display import display

assert sp.__version__ == "0.7.5", "Need sympy version 0.7.5 to render properly"
sp.init_printing(use_latex = True)
Populating the interactive namespace from numpy and matplotlib

Lecture 13: Numerical Methods for Partial Differential Equations

Topics

  • More on the stability of FDM for Black-Scholes
  • Multi spatial variables: ADI method
  • Numerical examples

More on the stability of FDM for Black-Scholes:

  • When $r \neq 0$:

\begin{aligned} e^{a\triangle t} & = \left\{ \frac{1}{2}\sigma^2 x_j^2 \frac{\triangle t}{\triangle x^2} - r x_j \frac{\triangle t}{2\triangle x} \right\} e^{-il_m\triangle x} \\ & + \left\{ 1 - \sigma^2 x_j^2 \frac{\triangle t}{\triangle x^2} -r \triangle t \right\} \\ & + \left\{ \frac{1}{2}\sigma^2 x_j^2 \frac{\triangle t}{\triangle x^2} + r x_j \frac{\triangle t}{2\triangle x} \right\} e^{il_m\triangle x} \end{aligned}

  • which is

$$ e^{a\triangle t} = 1 - r\triangle t + \sigma^2 x_j^2 \frac{\triangle t}{\triangle x^2} \cdot 2\sin^2(l_m\triangle x/2) + i\; r x_j \frac{\triangle t}{\triangle x} \sin(l_m\triangle x) $$

  • To satyisfiy $ |e^{a\triangle t}| < 1 $, we require

\begin{aligned} \renewcommand{PDut}{\frac{\partial u}{\partial t}} \renewcommand{PDux}{\frac{\partial u}{\partial x}} \renewcommand{PDutt}{\frac{\partial ^2u}{\partial t^2}} \renewcommand{PDuxx}{\frac{\partial ^2u}{\partial x^2}} \renewcommand{FDut}{\frac{u_{i,k+1}-u_{i,k}}{\triangle t}} \renewcommand{FDutb}{\frac{u_{i,k}-u_{i,k-1}}{\triangle t}} \renewcommand{FDutc}{\frac{u_{i,k+1}-u_{i,k-1}}{2\triangle t}} \renewcommand{FDutt}{\frac{u_{i,k+1}-2u_{i,k}+u_{i,k-1}}{\triangle t^2}} \renewcommand{FDux}{\frac{u_{i+1,k}-u_{i,k}}{\triangle x}} \renewcommand{FDuxb}{\frac{u_{i,k}-u_{i-1,k}}{\triangle x}} \renewcommand{FDuxc}{\frac{u_{i+1,k}-u_{i-1,k}}{2\triangle x}} \renewcommand{FDuxx}{\frac{u_{i+1,k}-2u_{i,k}+u_{i-1,k}}{\triangle x^2}} & r >0, \\ & \triangle t < \frac{\triangle x^2}{ \sigma^2 x_{max}^2 }, \\ & \triangle t < \frac{ \sigma^2 }{r^2 }. \end{aligned}

  • The first condition is trivial (until the FED decrees all rates to be negative!)

  • The last condition does not have much impact in practice unless the volatility is very small.

  • So the important one is the second condition, which is what we went through last lecture.

The Case of more than one spatial variables

  • Most numerical methods, FDM included, suffer the "dimentionality" curse, i.e. the size, complexity of the problem grow exponentially with the dimension.
  • Usually, Monte Carlo method is the only practical option in dimension $\geq 3$.
  • But for two dimensional problems, it is worthwhile to explore the FDM further.
  • Finance examples that you will need many sptial variables: stochastic vol model, convertible bonds, credit risky bonds, variable annuities, etc.

Heston Stochastic Volatility Model

  • Heston Stochastic Vol Model

\begin{aligned} & dS_t = rS_t dt + \sqrt{\nu_t}S_t dW_t^1 \\ & d\nu_t = \kappa(\theta - \nu_t) + \xi\sqrt{\nu_t} dW_t^2 \\ & \hspace{0.2in} \left< dW_t^1, dW_t^2 \right> = \rho dt \end{aligned}

PDE for Heston Stochastic Vol Model:

\begin{aligned} \renewcommand{PDuS}{\frac{\partial u}{\partial S}} \renewcommand{PDuSS}{\frac{\partial ^2u}{\partial S^2}} \PDut &+ rS\PDuS + [\kappa(\theta - \nu)-\lambda \nu]\frac{\partial u}{\partial \nu} \\ &+ \frac{1}{2}\nu S^2\PDuSS + \rho\xi\nu S\frac{\partial^2 u}{\partial S\partial \nu} + \frac{1}{2}\xi^2\nu\frac{\partial^2 u}{\partial \nu^2} - ru = 0 \end{aligned}

Alternating Direction Implicit Method

  • Write the Crank-Nicolson method as

\begin{aligned} \renewcommand{fD}{\mathfrak{D}} \FDut = & \frac{1}{4} \sigma^2 x_i^2 \left\{ \FDuxx + \frac{u_{i+1,k+1}-2u_{i,k+1}+u_{i-1,k+1}}{\triangle x^2} \right\} \\ & + \frac{1}{2} r x_i \left\{ \FDuxc + \frac{u_{i+1,k+1}-u_{i-1,k+1}}{2\triangle x} \right\} \\ & - \frac{1}{2} r \left\{ u_{i,k} + u_{i,k+1} \right\} \\ = & \frac{1}{2} \mathfrak{D}\cdot ( u_{i,k} + u_{i,k+1} ) \end{aligned}

where $ \mathfrak{D}\cdot u_{i,k} $ can be considered as the Crank-Nicolson finite difference operator on $u_{i,k}$.

Crank-Nicolson in operator format

  • The Crank-Nicolson scheme can be denoted as

$$ (1 + \frac{1}{2}\triangle t\fD)\cdot u_{i,k+1} = (1-\frac{1}{2}\triangle t\fD)\cdot u_{i,k} $$

  • This is also applicable when the spatial variable $x_i$ is a vector.

  • For the one spatial variable case, the operator $\fD$ involves three points in one time slice.

  • For two dimension case (the Heston model above), the operator will involve five points in one time slice.

  • So instead of solving a tridiagonal system, now the linear system has five nonzero diagonals, whcih is much more costly to solve.

Operator split

  • For the general $n$ spatial variables case, the way to ease the linear system solving (still can't get rid of the problem of the number of discretization points exploded!) is splitting the operator $\fD$:

\begin{aligned} (1 + \frac{1}{2}\triangle t\fD^1)\cdot \tilde{u}^1_{i,k+1} &= (1-\frac{1}{2}\triangle t\fD^1)\cdot u_{i,k} \\ (1 + \frac{1}{2}\triangle t\fD^2)\cdot \tilde{u}^2_{i,k+1} &= (1-\frac{1}{2}\triangle t\fD^2)\cdot \tilde{u}^1_{i,k} \\ \vdots \\ (1 + \frac{1}{2}\triangle t\fD^n)\cdot \tilde{u}^n_{i,k+1} &= (1-\frac{1}{2}\triangle t\fD^n)\cdot \tilde{u}^{n-1}_{i,k} \end{aligned}

and set

$$ u_{i+1,k} = \tilde{u}^n_{i,k} $$

  • Essentially, this says trying to solve the problem in a multistep aproach: each step is equivalent to the one dimensional Crank-Nicolson method.
  • This is merely the basic form, the strategy can be customized to further improve the efficiency (not all steps are implicit) or accuracy (high order)

Numerical Examples

  • Black Scholes formulae and FDM methods for vanilla European call and up-and-out Barrier Call.
In [242]:
import numpy as np
from scipy.stats import norm
import time 

#Black and Scholes
def BlackScholesFormula(type, S0, K, r, sigma, T):
    dtmp1 = np.log(S0 / K)
    dtmp2 = 1.0/(sigma * np.sqrt(T))
    sigsq = 0.5 * sigma * sigma
    d1 =  dtmp2 * (dtmp1 + T * (r + sigsq))
    d2 =  dtmp2 * (dtmp1 + T * (r - sigsq))
    if type=="C":
        return S0 * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2)
    else:
        return K * np.exp(-r * T) * stats.cdf(-d2) - S0 * norm.cdf(-d1)

K = 100.0
r = 0.05
sigma = 0.35
T = 1
putCall ='C'

Smin = 0.0
Smax = 200.0
ns = 201
Ss = np.linspace(Smin, Smax, ns, endpoint=True)
Ss = Ss[1:-1]
#print Ss

t=time.time()
px = BlackScholesFormula(putCall, Ss, K, r, sigma, T)
elapsed=time.time()-t
print px
print "Elapsed Time:", elapsed
#idx = 200-1
#print Ss[idx]
#print px[idx]

payoff = clip(Ss-K, 0.0, 1e600)
#print "payoff = ", payoff

figure(figsize=[10, 4])
subplot(1, 2, 1)
plot(Ss, payoff)
xlabel('Stock', fontsize=14);
title('Payoff' , fontsize=16);

subplot(1, 2, 2)
plot(Ss, px)
xlabel('Stock', fontsize=14);
title('Analytical Solution' , fontsize=16);
[  1.28766561e-39   5.53362091e-29   1.53135787e-23   5.00445199e-20
   1.68889238e-17   1.46407871e-15   5.18474263e-14   9.78800798e-13
   1.16276880e-11   9.70067620e-11   6.13310850e-10   3.10394648e-09
   1.30958867e-08   4.75053666e-08   1.51752856e-07   4.35044961e-07
   1.13639156e-06   2.73830396e-06   6.14907826e-06   1.29773535e-05
   2.59235792e-05   4.93116756e-05   8.97803630e-05   1.57147519e-04
   2.65452935e-04   4.34175578e-04   6.89611730e-04   1.06639082e-03
   1.60909722e-03   2.37395905e-03   3.43056014e-03   4.86352770e-03
   6.77414796e-03   9.28186285e-03   1.25256040e-02   1.66649252e-02
   2.18809002e-02   2.83767600e-02   3.63782504e-02   4.61336992e-02
   5.79137892e-02   7.20110398e-02   8.87390076e-02   1.08431222e-01
   1.31439872e-01   1.58134278e-01   1.88899163e-01   2.24132765e-01
   2.64244808e-01   3.09654380e-01   3.60787729e-01   4.18076027e-01
   4.81953109e-01   5.52853236e-01   6.31208890e-01   7.17448635e-01
   8.11995048e-01   9.15262759e-01   1.02765660e+00   1.14956987e+00
   1.28138275e+00   1.42346084e+00   1.57615385e+00   1.73979446e+00
   1.91469728e+00   2.10115801e+00   2.29945271e+00   2.50983725e+00
   2.73254683e+00   2.96779573e+00   3.21577708e+00   3.47666283e+00
   3.75060382e+00   4.03772986e+00   4.33815003e+00   4.65195304e+00
   4.97920754e+00   5.31996271e+00   5.67424873e+00   6.04207739e+00
   6.42344276e+00   6.81832181e+00   7.22667519e+00   7.64844792e+00
   8.08357019e+00   8.53195807e+00   8.99351435e+00   9.46812932e+00
   9.95568151e+00   1.04560385e+01   1.09690578e+01   1.14945874e+01
   1.20324666e+01   1.25825269e+01   1.31445924e+01   1.37184810e+01
   1.43040045e+01   1.49009696e+01   1.55091783e+01   1.61284289e+01
   1.67585161e+01   1.73992318e+01   1.80503657e+01   1.87117056e+01
   1.93830378e+01   2.00641481e+01   2.07548214e+01   2.14548427e+01
   2.21639973e+01   2.28820712e+01   2.36088511e+01   2.43441252e+01
   2.50876830e+01   2.58393160e+01   2.65988176e+01   2.73659834e+01
   2.81406114e+01   2.89225024e+01   2.97114597e+01   3.05072897e+01
   3.13098016e+01   3.21188080e+01   3.29341245e+01   3.37555702e+01
   3.45829676e+01   3.54161424e+01   3.62549242e+01   3.70991457e+01
   3.79486436e+01   3.88032578e+01   3.96628321e+01   4.05272138e+01
   4.13962537e+01   4.22698063e+01   4.31477298e+01   4.40298856e+01
   4.49161390e+01   4.58063586e+01   4.67004164e+01   4.75981879e+01
   4.84995522e+01   4.94043912e+01   5.03125907e+01   5.12240391e+01
   5.21386286e+01   5.30562539e+01   5.39768133e+01   5.49002078e+01
   5.58263413e+01   5.67551209e+01   5.76864561e+01   5.86202595e+01
   5.95564461e+01   6.04949339e+01   6.14356432e+01   6.23784968e+01
   6.33234201e+01   6.42703407e+01   6.52191888e+01   6.61698966e+01
   6.71223986e+01   6.80766314e+01   6.90325338e+01   6.99900465e+01
   7.09491123e+01   7.19096758e+01   7.28716834e+01   7.38350835e+01
   7.47998261e+01   7.57658631e+01   7.67331477e+01   7.77016349e+01
   7.86712814e+01   7.96420452e+01   8.06138857e+01   8.15867639e+01
   8.25606420e+01   8.35354837e+01   8.45112537e+01   8.54879183e+01
   8.64654447e+01   8.74438014e+01   8.84229580e+01   8.94028851e+01
   9.03835545e+01   9.13649388e+01   9.23470118e+01   9.33297481e+01
   9.43131232e+01   9.52971137e+01   9.62816968e+01   9.72668506e+01
   9.82525541e+01   9.92387868e+01   1.00225529e+02   1.01212762e+02
   1.02200468e+02   1.03188629e+02   1.04177229e+02]
Elapsed Time: 0.000999927520752

In [243]:
from scipy import sparse
import scipy.sparse.linalg.dsolve as linsolve

class BS_FDM_explicit:
  def __init__(self, 
               r, 
               sigma, 
               maturity, 
               Smin, 
               Smax, 
               Fl, 
               Fu, 
               payoff, 
               nt, 
               ns):
    self.r  = r 
    self.sigma = sigma 
    self.maturity  = maturity

    self.Smin = Smin     
    self.Smax = Smax
    self.Fl = Fl        
    self.Fu = Fu

    self.nt  = nt
    self.ns  = ns
    
    self.dt = float(maturity)/nt
    self.dx = float(Smax-Smin)/(ns+1)
    self.xs = Smin/self.dx

    self.u = empty((nt + 1, ns))
    self.u[0,:] = payoff

    ## Building Coefficient matrix:        
    A = sparse.lil_matrix((self.ns, self.ns))

    for j in xrange(0, self.ns):
      xd = j + 1 + self.xs
      sx = self.sigma * xd
      sxsq = sx * sx
      
      dtmp1 = self.dt * sxsq
      dtmp2 = self.dt * self.r
      A[j,j] = 1.0 - dtmp1 - dtmp2
      dtmp1 = 0.5 * dtmp1
      dtmp2 = 0.5 * dtmp2 * xd
      if j > 0:
        A[j,j-1] = dtmp1 - dtmp2
      if j < self.ns - 1:
        A[j,j+1] = dtmp1 + dtmp2

    self.A = A.tocsr()

    ### Building bc_coef:
    nxl = 1 + self.xs
    sxl = self.sigma * nxl
    nxu = self.ns + self.xs
    sxu = self.sigma * nxu
    
    self.blcoef = 0.5 * self.dt * (sxl * sxl - self.r * nxl)
    self.bucoef = 0.5 * self.dt * (sxu * sxu + self.r * nxu)
    
  def solve(self):
    for i in xrange(0, m):
        self.u[i+1,:]          = self.A * self.u[i,:]
        self.u[i+1,0]         += self.blcoef * self.Fl[i]
        self.u[i+1,self.ns-1] += self.bucoef * self.Fu[i]

    return self.u

dx = (Smax - Smin)/(ns-1)
print "ns =", ns
print "dx =", dx
print "sigma =", sigma
dt_max = dx*dx/(sigma*sigma*Smax*Smax)
print "by CFL, dt <", dt_max
mt_min = int(T/dt_max)+1
print "mt_min ~= ", mt_min
ns = 201
dx = 1.0
sigma = 0.35
by CFL, dt < 0.000204081632653
mt_min ~=  4900

In [238]:
n = ns-2
X = linspace(0.0, Smax, n+2)
X = X[1:-1]

payoff = clip(X-K, 0.0, 1e600)
#print "payoff = ", payoff
  
m = 4555 
Fl = zeros((m+1,))
Fu = Smax - K*exp(-r * linspace(0.0, T, m+1))
    
t = time.time()
bs1 = BS_FDM_explicit(r, sigma, T, Smin, Smax, Fl, Fu, payoff, m, n)
px_fd_mat = bs1.solve()
elapsed=time.time()-t
print "Elapsed Time1:", elapsed

##print  px_fd_mat.shape
nrow = len(px_fd_mat[:,1])
print px_fd_mat[nrow-1,:]

figure(figsize=[15, 4]);
subplot(1, 3, 1)
plot(X, px_fd_mat[nrow-1,:])
xlabel('Stock', fontsize=14);
title('nx = %.f'%m, fontsize=16);

m = 4560
Fl = zeros((m+1,))
Fu = Smax - K*exp(-r * linspace(0.0, T, m+1))

t = time.time()
bs2 = BS_FDM_explicit(r, sigma, T, Smin, Smax, Fl, Fu, payoff, m, n)
px_fd_mat = bs2.solve()
elapsed=time.time()-t
print "Elapsed Time2:", elapsed

subplot(1, 3, 2)
nrow = len(px_fd_mat[:,1])
plot(X, px_fd_mat[nrow-1,:])
xlabel('Stock', fontsize=14);
title('nx = %.f'%m, fontsize=16);

m = 4565
Fl = zeros((m+1,))
Fu = Smax - K*exp(-r * linspace(0.0, T, m+1))
    
t = time.time()
bs3 = BS_FDM_explicit(r, sigma, T, Smin, Smax, Fl, Fu, payoff, m, n)
px_fd_mat = bs3.solve()
elapsed=time.time()-t
print "Elapsed Time3:", elapsed

subplot(1, 3, 3)
nrow = len(px_fd_mat[:,1])
plot(X, px_fd_mat[nrow-1,:])
xlabel('Stock', fontsize=14);
title('nx = %.f' % m, fontsize=16);
Elapsed Time1: 0.0460000038147
[  4.07772413e-23   1.58474399e-20   1.73965059e-18   8.74535265e-17
   2.49568562e-15   4.58093482e-14   5.87935695e-13   5.60665034e-12
   4.16258352e-11   2.49732862e-10   1.24803027e-09   5.32745593e-09
   1.98368066e-08   6.55753768e-08   1.95342914e-07   5.31044478e-07
   1.33172299e-06   3.10914675e-06   6.81146334e-06   1.40982161e-05
   2.77310391e-05   5.21031331e-05   9.39269921e-05   1.63093911e-04
   2.73710967e-04   4.45312032e-04   7.04229658e-04   1.08510517e-03
   1.63250565e-03   2.40260928e-03   3.46491532e-03   4.90393153e-03
   6.82079120e-03   9.33475279e-03   1.25845383e-02   1.67294712e-02
   2.19503804e-02   2.84502447e-02   3.64545569e-02   4.62113982e-02
   5.79912176e-02   7.20863197e-02   8.88100703e-02   1.08495836e-01
   1.31495676e-01   1.58178815e-01   1.88929915e-01   2.24147190e-01
   2.64240376e-01   3.09628608e-01   3.60738215e-01   4.18000478e-01
   4.81849371e-01   5.52719318e-01   6.31042986e-01   7.17249139e-01
   8.11760571e-01   9.14992136e-01   1.02734890e+00   1.14922439e+00
   1.28099903e+00   1.42303865e+00   1.57569319e+00   1.73929554e+00
   1.91416052e+00   2.10058402e+00   2.29884230e+00   2.50919136e+00
   2.73186658e+00   2.96708236e+00   3.21503195e+00   3.47588741e+00
   3.74979962e+00   4.03689847e+00   4.33729310e+00   4.65107220e+00
   4.97830447e+00   5.31903906e+00   5.67330611e+00   6.04111737e+00
   6.42246685e+00   6.81733144e+00   7.22567170e+00   7.64743254e+00
   8.08254400e+00   8.53092204e+00   8.99246929e+00   9.46707585e+00
   9.95462009e+00   1.04549694e+01   1.09679810e+01   1.14935027e+01
   1.20313735e+01   1.25814247e+01   1.31434803e+01   1.37173575e+01
   1.43028680e+01   1.48998182e+01   1.55080097e+01   1.61272404e+01
   1.67573046e+01   1.73979938e+01   1.80490973e+01   1.87104022e+01
   1.93816947e+01   2.00627597e+01   2.07533816e+01   2.14533451e+01
   2.21624346e+01   2.28804354e+01   2.36071337e+01   2.43423168e+01
   2.50857736e+01   2.58372947e+01   2.65966726e+01   2.73637021e+01
   2.81381802e+01   2.89199068e+01   2.97086840e+01   3.05043172e+01
   3.13066146e+01   3.21153874e+01   3.29304500e+01   3.37516204e+01
   3.45787194e+01   3.54115716e+01   3.62500049e+01   3.70938507e+01
   3.79429437e+01   3.87971226e+01   3.96562292e+01   4.05201090e+01
   4.13886111e+01   4.22615879e+01   4.31388956e+01   4.40203938e+01
   4.49059452e+01   4.57954165e+01   4.66886774e+01   4.75856011e+01
   4.84860639e+01   4.93899457e+01   5.02971292e+01   5.12075005e+01
   5.21209489e+01   5.30373665e+01   5.39566484e+01   5.48786928e+01
   5.58034006e+01   5.67306756e+01   5.76604244e+01   5.85925560e+01
   5.95269824e+01   6.04636179e+01   6.14023794e+01   6.23431862e+01
   6.32859600e+01   6.42306247e+01   6.51771066e+01   6.61253340e+01
   6.70752380e+01   6.80267498e+01   6.89798084e+01   6.99343360e+01
   7.08903187e+01   7.18475437e+01   7.28064348e+01   7.37654549e+01
   7.47290004e+01   7.56839518e+01   7.66677184e+01   7.75754125e+01
   7.86933146e+01   7.92584878e+01   8.12530925e+01   7.96565621e+01
   8.68588532e+01   7.30811291e+01   1.07990554e+02   3.30302708e+01
   1.99034047e+02  -1.48175809e+02   5.65252293e+02  -8.46307420e+02
   1.86517555e+03  -3.16154189e+03   5.84112652e+03  -9.68847278e+03
   1.60815607e+04  -2.49405793e+04   3.75380247e+04  -5.31645025e+04
   7.17829508e+04  -9.05316708e+04   1.06627979e+05  -1.14453853e+05
   1.09689360e+05  -8.78344405e+04   4.96751069e+04]
Elapsed Time2: 0.0450000762939
Elapsed Time3: 0.0410001277924

In [258]:
Smax = 120
n = ns-2
X = linspace(0.0, Smax, n+2)
X = X[1:-1]

payoff = clip(X-K, 0.0, 1e600)
#print "payoff = ", payoff
  
m = 4570
Fl = zeros((m+1,))
Fu = zeros((m+1,))
    
t = time.time()
bs1 = BS_FDM_explicit(r, sigma, T, Smin, Smax, Fl, Fu, payoff, m, n)
px_fd_mat = bs1.solve()
elapsed=time.time()-t
print "Elapsed Time1:", elapsed

##print  px_fd_mat.shape
nrow = len(px_fd_mat[:,1])
print px_fd_mat[nrow-1,:]

figure(figsize=[15, 4]);
subplot(1, 3, 1)
plot(X, px_fd_mat[nrow-1,:])
xlabel('Stock', fontsize=14);
title('nx = %.f'%m, fontsize=16);

m = 4573
Fl = zeros((m+1,))
Fu = zeros((m+1,))

t = time.time()
bs2 = BS_FDM_explicit(r, sigma, T, Smin, Smax, Fl, Fu, payoff, m, n)
px_fd_mat = bs2.solve()
elapsed=time.time()-t
print "Elapsed Time2:", elapsed

subplot(1, 3, 2)
nrow = len(px_fd_mat[:,1])
plot(X, px_fd_mat[nrow-1,:])
xlabel('Stock', fontsize=14);
title('nx = %.f'%m, fontsize=16);

m = 4580
Fl = zeros((m+1,))
Fu = zeros((m+1,))
    
t = time.time()
bs3 = BS_FDM_explicit(r, sigma, T, Smin, Smax, Fl, Fu, payoff, m, n)
px_fd_mat = bs3.solve()
elapsed=time.time()-t
print "Elapsed Time3:", elapsed

subplot(1, 3, 3)
nrow = len(px_fd_mat[:,1])
plot(X, px_fd_mat[nrow-1,:])
xlabel('Stock', fontsize=14);
title('nx = %.f' % m, fontsize=16);
Elapsed Time1: 0.0460000038147
[  1.01704397e-28   5.07283379e-26   7.18984489e-24   4.68830891e-22
   1.74072660e-20   4.16234095e-19   6.95467412e-18   8.61238308e-17
   8.26824165e-16   6.37765319e-15   4.06932208e-14   2.20044937e-13
   1.02914359e-12   4.23570647e-12   1.55695816e-11   5.17651596e-11
   1.57382418e-10   4.41714179e-10   1.15387760e-09   2.82564962e-09
   6.52727942e-09   1.43015144e-08   2.98649516e-08   5.96924592e-08
   1.14628045e-07   2.12191890e-07   3.79775698e-07   6.58934313e-07
   1.11098825e-06   1.82414722e-06   2.92234764e-06   4.57596663e-06
   7.01453177e-06   1.05414905e-05   1.55510379e-05   2.25469294e-05
   3.21631260e-05   4.51860447e-05   6.25781067e-05   8.55022075e-05
   1.15346670e-04   1.53750189e-04   2.02626238e-04   2.64186378e-04
   3.40961911e-04   4.35823312e-04   5.51996896e-04   6.93078223e-04
   8.63041768e-04   1.06624646e-03   1.30743677e-03   1.59173903e-03
   1.92465290e-03   2.31203775e-03   2.76009409e-03   3.27534000e-03
   3.86458272e-03   4.53488566e-03   5.29353117e-03   6.14797920e-03
   7.10582258e-03   8.17473908e-03   9.36244092e-03   1.06766222e-02
   1.21249047e-02   1.37147828e-02   1.54535680e-02   1.73483331e-02
   1.94058580e-02   2.16325751e-02   2.40345184e-02   2.66172723e-02
   2.93859255e-02   3.23450254e-02   3.54985376e-02   3.88498080e-02
   4.24015289e-02   4.61557096e-02   5.01136502e-02   5.42759210e-02
   5.86423455e-02   6.32119876e-02   6.79831445e-02   7.29533428e-02
   7.81193390e-02   8.34771256e-02   8.90219392e-02   9.47482747e-02
   1.00649901e-01   1.06719884e-01   1.12950607e-01   1.19333799e-01
   1.25860566e-01   1.32521423e-01   1.39306324e-01   1.46204708e-01
   1.53205527e-01   1.60297295e-01   1.67468123e-01   1.74705766e-01
   1.81997660e-01   1.89330975e-01   1.96692647e-01   2.04069434e-01
   2.11447949e-01   2.18814709e-01   2.26156179e-01   2.33458809e-01
   2.40709078e-01   2.47893532e-01   2.54998828e-01   2.62011762e-01
   2.68919314e-01   2.75708677e-01   2.82367293e-01   2.88882878e-01
   2.95243460e-01   3.01437400e-01   3.07453417e-01   3.13280617e-01
   3.18908511e-01   3.24327033e-01   3.29526565e-01   3.34497943e-01
   3.39232480e-01   3.43721975e-01   3.47958722e-01   3.51935520e-01
   3.55645682e-01   3.59083036e-01   3.62241932e-01   3.65117244e-01
   3.67704370e-01   3.69999230e-01   3.71998265e-01   3.73698434e-01
   3.75097207e-01   3.76192563e-01   3.76982977e-01   3.77467420e-01
   3.77645341e-01   3.77516663e-01   3.77081772e-01   3.76341503e-01
   3.75297128e-01   3.73950345e-01   3.72303264e-01   3.70358392e-01
   3.68118620e-01   3.65587208e-01   3.62767768e-01   3.59664251e-01
   3.56280934e-01   3.52622398e-01   3.48693515e-01   3.44499436e-01
   3.40045570e-01   3.35337570e-01   3.30381318e-01   3.25182908e-01
   3.19748632e-01   3.14084962e-01   3.08198542e-01   3.02096154e-01
   2.95784760e-01   2.89271303e-01   2.82563285e-01   2.75666908e-01
   2.68592676e-01   2.61338692e-01   2.53939079e-01   2.46326719e-01
   2.38712943e-01   2.30562309e-01   2.23293232e-01   2.13339863e-01
   2.09528451e-01   1.90671166e-01   2.06361812e-01   1.43753470e-01
   2.52562922e-01  -3.99580310e-03   4.94288821e-01  -5.19976227e-01
   1.40031289e+00  -2.18719234e+00   4.21080703e+00  -6.85231546e+00
   1.14799447e+01  -1.77278041e+01   2.67320910e+01  -3.78361940e+01
   5.10864915e+01  -6.44524111e+01   7.58680118e+01  -8.14990126e+01
   7.80272499e+01  -6.25720798e+01   3.52918730e+01]
Elapsed Time2: 0.0469999313354
Elapsed Time3: 0.047000169754

In [244]:
from scipy import sparse
import scipy.sparse.linalg.dsolve as linsolve

class BS_FDM_implicit:
  def __init__(self, 
               r, 
               sigma, 
               maturity, 
               Smin, 
               Smax, 
               Fl, 
               Fu, 
               payoff, 
               nt, 
               ns):
    self.r  = r 
    self.sigma = sigma 
    self.maturity  = maturity

    self.Smin = Smin     
    self.Smax = Smax
    self.Fl = Fl        
    self.Fu = Fu

    self.nt  = nt
    self.ns  = ns
    
    self.dt = float(maturity)/nt
    self.dx = float(Smax-Smin)/(ns+1)
    self.xs = Smin/self.dx

    self.u = empty((nt + 1, ns))
    self.u[0,:] = payoff

    ## Building Coefficient matrix:        
    A = sparse.lil_matrix((self.ns, self.ns))

    for j in xrange(0, self.ns):
      xd = j + 1 + self.xs
      sx = self.sigma * xd
      sxsq = sx * sx
      
      dtmp1 = self.dt * sxsq
      dtmp2 = self.dt * self.r
      A[j,j] = 1.0 + dtmp1 + dtmp2
        
      dtmp1 = -0.5 * dtmp1
      dtmp2 = -0.5 * dtmp2 * xd
      if j > 0:
        A[j,j-1] = dtmp1 - dtmp2
      if j < self.ns - 1:
        A[j,j+1] = dtmp1 + dtmp2

    self.A = linsolve.splu(A)
    self.rhs = empty((self.ns, ))
    
    ### Building bc_coef:
    nxl = 1 + self.xs
    sxl = self.sigma * nxl
    nxu = self.ns + self.xs
    sxu = self.sigma * nxu
    
    self.blcoef = 0.5 * self.dt * (- sxl * sxl + self.r * nxl)
    self.bucoef = 0.5 * self.dt * (- sxu * sxu - self.r * nxu)    
    
  def solve(self):
    for i in xrange(0, m):
        self.rhs[:] = self.u[i,:]
        self.rhs[0]         -= self.blcoef * self.Fl[i]
        self.rhs[self.ns-1] -= self.bucoef * self.Fu[i]
        self.u[i+1,:] = self.A.solve(self.rhs)

    return self.u
In [237]:
Smax = 200
n = ns-2
X = linspace(0.0, Smax, n+2)
X = X[1:-1]

payoff = clip(X-K, 0.0, 1e600)
  
m = 4555 
Fl = zeros((m+1,))
Fu = Smax - K*exp(-r * linspace(0.0, T, m+1))
    
t = time.time()
bs1 = BS_FDM_implicit(r, sigma, T, Smin, Smax, Fl, Fu, payoff, m, n)
px_fd_mat = bs1.solve()
elapsed=time.time()-t
print "Elapsed Time1:", elapsed

#print  px_fd_mat.shape

figure(figsize=[15, 4]);
subplot(1, 3, 1)
nrow = len(px_fd_mat[:,1])
plot(X, px_fd_mat[nrow-1,:])
xlabel('Stock', fontsize=14);
title('nx = %.f'%m,  fontsize=16);

m = 1000
Fl = zeros((m+1,))
Fu = Smax - K*exp(-r * linspace(0.0, T, m+1))

t = time.time()
bs2 = BS_FDM_implicit(r, sigma, T, Smin, Smax, Fl, Fu, payoff, m, n)
px_fd_mat = bs2.solve()
elapsed=time.time()-t
print "Elapsed Time2:", elapsed

subplot(1, 3, 2)
nrow = len(px_fd_mat[:,1])
plot(X, px_fd_mat[nrow-1,:])
xlabel('Stock', fontsize=14);
title('nx = %.f'%m, fontsize=16);

m = 100
Fl = zeros((m+1,))
Fu = Smax - K*exp(-r * linspace(0.0, T, m+1))
    
t = time.time()
bs3 = BS_FDM_implicit(r, sigma, T, Smin, Smax, Fl, Fu, payoff, m, n)
px_fd_mat = bs3.solve()
elapsed=time.time()-t
print "Elapsed Time3:", elapsed

subplot(1, 3, 3)
nrow = len(px_fd_mat[:,1])
plot(X, px_fd_mat[nrow-1,:])
xlabel('Stock', fontsize=14);
title('nx = %.f'%m, fontsize=16);


print px_fd_mat[nrow-1,:]
Elapsed Time1: 0.0649998188019
Elapsed Time2: 0.0190000534058
Elapsed Time3: 0.00699996948242
[  2.98641926e-21   7.95294389e-19   6.07132757e-17   2.16141936e-15
   4.46125332e-14   6.06271497e-13   5.90512355e-12   4.38258315e-11
   2.59592502e-10   1.27238057e-09   5.31142971e-09   1.93277995e-08
   6.24868999e-08   1.82323286e-07   4.86411656e-07   1.19954343e-06
   2.75978915e-06   5.97002567e-06   1.22239975e-05   2.38272713e-05
   4.44331425e-05   7.96114517e-05   1.37564347e-04   2.29997480e-04
   3.73148280e-04   5.88965351e-04   9.06425082e-04   1.36296400e-03
   2.00599845e-03   2.89449764e-03   4.10057163e-03   5.71103357e-03
   7.82889451e-03   1.05747499e-02   1.40880193e-02   1.85280050e-02
   2.40747384e-02   3.09295924e-02   3.93156382e-02   4.94777381e-02
   6.16823668e-02   7.62171638e-02   9.33902216e-02   1.13529124e-01
   1.36979747e-01   1.64104854e-01   1.95282487e-01   2.30904205e-01
   2.71373176e-01   3.17102172e-01   3.68511464e-01   4.26026688e-01
   4.90076661e-01   5.61091218e-01   6.39499062e-01   7.25725659e-01
   8.20191211e-01   9.23308698e-01   1.03548203e+00   1.15710433e+00
   1.28855625e+00   1.43020460e+00   1.58240089e+00   1.74548019e+00
   1.91976005e+00   2.10553959e+00   2.30309872e+00   2.51269751e+00
   2.73457569e+00   2.96895229e+00   3.21602540e+00   3.47597205e+00
   3.74894819e+00   4.03508884e+00   4.33450820e+00   4.64730004e+00
   4.97353796e+00   5.31327594e+00   5.66654874e+00   6.03337257e+00
   6.41374561e+00   6.80764873e+00   7.21504618e+00   7.63588627e+00
   8.07010218e+00   8.51761268e+00   8.97832294e+00   9.45212526e+00
   9.93889993e+00   1.04385160e+01   1.09508320e+01   1.14756967e+01
   1.20129500e+01   1.25624237e+01   1.31239419e+01   1.36973221e+01
   1.42823756e+01   1.48789083e+01   1.54867214e+01   1.61056120e+01
   1.67353733e+01   1.73757958e+01   1.80266675e+01   1.86877743e+01
   1.93589007e+01   2.00398302e+01   2.07303457e+01   2.14302298e+01
   2.21392656e+01   2.28572363e+01   2.35839265e+01   2.43191215e+01
   2.50626084e+01   2.58141760e+01   2.65736149e+01   2.73407182e+01
   2.81152810e+01   2.88971013e+01   2.96859798e+01   3.04817200e+01
   3.12841285e+01   3.20930149e+01   3.29081921e+01   3.37294765e+01
   3.45566877e+01   3.53896488e+01   3.62281863e+01   3.70721305e+01
   3.79213151e+01   3.87755775e+01   3.96347584e+01   4.04987026e+01
   4.13672580e+01   4.22402765e+01   4.31176133e+01   4.39991273e+01
   4.48846808e+01   4.57741397e+01   4.66673732e+01   4.75642541e+01
   4.84646585e+01   4.93684656e+01   5.02755581e+01   5.11858218e+01
   5.20991457e+01   5.30154219e+01   5.39345454e+01   5.48564144e+01
   5.57809297e+01   5.67079952e+01   5.76375175e+01   5.85694058e+01
   5.95035723e+01   6.04399313e+01   6.13784001e+01   6.23188981e+01
   6.32613474e+01   6.42056721e+01   6.51517989e+01   6.60996564e+01
   6.70491758e+01   6.80002898e+01   6.89529337e+01   6.99070444e+01
   7.08625609e+01   7.18194239e+01   7.27775760e+01   7.37369617e+01
   7.46975269e+01   7.56592194e+01   7.66219884e+01   7.75857847e+01
   7.85505608e+01   7.95162703e+01   8.04828685e+01   8.14503119e+01
   8.24185583e+01   8.33875669e+01   8.43572980e+01   8.53277132e+01
   8.62987752e+01   8.72704478e+01   8.82426958e+01   8.92154852e+01
   9.01887830e+01   9.11625570e+01   9.21367761e+01   9.31114100e+01
   9.40864295e+01   9.50618059e+01   9.60375116e+01   9.70135196e+01
   9.79898040e+01   9.89663392e+01   9.99431006e+01   1.00920064e+02
   1.01897207e+02   1.02874505e+02   1.03851938e+02]

In [246]:
Smax = 120

n = ns-2
X = linspace(0.0, Smax, n+2)
X = X[1:-1]

payoff = clip(X-K, 0.0, 1e600)
  
m = 4555 
Fl = zeros((m+1,))
Fu = zeros((m+1,))
    
t = time.time()
bs1 = BS_FDM_implicit(r, sigma, T, Smin, Smax, Fl, Fu, payoff, m, n)
px_fd_mat = bs1.solve()
elapsed=time.time()-t
print "Elapsed Time1:", elapsed

#print  px_fd_mat.shape

figure(figsize=[15, 4]);
subplot(1, 3, 1)
nrow = len(px_fd_mat[:,1])
plot(X, px_fd_mat[nrow-1,:])
xlabel('Stock', fontsize=14);
title('nx = %.f'%m,  fontsize=16);

m = 1000
Fl = zeros((m+1,))
Fu = zeros((m+1,))

t = time.time()
bs2 = BS_FDM_implicit(r, sigma, T, Smin, Smax, Fl, Fu, payoff, m, n)
px_fd_mat = bs2.solve()
elapsed=time.time()-t
print "Elapsed Time2:", elapsed

subplot(1, 3, 2)
nrow = len(px_fd_mat[:,1])
plot(X, px_fd_mat[nrow-1,:])
xlabel('Stock', fontsize=14);
title('nx = %.f'%m, fontsize=16);

m = 100
Fl = zeros((m+1,))
Fu = zeros((m+1,))
    
t = time.time()
bs3 = BS_FDM_implicit(r, sigma, T, Smin, Smax, Fl, Fu, payoff, m, n)
px_fd_mat = bs3.solve()
elapsed=time.time()-t
print "Elapsed Time3:", elapsed

subplot(1, 3, 3)
nrow = len(px_fd_mat[:,1])
plot(X, px_fd_mat[nrow-1,:])
xlabel('Stock', fontsize=14);
title('nx = %.f'%m, fontsize=16);


print px_fd_mat[nrow-1,:]
Elapsed Time1: 0.0629999637604
Elapsed Time2: 0.0190000534058
Elapsed Time3: 0.00899982452393
[  7.86391997e-26   2.43630771e-23   2.17528767e-21   9.09456815e-20
   2.21047505e-18   3.54181257e-17   4.06680009e-16   3.55313181e-15
   2.47154878e-14   1.41799741e-13   6.90208717e-13   2.91621919e-12
   1.08981438e-11   3.65882728e-11   1.11800634e-10   3.14357643e-10
   8.20959540e-10   2.00717719e-09   4.62568425e-09   1.01075920e-08
   2.10483475e-08   4.19585339e-08   8.03800929e-08   1.48488656e-07
   2.65321208e-07   4.59783507e-07   7.74601788e-07   1.27138672e-06
   2.03697314e-06   3.19118575e-06   4.89615859e-06   7.36730436e-06
   1.08859900e-05   1.58139276e-05   2.26092360e-05   3.18440739e-05
   4.42236828e-05   6.06066236e-05   8.20259326e-05   1.09710872e-04
   1.45108906e-04   1.89907492e-04   2.46055265e-04   3.15782142e-04
   4.01617915e-04   5.06408855e-04   6.33331909e-04   7.85906063e-04
   9.68000495e-04   1.18383919e-03   1.43800172e-03   1.73541999e-03
   2.08137073e-03   2.48146367e-03   2.94162538e-03   3.46807874e-03
   4.06731814e-03   4.74608068e-03   5.51131338e-03   6.37013683e-03
   7.32980557e-03   8.39766541e-03   9.58110837e-03   1.08875253e-02
   1.23242571e-02   1.38985441e-02   1.56174758e-02   1.74879390e-02
   1.95165671e-02   2.17096898e-02   2.40732837e-02   2.66129244e-02
   2.93337411e-02   3.22403726e-02   3.53369270e-02   3.86269431e-02
   4.21133563e-02   4.57984671e-02   4.96839136e-02   5.37706478e-02
   5.80589157e-02   6.25482418e-02   6.72374173e-02   7.21244924e-02
   7.72067733e-02   8.24808223e-02   8.79424628e-02   9.35867870e-02
   9.94081690e-02   1.05400280e-01   1.11556106e-01   1.17867972e-01
   1.24327569e-01   1.30925975e-01   1.37653695e-01   1.44500684e-01
   1.51456391e-01   1.58509791e-01   1.65649421e-01   1.72863426e-01
   1.80139595e-01   1.87465404e-01   1.94828057e-01   2.02214531e-01
   2.09611616e-01   2.17005958e-01   2.24384105e-01   2.31732543e-01
   2.39037742e-01   2.46286193e-01   2.53464452e-01   2.60559173e-01
   2.67557150e-01   2.74445350e-01   2.81210949e-01   2.87841363e-01
   2.94324281e-01   3.00647696e-01   3.06799929e-01   3.12769658e-01
   3.18545941e-01   3.24118239e-01   3.29476437e-01   3.34610862e-01
   3.39512300e-01   3.44172009e-01   3.48581737e-01   3.52733727e-01
   3.56620734e-01   3.60236024e-01   3.63573389e-01   3.66627143e-01
   3.69392131e-01   3.71863730e-01   3.74037843e-01   3.75910904e-01
   3.77479872e-01   3.78742226e-01   3.79695962e-01   3.80339584e-01
   3.80672096e-01   3.80692997e-01   3.80402265e-01   3.79800354e-01
   3.78888175e-01   3.77667089e-01   3.76138892e-01   3.74305803e-01
   3.72170448e-01   3.69735846e-01   3.67005398e-01   3.63982863e-01
   3.60672354e-01   3.57078312e-01   3.53205495e-01   3.49058961e-01
   3.44644053e-01   3.39966381e-01   3.35031804e-01   3.29846420e-01
   3.24416543e-01   3.18748691e-01   3.12849567e-01   3.06726049e-01
   3.00385168e-01   2.93834095e-01   2.87080130e-01   2.80130679e-01
   2.72993250e-01   2.65675428e-01   2.58184871e-01   2.50529291e-01
   2.42716442e-01   2.34754109e-01   2.26650094e-01   2.18412206e-01
   2.10048248e-01   2.01566006e-01   1.92973243e-01   1.84277682e-01
   1.75487001e-01   1.66608823e-01   1.57650706e-01   1.48620136e-01
   1.39524521e-01   1.30371177e-01   1.21167330e-01   1.11920100e-01
   1.02636504e-01   9.33234429e-02   8.39876998e-02   7.46359341e-02
   6.52746771e-02   5.59103273e-02   4.65491469e-02   3.71972583e-02
   2.78606405e-02   1.85451268e-02   9.25640199e-03]

Homework

  • Implement the FDM for Black-Scholes PDE using the Crank-Nicolson Scheme and test your code using the same example here for both the vanilla European Call and the Up-and-out Barrier Call.