Practical Solution of Partial Differential Equations in Finance PDF
Practical Solution of Partial Differential Equations in Finance PDF
Peter Austing
[email protected]
August, 2022
Abstract
It is now possible to numerically solve partial dierential pricing equations so that vanilla
option values are exactly recovered. The nite dierence scheme is not hard to implement,
but it must be done exactly correctly to achieve results to machine precision. We provide a
step by step guide to implementation with numerical examples and python code. We also
correct some formulas in the original proof of Austing (2020), and analyse the impact of
implicit versus semi-implicit stepping.
1 Introduction
In Austing (2020), we demonstrated a nite dierence scheme for solving partial dierential
pricing equations with the property that any vanilla option having its strike and expiry on
the nite dierence grid is exactly recovered. In the intervening period, it has been put to
use at a number of funds and investment banks in asset classes as diverse as foreign exchange,
commodities, equities and interest rate hybrids.
It is important but quite ddly to set up the matrix operators correctly. A number of
readers have highlighted that the structure of the original paper does not ease this process.
In this article we run through a numerical example step by step, and provide python code, to
make it easier to implement the scheme. Our main numerical example covers the particular
case of local volatility with fully implicit stepping. We then present the formulas for the
semi-implicit scheme and investigate the rate of convergence for a barrier option.
Once one has the local volatility case working, it can be extended to other models such
as local-stochastic volatility and three factor interest rate hybrid models as outlined in the
original article.
As with the original paper, our results are set in the context of a number of earlier works.
Two key are Andreasen and Huge (2011a,b). A core principle of the former is that the adjoint
In that they achieve exact calibration, our results for the nite dierence approach have
parallels with the powerful particle methods of Guyon and Henry-Labordère (2012) for Monte
Carlo. More recently, there is continued practitioner interest in specic exact discretization
approaches, as in Bang and Daboussi (2022).
2 Solving a PDE
dS
= µdt + σloc (St , t)dW (1)
S
with corresponding backwards pricing equation
∂V ∂V 1 ∂ 2V 2
+ Sµ + S σloc (St , t)2 − V r2 = 0, (2)
∂t ∂S 2 ∂S 2
and forward probability equation
∂ ∂ 1 ∂2 2
q(S, t) = − [Sµq(S, t)] + [S σloc (S, t)2 q(S, t)]. (3)
∂t ∂S 2 ∂S 2
Dupire (1992) tells us that the local volatility function
∂
C(K, T ) ∂
+ Kµ ∂K C(K, T ) + (r2 − µ)C(K, T )
1
σ (K, T )2
2 loc
= ∂T
2
∂
(4)
K 2 ∂K 2 C(K, T )
equity pricing case (but note we do not consider the case of discrete dividends). In the FX case, the dividend
S= 1, Spot
We are going to run through a numerical example, in parallel with explaining the steps. This
will help those who are building their own implementations to check each step is correct. We
will only print numbers to around three signicant gures. This should be enough to check
individual implementations are correct, but readers should not copy truncated intermdiate
numbers directly from this article for numerical experiments.
The spot grid is denoted {x1 < x2 < · · · < xn } in A20, and we insist on x1 = 0 and xn
must be large. In practice, large means that a call option with strike xn can be assumed
to have zero value. For our numerical example, we are going to use the following randomly
generated grid
{0, 0.74, 0.78, 0.9, 1.11, 1.23, 1.26, 1.31, 13.11}. (5)
To generate this grid, we rst chose grid boundaries suitable for the problem and then drew
seven random variables between them. Finally we added 0 at the beginning and appended
ten times the nal point at the end.
Di,i = 0 , i = 2, · · · , n − 1 (11)
Then UL is our second derivative operator, while D is our rst derivative operator.
It is important to note two dierences between the D matrix dened here compared to A20,
1. In A20, the rst and last row of D are zeros (corresponding to zero boundary condi-
tions). This is an error in A20. Here we have values in those rows corresponding to
zero gamma boundary conditions.
µτ = r τ − d τ . (25)
In our numerical example, the continuous compounding rates r2 = 0.03 and r1 = 0.05 lead
to discretized interest rate and drift
rτ = 0.030045045 , τ = 1, · · · , 10 (26)
µτ = −0.020080164 , τ = 1, · · · , 10 (27)
3
and
(L−1 L† )ij = (diag {0, 1, · · · , 1, 0})ij . (30)
Let us dene, for a given expiry, {ci } to be market call option prices with strikes on the grid
points xi , and {pi } to be market put option prices with strikes xi . Then
q = Lc ≡ Lp (31)
∑
n
qi = df (32)
i=1
If we apply L −1
to the probability vector q, we retrieve put prices
L−1 q = p. (33)
Note 2: in A20 there is confusion between L and L† with the result that this relation
is incorrectly asserted to retrieve call prices (rather than put prices) just before equation
A20.16.
In our numerical example, the inverse L matrix is
0. 0. 0. 0. 0. 0. 0. 0. 0.
0.74 0. 0. 0. 0. 0. 0. 0. 0.
0.78 0.04 0. 0. 0. 0. 0. 0. 0.
0.9 0.16 0.12 0. 0. 0. 0. 0. 0.
L−1
= 1.11 0.37 0.33 0.21 0. 0. 0. 0. 0. (34)
1.23 0.49 0.45 0.33 0.12 0. 0. 0. 0.
1.26 0.52 0.48 0.36 0.15 0.03 0. 0. 0.
1.31 0.57 0.53 0.41 0.2 0.08 0.05 0. 0.
13.11 12.37 12.33 12.21 12. 11.88 11.85 11.8 0.
(q τ +1 − q τ ) 1
= D† Xµτ +1 q τ +1 + L† X 2 V τ +1 U q τ +1 − rτ +1 q τ +1 , τ = 0, · · · , ntime − 1. (35)
∆t 2
This is a fully implicit scheme because we use time indices τ + 1 (rather than τ ) on the right
hand side. We are using notation q for (discounted) probabilities so that we free up p for put
option prices. This leads to an inconsistency of notation versus A20, but will hopefully not
be too confusing. X is the diagonal matrix formed from the spot grid X = diag ({xi }) and
V τ = diag ({(σiτ )2 }) is a diagonal matrix of local volatility squares and is to be determined.
Since q 1 ≡ qn ≡ 0 , we will set V11 = Vnn = 0.
In addition, our formula 35 lacks the matrix U in the rst term on the right compared to
A20.15 because we included U in our denition of D.
As in A20, we apply L to both sides of 35, but this time use the correct relations q = Lp
−1
(pτ +1 − pτ )i 1
= (L−1 D† XµLpτ +1 + X 2 V U Lpτ +1 − rpτ +1 )i , 2 ≤ i ≤ n − 1. (36)
∆t 2
We do not want to use this formula directly to calculate the local volatilities because L−1 is
triangular requiring O(n ) operations for multiplication.
2
Fortunately, the matrix L−1 D† XL is almost tri-diagonal. The only elements not on the three
major diagonals are in the rst column. But they are irrelevant because p1 = 0 (the value
of a put option with strike zero).
xi (xi+1 − xi )
Ji,i−1 = − (37)
(xi+1 − xi−1 )(xi − xi−1 )
xi (xi+1 − xi ) − xi+1 (xi − xi−1 )
Ji,i = (38)
(xi+1 − xi )(xi − xi−1 )
xi (xi − xi−1 )
Ji,i+1 = (39)
(xi+1 − xi−1 )(xi+1 − xi )
Jij = 0 , otherwise. (40)
which is equal to the tri-diagonal part of −L−1 D† XL, plus two elements in the bottom right
corner (which are irrelevant because the local variance vector V has Vn = 0). Then, as in
A20, we arrive at the local volatility formula
(pτi +1 − pτi )/∆t + (µJ + r)pτi +1
(σiτ )2 = 1 , 2≤i≤n−1 (41)
2
(xi )2 (U Lpτ +1 )i
(σiτ )2 = 0 , otherwise. (42)
In this formula we use put values p, while A20.23 uses call values. Both versions work
numerically, and one can even switch from using puts to calls half way along the grid. No
To understand the matrix J, we can compare 41 with its continuous counterpart 4. We see
that J is simply a (rather non-intuitive) discretization of S
∂
∂S
− 1.
In our numerical example, we have
L−1 D† XL =
0 0 0 0 0 0 0 0 0
−0.9487 18.50 −17.55 0 0 0 0 0 0
−1 14.62 −12. −1.625 0 0 0 0 0
−1 0 4.773 −2.214 −1.558 0 0 0 0
−1 0 0 1.922 4.964 −5.886 0 0 0
−1 0 0 0 2.050 31.75 −32.80 0 0
−1 0 0 0 0 26.25 −15.80 −9.450 0
−1 0 0 0 0 0 26.09 −25.09 −0.0004684
−1 0 0 0 0 0 0 1.111 −0.111
(43)
and
J=
0 0 0 0 0 0 0 0 0
−0.05128 −18.50 17.55 0 0 0 0 0 0
0 −14.62 12.00 1.625 0 0 0 0 0
0 0 −4.773 2.214 1.558 0 0 0 0
0 0 0 −1.922 −4.964 5.886 0 0 0
0 0 0 0 −2.050 −31.75 32.80 0 0
0 0 0 0 0 −26.25 15.80 9.450 0
0 0 0 0 0 0 −26.09 25.09 0.0004684
0 0 0 0 0 0 0 0 0
(44)
and nally, their dierence,
0 0 0 0 0 0 0 0 0
−1 0 0 0 0 0 0 0 0
−1 0 0 0 0 0 0 0 0
−1 0 0 0 0 0 0 0 0
L D XL − (−J) =
−1 †
−1 0 0 0 0 0 0 0 0 (45)
−1 0 0 0 0 0 0 0 0
−1 0 0 0 0 0 0 0 0
−1 0 0 0 0 0 0 0 0
−1 0 0 0 0 0 0 1.111 −0.111
0. 0. 0. 0. 0.11 0.23 0.26 0.31 12.11
0. 0. 0. 0. 0.112 0.231 0.261 0.311 12.076
0. 0. 0. 0. 0.113 0.233 0.262 0.312 12.042
0. 0. 0. 0.001 0.115 0.234 0.264 0.313 12.007
0. 0. 0. 0.003 0.117 0.235 0.265 0.314 11.973
p = 0. 0. 0. 0.004 0.119 0.236 0.266 0.315 11.94
. (46)
0. 0. 0. 0.006 0.121 0.238 0.267 0.316 11.906
0. 0. 0.001 0.008 0.123 0.239 0.268 0.317 11.872
0. 0.001 0.001 0.01 0.125 0.24 0.269 0.318 11.838
0. 0.001 0.002 0.011 0.127 0.241 0.271 0.319 11.805
0. 0.001 0.002 0.013 0.13 0.243 0.272 0.32 11.771
In this matrix (pτ )i , the rst row are the values of put options at expiry time 0 for the ten
strikes and the nal row are the values at time to expiry 1 year. A time grid with 10 intervals
has 11 grid points, and so the index τ runs from 0 to 10 with 0 corresponding to time zero.
Later, we are going to solve the pricing PDE to recover the value of the put option which
has position p10,5 ≡ 0.13 in this matrix. These numbers have been rounded to three decimal
places, so we point out now that the full precision of this number calculated with the code
listing below on my PC was 0.12956379093157036.
The local volatility formula is (41)
0. −0.017 −0.001 −0.003 0.002 0. 0.004 0. 0.
0. 0.023 −0.001 0. 0.002 0. 0.002 0.213 0.
0. 0.089 0.001 0.005 0.003 0.001 0.004 0.331 0.
0. 0.147 0.005 0.008 0.004 0.001 0.006 0.512 0.
0. 0.18 0.009 0.01 0.005 0.001 0.008 0.695 0.
2 τ
(σ ) i = . (49)
0. 0.195 0.012 0.011 0.005 0.002 0.01 0.837 0.
0. 0.2 0.015 0.012 0.006 0.003 0.011 0.934 0.
0. 0.198 0.017 0.012 0.006 0.003 0.011 0.993 0.
0. 0.194 0.019 0.012 0.006 0.004 0.012 1.026 0.
0. 0.188 0.02 0.013 0.006 0.004 0.013 1.042 0.
This matrix has 10 rows, because local volatilities apply to time intervals, not to time points.
1.11
0.37
0.33
0.21
v=
0 .
(50)
0
0
0
0
v τ = (1 − ∆tQτ )v τ −1 . (52)
τ τ −1
Since we are trying to step backwards from v to v , we need to invert the tridiagonal
matrix A ≡ (1 − ∆tQ ) using a tridiagonal matrix inversion such as the Thomas (1949)
τ τ
algorithm.
In our numerical example, we have 10 time intervals, so we begin with the matrix A10 , which
10
1.077
0.373
0.335
0.221
v0 =
0.029 .
(55)
0.004
0.002
0.001
0.
11
0.
0.
0.
0.524
q0 =
0.476 (56)
0.
0.
0.
0.
On my PC, using the python code listing in section 5, this has error compared to the true
value listed earlier (after equation 46) only in the 16th digit.
Up to now, we have used a fully implicit scheme. We will now extend to the Crank and
Nicolson (1947) semi-implicit approach by introducing a parameter θ so that the discretized
pricing equation becomes
v τ −1 − v τ 1
= (µτ XD + V τ X 2 U L − rτ )(θv τ −1 + (1 − θ)v τ ). (58)
∆t 2
with θ = 1 being implicit, θ = 0.5 semi-implicit and θ = 0 explicit. Duy (2004) has
pointed out that great care is required when using the CrankNicolson scheme. It can
lead to oscillating errors if used near discontinuities in the payout or boundary conditions.
However, it is popular to do a few smaller fully implicit steps after any discontinuity, and
semi-implicit steps otherwise. This is known as Rannacher (1984) marching. For analysis of
the convergence of standard schemes with this approach, readers are referred to Giles and
Carter (2005).
We rst need to adjust the formulas for the discretized interest rate, dividend yield, and
local volatility. For the interest rate, as explained in section 4 of A20, we consider valuing a
cash payment, whose payout is represented by a vector of all ones, v = (1, · · · , 1)† . As both
D and L annihilate v, the discretized pricing equation 51 becomes
v τ −1 − v τ
= −rτ (θv τ −1 + (1 − θ)v τ ). (59)
∆t
Since v τ −1 is equal to vτ discounted back by discount factor df τ −1,τ , this tells us that
1 − df τ −1,τ
rτ = (60)
∆t(θdf τ −1,τ + 1 − θ)
12
v τ −1 − v τ
= −dτ (θv τ −1 + (1 − θ)v τ ). (61)
∆t
so that
τ −1,τ
τ 1 − dfdiv
d = τ −1,τ , (62)
∆t(θdfdiv + 1 − θ)
where dfdiv is the dividend yield discount factor between τ −1 and τ.
The discretized local volatility formula is a simple extension of 41,
Finally, to step back the PDE, we retain our denition of the matrix Qτ = (µτ XD +
V X 2 U L − rτ ). Then the pricing equation becomes
1 τ
2
v τ −1 − v τ
= Qτ (θv τ −1 + (1 − θ)v τ ), (65)
∆t
and we step backwards using
You can try this out by adjusting the value of the variable theta in the python code listed
in section 5.
The purest way to investigate is by valuing a no-touch option. This is a contract that pays
cash as long as a continuous barrier is not breached. We have included this experiment in the
code listing. This time, we consider a case with constant implied volatility of 10% by setting
the SABR vol of vol to zero, which allows us to compare against the analytic BlackScholes
PV available in Haug (1997). We set the barrier to 1.06 so that the PV of the option is
approximately 50% of the notional. We use a spot grid that is uniform in spot space and
goes from minus three standard deviations up to the barrier level, and we of course add
zero and a large value to the grid. When it comes to pricing, we take care to remove the
13
The conclusion is that semi-implicit stepping converges much faster than fully implicit. With
only 20 time steps, the semi-implicit method has an error smaller than the fully implicit
reaches even with 500 time steps. The results are collated in table 1 and plotted in gure 1,
scaled up to a realistic no-touch with $1,000,000 cash payment upon touch, in both dollars
and in basis points.
3,000
2,500
Semi-implicit
2,000
Fully-implicit
1,500
1,000
500
0
0 50 100 150 200 250 300 350 400 450 500
-500
Number of me steps
14
The results in the preceding section were generated with the following code. I will get around
to pushing this code to a public git repository, but in the meantime please email me for a
text le with the code (since copy / paste from this pdf will be very painful).
1 """
2 Demonstration of :
5 h t t p s : / / p a p e r s . s s r n . com/ s o l 3 / p a p e r s . cfm ? a b s t r a c t _ i d = 3 5 3 0 5 6 1
6
7 Note : this is not intended to be efficient or production quality .
10
11 Copyright 2022 Peter Austing
12
13 You may use this code freely , but please only distribute it by referencing the
15
16 """
17
18 import numpy as np
19 import pandas as pd
21 import math
23 import time
24 import inspect
25
26 def tridiagonalMatrix (a , b, c) :
28
29 | b0 c0 0 0 0 . . . 0 0|
30 | a1 b1 c1 0 0 . . . |
31 | 0 a2 b2 c2 0 . . . |
32 | 0 0 a3 b3 c3 . . . |
33 | . . . |
34 | 0 0 0 0 0 . . . an =1 bn =1|
35
36 :a: lower diagonal
38 : c : upper diagonal
40 """
42
43 def diagonalMatrix (d) :
45
46 :d: diagonal
15
49 return np . d i a g ( d , 0)
50
51
52 def i n v e r t A n d M u l t i p l y (T, x ) :
54
55 :T: tridiagonal matrix
58 """
59 dl = np . d i a g o n a l ( T, =1)
60 d = np . d i a g o n a l ( T , 0 )
61 du = np . d i a g o n a l ( T , 1 )
65 return res [ 3 ]
66
67 def getLOperator ( g r i d ) :
68 " " " Get the second order derivative matrix operator L
69
70 : grid : spot grid
71 : returns : matrix L
72 """
73 n = len ( grid )
75 for r in range (1 , n = 1) :
76 dxup = grid [ r + 1] = grid [ r ] ;
78 x = 1. / dxup ;
79 y = = 1. / dxup = 1. / dxdw ;
80 z = 1. / dxdw ;
81 b[ r ] = y;
82 a[ r ] = z ;
83 c [ r ] = x;
84 L = tridiagonalMatrix (a , b, c) ;
85 return L
86
87 def getLInverseOperator ( grid ) :
88 " " " Get the ' inverse ' of the second order derivative matrix L
89 It is not a true matrix inverse , but is the inverse on the space we care
about
90
91 : grid : spot grid
92 : returns : inverse of L
93 """
94 n = len ( grid )
95 M = np . z e r o s ( ( n , n ) )
96 for i in range (0 , n) :
97 for j in range (0 , n) :
99 return M
100
16
103
104 : grid : spot grid
106 """
109 for i in = 1) :
range (1 , n
110 a [ i ] = =g r i d [ i ] * ( g r i d [ i + 1 ] = g r i d [ i ] ) / ( ( g r i d [ i + 1 ] = g r i d [ i =
113 t = tridiagonalMatrix (a , b, c)
114 return t
115
116 def getUOperator ( g r i d ) :
118
119 : grid : spot grid
121 """
122
123 n = len ( grid )
124 d = [0] * n
127 d[ r ] = 1. / dx
129 return t
130
131 def getDOperator ( g r i d ) :
132 " " " Get the first order derivative operator matrix D
133
134 : grid : spot grid
136 """
141 x = 1. / dx
142 a[ r ] = =x
143 b[ r ] = 0.
144 c [ r ] = x
145
146 dx = grid [ 1 ] = grid [ 0 ]
147 b[0] = = 1. / dx
148 c [0] = 1. / dx
151 b[n = 1] = 1. / dx
17
153 return t
154
155
156
157 def s a b r V o l s ( alpha , nu , rho , T, fwd , strike_grid ) :
168 """
171 if ( nu == 0.) :
172 r e s . f i l l ( alpha )
179
180 def b l a c k S c h o l e s P V ( fwd , t , strike_grid , vol , is_call ) :
182
183 : fwd : float forward rate
189 """
190 if t == 0.0:
192 else :
196 d2 = d1 = vrt
199
200 if is_call :
202
203 puts = calls + strike_grid = fwd
18
208
209 : method : string ' linear ' , ' random '
215 """
n_points = 2)
222
223 # The grid must contain zero , and a large number
224 grid = np . i n s e r t ( g r i d , 0, 0)
225 # We use 10 times upper_bndy for the ' large ' value .
226 # All that matters is the PV of a call must be very close to zero at this
strike
234 grid = np . s o r t ( g r i d )
235
236 return grid
237
238 def retrieve_name ( var ) :
239 " " "A function to look in the call stack and get the name of a variable 2
frames
240 back . This allows us to print the name of an array when we print its
values
241
242 : var : the variable
244 """
var ]
249
250 def aprint ( array ) :
253
19
255 """
256 M = a r r a y . copy ( )
261 print ( df )
262
263 def l p r i n t ( array , precision = None ) :
265
266 : array : numpy array
268 """
270 raise ValueError ( ' bmatrix can at most display two dimensions ' )
272 lines = s t r ( a r r a y ) . r e p l a c e ( ' \n ' , ' ' ) . r e p l a c e ( ' [ ' , ' ' ) . replace ( ' ] ' , ' \n ' ) .
s p l i t l i n e s ()
274 rv += [ ' ' + ' & ' . join ( l . s p l i t () ) + r ' \\ ' for l in lines ]
276 np . s e t _ p r i n t o p t i o n s ( )
280
281 def noTouchBlackPV ( s p o t , r1 , r2 , sigma , T, barrier ) :
282 """
284 touched . See The Complete Guide to Option Pricing Formulas by Espen
Gaarder
286
287 : spot : float
293 : returns : PV
294 """
298
299 if S > H:
301
302 eta = = 1; phi = =1 # Case 10 in Haug book
20
= eta * sigmart )
309
310 return B2 = B4
311
312 def getDiscretizedParams ( strike_grid , time_grid , local_vars , drifts , d o m _r a t e s
314 L = getLOperator ( s t r i k e _ g r i d )
315
316 # J is the tridiagonal matrix that appears in the numerator of the special
319
320 U = getUOperator ( s t r i k e _ g r i d )
321
322 D = getDOperator ( s t r i k e _ g r i d )
323
324 # S is a diagonal matrix representing ' spot ' in differential equations
326
327 Ssqr = np . matmul ( S , S)
328
329 n = len ( strike_grid )
330 if remove_last_point :
331 n == 1
332
333 # Set up the array of true market vanilla prices
336 call_prices = []
338 t = time_grid [ j ]
347
348 c a l l _ p r i c e s . append ( c a l l s )
349
350 call_prices = np . a r r a y ( c a l l _ p r i c e s )
351
352 # Calculate the local vols , the drifts and the interest rates
353 probs = []
21
362
363 r = domrates [ j ]
364 mu = drifts [ j ]
365
366 c = theta * call_prices [ j + 1] + (1. = theta ) * call_prices [ j ]
367 Jc = np . matmul ( J , c)
368
369 prob = np . matmul ( L , c)
370
371 sum_prob = np . sum ( p r o b )
381 if floor_at_zero :
383 l o c a l _ v a r s . append ( l v s q r [ 0 : n ] )
384
385 local_vars = 2 #np . a r r a y ( l o c a l _ v a r s )
387
388 def solvePDE ( s t r i k e _ g r i d , time_grid , domrates , drifts , local_vars , p0 , payoff ,
is_upper_barrier = False ) :
390 L = getLOperator ( s t r i k e _ g r i d )
393 U = getUOperator ( s t r i k e _ g r i d )
394 D = getDOperator ( s t r i k e _ g r i d )
395 if is_upper_barrier :
396 D[ =1][ =1] = 0.; D[ =1][ =2] = 0. # correct boundary condition of there
is a barrier
400 r = domrates [ j = 1]
401 mu = drifts [ j = 1]
402
403 V = diagonalMatrix ( local_vars [ j = 1])
405 UL = np . matmul (U , L)
407 A = I = theta * dt * Q
22
411
412 if is_upper_barrier :
414
415 pv = np . d o t ( p0 , solution )
416 return pv
417
418 if __name__ == "__main__" :
419
420 t0 = time . perf_counter ( )
421
422 # Financial parameters
423 # For demonstration purposes , we will generate ' market ' prices using a
SABR model
424 spot = 1
431
432 # Solver parameters
433 nspot = 10
434 ntime = 10
435 std = 3
439 # A real application would choose the boundaries more carefully taking
440 # smile into account , and would make the grid evenly spaced in log =s p o t
441 # terms and it would place strikes of importance on the grid ( see Tavella
443 lower_bndy = spot * math . e x p ( =0.5 * alpha * alpha * T = alpha * std * math
. s q r t (T) )
444 upper_bndy = spot * math . e x p ( =0.5 * alpha * alpha * T + alpha * std * math
. s q r t (T) )
d e c i m a l _ p l a c e s =2)
447
448 domrates , drifts , local_vars , p0 = [] , [] , [] , []
p0 )
450
451 # Value an option
23
461
462 # Solve the PDE
payoff )
464
465
466 pverror = pv = true_pv
468
469 p r i n t ( 'PV : ' , pv )
473
474 # Now verify numerically some of the formulas in the article
476 # You can do more precise checks of course . There is an aprint function
478
479 # L^{ =1} as an ' inverse ' of L
480 L = getLOperator ( s t r i k e _ g r i d )
483
484 # Print L L^{ =1} in latex format . It is equal to diag (0 , 1, 1, . . . , 1, 0)
487
488 # Print L^{ =1} L^\ d a g g e r
491 a p r i n t ( LmLdag )
492
493 # Print L^{ =1} L
494 # It is * not * e q u a l to diag (0 , 1, 1, . . . , 1, 0)
496 l p r i n t (LmL, 3)
497
498 # Derivation of the matrix J
502 lprint (J , 3)
503
504 # Here we print L^{ =1} D^\ d a g g e r X L so we can compare it to =J
505 X = diagonalMatrix ( strike_grid )
506 D = getDOperator ( s t r i k e _ g r i d )
508 l p r i n t ( LmDdagXL , 3)
509
510 # Here we print L^{ =1} D^\ d a g g e r X L = (= J ) so we can check it only has
24
513 l p r i n t (M, 3)
514
515 # Now value a no =t o u c h option
516 # We will set vol of vol to 0 so we can compare with the Black =S c h o l e s
value
517 spot = 1
524
525 # We will use more grid points than the above demo
527 ntime = 20
528 std = 3
534
535 # Loop over choices of theta and ntime to check convergence of PDE against
analytic price
542
543 domrates , drifts , local_vars , p0 = [] , [] , [] , []
549 payoff [ =1] = 0.0 # At the top of the barrier , we have touched the
550
551 # Solve the PDE
, p0 , payoff , i s _ u p p e r _ b a r r i e r=T r u e )
553
554 pv_error = pv = true_pv
555
556 p r i n t ( theta , ntime , pv_error )
25
Andreasen, Jesper and Brian Norsk Huge. Finite dierence based calibration and simula-
tion. Risk July (2011).
Austing, Peter. Finite dierence schemes with exact recovery of vanilla option prices. Risk
November (2020). http://ssrn.com/abstract=3530561.
Bang, Dominique RA. Local-Stochastic Volatility for Vanilla Modeling: A Tractable and
Arbitrage Free Approach to Option Pricing. Available at SSRN 3171877 (2019).
Crank, John and Phyllis Nicolson. A practical method for numerical evaluation of solutions
of partial dierential equations of the heat-conduction type. In: Mathematical Proceed-
ings of the Cambridge Philosophical Society. Vol. 43. 1. Cambridge University Press. 1947,
pp. 5067.
Duy, Daniel J. A critique of the crank nicolson scheme strengths and weaknesses for
nancial instrument pricing. The Best of Wilmott (2004), p. 333.
Dupire, Bruno. Arbitrage pricing with stochastic volatility. In: Proceedings of AFFI con-
ference. Paris, 1992.
Giles, M and R Carter. Convergence analysis of Crank-Nicolson and Rannacher time-
marching. Technical report (2005). http://eprints.maths.ox.ac.uk/1137/1/NA-05-16.pdf.
Guyon, Julien and Pierre Henry-Labordère. Being particular about calibration. Risk 25.1
(2012), p. 88.
Hagen, Patrick S., Deep Kumar, Andrew S Lesniewski, and Diana E Woodward. Managing
Wilmott magazine (2002).
Smile Risk.
Haug, Espen Gaarder. The Complete Guide to Option Pricing Formulas. McGraw-Hill, 1997.
Rannacher, Rolf. Finite element solution of diusion problems with irregular data. Nu-
merische Mathematik 43.2 (1984), pp. 309327.
Saporito, Yuri F, Xu Yang, and Jorge P Zubelli. The calibration of stochastic local-volatility
models: An inverse problem perspective. Computers & Mathematics with Applications
77.12 (2019), pp. 30543067.
Tavella, D. and C. Randall. Pricing Financial Instruments: The Finite Dierence Method.
John Wiley & Sons, Inc., 2000. Chap. 5.
Thomas, Llewellyn. Elliptic problems in linear dierential equations over a network: Watson
scientic computing laboratory. Columbia Univ., NY (1949).
26
27