The COS method is a numerical technique in computational finance to efficiently price European plain vanilla put and call options provided the characteristic function of the logarithmic returns is given in closed-form. The main idea of the COS method is to approximate the (usually unknown) probability density function of the logarithmic returns by a cosine series expansion. The method has been developed in 2008 by Fang Fang and Cornelis W. Oosterlee.[1] The COS method converges for a large family of densities.[2] The COS method converges exponentially if the density of the logarithmic returns is smooth and has semi-heavy tails.[3]

Introduction edit

Consider a financial market with a bank-account paying a risk-free rate   and stock with deterministic price   today and random price   at some future date  . Let   be the expectation of   under the risk-neutral measure and assume the characteristic function   of the centralized log-returns   is given in closed-form. The characteristic function   is given explicitly for many models and processes, e.g., the Black-Scholes model, the variance gamma process, the CGMY model or the Heston model[4]. The price of a European put option with maturity   and strike   is given by the fundamental theorem of asset pricing by

 
where   is the probability density function of  . The price of a call option can be obtained by the put-call parity. Very often,   is not given explicitly and the COS method is used to approximate   and the price of the option.

The method edit

For some  , the density   is truncated and the truncated density is approximated by a cosine series expansion:

 
where for   the coefficients   are defined by
 
  denotes the real part of a complex number   and   is the imaginary unit. The price of a European put option can be approximated by[1]
 
where
 
The coefficients   are given in closed-form provided   is given analytically and the coefficients   can also be computed explicitly in important cases, e.g., for plain vanilla European put or call options and digital options. This makes the COS method numerically very efficient and robust.

Let  . For a European put option it holds that   if   and otherwise

 
where

 
and
 
To price a call option it is numerically more stable to price a put option instead and use the put-call parity.[1]

Parameters edit

To apply the COS method, one has to specify the truncation range   and the number of terms  . Let   be the error tolerance between the true price and its approximation by the COS method. If   is small enough and   has semi-heavy tails, the truncation range of a put option can be chosen using Markov’s inequality by[2][3]

 
where   is even and   is the  -th moment of  , which can be obtained using a computer algebra system and the following relation of   and the characteristic function:
 
Often,   is a reasonable choice.[2] In particular, the fourth moment can also be obtained by the Kurtosis and variance of   by  .

If   is also a   times differentiable function with bounded derivatives, the number of terms can then be chosen using integration by parts by[3]

 
  denotes the uniform norm. There is the following useful inequality:[3]
 
The last integral can be solved numerically with standard techniques and it is also given explicitly in some cases.[3]

Black-Scholes model edit

In the Black-Scholes model with volatility  , the stock price is modelled at maturity   by

 
where   is a standard normal random variable. The expectation of   under the risk-neutral measure is given by
 
and the characteristic function of   is given by
 
since   follows a normal distribution. The  -th moment of   for even   in the Black-Scholes model is given by
 
The derivative   of the probability density function of   is bounded by[3]
 
where   is the Gamma function.

Simple implementation edit

In the following code, the COS method is implemented in R for the Black-Scholes model to price European call option and the Heston model to price a put option.

########### Black-Scholes model ###########
#Characteristic function of the centralized log-returns in the BS model. params is volatility
phi = function(u, mat, params, S0, r){
  return(exp(-params^2 * mat * u^2 / 2))
}

#mu is equal to E[log(S_T)]
mu = function(mat, params, S0, r){
    return(log(S0) + r * mat - 1/2 * params^2 * mat)
}   

#cosine coefficients of the density.
ck = function(L, mat, N, params, S0, r){
  k = 0:N
  return(1 / L * Re(phi(k * pi / (2 * L), mat, params, S0, r) *  exp(1i * k * pi/2)))
}

#cosine coefficients of a put option, see Appendix Junike and Pankrashkin (2022).
vk = function(K, L, mat, N, params, S0, r){
  mymu = mu(mat, params, S0, r)   #mu = E[log(S_T)]
  d = min(log(K) - mymu, L)
  if(d <= -L)
    return(rep(0, N + 1)) #Return zero vector
  k = 0:N 
  psi0 = 2 * L / (k * pi) * (sin(k * pi * (d + L) / (2 * L)))
  psi0[1] = d + L
  tmp1 = k * pi / (2 * L) * sin( k * pi * (d + L) / (2 * L))
  tmp2 = cos(k * pi * (d + L) / (2 * L))
  tmp3 = 1 + (k * pi / (2 * L))^2
  psi1 = (exp(d) * (tmp1 + tmp2) - exp(-L)) / tmp3
  return(exp(-r * mat) * (K * psi0 - exp(mymu) * psi1))
}

#approximation of put option by COS method
put_COS = function(K, L, mat, N, params, S0, r){
  tmp = ck(L, mat, N, params, S0, r) * vk(K, L, mat, N, params, S0, r)
  tmp[1] = 0.5 * tmp[1] #First term is weighted by 1/2
  return(sum(tmp))
}

#approximation of call option by COS method using put-call parity
call_COS = function(K, L, mat, N, params, S0, r){
  return(put_COS(K, L, mat, N, params, S0, r) + S0 - K * exp(-r * mat))
}

#Price a call option in BS model by the COS method
eps = 10^-4 #error tolerance
K = 90 #strike
S0 = 100 #current stock price
r = 0.1 #interest rates
params = 0.2 #volatility
mat = 0.7 #maturity
n = 8 #number of moments to get truncation range
mu_n = (params * sqrt(mat))^n * prod(seq(1, n, by = 2)) #n-th moment of log-returns
L = (2 * K * exp(-r * mat) * mu_n / eps)^(1 / n) #Truncation range, Junike (2024, Eq. (3.10))
s = 40 #number of derivatives to determine the number of terms
boundDeriv = gamma(s / 2 + 1) * 2^(s / 2) / (pi * (params * sqrt(mat))^(s + 2))
tmp = 2^(s + 5 / 2) * boundDeriv * L^(s + 2) * 12 * K * exp(-r * mat)
N = ceiling((tmp / (s * pi^(s + 1) * eps))^(1 / s)) #Number of terms, Junike (2024, Sec. 6.1)
call_COS(K, L, mat, N, params, S0, r) #The price of call option is 17.24655.


########### Heston model ###########
#Use functions vk, put_COS, call_COS from the Black-Scholes model above.

#characteristic function of log-returns in the Heston with parameters params.
#The characteristic function is taken from Schoutens et. al (2004).
psiLogST_Heston = function(u, mat, params, S0, r){
  kappa = params[1] #speed of mean reversion
  theta = params[2] #level of mean reversion
  xi = params[3] #vol of vol
  rho = params[4] #correlation vol stock
  v0 = params[5] #initial vol
  d = sqrt((rho * xi * u * 1i - kappa)^2 - xi^2 * (-1i * u - u^2))
  mytmp = kappa - rho * xi * u * 1i
  g = (mytmp - d) / (mytmp + d)
  expdmat = exp(-d * mat)
  tmp0 = 1i * u * (log(S0) + r * mat)
  tmp1 = (mytmp - d) * mat - 2 * log((1 - g * expdmat) / (1 - g))
  tmp2 = theta * kappa * xi^(-2) * tmp1
  tmp3 = v0 * xi^(-2) * (mytmp - d) * (1 - expdmat) / (1 - g * expdmat)
  exp(tmp0 + tmp2 + tmp3)
}

library(Deriv) #There are much faster alternatives like SageMath.
psiLogST_Heston1=Deriv(psiLogST_Heston, "u") 

#mu is equal to E[log(S_T)]
mu = function(mat, params, S0, r){
    Re(-1i * psiLogST_Heston1(0, mat, params, S0, r))
}
#Characteristic function of centralized log-returns in the Heston model.
phi = function(u, mat, params, S0, r){
    psiLogST_Heston(u, mat, params, S0, r) * exp(-1i * u * mu(mat, params, S0, r))
}

#Derivatives of the characteristic function of the centralized log-returns in the Heston model.
phi1 = Deriv(phi, "u")
phi2 = Deriv(phi1, "u")
phi3 = Deriv(phi2, "u") #Takes very long but has to be done only once.
phi4 = Deriv(phi3, "u") #Takes very long but has to be done only once.
save(phi4, file = "phi4.RData") #save for later use. Load with load("phi4.RData").

#Price a put option in Heston model by the COS method.
eps = 10^-6 #error tolerance
K = 90 #strike
S0 = 100 #current stock price
r = 0.1 #interest rates
params = c(0.6067, 0.0707, 0.2928, -0.7571, 0.0654)
mat = 0.7 #maturity
mu_n = abs(phi4(0, mat, params, S0, r)) #4-th moment of log-returns. 
L = (2 * K * exp(-r * mat) * mu_n / eps)^(1 / 4) #Truncation range, Junike (2024, Eq. (3.10)). It is 36.99.
s = 20 #number of derivatives to determine the number of terms
integrand = function(u){1 / (2 * pi) * abs(u)^(s + 1) * abs(phi(u, mat, params, S0, r))} 
boundDeriv = integrate(integrand, -Inf, Inf)$value
tmp = 2^(s + 5 / 2) * boundDeriv * L^(s + 2) * 12 * K * exp(-r * mat)
N = ceiling((tmp / (s * pi^(s + 1) * eps))^(1 / s)) #Number of terms, Junike (2024, Sec. 6.1). It is 4418.   
put_COS(K, L, mat, N, params, S0, r) #The price of put option is 2.773954.

See also edit

References edit

  1. ^ a b c Fang, Fang; Oosterlee, Cornelis W. (2008). "A novel pricing method for European options based on Fourier-cosine series expansions". SIAM Journal on Scientific Computing. 31 (2): 826–848. doi:10.1137/080718061.
  2. ^ a b c Junike, Gero; Pankrashkin, Konstantin (2022). "Precise option pricing by the cos method–How to choose the truncation range" (PDF). Applied Mathematics and Computation. 451 (126935): 1–14. doi:10.1016/j.amc.2022.126935.
  3. ^ a b c d e f Junike, Gero (2024). "On the number of terms in the COS method for European option pricing" (PDF). Numerische Mathematik. doi:10.1007/s00211-024-01402-1.
  4. ^ Schoutens, Wim; Simons, Erwin; Tistaert, Jurgen (2004). "A perfect calibration! Now what?" (PDF). The Best of Wilmott: 66–78. doi:10.1002/wilm.42820040216 (inactive 2024-04-18).{{cite journal}}: CS1 maint: DOI inactive as of April 2024 (link)