Computing option prices for the Heston model

This note sketches how to compute option prices under the Heston model for an array of strikes and maturities. The example is based on the implementation described in Gilli and Schumann (2010/2011). (Many more explanations are in Gilli (2019), tough mostly with MATLAB code.)

The NMOF manual has for a long time1 contained a function priceMatrix, with an example for pricing with the characteristic function under Black–Scholes–Merton .

priceMatrix <- function(cf, S, Xvec, tauvec,
                        r, q = 0, ...,
                        nodes = NULL, weights = NULL,
                        n = 200) {

    if (is.null(nodes)) {
        tmp <- xwGauss(n)
        tmp <- changeInterval(tmp$nodes, tmp$weights,
                              oldmin = -1, oldmax = 1,
                              newmin =  0, newmax = 200)
        nodes <- tmp$nodes
        weights <- tmp$weights
    }

    callprices <- array(NA_real_, dim = c(length(Xvec), length(tauvec)))
    tmpmat <- array(NA_real_, dim = c(length(Xvec), length(weights)))

    inodes <- 1i * nodes
    itau <- 0L
    for (tau in tauvec) {
        itau <- itau + 1L
        cfi <- S * exp((r - q) * tau)
        cf1 <- cf(nodes - 1i, S, tau, r, q, ...)/inodes/cfi
        cf2 <- cf(nodes,      S, tau, r, q, ...)/inodes
        iX <- 0L
        for (X in Xvec) {
            iX <- iX + 1L
            if (itau == 1L)
                tmpmat[iX, ] <- exp(-inodes * log(X))
            P1 <- 0.5 + weights %*% Re(tmpmat[iX, ] * cf1)/pi
            P2 <- 0.5 + weights %*% Re(tmpmat[iX, ] * cf2)/pi
            callprices[iX, itau] <-
                exp(-q*tau) * S * P1 - exp(-r*tau) * X * P2
        }
   }
    callprices
}

We fix settings for the Heston model.

library("NMOF")

  Xvec <- 80:120
    tauvec <- c(c(3, 6, 9)/12,  ## 3, 6, 9 months
                1, 2, 3, 4, 5)  ## 1..5 years

    S <- 100
    r <- 0
    q <- 0
    v0 <- 0.2^2  ## variance, not volatility
    vT <- 0.2^2  ## variance, not volatility
    v <- vT
    rho <- -0.3
    k <- 0.5
    sigma <- 0.9

    n <- 50  ## number of nodes

Now for pricing.

## VARIANT 1: using priceMatrix
ans1 <- priceMatrix(cf = cfHeston, S, Xvec, tauvec, r, q = q,
                    v0 = v0, vT = vT, rho = rho, k=k, sigma = sigma,
                    n = n)
colnames(ans1) <- paste("tau", tauvec)
rownames(ans1) <- Xvec
## VARIANT 2: using callHestoncf
ans2 <- array(NA_real_, dim = c(length(Xvec), length(tauvec)))
colnames(ans2) <- paste("tau", tauvec)
rownames(ans2) <- Xvec


for (x in Xvec)
    for (tau in tauvec)
        ans2[x == Xvec, tau == tauvec] <-
            callHestoncf(X = x, tau = tau,
                         S = S, r = r, q =q, v0 = v0, vT = vT,
                         rho = rho, k = k, sigma = sigma,
                         rel.tol = .Machine$double.eps^0.5)
message("Max. rel. difference ",
        round(max(abs(ans1 - ans2)/ans2), 6))
Max. rel. difference 0.000609
## SPEED COMPARISON
library("rbenchmark")
benchmark(
  with_priceMatrix = priceMatrix(cf = cfHeston, S, Xvec, tauvec, r, q = q,
                                 v0 = v0, vT = vT, rho = rho, k = k, sigma = sigma,
                                 n = n),
  with_callHestoncf = for (x in Xvec)
                        for (tau in tauvec)
                          callHestoncf(X = x, tau = tau,
                                       S = S, r = r, q =q, v0 = v0, vT = vT,
                                       rho = rho, k = k, sigma = sigma,
                                       rel.tol = .Machine$double.eps^0.5),
  replications = 100, order = "relative")[, 1:4]
               test replications elapsed relative
1  with_priceMatrix          100   0.353    1.000
2 with_callHestoncf          100  24.954   70.691
@INCOLLECTION{Gilli2010,
  author       = {Manfred Gilli and Enrico Schumann},
  title        = {Calibrating the {H}eston Model with {D}ifferential
		  {E}volution},
  booktitle    = {EvoApplications 2010, Part II},
  publisher    = {Springer},
  year         = 2010,
  editor       = {Di Chio, Cecilia and Brabazon, Anthony and Di Caro,
		  Gianni A.  and Ebner, Marc and Farooq, Muddassar and
		  Fink, Andreas and Grahl, J{\"o}rn and Greenfield,
		  Gary and Machado, Penousal and O'Neill, Michael and
		  Tarantino, Ernesto and Urquhart, Neil},
  number       = 6025,
  series       = {Lecture Notes in Computer Science},
  pages        = {242--250}
}

@INCOLLECTION{Gilli2011,
  author       = {Manfred Gilli and Enrico Schumann},
  title        = {Calibrating Option Pricing Models with Heuristics},
  booktitle    = {Natural Computing in Computational Finance},
  publisher    = {Springer},
  year         = 2011,
  editor       = {Brabazon, Anthony and O'Neill, Michael and Maringer, Dietmar},
  volume       = 4,
}

@BOOK{Gilli2019,
  author       = {Gilli, Manfred and Maringer, Dietmar and Schumann, Enrico},
  title        = {Numerical Methods and Optimization in Finance},
  publisher    = {Elsevier/Academic Press},
  year         = 2019,
  edition      = 2
}

Footnotes:

1

The first commit in Git in 2012 (before that, I had used Subversion) already contains the function, and git log -S informs me that I have not touched the function since.