feynmanKac¶
This function solves the generalized Feynman-Kac PDE on a rectangular grid using a finite element Galerkin method. Initial and boundary conditions are satisfied. The solution is represented by a series of \(C^2\) Hermite quintic splines.
Synopsis¶
feynmanKac (nlbcd, nrbcd, xgrid, tgrid, fcnFkcfiv, fcnFkbcp, y, yPrime)
Required Arguments¶
- int
nlbcd
(Input) - Number of left boundary conditions. It is required that 1 ≤
nlbcd
≤3
. - int
nrbcd
(Input) - Number of right boundary conditions. It is required that 1 ≤
nrbcd
≤3
. - float
xgrid[]
(Input) - Array of length
nxgrid
containing the breakpoints for the Hermite quintic splines used in the x discretization. The points inxgrid
must be strictly increasing. The valuesxgrid[0]
andxgrid[nxgrid
-1]
are the endpoints of the interval. - float
tgrid[]
(Input) - Array of length
ntgrid
containing the set of time points (in time-remaining units) where an approximate solution is returned. The points intgrid
must be positive and strictly increasing. - void
fcnFkcfiv
(x
,t
,iflag
,value
) User-supplied function to compute the values of the coefficients \(\sigma, \sigma', \mu, \kappa\) for the Feynman-Kac PDE and the initial data function \(p(x), x_{\min} \leq x \leq x_{\max}\) .
- float
x
(Input) - Space variable.
- float
t
(Input) - Time variable.
- int
iflag
(Input/Output) On entry,
iflag
indicates which coefficient or data function is to be computed. The following table shows which value has to be returned byfcnFkcfiv
for all possible values ofiflag
:iflag
Computed coefficient -1 \(\sigma' = \tfrac{\partial \sigma(x,t)}{\partial x}\) 0 \(p(x)\) 1 \(\sigma\) 2 \(\mu\) 3 \(\kappa\) For non-zero input values of
iflag
, note when a coefficient does not depend on t. This is done by settingiflag = 0
after the coefficient is defined. If there is time dependence, the value ofiflag
should not be changed. This action will usually yield a more efficient algorithm because some finite element matrices do not have to be reassembled for each t value.- float
value
(Output) - Value of the coefficient or initial data function. Which value is
computed depends on the input value for
iflag
, see description ofiflag
.
- float
- void
fcnFkbcp
(nbc
,t
,iflag
,values[])
User-supplied function to define boundary values that the solution of the differential equation must satisfy. There are
nlbcd
conditions specified at the left end, \(x_{\text{min}}\), andnrbcd
conditions at the right end, \(x_{\text{max}}\). The boundary conditions are\[a(x,t)f + b(x,t)f_x + c(x,t)f_{xx} = d(x,t), \phantom{...} x = x_{\min} \text{ or } x = x_{\max}\]- int
nbc
(Input) - Number of boundary conditions. At \(x_{min}\),
nbc=nlbcd
, at \(x_{max}\),nbc = nrbcd
. - float
t
(Input) - Time point of the boundary conditions.
- int
iflag
(Input/Output) On entry,
iflag
indicates whether the coefficients for the left or right boundary conditions are to be computed:iflag
Computed boundary conditions 1 Left end, \(x = x_{min}\) 0 Right end, \(x = x_{max}\) If there is no time dependence for one of the boundaries then set
iflag = 0
after the arrayvalues
is defined for either end point. This flag can avoid unneeded continued computation of the finite element matrices.- float
values[]
(Output) Array of length 4 * max (
nlbcd
,nrbcd
) containing the values of the boundary condition coefficients in its first4*nbc
locations. The coefficients for \(x_{min}\)are stored row-wise according to the following scheme:\[\begin{split}\left( \begin{array}{c} a_1\left(x_{\min}, t\right), b_1\left(x_{\min}, t\right), c_1\left(x_{\min}, t\right), d_1\left(x_{\min}, t\right) \\ \vdots \\ a_{\mathit{nlbcd}}\left(x_{\min}, t\right), b_{\mathit{nlbcd}}\left(x_{\min}, t\right), c_{\mathit{nlbcd}}\left(x_{\min}, t\right), d_{\mathit{nlbcd}}\left(x_{\min}, t\right) \\ \end{array} \right)\end{split}\]The coefficients for \(x_{max}\) are stored similarly.
- int
- float
y[]
(Output) An array of size (
ntgrid
+1) by (3 ×nxgrid
) containing the coefficients of the Hermite representation of the approximate solution for the Feynman-Kac PDE at time points 0,tgrid[0]
, …,tgrid[ntgrid-1]
. The approximate solution is given by\[f(x,t) = \sum_{j=0}^{3 * \mathtt{nxgrid}-1} y_{ij} \beta_j(x)\]for
\[t = \mathtt{tgrid}[i-1], i = 1, \ldots, \mathtt{ntgrid}\]The representation for the initial data at \(t = 0\) is
\[p(x) = \sum_{j=0}^{3 * \mathtt{nxgrid}-1} y_{0j} \beta_j(x)\]The (
ntgrid
+ 1) by (3 *nxgrid
) matrix\[\left(y_{ij}\right)_{i=0,\ldots,\mathtt{ntgrid}}^{j=0,\ldots,3*\mathtt{nxgrid}-1}\]is stored row-wise in array
y.
After the integration, use row
i
of arrayy
as input argumentcoef
in functionfeynmanKacEvaluate
to obtain an array of values for f(
x, t)
or its partials \(f_x, f_{xx}, f_{xxx}\) at time pointt=0
,tgrid[i-1]
,i=1
,…,ntgrid.
The expressions for the basis functions \(\beta_j(x)\) are represented piece-wise and can be found in Hanson, R. (2008) “Integrating Feynman-Kac Equations Using Hermite Quintic Finite Elements.”
- float
yPrime[]
(Output) An array of size (
ntgrid
+ 1) by (3 ×nxgrid
) containing the first derivatives ofy
at time points0
,tgrid[0]
,...
,tgrid[ntgrid - 1]
, i.e.,\[f_t\left(x,\overline{t}\right) = \sum_{j=0}^{3*\mathtt{nxgrid}-1} y'_{ij} \beta_j(x)\]for
\[\overline{t} = \mathtt{tgrid}[i-1], i=1, \ldots, \mathtt{ntgrid}\]and
\[f_t\left(x,\overline{t}\right) = \sum_{j=0}^{3*\mathtt{nxgrid}-1} y'_{0j} \beta_j(x) \phantom{...} \text{ for } \overline{t} = 0 \text{.}\]The (
ntgrid
+ 1) by (3\*nxgrid
) matrix\[\left(y'_{ij}\right)_{i=0,\ldots,\mathtt{ntgrid}}^{j=0,\ldots,3*\mathtt{nxgrid}-1}\]is stored row-wise in array
yPrime.
After the integration, use row
i
of arrayyPrime
as input argumentcoef
in functionfeynmanKacEvaluate
to obtain an array of values for the partials \(f_t, f_{tx}, f_{txx}, f_{txxx}\) at time pointt=tgrid[i-1]
,i=1
,...
,ntgrid
, and row0
for the partials att=0
.
Optional Arguments¶
fcnInit
, voidfcnInit
(nxgrid
,ntgrid
,xgrid[]
,tgrid[]
,time
,yprime[]
,y[]
,atol[]
,rtol[]
) (Input)- User-supplied function for adjustment of initial data or as an
opportunity for output during the integration steps. The solution values
of the model parameters are presented in the arrays
y
andyprime
at the integration pointstime = 0
,tgrid[j]
,j=0
,…,ntgrid-1
. At the initial point, integral least-squares estimates are made for representing the initial data \(p(x)\). If this is not satisfactory,fcnInit
can change the contents ofy[]
to match data for any reason. - int
nxgrid
(Input) - Number of grid lines in the x-direction.
- int
ntgrid
(Input) - Number of time points where an approximate solution is returned.
- float
xgrid[]
(Input)
- Vector of length
nxgrid
containing the breakpoints for the Hermite quintic splines used in the x discretization. - float
tgrid[]
(Input)
- Vector of length
ntgrid
containing the set of time points (in time-remaining units) where an approximate solution is returned. - float
time
(Input) - Time variable.
- float
yprime[]
(Input) - Vector of length
3\*nxgrid
containing the derivative of the coefficients of the Hermite quintic spline at time pointtime
. - float
y[]
(Input/Output) - Vector of length 3 ×
nxgrid
containing the coefficients of the Hermite quintic spline at time pointtime
. - float
atol[]
(Input/Output) - Array of length 3 ×
nxgrid
containing absolute error tolerances. - float
rtol[]
(Input/Output) - Array of length 3 ×
nxgrid
containing relative error tolerances. fcnForce
, voidfcnForce
(interval
,ndeg
,nxgrid
,y[]
,time
,width
,xlocal[]
,qw[]
,u[]
,phi[]
,dphi[])
(Input)
- Function
fcnForce
is used in case there is a non-zero term \(\phi(f, x, t)\) in the Feynman-Kac differential equation. If functionfcnForce
is not used, it is assumed that \(\phi(f, x, t)\) is identically zero. - int
interval
(Input) - Index indicating the bounds
xgrid[interval-1]
andxgrid[interval]
of the integration interval,1
≤interval
≤nxgrid-1
. - int
ndeg
(Input) - The degree used for the Gauss-Legendre formulas.
- int
nxgrid
(Input) - Number of grid lines in the x-direction.
- float
y[]
(Input) - Vector of length
3\*nxgrid
containing the coefficients of the Hermite quintic spline representing the solution of the Feynman-Kac equation at time pointtime
. - float
time
(Input) - Time variable.
- float
width
(Input) - The interval width,
width =
xgrid[interval]
-
xgrid[interval-1]
. - float
xlocal[]
(Input) - Array of length
ndeg
containing the Gauss-Legendre points translated and normalized to the interval[xgrid[interval-1],
xgrid[interval]]
. - float
qw[]
(Input)
- Vector of length
ndeg
containing the Gauss-Legendre weights. - float
u[]
(Input) - Array of dimension 12 by
ndeg
containing the basis function values that define \(\tilde{\beta}(x)\) at the Gauss-Legendre pointsxlocal
. Setting \(u_{k,i} :=\)u[i+k\*ndeg]
and \(x_i :=\)xlocal[i]
, \(\tilde{\beta}\left( x_i \right)\) is defined as
- float
phi[]
(Output) Vector of length 6 containing Gauss-Legendre approximations for the local contributions
\[\varphi_{\mathrm{t}} := \int_{\mathrm{xgrid[interval-1]}}^{\mathrm{xgrid[interval]}} \mathit{\Phi}(f,x,t) \tilde{\beta}(x) dx,\]where \(t =\) time and
\[\tilde{\beta}(x) := \left(\beta_{3*(\mathrm{interval}-1)}(x), \ldots, \beta_{3*(\mathrm{interval}+2)}(x)\right)^T\]Vector phi contains elements
\[\mathrm{phi}[i] = \mathrm{width} * \textstyle\sum_{j=0}^{\mathrm{ndegGL}-1} qw[j] \tilde{\beta}_i \left(x_j\right) \phi(f, \mathrm{xlocal}[j], \mathrm{time})\]for
i=
0,…,5.- float
dphi[]
(Output) Array of dimension 6
by
6 containing a Gauss-Legendre approximation for the Jacobian of the local contributions \(\phi_t\) at \(t =\) time,\[\frac{\partial \varphi_t}{\partial y} = \int_{\mathrm{xgrid[interval-1]}}^{\mathrm{xgrid[interval]}} \frac{\partial \mathit{\Phi}(f,x,t)}{\partial f} \tilde{\beta}(x)\tilde{\beta}^T(x) dx.\]The approximation to this symmetric matrix is stored row-wise, i.e.
\[\mathrm{dphi}[j+i*s] = \mathrm{width} * \textstyle\sum_{k=0}^{n\mathrm{deg}GL-1} qw[k]\phantom{...} \tilde{\beta}_i\left(x_k\right) \tilde{\beta}_j\left(x_{\mathrm{k}}\right) \frac{\partial \mathit{\Phi}}{\partial f} |_{x=\mathit{xlocal}[\mathrm{k}],t=\mathit{time}}\]for \(i, j = 0, …, 5\).
atolRtolScalars
, floatatol
, floatrtol
(Input)Scalar values that apply to the error estimates of all components of the solution
y
in the differential equation solverSDASLX
. See optional argumentatolRtolArrays
if separate tolerances are to be applied to each component ofy
.Default:
atol
andrtol
are set to \(10^{-3}\) in single precision and \(10^{-5}\) in double precision.atolRtolArrays
, floatatol[]
, floatrtol[]
, (Input)Componentwise tolerances are used for the computation of solution
y
in the differential equation solverSDASLX
. Argumentsatol
andrtol
are arrays of length 3 ×nxgrid
to be used for the absolute and relative tolerance and to be applied to each component of the solution,y
. See optional argumentatolRtolScalars
if scalar values of absolute and relative tolerances are to be applied to all components.
Default: All elements of
atol
andrtol
are set to \(10^{-3}\) in single precision and \(10^{-5}\) in double precision.nDegree,
int (Input)The degree used for the Gauss-Legendre formulas for constructing the finite element matrices. It is required that
nDegree ≥ 6.
Default:
nDegree = 6
.tdepend
(Output)- Vector of length 7 indicating time dependence of the coefficients,
boundary conditions and function φ in the Feynman-Kac equation. If
tdepend[i]
= 0
then argumenti
is not time dependent, iftdepend[i]=1
then argumenti
is time dependent.
i | Computed coefficient |
---|---|
0 | \(\sigma'\) |
1 | \(\sigma\) |
2 | \(\mu\) |
3 | \(\kappa\) |
4 | Left boundary conditions |
5 | Right boundary conditions |
6 | φ |
maxStep
, float (Input)This is the maximum step size the integrator may take.
Default:
maxStep
=machine
(2), the largest possible machine number.initialStepsize
, float (Input)The starting step size for the integration. Must be less than zero since the integration is internally done from
t=0
tot=tgrid[ntgrid-1]
in a negative direction.Default:
initialStepsize
= -Ɛ, where Ɛ is the machine precision.maxNumberSteps
, int (Input)The maximum number of steps between each output point of the integration.
Default:
maxNumberSteps = 500000
.stepControl
, int (Input)Indicates which step control algorithm is used for the integration. If
stepControl
= 0, then the step control method of Söderlind is used. IfstepControl
= 1, then the method used by the original Petzold codeSASSL
is used.Default:
stepControl = 0.
maxBdfOrder
, int (Input)Maximum order of the backward differentiation formulas used in the integrator. It is required that 1≤
maxBdfOrder
≤ 5.Default:
maxBdfOrder = 5
.tBarrier
, float (Input)This optional argument controls whether the code should integrate past a special point,
tBarrier
, and then interpolate to get \(y\) and \(y'\) at the points intgrid[]
. If this optional argument is present, the integrator assumes the equations either change on the alternate sides oftBarrier
or they are undefined there. In this case, the code creeps up totBarrier
in the direction of integration. It is required thattBarrier
≥tgrid[ntgrid-1].
Default:
tBarrier = tgrid[ntgrid-1]
.istate
(Output)An array of length 7 whose entries flag the state of computation for the matrices and vectors required in the integration. For each entry, a zero indicates that no computation has been done or there is a time dependence. A one indicates that the entry has been computed and there is no time dependence. The
istate
entries are as follows:i
Entry in istate 0 Mass Matrix, M 1 Stiffness matrix, N 2 Bending matrix, R 3 Weighted mass, K 4 Left boundary conditions 5 Right- boundary conditions 6 Forcing term Default:
istate[i]
= 0 forI
= 0,…,6.evals
(Output)- Array of length 3 summarizing the number of evaluations required during the integration.
i |
evals[i] |
---|---|
0 | Number of residual function evaluations of the DAE used in the model. |
1 | Number of factorizations of the differential matrix associated with solving the DAE. |
2 | Number of linear system solve steps using the differential matrix. |
Description¶
The generalized Feynman-Kac differential equation has the form
where the initial data satisfies \(f(x,T) = p(x)\). The derivatives are
The function feynmanKac
uses a finite element Galerkin method over the
rectangle
in \((x,t)\) to compute the approximate solution. The interval \(\left[ x_{\min}, x_{\max} \right]\) is decomposed with a grid
On each subinterval the solution is represented by
The values
are time-dependent coefficients associated with each interval. The basis functions \(b_0, b_1, b_2\) are given for
by
The Galerkin principle is then applied. Using the provided initial and boundary conditions leads to an Index 1 differential-algebraic equation for the time-dependent coefficients
This system is integrated using the variable order, variable step algorithm
DDASLX/SDASLX
noted in Hanson and Krogh, R. (2008)
Solving Constrained Differential-Algebraic Systems Using Projections.
Solution values and their time derivatives are returned at a grid preceding
time T, expressed in units of time remaining. For further details of
deriving and solving the system see Hanson, R. (2008)
Integrating Feynman-Kac Equations Using Hermite Quintic Finite Elements.
To evaluate f or its partial derivatives at any time point in the grid,
use the function feynmanKacEvaluate.
Examples¶
Example 1¶
The value of the American Option on a Vanilla Put can be no smaller than its
European counterpart. That is due to the American Option providing the
opportunity to exercise at any time prior to expiration. This example
compares this difference – or premium value of the American Option – at two
time values using the Black-Scholes model. The example is based on Wilmott
et al. (1996, p. 176), and uses the non-linear forcing or
weighting term described in Hanson, R. (2008), “Integrating
Feynman-Kac Equations Using Hermite Quintic Finite Elements”, for
evaluating the price of the American Option. The coefficients, payoff,
boundary conditions and forcing term for American and European options are
defined by the user functions fkcfiv_put
, fkbcp_put
and
fkforce_put
, respectively. One breakpoint is set exactly at the strike
price.
The sets of parameters in the computation are:
- Strike price \(K = \{ 10.0 \}\).
- Volatility \(\sigma = \{0.4\}\).
- Times until expiration \(= \{ 1/4, 1/2 \}\).
- Interest rate \(r = 0.1\).
- \(x_{\min} = 0.0, \phantom{...} x_{\max} = 30.0\).
- \(nx = 61, \phantom{...} n = 3 \times nx = 183\).
The payoff function is the “vanilla option”, \(p(x) = \max (K - x, 0)\).
from __future__ import print_function
from numpy import empty, zeros
from pyimsl.math.feynmanKac import feynmanKac
from pyimsl.math.feynmanKacEvaluate import feynmanKacEvaluate
# These routines define the coefficients, payoff, boundary conditions
# and forcing term for American and European Options.
def fkcfiv_put(x, tx, iflag, value):
if iflag[0] == 0:
# The payoff function
value[0] = max(strike_price - x, 0.0)
elif iflag[0] == -1:
# The coefficient derivative d sigma/ dx
value[0] = sigma
elif iflag[0] == 1:
# The coefficient sigma(x)
value[0] = sigma * x
elif iflag[0] == 2:
# The coefficient mu(x)
value[0] = (interest_rate - dividend) * x
elif iflag[0] == 3:
# The coefficient kappa(x)
value[0] = interest_rate
# Note that there is no time dependence
iflag[0] = 0
def fkbcp_put(nbc, tx, iflag, val):
if iflag[0] == 1:
val[0] = 0.0
val[1] = 1.0
val[2] = 0.0
val[3] = -1.0
val[4] = 0.0
val[5] = 0.0
val[6] = 1.0
val[7] = 0.0
elif iflag[0] == 2:
val[0] = 1.0
val[1] = 0.0
val[2] = 0.0
val[3] = 0.0
val[4] = 0.0
val[5] = 1.0
val[6] = 0.0
val[7] = 0.0
val[8] = 0.0
val[9] = 0.0
val[10] = 1.0
val[11] = 0.0
# Note no time dependence
iflag[0] = 0
def fkforce_put(interval, ndeg, nxgrid, y, time,
width, xlocal, qw, u, phi, dphi):
local = 6
yl = empty(6)
bf = empty(6)
for i in range(0, local):
yl[i] = y[3 * interval - 3 + i]
phi[i] = 0.0
mu = 2
# This is the local definition of the forcing term
for j in range(1, local + 1):
for l in range(1, ndeg + 1):
bf[0] = u[(l - 1)]
bf[1] = u[(l - 1) + ndeg]
bf[2] = u[(l - 1) + 2 * ndeg]
bf[3] = u[(l - 1) + 6 * ndeg]
bf[4] = u[(l - 1) + 7 * ndeg]
bf[5] = u[(l - 1) + 8 * ndeg]
rt = 0.0
for k in range(0, local):
rt += yl[k] * bf[k]
rt = value / (rt + value - (strike_price - xlocal[l - 1]))
phi[j - 1] += qw[l - 1] * bf[j - 1] * pow(rt, mu)
for i in range(0, local):
phi[i] = -phi[i] * width * interest_rate * strike_price
# This is the local derivative matrix for the forcing term
for i in range(0, local * local):
dphi[i] = 0.0
for j in range(1, local + 1):
for i in range(1, local + 1):
for l in range(1, ndeg + 1):
bf[0] = u[(l - 1)]
bf[1] = u[(l - 1) + ndeg]
bf[2] = u[(l - 1) + 2 * ndeg]
bf[3] = u[(l - 1) + 6 * ndeg]
bf[4] = u[(l - 1) + 7 * ndeg]
bf[5] = u[(l - 1) + 8 * ndeg]
rt = 0.0
for k in range(0, local):
rt += yl[k] * bf[k]
rt = 1.0 / (rt + value - (strike_price - xlocal[l - 1]))
dphi[i - 1 + (j - 1) * local] += qw[l - 1] * \
bf[i - 1] * bf[j - 1] * pow(rt, mu + 1)
for i in range(0, local * local):
dphi[i] = mu * dphi[i] * width * \
pow(value, mu) * interest_rate * strike_price
# Compute American Option Premium for Vanilla Put
NXGRID = 61
NTGRID = 2
NV = 9
# The strike price
strike_price = 10.0
# The sigma value
sigma = 0.4
# Time values for the options
nt = 2
time = [0.25, 0.5]
# Values of the underlying where evaluations are made
xs = [0.0, 2.0, 4.0, 6.0, 8.0, 10.0, 12.0, 14.0, 16.0]
# Value of the interest rate and continuous dividend
interest_rate = 0.1
dividend = 0.0
# Values of the min and max underlying values modeled
x_min = 0.0
x_max = 30.0
# Define parameters for the integration step.
nint = NXGRID - 1
n = 3 * NXGRID
xgrid = empty(NXGRID)
ye = empty(0)
yeprime = empty(0)
ya = empty(0)
yaprime = empty(0)
fe = zeros([nt, NV])
fa = zeros([nt, NV])
nlbcd = 2
nrbcd = 3
value = 0.2e-2
# Define an equally-spaced grid of points for the
# underlying price
dx = (x_max - x_min) / float(nint)
xgrid[0] = x_min
xgrid[NXGRID - 1] = x_max
for i in range(2, NXGRID):
xgrid[i - 1] = xgrid[i - 2] + dx
feynmanKac(nlbcd, nrbcd, xgrid, time,
fkcfiv_put, fkbcp_put, ye, yeprime,
atolRtolScalars={'atol': 0.5e-2, 'rtol': 0.5e-2})
feynmanKac(nlbcd, nrbcd, xgrid, time,
fkcfiv_put, fkbcp_put, ya, yaprime,
fcnForce=fkforce_put,
atolRtolScalars={'atol': 0.5e-2, 'rtol': 0.5e-2})
# Evaluate solutions at vector of points XS(:), at each
# time value prior to expiration.
for i in range(0, nt):
tmp = feynmanKacEvaluate(xgrid, xs, ye[i + 1, ])
fe[i, :] = tmp
tmp = feynmanKacEvaluate(xgrid, xs, ya[i + 1, ])
fa[i, :] = tmp
print("\nAmerican Option Premium for Vanilla Put, 3 and 6 Months Prior to Expiry")
print(" Number of equally spaced spline knots:%4d" % NXGRID)
print(" Number of unknowns:%4d" % n)
print(" Strike=%6.2f, sigma=%5.2f, Interest Rate=%5.2f\n" %
(strike_price, sigma, interest_rate))
print(" Underlying European American")
for i in range(0, NV):
print(" %10.4f%10.4f%10.4f%10.4f%10.4f" % (xs[i], fe[0, i],
fe[1, i], fa[0, i], fa[1, i]))
Output¶
American Option Premium for Vanilla Put, 3 and 6 Months Prior to Expiry
Number of equally spaced spline knots: 61
Number of unknowns: 183
Strike= 10.00, sigma= 0.40, Interest Rate= 0.10
Underlying European American
0.0000 9.7535 9.5139 10.0000 10.0000
2.0000 7.7535 7.5139 8.0000 8.0000
4.0000 5.7536 5.5159 6.0000 6.0000
6.0000 3.7607 3.5691 4.0000 4.0000
8.0000 1.9061 1.9168 2.0174 2.0856
10.0000 0.6560 0.8533 0.6780 0.9035
12.0000 0.1638 0.3362 0.1681 0.3521
14.0000 0.0364 0.1269 0.0371 0.1322
16.0000 0.0083 0.0486 0.0083 0.0501
Example 2¶
In Beckers (1980) there is a model for a Stochastic Differential Equation of option pricing. The idea is a “constant elasticity of variance diffusion (or CEV) class”
The Black-Scholes model is the limiting case \(\alpha \to 2\) . A numerical solution of this diffusion model yields the price of a call option. Various values of the strike price \(K\), time values, \(\sigma\) and power coefficient \(\alpha\) are used to evaluate the option price at values of the underlying price. The sets of parameters in the computation are:
- power \(\alpha = \{2.0, 1.0, 0.0\}\).
- strike price \(K = \{15.0, 20.0, 25.0\}\).
- volatility \(\sigma = \{0.2, 0.3, 0.4\}\).
- times until expiration \(= \{ 1/12, 4/12, 7/12 \}\).
- underlying prices \(= \{ 19.0, 20.0, 21.0 \}\).
- interest rate \(r = 0.05\).
- \(x_{\min} = 0, x_{\max} = 60\).
- \(nx = 121, \phantom{...} n = 3 \times nx = 363\).
With this model the Feynman-Kac differential equation is defined by identifying:
The payoff function is the “vanilla option”, \(p(x) = \max (x - K, 0)\).
from __future__ import print_function
from numpy import *
from pyimsl.math.feynmanKac import feynmanKac
from pyimsl.math.feynmanKacEvaluate import feynmanKacEvaluate
def fcn_fkcfiv(x, tx, iflag, value):
if iflag[0] == 0:
# The payoff function
value[0] = max(x - strike_price, 0.0)
elif iflag[0] == -1:
# The coefficient derivative d sigma/ dx
value[0] = 0.5 * alpha * sigma * pow(x, alpha * 0.5 - 1.0)
elif iflag[0] == 1:
# The coefficient sigma(x)
value[0] = sigma * pow(x, alpha * 0.5)
elif iflag[0] == 2:
# The coefficient mu(x)
value[0] = (interest_rate - dividend) * x
elif iflag[0] == 3:
# The coefficient kappa(x)
value[0] = interest_rate
# Note that there is no time dependence
iflag[0] = 0
def fcn_fkbcp(nbc, tx, iflag, val):
if iflag[0] == 1:
val[0] = 1.0
val[1] = 0.0
val[2] = 0.0
val[3] = 0.0
val[4] = 0.0
val[5] = 1.0
val[6] = 0.0
val[7] = 0.0
val[8] = 0.0
val[9] = 0.0
val[10] = 1.0
val[11] = 0.0
# Note no time dependence
iflag[0] = 0
elif iflag[0] == 2:
df = exp(interest_rate * tx)
val[0] = 1.0
val[1] = 0.0
val[2] = 0.0
val[3] = x_max - df * strike_price
val[4] = 0.0
val[5] = 1.0
val[6] = 0.0
val[7] = 1.0
val[8] = 0.0
val[9] = 0.0
val[10] = 1.0
val[11] = 0.0
# Compute Constant Elasticity of Variance Model for Vanilla Call
NXGRID = 121
NTGRID = 3
NV = 3
# The set of strike prices
KS = [15.0, 20.0, 25.0]
# The set of sigma values
sigmas = [0.2, 0.3, 0.4]
# The set of model diffusion powers
alphas = [2.0, 1.0, 0.0]
# Time values for the options
nt = 3
time = [1.0 / 12.0, 4.0 / 12.0, 7.0 / 12.0]
# Values of the underlying where evaluations are made
xs = [19.0, 20.0, 21.0]
# Value of the interest rate and continuous dividend
interest_rate = 0.05
dividend = 0.0
# Values of the min and max underlying values modeled
x_min = 0.0
x_max = 60.0
# Define parameters for the integration step.
nint = NXGRID - 1
n = 3 * NXGRID
xgrid = empty(NXGRID)
y = empty(0)
yprime = empty(0)
f = empty([NTGRID, NV])
# Number of left/right boundary conditions
nlbcd = 3
nrbcd = 3
# Define equally-spaced grid of points for the underlying price
dx = (x_max - x_min) / float(nint)
xgrid[0] = x_min
xgrid[NXGRID - 1] = x_max
for i in range(2, NXGRID):
xgrid[i - 1] = xgrid[i - 2] + dx
print(" Constant Elasticity of Variance Model for Vanilla Call")
print(" Interest Rate:%7.3f\tContinuous Dividend:%7.3f" %
(interest_rate, dividend))
print(" Minimum and Maximum Prices of Underlying:%7.2f%7.2f" % (x_min, x_max))
print(" Number of equally spaced spline knots:%4d" % (NXGRID - 1))
print(" Number of unknowns:%4d\n" % n)
print(" Time in Years Prior to Expiration:%7.4f%7.4f%7.4f"
% (time[0], time[1], time[2]))
print(" Option valued at Underlying Prices:%7.2f%7.2f%7.2f\n"
% (xs[0], xs[1], xs[2]))
for i1 in range(1, 4): # Loop over power
for i2 in range(1, 4): # Loop over volatility
for i3 in range(1, 4): # Loop over strike price
# Pass data through into evaluation routines.
strike_price = KS[i3 - 1]
sigma = sigmas[i2 - 1]
alpha = alphas[i1 - 1]
feynmanKac(nlbcd, nrbcd, xgrid, time,
fcn_fkcfiv, fcn_fkbcp, y, yprime)
# Evaluate solution at vector of points xs, at each time
# Value prior to expiration.
for i in range(0, NTGRID):
tmp = feynmanKacEvaluate(xgrid, xs, y[i + 1, ])
f[i, :] = tmp
print(" Strike=%5.2f, Sigma=%5.2f, Alpha=%5.2f" %
(KS[i3 - 1], sigmas[i2 - 1], alphas[i1 - 1]))
for i in range(0, NV):
print(" Call Option Values", end=' ')
for j in range(0, nt):
print("%7.4f " % f[j, i], end=' ')
print()
print()
Output¶
Constant Elasticity of Variance Model for Vanilla Call
Interest Rate: 0.050 Continuous Dividend: 0.000
Minimum and Maximum Prices of Underlying: 0.00 60.00
Number of equally spaced spline knots: 120
Number of unknowns: 363
Time in Years Prior to Expiration: 0.0833 0.3333 0.5833
Option valued at Underlying Prices: 19.00 20.00 21.00
Strike=15.00, Sigma= 0.20, Alpha= 2.00
Call Option Values 4.0624 4.2576 4.4734
Call Option Values 5.0624 5.2505 5.4492
Call Option Values 6.0624 6.2486 6.4386
Strike=20.00, Sigma= 0.20, Alpha= 2.00
Call Option Values 0.1312 0.5951 0.9693
Call Option Values 0.5024 1.0880 1.5093
Call Option Values 1.1980 1.7478 2.1745
Strike=25.00, Sigma= 0.20, Alpha= 2.00
Call Option Values 0.0000 0.0111 0.0751
Call Option Values 0.0000 0.0376 0.1630
Call Option Values 0.0006 0.1036 0.3150
Strike=15.00, Sigma= 0.30, Alpha= 2.00
Call Option Values 4.0636 4.3405 4.6627
Call Option Values 5.0625 5.2951 5.5794
Call Option Values 6.0624 6.2712 6.5248
Strike=20.00, Sigma= 0.30, Alpha= 2.00
Call Option Values 0.3107 1.0261 1.5479
Call Option Values 0.7317 1.5404 2.0999
Call Option Values 1.3762 2.1674 2.7362
Strike=25.00, Sigma= 0.30, Alpha= 2.00
Call Option Values 0.0005 0.1124 0.3564
Call Option Values 0.0035 0.2184 0.5565
Call Option Values 0.0184 0.3869 0.8230
Strike=15.00, Sigma= 0.40, Alpha= 2.00
Call Option Values 4.0755 4.5143 4.9673
Call Option Values 5.0660 5.4210 5.8328
Call Option Values 6.0633 6.3588 6.7306
Strike=20.00, Sigma= 0.40, Alpha= 2.00
Call Option Values 0.5109 1.4625 2.1260
Call Option Values 0.9611 1.9934 2.6915
Call Option Values 1.5807 2.6088 3.3202
Strike=25.00, Sigma= 0.40, Alpha= 2.00
Call Option Values 0.0081 0.3302 0.7795
Call Option Values 0.0287 0.5178 1.0656
Call Option Values 0.0820 0.7690 1.4097
Strike=15.00, Sigma= 0.20, Alpha= 1.00
Call Option Values 4.0624 4.2479 4.4312
Call Option Values 5.0624 5.2479 5.4312
Call Option Values 6.0624 6.2479 6.4312
Strike=20.00, Sigma= 0.20, Alpha= 1.00
Call Option Values 0.0000 0.0219 0.1051
Call Option Values 0.1497 0.4107 0.6484
Call Option Values 1.0832 1.3314 1.5773
Strike=25.00, Sigma= 0.20, Alpha= 1.00
Call Option Values -0.0000 -0.0000 0.0000
Call Option Values -0.0000 -0.0000 0.0000
Call Option Values -0.0000 -0.0000 0.0000
Strike=15.00, Sigma= 0.30, Alpha= 1.00
Call Option Values 4.0624 4.2479 4.4312
Call Option Values 5.0624 5.2479 5.4312
Call Option Values 6.0624 6.2479 6.4312
Strike=20.00, Sigma= 0.30, Alpha= 1.00
Call Option Values 0.0010 0.0786 0.2208
Call Option Values 0.1993 0.4997 0.7539
Call Option Values 1.0835 1.3444 1.6022
Strike=25.00, Sigma= 0.30, Alpha= 1.00
Call Option Values -0.0000 0.0000 0.0000
Call Option Values -0.0000 0.0000 0.0000
Call Option Values -0.0000 0.0000 0.0004
Strike=15.00, Sigma= 0.40, Alpha= 1.00
Call Option Values 4.0624 4.2479 4.4312
Call Option Values 5.0624 5.2479 5.4312
Call Option Values 6.0624 6.2479 6.4312
Strike=20.00, Sigma= 0.40, Alpha= 1.00
Call Option Values 0.0072 0.1540 0.3446
Call Option Values 0.2498 0.5950 0.8728
Call Option Values 1.0868 1.3795 1.6586
Strike=25.00, Sigma= 0.40, Alpha= 1.00
Call Option Values 0.0000 0.0000 0.0000
Call Option Values 0.0000 0.0000 0.0005
Call Option Values 0.0000 0.0002 0.0057
Strike=15.00, Sigma= 0.20, Alpha= 0.00
Call Option Values 4.0624 4.2479 4.4311
Call Option Values 5.0624 5.2479 5.4312
Call Option Values 6.0624 6.2479 6.4312
Strike=20.00, Sigma= 0.20, Alpha= 0.00
Call Option Values 0.0001 0.0001 0.0002
Call Option Values 0.0816 0.3316 0.5748
Call Option Values 1.0817 1.3308 1.5748
Strike=25.00, Sigma= 0.20, Alpha= 0.00
Call Option Values 0.0000 -0.0000 -0.0000
Call Option Values 0.0000 -0.0000 -0.0000
Call Option Values -0.0000 0.0000 -0.0000
Strike=15.00, Sigma= 0.30, Alpha= 0.00
Call Option Values 4.0624 4.2479 4.4312
Call Option Values 5.0624 5.2479 5.4312
Call Option Values 6.0624 6.2479 6.4312
Strike=20.00, Sigma= 0.30, Alpha= 0.00
Call Option Values 0.0000 -0.0000 0.0026
Call Option Values 0.0894 0.3326 0.5753
Call Option Values 1.0826 1.3306 1.5749
Strike=25.00, Sigma= 0.30, Alpha= 0.00
Call Option Values 0.0000 -0.0000 0.0000
Call Option Values 0.0000 -0.0000 0.0000
Call Option Values 0.0000 -0.0000 -0.0000
Strike=15.00, Sigma= 0.40, Alpha= 0.00
Call Option Values 4.0624 4.2479 4.4312
Call Option Values 5.0624 5.2479 5.4312
Call Option Values 6.0624 6.2479 6.4312
Strike=20.00, Sigma= 0.40, Alpha= 0.00
Call Option Values -0.0000 0.0001 0.0108
Call Option Values 0.0985 0.3383 0.5781
Call Option Values 1.0830 1.3306 1.5749
Strike=25.00, Sigma= 0.40, Alpha= 0.00
Call Option Values 0.0000 0.0000 0.0000
Call Option Values 0.0000 -0.0000 0.0000
Call Option Values 0.0000 -0.0000 0.0000
Example 3¶
This example evaluates the price of a European Option using two payoff strategies: Cash-or-Nothing and Vertical Spread. In the first case the payoff function is
The value B is regarded as the bet on the asset price, see Wilmott et al. (1995, p. 39-40). The second case has the payoff function
Both problems use the same boundary conditions. Each case requires a separate integration of the Black-Scholes differential equation, but only the payoff function evaluation differs in each case. The sets of parameters in the computation are:
- Strike and bet prices \(K_1 = {10.0}\), \(K_2 = {15.0}\), and \(B = {2.0}\).
- Volatility \(\sigma = \{0.4\}\).
- Times until expiration = \(\{1/4, 1/2\}\).
- Interest rate \(r = 0.1\).
- \(x_{min} = 0\), \(x_{max} = 30\).
- \(nx = 61\), \(n = 3 \times nx = 183\).
from __future__ import print_function
from numpy import *
from pyimsl.math.feynmanKac import feynmanKac
from pyimsl.math.feynmanKacEvaluate import feynmanKacEvaluate
# These routines define the coefficients, payoff, boundary conditions
# and forcing term for American and European Options.
def fkcfiv_call(x, tx, iflag, value):
if iflag[0] == 0:
# The payoff function - Use flag passed to decide which
if type == 1:
# After reaching the strike price the payoff jumps
# from zero to the bet value.
if x > strike_price:
value[0] = bet
else:
value[0] = 0.0
else: # type=2
# Function is zero up to strike price.
# Then linear between strike price and spread.
# Then has constant value Spread-Strike Price after
# the value Spread.
value[0] = max(x - strike_price, 0.0) - max(x - spread, 0.0)
elif iflag[0] == -1:
# The coefficient derivative d sigma/ dx
value[0] = sigma
elif iflag[0] == 1:
# The coefficient sigma(x)
value[0] = sigma * x
elif iflag[0] == 2:
# The coefficient mu(x)
value[0] = (interest_rate - dividend) * x
elif iflag[0] == 3:
# The coefficient kappa(x)
value[0] = interest_rate
# Note that there is no time dependence
iflag[0] = 0
def fkbcp_call(nbc, tx, iflag, val):
if iflag[0] == 1:
val[0] = 1.0
val[1] = 0.0
val[2] = 0.0
val[3] = 0.0
val[4] = 0.0
val[5] = 1.0
val[6] = 0.0
val[7] = 0.0
val[8] = 0.0
val[9] = 0.0
val[10] = 1.0
val[11] = 0.0
# Note no time dependence in case (1) for IFLAG
iflag[0] = 0
if iflag[0] == 2:
# This is the discount factor using the risk-free
# interest rate
df = exp(interest_rate * tx)
# Use flag passed to decide on boundary condition
if type == 1:
val[0] = 1.0
val[1] = 0.0
val[2] = 0.0
val[3] = bet * df
else: # type=2
val[0] = 1.0
val[1] = 0.0
val[2] = 0.0
val[3] = (spread - strike_price) * df
val[4] = 0.0
val[5] = 1.0
val[6] = 0.0
val[7] = 0.0
val[8] = 0.0
val[9] = 0.0
val[10] = 1.0
val[11] = 0.0
NXGRID = 61
NTGRID = 2
NV = 12
# The strike price
strike_price = 10.0
# The spread value
spread = 15.0
# The Bet for the Cash-or-Nothing Call
bet = 2.0
# The sigma value
sigma = 0.4
# Time values for the options
nt = 2
time = [0.25, 0.5]
# Values of the underlying where evaluations are made
xs = empty(NV)
# Value of the interest rate and continuous dividend
interest_rate = 0.1
dividend = 0.0
# Values of the min and max underlying values modeled
x_min = 0.0
x_max = 30.0
# Define parameters for the integration step.
nint = NXGRID - 1
n = 3 * NXGRID
xgrid = empty(NXGRID)
yb = empty(0)
ybprime = empty(0)
yv = empty(0)
yvprime = empty(0)
fb = empty([NTGRID, NV])
fv = empty([NTGRID, NV])
# Number of left/right boundary conditions.
nlbcd = 3
nrbcd = 3
# Define an equally-spaced grid of points for the underlying
# price
dx = (x_max - x_min) / ((float)(nint))
xgrid[0] = x_min
xgrid[NXGRID - 1] = x_max
for i in range(2, NXGRID):
xgrid[i - 1] = xgrid[i - 2] + dx
for i in range(1, NV + 1):
xs[i - 1] = 2.0 + (i - 1) * 2.0
# Flag the difference in payoff functions
# 1 for the Bet, 2 for the Vertical Spread
type = 1
feynmanKac(nlbcd, nrbcd, xgrid, time,
fkcfiv_call, fkbcp_call, yb, ybprime)
type = 2
feynmanKac(nlbcd, nrbcd, xgrid, time,
fkcfiv_call, fkbcp_call, yv, yvprime)
# Evaluate solutions at vector of points XS(:), at each time value
# prior to expiration.
for i in range(0, NTGRID):
tmp = feynmanKacEvaluate(xgrid, xs, yb[i, ])
fb[i, :] = tmp
tmp = feynmanKacEvaluate(xgrid, xs, yv[i, ])
fv[i, :] = tmp
print(" European Option Value for A Bet")
print(" and a Vertical Spread, 3 and 6 Months Prior to Expiry")
print(" Number of equally spaced spline knots:%4d" % NXGRID)
print(" Number of unknowns:%4d" % n)
print(" Strike=%5.2f, Sigma=%5.2f, Interest Rate=%5.2f" %
(strike_price, sigma, interest_rate))
print(" Bet=%5.2f, Spread Value=%5.2f\n" % (bet, spread))
print(" Underlying A Bet Vertical Spread")
for i in range(0, NV):
print(" %9.4f%9.4f%9.4f%9.4f%9.4f" %
(xs[i], fb[0, i], fb[1, i], fv[0, i], fv[1, i]))
Output¶
European Option Value for A Bet
and a Vertical Spread, 3 and 6 Months Prior to Expiry
Number of equally spaced spline knots: 61
Number of unknowns: 183
Strike=10.00, Sigma= 0.40, Interest Rate= 0.10
Bet= 2.00, Spread Value=15.00
Underlying A Bet Vertical Spread
2.0000 0.0000 -0.0000 -0.0000 -0.0000
4.0000 0.0000 0.0000 -0.0000 0.0000
6.0000 0.0000 0.0112 -0.0000 0.0038
8.0000 0.0017 0.2686 -0.0000 0.1486
10.0000 1.0000 0.9948 0.0124 0.8898
12.0000 1.9983 1.6103 2.0000 2.1904
14.0000 2.0000 1.8650 4.0003 3.4267
16.0000 2.0000 1.9335 5.0003 4.2274
18.0000 2.0000 1.9477 5.0000 4.6261
20.0000 2.0000 1.9501 5.0000 4.7903
22.0000 2.0000 1.9505 5.0000 4.8493
24.0000 2.0000 1.9506 5.0000 4.8685
Example 4¶
This example evaluates the price of a convertible bond. Here, convertibility means that the bond may, at any time of the holder’s choosing, be converted to a multiple of the specified asset. Thus a convertible bond with price \(x\) returns an amount \(K\) at time \(T\) unless the owner has converted the bond to \(\nu x, \nu \geq 1\), units of the asset at some time prior to \(T\). This definition, the differential equation and boundary conditions are given in Chapter 18 of Wilmott et al. (1996). Using a constant interest rate and volatility factor, the parameters and boundary conditions are:
- Bond face value \(K = \{ 1 \}\), conversion factor \(\nu = 1.125\)
- Volatility \(\sigma = \{ 0.25 \}\)
- Times until expiration \(= \{ 1/2, 1 \}\)
- Interest rate \(r = 0.1\) , dividend \(D = 0.02\)
- \(x_{\min} = 0, \phantom{...} x_{\max} = 4\)
- \(nx = 61, \phantom{...} n = 3 \times nx = 183\)
- Boundary conditions \(f(0,t) = K \exp(-r(T-t)), f \left(x_{\max}, t \right) = vx_{\max}\)
- Terminal data \(f(x,T) = \max(K, \nu x)\)
- Constraint for bond holder \(f(x,t) \geq \nu x\)
Note that the error tolerance is set to a pure absolute error of value
\(10^{-3}\). The free boundary constraint \(f(x,t) \geq \nu x\) is
achieved by use of a non-linear forcing term in the function
fkforce_cbond
. The terminal conditions are provided with the user
function fkinit_cbond.
from __future__ import print_function
from numpy import *
from pyimsl.math.feynmanKac import feynmanKac
from pyimsl.math.feynmanKacEvaluate import feynmanKacEvaluate
# These routines define the coefficients, payoff, boundary conditions
# and forcing term.
def fkcfiv_cbond(x, tx, iflag, value):
if iflag[0] == 0:
# The payoff function
value[0] = max(factor * x, strike_price)
elif iflag[0] == -1:
# The coefficient derivative d sigma/ dx
value[0] = sigma
elif iflag[0] == 1:
# The coefficient sigma(x)
value[0] = sigma * x
elif iflag[0] == 2:
# The coefficient mu(x)
value[0] = (interest_rate - dividend) * x
elif iflag[0] == 3:
# The coefficient kappa(x)
value[0] = interest_rate
# Note that there is no time dependence
iflag[0] = 0
def fkbcp_cbond(nbc, tx, iflag, val):
if iflag[0] == 1:
dp = strike_price * exp(tx * interest_rate)
val[0] = 1.0
val[1] = 0.0
val[2] = 0.0
val[3] = dp
val[4] = 0.0
val[5] = 1.0
val[6] = 0.0
val[7] = 0.0
val[8] = 0.0
val[9] = 0.0
val[10] = 1.0
val[11] = 0.0
elif iflag[0] == 2:
val[0] = 1.0
val[1] = 0.0
val[2] = 0.0
val[3] = factor * x_max
val[4] = 0.0
val[5] = 1.0
val[6] = 0.0
val[7] = factor
val[8] = 0.0
val[9] = 0.0
val[10] = 1.0
val[11] = 0.0
# Note no time dependence
iflag[0] = 0
def fkforce_cbond(interval, ndeg, nxgrid, y,
time, width, xlocal, qw, u, phi, dphi):
local = 6
yl = empty(6)
bf = empty(6)
for i in range(0, local):
yl[i] = y[3 * interval - 3 + i]
phi[i] = 0.0
for i in range(0, local * local):
dphi[i] = 0.0
mu = 2
# This is the local definition of the forcing term -
# It "forces" the constraint f >= factor*x.
for j in range(1, local + 1):
for l in range(1, ndeg + 1):
bf[0] = u[(l - 1)]
bf[1] = u[(l - 1) + ndeg]
bf[2] = u[(l - 1) + 2 * ndeg]
bf[3] = u[(l - 1) + 6 * ndeg]
bf[4] = u[(l - 1) + 7 * ndeg]
bf[5] = u[(l - 1) + 8 * ndeg]
rt = 0.0
for k in range(0, local):
rt += yl[k] * bf[k]
rt = value / (rt + value - factor * xlocal[l - 1])
phi[j - 1] += qw[l - 1] * bf[j - 1] * pow(rt, mu)
for i in range(0, local):
phi[i] = -phi[i] * width * factor * strike_price
# This is the local derivative matrix for the forcing term
for j in range(1, local + 1):
for i in range(1, local + 1):
for l in range(1, ndeg + 1):
bf[0] = u[(l - 1)]
bf[1] = u[(l - 1) + ndeg]
bf[2] = u[(l - 1) + 2 * ndeg]
bf[3] = u[(l - 1) + 6 * ndeg]
bf[4] = u[(l - 1) + 7 * ndeg]
bf[5] = u[(l - 1) + 8 * ndeg]
rt = 0.0
for k in range(0, local):
rt += yl[k] * bf[k]
rt = 1.0 / (rt + value - factor * xlocal[l - 1])
dphi[i - 1 + (j - 1) * local] += qw[l - 1] * bf[i - 1] * \
bf[j - 1] * pow(value * rt, mu) * rt
for i in range(0, local * local):
dphi[i] = -mu * dphi[i] * width * factor * strike_price
def fkinit_cbond(nxgrid, ntgrid, xgrid, tgrid,
time, yprime, y, atol, rtol):
if time == 0.0:
# Set initial data precisely.
for i in range(1, nxgrid):
if xgrid[i - 1] * factor < strike_price:
y[3 * i - 3] = strike_price
y[3 * i - 2] = 0.0
y[3 * i - 1] = 0.0
else:
y[3 * i - 3] = xgrid[i - 1] * factor
y[3 * i - 2] = factor
y[3 * i - 1] = 0.0
# Compute value of a Convertible Bond
NXGRID = 61
NTGRID = 2
NV = 13
# The face value
strike_price = 1.0
# The sigma or volatility value
sigma = 0.25
# Time values for the options
time = [0.5, 1.0]
# Values of the underlying where evaluation are made
xs = empty(NV)
# Value of the interest rate, continuous dividend and factor
interest_rate = 0.1
dividend = 0.02
factor = 1.125
# Values of the min and max underlying values modeled
x_min = 0.0
x_max = 4.0
# Define parameters for the integration step.
nint = NXGRID - 1
n = 3 * NXGRID
xgrid = empty(NXGRID)
y = empty(0)
yprime = empty(0)
f = empty([NTGRID + 1, NV])
# Number of left/right boundary conditions.
nlbcd = 3
nrbcd = 3
# Define an equally-spaced grid of points for the
# underlying price
dx = (x_max - x_min) / float(nint)
xgrid[0] = x_min
xgrid[NXGRID - 1] = x_max
for i in range(2, NXGRID):
xgrid[i - 1] = xgrid[i - 2] + dx
for i in range(1, NV + 1):
xs[i - 1] = (i - 1) * 0.25
# Use a pure absolute error tolerance for the integration
value = 1.0e-3
# Compute value of convertible bond
feynmanKac(nlbcd, nrbcd, xgrid, time,
fkcfiv_cbond, fkbcp_cbond, y, yprime,
fcnInit=fkinit_cbond,
fcnForce=fkforce_cbond,
atolRtolScalars={'atol': 1.0e-3, 'rtol': 0.0e0})
# Evaluate and display solutions at vector of points XS(:),
# at each time value prior to expiration.
for i in range(0, NTGRID + 1):
tmp = feynmanKacEvaluate(xgrid, xs, y[i, ])
f[i, :] = tmp
print(" Convertible Bond Value, 0+, 6 and 12 Months Prior to Expiry")
print(" Number of equally spaced spline knots:%4d" % NXGRID)
print(" Number of unknowns:%4d" % n)
print(" Strike=%5.2f, Sigma=%5.2f" % (strike_price, sigma))
print(" Interest Rate=%5.2f, Dividend=%5.2f, Factor=%6.3f\n" %
(interest_rate, dividend, factor))
print(" Underlying Bond Value")
for i in range(0, NV):
print(" %8.4f%8.4f%8.4f%8.4f" %
(xs[i], f[0, i], f[1, i], f[2, i]))
Output¶
Convertible Bond Value, 0+, 6 and 12 Months Prior to Expiry
Number of equally spaced spline knots: 61
Number of unknowns: 183
Strike= 1.00, Sigma= 0.25
Interest Rate= 0.10, Dividend= 0.02, Factor= 1.125
Underlying Bond Value
0.0000 1.0000 0.9512 0.9048
0.2500 1.0000 0.9512 0.9049
0.5000 1.0000 0.9513 0.9065
0.7500 1.0000 0.9737 0.9605
1.0000 1.1250 1.1416 1.1464
1.2500 1.4062 1.4117 1.4121
1.5000 1.6875 1.6922 1.6922
1.7500 1.9688 1.9731 1.9731
2.0000 2.2500 2.2540 2.2540
2.2500 2.5312 2.5349 2.5349
2.5000 2.8125 2.8160 2.8160
2.7500 3.0938 3.0970 3.0970
3.0000 3.3750 3.3781 3.3781
Fatal Errors¶
IMSL_STOP_USER_FCN |
Request from user supplied function to stop algorithm. User flag = “#”. |