Skip to content

Instantly share code, notes, and snippets.

@wilsonfreitas
Created March 2, 2018 22:35
Show Gist options
  • Save wilsonfreitas/b1532177263d1b60581227898aab6b63 to your computer and use it in GitHub Desktop.
Save wilsonfreitas/b1532177263d1b60581227898aab6b63 to your computer and use it in GitHub Desktop.
#' Computes option price via Black-76 formula.
#'
#' Black-76 formula implementation, pricing european options.
#'
#' @param type 'call' for call option, any other value for put.
#' @param future current future price.
#' @param strike the strike price.
#' @param time time to option expiration in years.
#' @param rate the risk-free interest rate.
#' @param sigma volatility of the future price.
#'
#' @section Recycle rule:
#'
#' These arguments handle the recycle rule so vectors can be provided
#' and once those vectors differs in length the recycle rule is applied.
#'
#' @return
#' Vector with price of european options on futures, according to Black-76 model.
#'
#' @seealso
#' \code{\link{bsmprice}} for pricing options on stocks, with
#' Black-Scholes-Merton model.
#'
#' @examples
#' blackprice(c('put', 'call'), c(50, 49, 48, 47), 45, 0.25, 0.13, 0.3)
#' blackprice('p', 100:500/10, 45, 0.25, 0.13, 0.17)
#' blackprice('call', 50, 100:900/10, 2, 0.1, 0.18)
#'
#' @export
blackprice <- function(type, future, strike, time, rate, sigma) {
df <- data.frame(type, future, strike, time, rate, sigma)
with(df, {
d1 <- (log(future / strike) + (sigma * sigma/2) * time) / (sigma * sqrt(time))
d2 <- d1 - sigma * sqrt(time)
ifelse(tolower(as.character(type)) == "call", {
future * exp((- rate) * time) * pnorm(d1) - strike * exp(- rate * time) * pnorm(d2)
}, {
strike * exp(- rate * time) * pnorm(- d2) - future * exp((rate) * time) * pnorm(-d1)
})
})
}
#' Computes the desired greek letter from Black-76 model.
#'
#' Greeks formula implementation, from Black-76 model.
#'
#' @param greeks desired greek letter: 'delta', 'gamma', 'vega', 'rho' or 'theta'.
#' @param type 'call' for call option, any other value for put.
#' @param future current underlying forward price.
#' @param strike the strike price.
#' @param time time to option expiration in years.
#' @param rate the risk-free interest rate.
#' @param sigma volatility of the stock price.
#'
#' @section Recycle rule:
#'
#' These arguments handle the recycle rule so vectors can be provided
#' and once those vectors differs in length the recycle rule is applied.
#'
#' @return
#' Vector with desired greeks for input data.
#'
#' @examples
#' blackgreeks('delta', 'put', 20, 30, 0:2000/1000, 0.15, 0.25)
#'
#' @export
blackgreeks <- function(greeks=c('delta', 'gamma', 'vega', 'rho', 'theta'),
type, future, strike, time, rate, sigma) {
df <- data.frame(type, future, strike, time, rate, sigma)
greeks <- match.arg(greeks)
with(df, {
if(greeks=='delta') blackdelta(type, future, strike, time, rate, sigma)
if(greeks=='gamma') blackgamma(type, future, strike, time, rate, sigma)
if(greeks=='vega') blackvega(type, future, strike, time, rate, sigma)
if(greeks=='rho') blackrho(type, future, strike, time, rate, sigma)
if(greeks=='theta') blacktheta(type, future, strike, time, rate, sigma)
})
}
#' @rdname blackgreeks
#'
#' @section blackdelta:
#' Delta measures the rate of change of the theoretical option value with respect to changes in the underlying asset's price.
#'
#' @seealso
#' \code{\link{bsmdelta}} for delta from Black-Scholes-Merton model.
#'
#' @examples
#' blackdelta(c('call', 'put'), c(50, 49, 48, 47), c(45, 40), 0.25, 0.13, 0.2)
#' blackdelta('call', 50, 45, 0.25, 0.13, 1:1000/1000)
#' blackdelta('put', 20, 30, 0:2000/1000, 0.15, 0.25)
#'
#' @export
blackdelta <- function(type, future, strike, time, rate, sigma){
d1 <- (log(future / strike) + (sigma * sigma / 2) * time) / (sigma * sqrt(time))
if (tolower(as.character(type)) == "call"){
result <- exp((- rate) * time) * pnorm(d1)
} else {
result <- exp((- rate) * time) * (pnorm(d1) - 1)
}
result
}
#' @rdname blackgreeks
#'
#' @section blackvega:
#' Vega measures sensitivity of volatility.
#'
#' @seealso
#' \code{\link{bsmvega}} for vega from Black-Scholes-Merton model.
#'
#' @examples
#' blackvega(c('call', 'put'), c(50, 49, 48, 47), c(45, 40), 0.25, 0.13, 0.2)
#' blackvega('call', 50, 45, 0.25, 0.13, 1:1000/1000)
#' blackvega('put', 20, 30, 0:2000/1000, 0.15, 0.25)
#'
#' @export
blackvega <- function(type, future, strike, time, rate, sigma){
d1 <- (log(future / strike) + (sigma * sigma / 2) * time) / (sigma * sqrt(time))
result <- future * exp((- rate) * time) * dnorm(d1) * sqrt(time)
result
}
#' @rdname blackgreeks
#'
#' @section blackgamma:
#' Gamma measures the rate of change in the delta with respect to changes in the underlying price.
#'
#' @seealso
#' \code{\link{bsmgamma}} for gamma from Black-Scholes-Merton model.
#'
#' @examples
#' blackgamma(c('call', 'put'), c(50, 49, 48, 47), c(45, 40), 0.25, 0.13, 0.2)
#' blackgamma('call', 50, 45, 0.25, 0.13, 1:1000/1000)
#' blackgamma('put', 20, 30, 0:2000/1000, 0.15, 0.25)
#'
#' @export
blackgamma <- function(type, future, strike, time, rate, sigma){
d1 <- (log(future / strike) + (sigma * sigma / 2) * time) / (sigma * sqrt(time))
result <- exp((- rate) * time) * dnorm(d1)/(future * sigma * sqrt(time))
result
}
#' @rdname blackgreeks
#'
#' @section blacktheta:
#' Theta measures the sensitivity of the value of the derivative to the passage of time.
#'
#' @seealso
#' \code{\link{bsmtheta}} for theta from Black-Scholes-Merton model.
#'
#' @examples
#' blacktheta(c('call', 'put'), c(50, 49, 48, 47), c(45, 40), 0.25, 0.13, 0.2)
#' blacktheta('call', 50, 45, 0.25, 0.13, 1:1000/1000)
#' blacktheta('put', 20, 30, 0:2000/1000, 0.15, 0.25)
#'
#' @export
blacktheta <- function(type, future, strike, time, rate, sigma){
d1 <- (log(future / strike) + (sigma * sigma / 2) * time) / (sigma * sqrt(time))
d2 <- d1 - sigma * sqrt(time)
Theta1 <- -(future * exp((- rate) * time) * dnorm(d1) * sigma) / (2 * sqrt(time))
if (tolower(as.character(type)) == "call"){
result <- Theta1 - (- rate) * future * exp((- rate) * time) * pnorm(+ d1) - rate * strike * exp(- rate * time) * pnorm(+ d2)
} else {
result <- Theta1 + (- rate) * future * exp((- rate) * time) * pnorm(- d1) + rate * strike * exp(- rate * time) * pnorm(- d2)
}
result
}
#' @rdname blackgreeks
#'
#' @section blackrho:
#' Rho measures sensitivity to the interest rate: it is the derivative of the option value with respect to the risk free interest rate.
#'
#' @seealso
#' \code{\link{bsmrho}} for rho from Black-Scholes-Merton model.
#'
#' @examples
#' blackrho(c('call', 'put'), c(50, 49, 48, 47), c(45, 40), 0.25, 0.13, 0.2)
#' blackrho('call', 50, 45, 0.25, 0.13, 1:1000/1000)
#' blackrho('put', 20, 30, 0:2000/1000, 0.15, 0.25)
#'
#' @export
blackrho <- function(type, future, strike, time, rate, sigma){
d1 <- (log(future / strike) + (sigma * sigma / 2) * time) / (sigma * sqrt(time))
d2 <- d1 - sigma * sqrt(time)
CallPut = bsmprice(type, future, strike, time, rate, rate, sigma)
result <- - time * CallPut
result
}
#' Computes d1 from Black-76 model.
#'
#' d1 formula implementation, from Black-76 model.
#'
#' @param future current future price.
#' @param strike the strike price.
#' @param time time to option expiration in years.
#' @param rate the risk-free interest rate.
#' @param sigma volatility of the stock price.
#'
#' @section Recycle rule:
#'
#' These arguments handle the recycle rule so vectors can be provided
#' and once those vectors differs in length the recycle rule is applied.
#'
#' @return
#' Vector with d1.
#'
#' @seealso
#' \code{\link{bsmd1}} for d1 from Black-Scholes-Merton model.
#'
#' @examples
#' blackd1(c(50, 49, 48, 47), 45, 0.25, 0.13, 0.2)
#' blackd1(50, 45, 0.25, 0.13, 1:1000/1000)
#' blackd1(20, 30, 0:2000/1000, 0.15, 0.25)
#'
#' @export
blackd1 <- function(future, strike, time, rate, sigma) {
(log(future / strike) + (sigma * sigma / 2) * time) / (sigma * sqrt(time))
}
#' Computes a measure of moneyness for Black-76 model.
#'
#' Moneyness formula implementation, for Black-76 model.
#'
#' @param future current future price.
#' @param strike the strike price.
#'
#' @section Recycle rule:
#'
#' These arguments handle the recycle rule so vectors can be provided
#' and once those vectors differs in length the recycle rule is applied.
#'
#' @return
#' Vector with moneyness.
#'
#' @seealso
#' \code{\link{bsmmoneyness}} for one measure for moneyness in
#' Black-Scholes-Merton model.
#'
#' @examples
#' blackmoneyness(2:51, 1:50)
#' blackmoneyness(10, 50:150/10)
#' blackmoneyness(0:600/10, 30)
#'
#' @export
blackmoneyness <- function(future, strike) {
1 - future / strike
}
blackstrike <- function(future, delta, sigma, t) {
inv <- qnorm(delta)
ee <- sigma*sqrt(t)
strike <- future / ( exp((inv - 0.5*ee) * ee))
strike
}
#' Computes option price via Black-Scholes-Merton formula.
#'
#' Black-Scholes-Merton formula implementation, pricing european options.
#'
#' @param type 'call' for call option, any other value for put.
#' @param spot current stock price.
#' @param strike the strike price.
#' @param time time to option expiration in years.
#' @param rate the risk-free interest rate.
#' @param yield the dividends that are expected to be paid.
#' @param sigma volatility of the stock price.
#'
#' @section Recycle rule:
#'
#' These arguments handle the recycle rule so vectors can be provided
#' and once those vectors differs in length the recycle rule is applied.
#'
#' @return
#' Vector with price of european options on stocks, according to
#' Black-Scholes-Merton model.
#'
#' @seealso
#' \code{\link{blackprice}} for pricing options on futures, with Black-76 model.
#'
#' @examples
#' bsmprice(c('call', 'put'), c(50, 49, 48, 47), 45, 0.25, 0.13, 0.01, 0.2)
#' bsmprice('call', 50, 45, 0.25, 0.13, 0.01, 1:1000/1000)
#' bsmprice('put', 20, 30, 0:2000/1000, 0.15, 0, 0.25)
#'
#' @export
bsmprice <- function(type, spot, strike, time, rate, yield, sigma) {
df <- data.frame(type, spot, strike, time, rate, yield, sigma)
with(df, {
d1 <- (log(spot / strike) + (rate - yield + sigma * sigma/2) * time) / (sigma * sqrt(time))
d2 <- d1 - sigma * sqrt(time)
ifelse(tolower(as.character(type)) == "call", {
spot * exp((- yield) * time) * pnorm(d1) - strike * exp(- rate * time) * pnorm(d2)
}, {
strike * exp(- rate * time) * pnorm(- d2) - spot * exp((- yield) * time) * pnorm(-d1)
})
})
}
#' Computes d1 from Black-Scholes-Merton model.
#'
#' d1 formula implementation, from Black-Scholes-Merton model.
#'
#' @param spot current stock price.
#' @param strike the strike price.
#' @param time time to option expiration in years.
#' @param rate the risk-free interest rate.
#' @param yield the dividends that are expected to be paid.
#' @param sigma volatility of the stock price.
#'
#' @section Recycle rule:
#'
#' These arguments handle the recycle rule so vectors can be provided
#' and once those vectors differs in length the recycle rule is applied.
#'
#' @return
#' Vector with d1.
#'
#' @seealso
#' \code{\link{blackd1}} for d1 from Black-76 model.
#'
#' @examples
#' bsmd1(c(50, 49, 48, 47), 45, 0.25, 0.13, 0.01, 0.2)
#' bsmd1(50, 45, 0.25, 0.13, 0.01, 1:1000/1000)
#' bsmd1(20, 30, 0:2000/1000, 0.15, 0, 0.25)
#'
#' @export
bsmd1 <- function(spot, strike, time, rate, yield, sigma) {
(log(spot / strike) + (rate - yield + sigma * sigma / 2) * time) / (sigma * sqrt(time))
}
#' Computes the desired greek letter from Black-Scholes-Merton model.
#'
#' Greeks formula implementation, from Black-Scholes-Merton model.
#'
#' @param greeks desired greek letter: 'delta', 'gamma', 'vega', 'rho' or 'theta'.
#' @param type 'call' for call option, any other value for put.
#' @param spot current stock price.
#' @param strike the strike price.
#' @param time time to option expiration in years.
#' @param rate the risk-free interest rate.
#' @param yield the dividends that are expected to be paid.
#' @param sigma volatility of the stock price.
#'
#' @section Recycle rule:
#'
#' These arguments handle the recycle rule so vectors can be provided
#' and once those vectors differs in length the recycle rule is applied.
#'
#' @return
#' Vector with desired greeks for input data.
#'
#' @examples
#' bsmgreeks('delta', 'put', 20, 30, 0:2000/1000, 0.15, 0, 0.25)
#'
#' @export
bsmgreeks <- function(greeks=c('delta', 'gamma', 'vega', 'rho', 'theta'),
type, spot, strike, time, rate, yield, sigma) {
df <- data.frame(type, spot, strike, time, rate, yield, sigma)
greeks <- match.arg(greeks)
with(df, {
if(greeks=='delta') bsmdelta(type, spot, strike, time, rate, yield, sigma)
if(greeks=='gamma') bsmgamma(type, spot, strike, time, rate, yield, sigma)
if(greeks=='vega') bsmvega(type, spot, strike, time, rate, yield, sigma)
if(greeks=='rho') bsmrho(type, spot, strike, time, rate, yield, sigma)
if(greeks=='theta') bsmtheta(type, spot, strike, time, rate, yield, sigma)
})
}
#' @rdname bsmgreeks
#'
#' @section bsmdelta:
#' Delta greek letter measures the rate of change of the theoretical option value with respect to changes in the underlying asset's price.
#'
#' @seealso
#' \code{\link{blackdelta}} for delta from Black-76 model.
#'
#' @examples
#' bsmdelta(c('call', 'put'), c(50, 49, 48, 47), 45, 0.25, 0.13, 0.01, 0.2)
#' bsmdelta('call', 50, 45, 0.25, 0.13, 0.01, 1:1000/1000)
#' bsmdelta('put', 20, 30, 0:2000/1000, 0.15, 0, 0.25)
#'
#' @export
bsmdelta <- function(type, spot, strike, time, rate, yield, sigma){
d1 <- (log(spot / strike) + (rate - yield + sigma * sigma / 2) * time) / (sigma * sqrt(time))
if (tolower(as.character(type)) == "call"){
result <- exp((- yield) * time) * pnorm(d1)
} else {
result <- exp((- yield) * time) * (pnorm(d1) - 1)
}
result
}
#' @rdname bsmgreeks
#'
#' @section bsmvega:
#' Vega greek letter measures sensitivity of volatility.
#'
#' @seealso
#' \code{\link{blackvega}} for vega from Black-76 model.
#'
#' @examples
#' bsmvega(c('call', 'put'), c(50, 49, 48, 47), 45, 0.25, 0.13, 0.01, 0.2)
#' bsmvega('call', 50, 45, 0.25, 0.13, 0.01, 1:1000/1000)
#' bsmvega('put', 20, 30, 0:2000/1000, 0.15, 0, 0.25)
#'
#' @export
bsmvega <- function(type, spot, strike, time, rate, yield, sigma){
d1 <- (log(spot / strike) + (rate - yield + sigma * sigma / 2) * time) / (sigma * sqrt(time))
result <- spot * exp((- yield) * time) * dnorm(d1) * sqrt(time)
result
}
#' @rdname bsmgreeks
#'
#' @section bsmgamma:
#' Gamma greek letter measures the rate of change in the delta with respect to changes in the underlying price.
#'
#' @seealso
#' \code{\link{blackgamma}} for gamma from Black-76 model.
#'
#' @examples
#' bsmgamma(c('call', 'put'), c(50, 49, 48, 47), 45, 0.25, 0.13, 0.01, 0.2)
#' bsmgamma('call', 50, 45, 0.25, 0.13, 0.01, 1:1000/1000)
#' bsmgamma('put', 20, 30, 0:2000/1000, 0.15, 0, 0.25)
#'
#' @export
bsmgamma <- function(type, spot, strike, time, rate, yield, sigma){
d1 <- (log(spot / strike) + (rate - yield + sigma * sigma / 2) * time) / (sigma * sqrt(time))
result <- exp((- yield) * time) * dnorm(d1)/(spot * sigma * sqrt(time))
result
}
#' @rdname bsmgreeks
#'
#' @section bsmtheta:
#' Theta measures the sensitivity of the value of the derivative to the passage of time.
#
#' @seealso
#' \code{\link{blacktheta}} for theta from Black-76 model.
#'
#' @examples
#' bsmtheta(c('call', 'put'), c(50, 49, 48, 47), 45, 0.25, 0.13, 0.01, 0.2)
#' bsmtheta('call', 50, 45, 0.25, 0.13, 0.01, 1:1000/1000)
#' bsmtheta('put', 20, 30, 0:2000/1000, 0.15, 0, 0.25)
#'
#' @export
bsmtheta <- function(type, spot, strike, time, rate, yield, sigma){
d1 <- (log(spot / strike) + (rate - yield + sigma * sigma / 2) * time) / (sigma * sqrt(time))
d2 <- d1 - sigma * sqrt(time)
Theta1 <- -(spot * exp((- yield) * time) * dnorm(d1) * sigma) / (2 * sqrt(time))
if (tolower(as.character(type)) == "call"){
result <- Theta1 - (- yield) * spot * exp((- yield) * time) * pnorm(+ d1) -
rate * strike * exp(- rate * time) * pnorm(+ d2)
} else {
result <- Theta1 + (- yield) * spot * exp((- yield) * time) * pnorm(- d1) +
rate * strike * exp(- rate * time) * pnorm(- d2)
}
result
}
#' @rdname bsmgreeks
#'
#' @section bsmrho:
#' Rho measures sensitivity to the interest rate: it is the derivative of the option value with respect to the risk free interest rate.
#'
#' @seealso
#' \code{\link{blackrho}} for rho from Black-76 model.
#'
#' @examples
#' bsmrho(c('call', 'put'), c(50, 49, 48, 47), 45, 0.25, 0.13, 0.01, 0.2)
#' bsmrho('call', 50, 45, 0.25, 0.13, 0.01, 1:1000/1000)
#' bsmrho('put', 20, 30, 0:2000/1000, 0.15, 0, 0.25)
#'
#' @export
bsmrho <- function(type, spot, strike, time, rate, yield, sigma){
d1 <- (log(spot / strike) + (rate - yield + sigma * sigma / 2) * time) / (sigma * sqrt(time))
d2 <- d1 - sigma * sqrt(time)
CallPut = bsmprice(type, spot, strike, time, rate, yield, sigma)
if (tolower(as.character(type)) == "call") {
if (rate - yield != 0) {
result <- time * strike * exp(- rate * time) * pnorm(d2)
}
else {
result <- - time * CallPut
}
} else {
if (rate - yield != 0) {
result <- - time * strike * exp(- rate * time) * pnorm(- d2)
}
else {
result <- - time * CallPut
}
}
result
}
#' Computes a measure of moneyness for Black-Scholes-Merton model.
#'
#' Moneyness formula implementation, for Black-Scholes-Merton model.
#'
#' @param spot current stock price.
#' @param strike the strike price.
#' @param time time to option expiration in years.
#' @param rate the risk-free interest rate.
#' @param yield the dividends that are expected to be paid.
#'
#' @section Recycle rule:
#'
#' These arguments handle the recycle rule so vectors can be provided
#' and once those vectors differs in length the recycle rule is applied.
#'
#' @return
#' Vector with moneyness.
#'
#' @seealso
#' \code{\link{blackmoneyness}} for one measure for moneyness in Black-76 model.
#'
#' @examples
#' bsmmoneyness(0:49, 1:50, 3, 0.1, 0)
#' bsmmoneyness(10, 10, 1, 0.12, 0:1000/1000)
#' bsmmoneyness(10, 10, 1, 0:1000/1000, 0)
#'
#' @export
bsmmoneyness <- function(spot, strike, time, rate, yield) {
1 - spot * exp( - yield * time) / (strike * exp( - rate * time))
}
#' Computes DI future option price via Black-Scholes formula.
#'
#' Black-Scholes formula implementation, pricing DI future options.
#'
#' @param type 'call' for call option, 'put' for put option. May be abbreviated.
#' @param short_price unit price, based on the date of option expiration.
#' @param long_price unit price, based on the date of underlying asset expiration.
#' @param strike the strike price.
#' @param short_t_cur time to option expiration in years, based in current days.
#' @param long_t_cur time to underlying asset expiration in years, based in
#' current days.
#' @param short_t_biz time to option expiration in years, based in working days.
#' @param long_t_biz time to underlying asset expiration in years, based in
#' working days.
#' @param sigma volatility of the underlying asset price.
#'
#' @return
#' DI future option price.
#'
#' @seealso
#' \code{\link{bsmprice}} for pricing options on stocks, with
#' Black-Scholes-Merton model. \\
#' \code{\link{blackprice}} for pricing options
#' on futures, with Black-76 model.
#'
#' @examples
#' blackDI('call', 110000, 100000, 0.14, 1, 2, 1, 2, 0.15)
#' blackDI('p', 100000, 90000, 0.08, 1, 2, 1, 2, 0.15)
#'
#' @export
blackDI <- function(type = c("call", "put"), short_price, long_price, strike,
short_t_cur, long_t_cur, short_t_biz, long_t_biz, sigma) {
type <- match.arg(type)
df <- data.frame(short_price, long_price, strike,
short_t_cur, long_t_cur, short_t_biz, long_t_biz, sigma)
df <- within(df, {
Kast <- ((1 + strike) ^ (long_t_biz - short_t_biz) - 1) * 1 / (long_t_cur - short_t_cur)
Szero <- (short_price / long_price - 1) * 1 / (long_t_cur - short_t_cur)
delta <- (long_price * (long_t_cur - short_t_cur)) / (1 + Kast * (long_t_cur - short_t_cur))
d1 <- (log(Szero / Kast) + (sigma * sigma) / 2 * short_t_cur) / (sigma * (short_t_cur) ^ 0.5)
d2 <- d1 - sigma * (short_t_cur) ^ 0.5
})
with(df,
ifelse(type == "call",
delta * (Szero * pnorm(d1) - Kast * pnorm(d2)),
delta * ( - Szero * pnorm( - d1) + Kast * pnorm( - d2))
))
}
#' @export
DELTAS <- c(1, 5, 10, 25, 35, 37, 50, 65, 67, 75, 90, 95, 99)
# Default SABR initial parameters
#' @export
ALPHA <- 0.001
#' @export
NU <- 0.001
#' @export
RHO <- 0.3
#' @export
BETA <- 0
# Default SVI initial parameters
#' @export
SVI.A <- 1e-12
#' @export
SVI.B <- 1e-8
#' @export
SVI.SIGMA <- 1e-8
#' @export
SVI.RHO <- -0.8
#' @export
SVI.M <- 1e-8
#' @export
SVI.ERR <- 0.02
# Default corrado-su initial parameters
#' @export
CS.SKEW <- 1e-8
#' @export
CS.KURT <- 1
#' @export
CS.SIGMA <- 1e-8
#' Computes w from modified Corrado-Su model.
#'
#' w formula implementation from modified Corrado-Su model.
#'
#' @param sigma volatility of the stock price.
#' @param time time to option expiration in years.
#' @param mu3 skewness.
#' @param mu4 kurtosis.
#'
#' @section Recycle rule:
#'
#' These arguments handle the recycle rule so vectors can be provided
#' and once those vectors differs in length the recycle rule is applied.
#'
#' @return
#' Vector with w.
#'
#' @seealso
#' \code{\link{csmd}} for d from modified Corrado-Su model.\\
#' \code{\link{csmq3}} for Q_3 from modified Corrado-Su model.\\
#' \code{\link{csmq4}} for Q_4 from modified Corrado-Su model.\\
#' \code{\link{csmprice}} for modified Corrado-Su option pricing formula.
#'
#' @examples
#' csmw(0.25, 1.5, 0, -100:100/100)
#' csmw(0.25, 1.5, -100:100/100, 3)
#' csmw(0:100/100, 1.5, 0, 3)
#'
#' @export
csmw <- function(sigma, time, mu3, mu4) {
(mu3 / 6) * (sigma ^ 3) * (sqrt(time ^ 3)) + (mu4 / 24) * (sigma ^ 4) * (time ^ 2)
}
#' Computes d from modified Corrado-Su model.
#'
#' d formula implementation from modified Corrado-Su model.
#'
#' @param spot current stock price.
#' @param strike the strike price.
#' @param time time to option expiration in years.
#' @param rate the risk-free interest rate.
#' @param yield the dividends that are expected to be paid.
#' @param sigma volatility of the stock price.
#' @param w constant from modified Corrado-Su model.
#'
#' @section Recycle rule:
#'
#' These arguments handle the recycle rule so vectors can be provided
#' and once those vectors differs in length the recycle rule is applied.
#'
#' @return
#' Vector with d.
#'
#' @seealso
#' \code{\link{csmw}} for w from modified Corrado-Su model.\\
#' \code{\link{csmq3}} for Q_3 from modified Corrado-Su model.\\
#' \code{\link{csmq4}} for Q_4 from modified Corrado-Su model.\\
#' \code{\link{csmprice}} for modified Corrado-Su option pricing formula.
#'
#' @examples
#' csmd(14, 120:160/10, 0.5, 0.15, 0.1, 0.18, csmw(0.18, 0.5, 0, 3))
#' csmd(14, 13, 0.5, 0.15, 0.1, 0.18, csmw(0.18, 0.5, -1000:1000/100, 3))
#' csmd(14, 13, 0.5, 0.15, 0.1, 0.18, csmw(0.18, 0.5, 0, -1000:1000/100))
#'
#' @export
csmd <- function(spot, strike, time, rate, yield, sigma, w) {
(log(spot / strike) + (rate - yield + 0.5 * sigma ^ 2) * time - log(1 + w)) / (sigma * sqrt(time))
}
#' Computes Q_3 from modified Corrado-Su model.
#'
#' Q_3 formula implementation from modified Corrado-Su model.
#'
#' @param spot current stock price.
#' @param sigma volatility of the stock price.
#' @param time time to option expiration in years.
#' @param d1 d value from modified Corrado-Su model.
#' @param w constant from modified Corrado-Su model.
#'
#' @section Recycle rule:
#'
#' These arguments handle the recycle rule so vectors can be provided
#' and once those vectors differs in length the recycle rule is applied.
#'
#' @return
#' Vector with Q_3.
#'
#' @seealso
#' \code{\link{csmw}} for w from modified Corrado-Su model.\\
#' \code{\link{csmd}} for d from modified Corrado-Su model.\\
#' \code{\link{csmq4}} for Q_4 from modified Corrado-Su model.\\
#' \code{\link{csmprice}} for modified Corrado-Su option pricing formula.
#'
#' @examples
#' spot <- 25
#' sigma <- 0.2
#' time <- 0.5
#' strike <- 26
#' rate <- 0.12
#' yield <- 0
#' skewness <- 0
#' kurtosis <- 3
#' w <- csmw(sigma, time, skewness, kurtosis)
#' d <- csmd(spot, strike, time, rate, yield, sigma, w)
#' csmq3(spot, sigma, time, d, w)
#' csmq3(200:320/10, sigma, time, d, w)
#'
#' @export
csmq3 <- function(spot, sigma, time, d1, w){
q3 <- spot * sigma * sqrt(time) / (6 * (1 + w))
q3 * (2 * sigma * sqrt(time) - d1) * dnorm(d1)
}
#' Computes Q_4 from modified Corrado-Su model.
#'
#' Q_4 formula implementation from modified Corrado-Su model.
#'
#' @param spot current stock price.
#' @param sigma volatility of the stock price.
#' @param time time to option expiration in years.
#' @param d1 d value from modified Corrado-Su model.
#' @param w constant from modified Corrado-Su model.
#'
#' @section Recycle rule:
#'
#' These arguments handle the recycle rule so vectors can be provided
#' and once those vectors differs in length the recycle rule is applied.
#'
#' @return
#' Vector with Q_4.
#'
#' @seealso
#' \code{\link{csmw}} for w from modified Corrado-Su model.\\
#' \code{\link{csmd}} for d from modified Corrado-Su model.\\
#' \code{\link{csmq3}} for Q_3 from modified Corrado-Su model.\\
#' \code{\link{csmprice}} for modified Corrado-Su option pricing formula.
#'
#' @examples
#' spot <- 25
#' sigma <- 0.2
#' time <- 0.5
#' strike <- 26
#' rate <- 0.12
#' yield <- 0
#' skewness <- 0
#' kurtosis <- 3
#' w <- csmw(sigma, time, skewness, kurtosis)
#' d <- csmd(spot, strike, time, rate, yield, sigma, w)
#' csmq4(spot, sigma, time, d, w)
#' csmq4(200:320/10, sigma, time, d, w)
#'
#' @export
csmq4 <- function(spot, sigma, time, d1, w){
q4 <- (d1 ^ 2 - 3 * d1 * sigma * sqrt(time) + 3 * sigma ^ 2 * time - 1) * dnorm(d1)
q4 <- q4 * spot * sigma * sqrt(time) / (24 * (1 + w))
return(q4)
}
#' Computes option price with modified Corrado-Su model.
#'
#' Implementation of modified Corrado-Su formula for option pricing.
#'
#' @param type 'call' for call option, any other value for put.
#' @param spot current stock price.
#' @param strike the strike price.
#' @param time time to option expiration in years.
#' @param rate the risk-free interest rate.
#' @param yield the dividends that are expected to be paid.
#' @param sigma volatility of the stock price.
#' @param mu3 skewness.
#' @param mu4 kurtosis.
#'
#' @section Recycle rule:
#'
#' These arguments handle the recycle rule so vectors can be provided
#' and once those vectors differs in length the recycle rule is applied.
#'
#' @return
#' Vector with option prices.
#'
#' @seealso
#' \code{\link{csmw}} for w from modified Corrado-Su model.\\
#' \code{\link{csmd}} for d from modified Corrado-Su model.\\
#' \code{\link{csmq3}} for Q_3 from modified Corrado-Su model.\\
#' \code{\link{csmq4}} for Q_4 from modified Corrado-Su model.
#'
#' @examples
#' spot <- 25
#' sigma <- 0.2
#' time <- 0.5
#' strike <- 26
#' rate <- 0.12
#' yield <- 0
#' skewness <- 1
#' kurtosis <- 4
#' csmprice(c('call', 'put'), spot, strike, time, rate, yield, sigma, 0, 3) ==
#' bsmprice(c('call', 'put'), spot, strike, time, rate, yield, sigma)
#' csmprice('put', spot, strike, time, rate, 0:100/100, sigma, skewness, kurtosis)
#'
#' @export
csmprice <- function(type, spot, strike, time, rate, yield, sigma, mu3, mu4) {
c_bs <- bsmprice('call', spot, strike, time, rate, yield, sigma)
w <- csmw(sigma, time, mu3, mu4)
d1 <- csmd(spot, strike, time, rate, yield, sigma, w)
q3 <- csmq3(spot, sigma, time, d1, w)
q4 <- csmq4(spot, sigma, time, d1, w)
callp <- c_bs + mu3 * q3 + (mu4 - 3) * q4
df <- data.frame(callp, type, spot, yield, time, rate, strike)
with(df, ifelse(tolower(as.character(type)) == 'call', callp, callp - spot * exp(- yield * time) + strike * exp(- rate * time)))
}
#' Computes Vega from modified Corrado-Su model.
#'
#' This function computes vega greek for an option based on modified Corrado-Su
#' pricing model.
#'
#' @param type 'call' for call option, any other value for put.
#' @param spot current stock price.
#' @param strike the strike price.
#' @param time time to option expiration in years.
#' @param rate the risk-free interest rate.
#' @param yield the dividends that are expected to be paid.
#' @param sigma volatility of the stock price.
#' @param mu3 skewness.
#' @param mu4 kurtosis.
#'
#' @return
#' Vector with options Vega.
#'
#' @examples
#' csmvega('call', 14, 13, 0.1, 0.15, 0, 0.5, 0.18, 3.53)
#' csmvega('put', 14, 13, 0.1, 0.15, 0, 0.5, 0.18, 2.53)
#'
#' @export
csmvega <- function(type, spot, strike, time, rate, yield, sigma, mu3, mu4){
dsig <- 0.001
csmu <- csmprice(type, spot, strike, time, rate, yield, sigma + dsig, mu3, mu4)
csmd <- csmprice(type, spot, strike, time, rate, yield, sigma - dsig, mu3, mu4)
return((csmu - csmd) / (2 * dsig))
}
#' Computes Objective Function for Optimizing Modified Corrado-Su Parameters.
#'
#' This is an auxiliary function that provides the objective function used in
#' the optimization to obtain the Modified Corrado-Su parameters.
#'
#' @param par a 3-dimension vector with the parameters
#' @param spot current stock price.
#' @param strike the strike price.
#' @param rate the risk-free interest rate.
#' @param time time to option expiration in years.
#' @param y.data known prices for options.
#' @param sy.data known standard deviation for prices for options.
#'
#' @return
#' Normalized error between prices obtained from data and the ones obtained
#' from model.
#'
#' @export
f.optim.csm <- function(par, spot, strike, rate, time, y.data, sy.data){
sigma <- par[1]
mu3 <- par[2]
mu4 <- par[3]
yf <- csmprice(spot, strike, sigma, rate, time, mu3, mu4)
return(sum(((yf - y.data) / sy.data) ^ 2))
}
#' Computes Inequality Constraint for Optimizing Modified Corrado-Su Parameters.
#'
#' This is an auxiliary function that provides the inequality constraint used in
#' the optimization to obtain the Modified Corrado-Su parameters.
#'
#' @param par a 3-dimension vector with the parameters
#' @param spot current stock price.
#' @param strike the strike price.
#' @param rate the risk-free interest rate.
#' @param time time to option expiration in years.
#' @param y.data known prices for options.
#' @param sy.data known standard deviation for prices for options.
#'
#' @return
#' Constraint to be considered in the optimization.
#'
#' @export
g_ineq <- function(par, spot, strike, rate, time, y.data, sy.data){
mu3 <- par[2]
mu4 <- par[3]
return(abs(mu3) - skew_gram(mu4))
#tst <- abs(mu3)-skew_gram(mu4)
#if(tst > 0){
# print(paste(mu3, mu4, tst, sep=' '))
#}
#return(tst)
}
#' Computes Gradient of Inequality Constraint for Optimizing Modified Corrado-Su Parameters.
#'
#' This is an auxiliary function that provides the gradient for inequality constraint
#' used in the optimization to obtain the Modified Corrado-Su parameters.
#'
#' @param par a 3-dimension vector with the parameters
#' @param spot current stock price.
#' @param strike the strike price.
#' @param rate the risk-free interest rate.
#' @param time time to option expiration in years.
#' @param y.data known prices for options.
#' @param sy.data known standard deviation for prices for options.
#'
#' @return
#' Three-dimension vector with the gradient of the inequality constraint.
#'
#' @export
grad_ineq <- function(par, spot, strike, rate, time, y.data, sy.data){
return(c(0, 1, 1))
}
#' Computes Gradient of the Objective Function for Optimizing Modified Corrado-Su Parameters.
#'
#' This is an auxiliary function that provides the gradient of the objective
#' function used in the optimization to obtain the Modified Corrado-Su parameters.
#'
#' @param par a 3-dimension vector with the parameters
#' @param type 'call' for call option, any other value for put.
#' @param spot current stock price.
#' @param strike the strike price.
#' @param time time to option expiration in years.
#' @param rate the risk-free interest rate.
#' @param yield the dividends that are expected to be paid.
#' @param y.data known prices for options.
#' @param sy.data known standard deviation for prices for options.
#'
#' @return
#' Matrix with the gradient components of the objective function.
#'
#' @export
grad <- function(par, type, spot, strike, time, rate, yield, y.data, sy.data){
#numeric vega for Corrado-Su mod
dcs.dsig <- csmvega(type, spot, strike, time, rate, yield,
par[1], par[2], par[3])
#other derivatives for the gradient
csmw <- csmw(par[1], time, par[2], par[3])
dmod <- csmd(spot, strike, 0, par[1], time, csmw)
q3 <- csmq3(spot, par[1], time, dmod, csmw)
q4 <- csmq4(spot, par[1], time, dmod, csmw)
premium <- csmprice(spot, strike, par[1], rate, time, par[2], par[3])
grad1 <- sum(2 * dcs.dsig * (premium - y.data) / (sy.data ^ 2))
grad2 <- sum(2 * q3 * (premium - y.data) / (sy.data ^ 2))
grad3 <- sum(2 * q4 * (premium - y.data) / (sy.data ^ 2))
return(cbind(grad1, grad2, grad3))
}
#' Computes Objective Function for Optimizing Bi-Modal Corrado-Su Parameters.
#'
#' This is an auxiliary function that provides the objective function used in
#' the optimization to obtain the Bi-Modal Corrado-Su parameters.
#'
#' @param par a 7-dimension vector with the parameters
#' @param spot current stock price.
#' @param strike the strike price.
#' @param rate the risk-free interest rate.
#' @param time time to option expiration in years.
#' @param y.data known prices for options.
#' @param sy.data known standard deviation for prices for options.
#'
#' @return
#' Normalized error between prices obtained from data and the ones obtained
#' from model.
#'
#' @export
f.optim.2csm <- function(par, spot, strike, rate, time, y.data, sy.data){
sigma1 <- par[1]
mu31 <- par[2]
mu41 <- par[3]
sigma2 <- par[4]
mu32 <- par[5]
mu42 <- par[6]
w <- par[7]
yf1 <- csmprice(spot, strike, sigma1, rate, time, mu31, mu41)
yf2 <- csmprice(spot, strike, sigma2, rate, time, mu32, mu42)
yf <- yf1 * w + yf2 * (1 - w)
return(sum(((yf - y.data) / sy.data) ^ 2))
}
#' @rdname f.optim.2csm
#' @export
csm.2opt <- function(par, spot, strike, rate, time){
sigma1 <- par[1]
mu31 <- par[2]
mu41 <- par[3]
sigma2 <- par[4]
mu32 <- par[5]
mu42 <- par[6]
w <- par[7]
yf1 <- csmprice(spot, strike, sigma1, rate, time, mu31, mu41)
yf2 <- csmprice(spot, strike, sigma2, rate, time, mu32, mu42)
return(yf1 * w + yf2 * (1 - w))
}
#' Computes Inequality Constraint for Optimizing Bi-Modal Corrado-Su Parameters.
#'
#' This is an auxiliary function that provides the inequality constraint used in
#' the optimization to obtain the Bi-Modal Corrado-Su parameters.
#'
#' @param par a 7-dimension vector with the parameters
#' @param spot current stock price.
#' @param strike the strike price.
#' @param rate the risk-free interest rate.
#' @param time time to option expiration in years.
#' @param y.data known prices for options.
#' @param sy.data known standard deviation for prices for options.
#'
#' @return
#' Constraint to be considered in the optimization.
#'
#' @export
g_ineq2 <- function(par, spot, strike, rate, time, y.data, sy.data){
mu31 <- par[2]
mu41 <- par[3]
mu32 <- par[5]
mu42 <- par[6]
tst1 <- abs(mu31) - skew_gram(mu41)
tst2 <- abs(mu32) - skew_gram(mu42)
if(tst1 > 0 || tst2 > 0){
#print('tst > 0')
return(1)
}else{
#print('tst < 0')
return(-1)
}
}
#' Computes Gradient of Inequality Constraint for Optimizing Bi-Modal Corrado-Su Parameters.
#'
#' This is an auxiliary function that provides the gradient for inequality constraint
#' used in the optimization to obtain the Bi-Modal Corrado-Su parameters.
#'
#' @param par a 7-dimension vector with the parameters
#' @param spot current stock price.
#' @param strike the strike price.
#' @param rate the risk-free interest rate.
#' @param time time to option expiration in years.
#' @param y.data known prices for options.
#' @param sy.data known standard deviation for prices for options.
#'
#' @return
#' Three-dimension vector with the gradient of the inequality constraint.
#'
#' @export
grad_ineq2 <- function(par, spot, strike, rate, time, y.data, sy.data){
return(c(0, 1, 1, 0, 1, 1, 0))
}
#' Computes Gradient of the Objective Function for Optimizing Bi-Modal Corrado-Su Parameters.
#'
#' This is an auxiliary function that provides the gradient of the objective
#' function used in the optimization to obtain the Bi-Modal Corrado-Su parameters.
#'
#' @param par a 7-dimension vector with the parameters
#' @param type 'call' for call option, any other value for put.
#' @param spot current stock price.
#' @param strike the strike price.
#' @param time time to option expiration in years.
#' @param rate the risk-free interest rate.
#' @param yield the dividends that are expected to be paid.
#' @param y.data known prices for options.
#' @param sy.data known standard deviation for prices for options.
#'
#' @return
#' Matrix with the gradient components of the objective function.
#'
#' @export
grad2 <- function(par, type, spot, strike, time, rate, yield, y.data, sy.data){
#numeric vega for Corrado-Su mod
dcs.dsig1 <- csmvega(type, spot, strike, time, rate,
yield, par[1], par[2], par[3])
dcs.dsig2 <- csmvega(type, spot, strike, time, rate,
yield, par[4], par[5], par[6])
#other derivatives for the gradient
csmw1 <- csmw(par[1], time, par[2], par[3])
dmod1 <- csmd(spot, strike, 0, par[1], time, csmw1)
q31 <- csmq3(spot, par[1], time, dmod1, csmw1)
q41 <- csmq4(spot, par[1], time, dmod1, csmw1)
csmw2 <- csmw(par[4], time, par[5], par[6])
dmod2 <- csmd(spot, strike, 0, par[4], time, csmw2)
q32 <- csmq3(spot, par[4], time, dmod2, csmw2)
q42 <- csmq4(spot, par[4], time, dmod2, csmw2)
premium1 <- csmprice(spot, strike, par[1], rate, time, par[2], par[3])
premium2 <- csmprice(spot, strike, par[4], rate, time, par[5], par[6])
grad1 <- sum(2 * dcs.dsig1 * par[7] * (premium1 - y.data) / (sy.data ^ 2))
grad2 <- sum(2 * q31 * par[7] * (premium1 - y.data) / (sy.data ^ 2))
grad3 <- sum(2 * q41 * par[7] * (premium1 - y.data) / (sy.data ^ 2))
grad4 <- sum(2 * dcs.dsig2 * par[7] * (premium2 - y.data) / (sy.data ^ 2))
grad5 <- sum(2 * q32 * par[7] * (premium2 - y.data) / (sy.data ^ 2))
grad6 <- sum(2 * q42 * par[7] * (premium2 - y.data) / (sy.data ^ 2))
grad7 <- sum((premium1 - premium2) * (premium2 - y.data) / (sy.data ^ 2))
#print('grd')
#print(cbind(grad1, grad2, grad3, grad4, grad5, grad6, grad7))
return(cbind(grad1, grad2, grad3, grad4, grad5, grad6, grad7))
}
#' Modified Corrado-Su std. Deltas
#'
#' Finds and returns a dataframe with the strikes and implied
#' volatilites for the provided set of deltas or for the
#' default deltas.
#'
#' @param type Option type, "call" for calls or anything else for puts
#' @param spot underlying spot price at a given date
#' @param time time in years from a given date to a maturity date
#' @param rate interest free rate
#' @param yield dividend yield
#' @param sigma volatility of the stock price
#' @param mu3 skewness
#' @param mu4 kurtosis
#' @param deltas deltas for which strikes and vols should be calculated, 1-99. Defaults to usual
#' delta values.
#'
#' @return
#' Dataframe with columns delta, strike and impvol
#' @export
csm_deltas <- function(type, spot, time, rate, yield, sigma, mu3, mu4, deltas = DELTAS) {
ft <- spot * exp((rate - yield) * time)
eps <- 1e-8
strk_cs <- sapply(deltas/100, function(delta) {
uniroot(f=csm_strikes, interval=c(1, spot*10), tol=eps,
delta=delta, type=type, spot=spot, time=time,
rate=rate, yield=yield, sigma=sigma, mu3=mu3, mu4=mu4, ft=ft)$root
})
impvol_cs <-blackimpvol(csmprice(type, spot, strk_cs, time, rate, yield, sigma, mu3, mu4),
type, ft, strk_cs, time, rate)
data.frame(delta=deltas, strike=strk_cs, impvol=impvol_cs)
}
csm_strikes <- function(x, delta, type, spot, strike, time, rate, yield, sigma, mu3, mu4, ft) {
csp <- csmprice(type, spot, x, time, rate, yield, sigma, mu3, mu4)
if (csp < 0) return(999)
varx <- blackimpvol(csp, type, ft, x, time, rate)
x - blackstrike(ft, delta, varx, time)
}
#' Check Gram Charlier limits from parameters
#'
#' This function verifies if kurtosis and skewness of a distribution satisfy
#' Gram-Charlier Kurtosis-Skewness limits.
#'
#' @param k kurtosis.
#' @param s skewness.
#' @param verbose TRUE or FALSE for whether to show or not warnings in console.
#'
#' @return
#' TRUE or FALSE, in or out of the limits, respectively.
#'
#' @seealso
#' \code{\link{check_gram_charlier_limits_4_series}}, \code{\link{adjmoments}}.
#'
#' @examples
#' check_gram_charlier_limits(4, .5)
#' check_gram_charlier_limits(6, -.5)
#' check_gram_charlier_limits(3, 0)
#'
#' @export
check_gram_charlier_limits <- function(k, s, verbose=FALSE) {
data(gram_charlier_limits)
rk <- range(gram_charlier_limits$points[,1])
if ((k < rk[1] || k > rk[2]) && verbose)
warning('kurtosis exceeded: ', k)
rs <- max(abs(gram_charlier_limits$points[,2]))
if (s > rs && verbose)
warning('skewness exceeded: ', s)
x <- gram_charlier_limits$fun(k) > s
if (is.na(x)) FALSE else x
}
#' Check Gram Charlier limits from series
#'
#' This function verifies if kurtosis and skewness of a distribution satisfy
#' Gram-Charlier Kurtosis-Skewness limits.
#'
#' @param series return series to verify limits.
#' @param verbose TRUE or FALSE for whether to show or not warnings in console.
#'
#' @return
#' TRUE or FALSE, in or out of the limits, respectively.
#'
#' @seealso
#' \code{\link{check_gram_charlier_limits}}, \code{\link{adjmoments}}.
#'
#' @examples
#' check_gram_charlier_limits_4_series(rnorm(100000))
#'
#' @export
check_gram_charlier_limits_4_series <- function(series, verbose=FALSE) {
k <- kurtosis(series, method='moment')
s <- abs(skewness(series, method='moment'))
check_gram_charlier_limits(k, s, verbose)
}
#' Out of the Boundaries Skewness and Kurtosis Adjustment
#'
#' In case skewness and kurtosis fall outside Gram-Charlier limits, use this
#' function to adjust them so they fall inside the borders of the region.
#'
#' @param s skewness
#' @param k kurtosis
#'
#' @return
#' Dataframe with adjusted skewness and kurtosis.
#'
#' @seealso
#' \code{\link{check_gram_charlier_limits}}, \code{\link{adjmoments}}.
#'
#' @examples
#' adjmoments(-200:200/100, 8)
#' adjmoments(0.5, 10:90/10)
#'
#' @export
adjmoments <- function(s, k){
data <- data.frame(s, k)
if(nrow(data)==0) return()
data(gram_charlier_limits)
edges <- as.data.frame(gram_charlier_limits$points)
data <- within(data,{
adj.skewness <- NA
adj.kurtosis <- NA
})
na <- with(data, (is.na(s) | is.na(k)))
kurtok <- with(data, !na & k >= 3 & k <= 7)
data.kurtok <- data[kurtok,]
data.kurtok <- within(data.kurtok, {
adj.kurtosis <- k
skew.max <- approx(edges[,c(1,3)], xout = k)[[2]]
adj.skewness <- ifelse(abs(s) <= skew.max, s,
sign(s) * skew.max)
})
data[kurtok,] <- data.kurtok[,names(data)]
sub.edges <- list(subset(edges[order(edges[,3]),], k <= 5),
subset(edges[order(edges[,3]),], k > 5))
skewok <- with(data, !na & !kurtok & s >= -1 & s <= 1)
data.skewok <- data[skewok,]
data.skewok <- within(data.skewok, {
adj.skewness <- s
adj.kurtosis <- ifelse(k < 3,
approx(sub.edges[[1]][,c(3,1)], xout=abs(s), ties = "ordered")[[2]],
approx(sub.edges[[2]][,c(3,1)], xout=abs(s), ties = "ordered")[[2]])
})
data[skewok,] <- data.skewok[,names(data)]
nok <- with(data, !na & !kurtok & !skewok)
data.nok <- data[nok,]
data.nok <- within(data.nok, {
adj.kurtosis <- ifelse(k > 7, 7, 3)
adj.skewness <- sign(s) * 0.5
})
data[nok,] <- data.nok[,names(data)]
return(data[,c('adj.skewness','adj.kurtosis')])
}
#' Computes Q_3 from Corrado-Su model.
#'
#' Implementation of Q_3 formula from Corrado-Su model. Q_3 measures the effects
#' of nonnormal skewness on the option price given by the model.
#'
#' @param spot current stock price.
#' @param sigma volatility of the stock price.
#' @param time time to option expiration in years.
#' @param d1 auxiliary value, same from Black-Scholes-Merton model.
#'
#' @section Recycle rule:
#'
#' These arguments handle the recycle rule so vectors can be provided
#' and once those vectors differs in length the recycle rule is applied.
#'
#' @return
#' Vector with Q_3.
#'
#' @seealso
#' \code{\link{csq4}} for Q_4 formula.\\
#' \code{\link{csprice}} for Corrado-Su option pricing formula.
#'
#' @examples
#' csq3(10, .3, 1.5, 0:100/100)
#' spot <- 25
#' sigma <- 0.2
#' time <- 0.5
#' strike <- 26
#' rate <- 0.12
#' yield <- 0
#' d1 <- bsmd1(spot, strike, time, rate, yield, sigma)
#' csq3(spot, sigma, time, d1)
#'
#' @export
csq3 <- function(spot, sigma, time, d1) {
q3 <- spot * sigma * sqrt(time) / 6
q3 * ((2 * sigma * sqrt(time) - d1) * dnorm(d1) + sigma * sigma * time * pnorm(d1))
}
#' Computes Q_4 from Corrado-Su model.
#'
#' Implementation of Q_4 formula from Corrado-Su model. Q_4 measures the effects
#' of nonnormal kurtosis on the option price given by the model.
#'
#' @param spot current stock price.
#' @param sigma volatility of the stock price.
#' @param time time to option expiration in years.
#' @param d1 auxiliary value, same from Black-Scholes-Merton model.
#'
#' @section Recycle rule:
#'
#' These arguments handle the recycle rule so vectors can be provided
#' and once those vectors differs in length the recycle rule is applied.
#'
#' @return
#' Vector with Q_4.
#'
#' @seealso
#' \code{\link{csq3}} for Q_3 formula.\\
#' \code{\link{csprice}} for Corrado-Su option pricing formula.
#'
#' @examples
#' csq4(10, .3, 1.5, 0:100/100)
#' spot <- 25
#' sigma <- 0.2
#' time <- 0.5
#' strike <- 26
#' rate <- 0.12
#' yield <- 0
#' d1 <- bsmd1(spot, strike, time, rate, yield, sigma)
#' csq4(spot, sigma, time, d1)
#'
#' @export
csq4 <- function(spot, sigma, time, d1) {
q4 <- (d1 ^ 2 - 1 - 3 * sigma * sqrt(time) * (d1 - sigma * sqrt(time))) * dnorm(d1)
q4 <- q4 + (sigma * sigma * sigma * time ^ (1.5) * pnorm(d1))
q4 * spot * sigma * sqrt(time) / 24
}
#' Computes option price with Corrado-Su model.
#'
#' Implementation of Corrado-Su formula for option pricing.
#'
#' @param type 'call' for call option, any other value for put.
#' @param spot current stock price.
#' @param strike the strike price.
#' @param time time to option expiration in years.
#' @param rate the risk-free interest rate.
#' @param yield the dividends that are expected to be paid.
#' @param sigma volatility of the stock price.
#' @param mu3 skewness.
#' @param mu4 kurtosis.
#'
#' @section Recycle rule:
#'
#' These arguments handle the recycle rule so vectors can be provided
#' and once those vectors differs in length the recycle rule is applied.
#'
#' @return
#' Vector with option prices.
#'
#' @seealso
#' \code{\link{csq3}} for Q_3 formula from Corrado-Su model.\\
#' \code{\link{csq4}} for Q_4 formula from Corrado-Su model.
#'
#' @examples
#' spot <- 25
#' sigma <- 0.2
#' time <- 0.5
#' strike <- 26
#' rate <- 0.12
#' yield <- 0
#' csprice('call', spot, strike, time, rate, yield, sigma, 0, 3) ==
#' bsmprice('call', spot, strike, time, rate, yield, sigma)
#' csprice('call', spot, strike, time, rate, yield, sigma, 0:100/100, 3)
#' csprice('call', spot, strike, time, rate, yield, sigma, 0.5, 0:1000/100)
#'
#' @export
csprice <- function(type, spot, strike, time, rate, yield, sigma, mu3, mu4) {
c_bs <- bsmprice('call', spot, strike, time, rate, yield, sigma)
d1 <- bsmd1(spot, strike, time, rate, yield, sigma)
q3 <- csq3(spot, sigma, time, d1)
q4 <- csq4(spot, sigma, time, d1)
callp <- c_bs + mu3 * q3 + (mu4 - 3) * q4
df <- data.frame(callp, type, spot, yield, time, rate, strike)
with(df, ifelse(tolower(as.character(type)) == 'call', callp, callp - spot * exp(- yield * time) + strike * exp(- rate * time)))
}
#' Computes Q_3 from Corrado-Su model options on futures.
#'
#' Implementation of Q_3 formula from Corrado-Su model for futures. Q_3 measures
#' the effects of nonnormal skewness on the option price given by the model.
#'
#' @param future current future price.
#' @param sigma volatility of the future price.
#' @param time time to option expiration in years.
#' @param d1 auxiliary value, same from Black-76 model.
#'
#' @section Recycle rule:
#'
#' These arguments handle the recycle rule so vectors can be provided
#' and once those vectors differs in length the recycle rule is applied.
#'
#' @return
#' Vector with Q_3.
#'
#' @seealso
#' \code{\link{blackcsq4}} for Q_4 formula for futures.\\
#' \code{\link{blackcsprice}} for Corrado-Su option pricing formula for futures.
#'
#' @examples
#' blackcsq3(10, .3, 1.5, 0:100/100)
#' future <- 25
#' sigma <- 0.2
#' time <- 0.5
#' strike <- 26
#' rate <- 0.12
#' d1 <- blackd1(future, strike, time, rate, sigma)
#' blackcsq3(future, sigma, time, d1)
#'
#' @export
blackcsq3 <- function(future, sigma, time, d1) {
q3 <- future * sigma * sqrt(time) / 6
q3 * ((2 * sigma * sqrt(time) - d1) * dnorm(d1) + sigma * sigma * time * pnorm(d1))
}
#' Computes Q_4 from Corrado-Su model options on futures.
#'
#' Implementation of Q_4 formula from Corrado-Su model for futures. Q_4 measures
#' the effects of nonnormal skewness on the option price given by the model.
#'
#' @param future current future price.
#' @param sigma volatility of the future price.
#' @param time time to option expiration in years.
#' @param d1 auxiliary value, same from Black-76 model.
#'
#' @section Recycle rule:
#'
#' These arguments handle the recycle rule so vectors can be provided
#' and once those vectors differs in length the recycle rule is applied.
#'
#' @return
#' Vector with Q_4.
#'
#' @seealso
#' \code{\link{blackcsq3}} for Q_3 formula for futures.\\
#' \code{\link{blackcsprice}} for Corrado-Su option pricing formula for futures.
#'
#' @examples
#' blackcsq4(10, .3, 1.5, 0:100/100)
#' future <- 25
#' sigma <- 0.2
#' time <- 0.5
#' strike <- 26
#' rate <- 0.12
#' d1 <- blackd1(future, strike, time, rate, sigma)
#' blackcsq4(future, sigma, time, d1)
#'
#' @export
blackcsq4 <- function(future, sigma, time, d1){
q4 <- (d1 ^ 2 - 1 - 3 * sigma * sqrt(time) * (d1 - sigma * sqrt(time))) * dnorm(d1)
q4 <- q4 + (sigma * sigma * sigma * time ^ (1.5) * pnorm(d1))
q4 * future * sigma * sqrt(time) / 24
}
#' Computes call option price with Corrado-Su model for futures.
#'
#' Implementation of Corrado-Su formula for option pricing for futures.
#'
#' @param type 'call' for call option, any other value for put.
#' @param future current future price.
#' @param strike the strike price.
#' @param time time to option expiration in years.
#' @param rate the risk-free interest rate.
#' @param sigma volatility of the future price.
#' @param mu3 skewness.
#' @param mu4 kurtosis.
#'
#' @section Recycle rule:
#'
#' These arguments handle the recycle rule so vectors can be provided
#' and once those vectors differs in length the recycle rule is applied.
#'
#' @return
#' Vector with option prices.
#'
#' @seealso
#' \code{\link{blackcsq3}} for Q_3 formula from Corrado-Su model for futures.\\
#' \code{\link{blackcsq4}} for Q_4 formula from Corrado-Su model for futures.
#'
#' @examples
#' spot <- 25
#' sigma <- 0.2
#' time <- 0.5
#' strike <- 26
#' rate <- 0.12
#' blackcsprice('call', spot, strike, time, rate, sigma, 0, 3) ==
#' blackprice('call', spot, strike, time, rate, sigma)
#' blackcsprice('call', spot, strike, time, rate, sigma, 0:100/100, 3)
#' blackcsprice('call', spot, strike, time, rate, sigma, 0.5, 0:1000/100)
#'
#' @export
blackcsprice <- function(type, future, strike, time, rate, sigma, mu3, mu4){
c_bs <- blackprice(type, future, strike, time, rate, sigma)
d1 <- (log(future / strike) + (sigma * sigma / 2) * time) / (sigma * sqrt(time))
q3 <- csq3(future, sigma, time, d1)
q4 <- csq4(future, sigma, time, d1)
callp <- c_bs + mu3 * q3 + (mu4 - 3) * q4
ifelse(tolower(type) == 'call', callp, callp - future + strike * exp(- rate * time))
}
#' Corrado-Su model fit for futures.
#'
#' Finds and returns Corrado-Su model parameters sigma, mu3 (skewness) and mu4 (kurtosis)
#' for a given set of options data.
#'
#' @param prices market prices for options
#' @param types 'call' for call option, any other value for put.
#' @param futures current future price.
#' @param strikes the strike price.
#' @param terms time to option expiration in years.
#' @param rates the risk-free interest rate.
#' @param initial_guess optional initial guess for optimization
#'
#' @return
#' List with model parameters sigma, mu3 and mu4
#'
#' @export
blackcs_fit <- function(prices, types, futures, strikes, terms, rates, initial_guess = NULL) {
lo <- c(0, -1, 1)
hi <- c(10, 1, 5)
if (is.null(initial_guess)) {
initial_guess <- c(CS.SIGMA, CS.SKEW, CS.KURT)
fit <- nloptr::nloptr(x0 = initial_guess,
eval_f = blackcs_minsq(prices, types, futures, strikes, terms, rates),
lb = lo,
ub = hi,
opts = list(algorithm = "NLOPT_GN_ISRES",
print_level = 0,
xtol_rel = 1e-4,
maxeval = 1000)
)
initial_guess <- fit$solution
}
fit <- nloptr::nloptr(x0 = initial_guess,
eval_f = blackcs_minsq(prices, types, futures, strikes, terms, rates),
lb = lo,
ub = hi,
opts = list(algorithm = "NLOPT_LN_COBYLA",
print_level = 0,
xtol_rel = 1e-4,
maxeval = 1000)
)
curr_param <- fit$solution
list(sigma = curr_param[1], mu3 = curr_param[2], mu4 = curr_param[3])
}
blackcs_minsq <- function(prices, types, futures, strikes, terms, rates) {
function(x) {
sigma <- x[1]
mu3 <- x[2]
mu4 <- x[3]
csp <- blackcsprice(types, futures, strikes, terms, rates, sigma, mu3, mu4)
if (any(is.nan(csp))) csp <- Inf
if (any(csp < 0)) csp[csp < 0] <- 0
sum((prices - csp)^2)
}
}
# used to estimate de implied volatility
# TODO: adaptar para corrado-su
cs.sigma.obj.func <- function(sigma, fut, strike, rate, time, mu3, mu4, price){
csprice(fut, strike, sigma, rate, time, mu3, mu4) - price
}
# TODO: adaptar para corrado-su -- por enquanto aplica apenas para calls
calc.sigma.cs <- function(batdf){
n <- length(batdf$Date)
sigma <- vector(mode='numeric', length=n)
for(i in 1:length(batdf$Date)){
root <- try(uniroot(f=cs.sigma.obj.func, interval=c(-0.1,1), tol=1e-15,
fut=batdf$Fut[i], strike=batdf$Strike[i],
rate=batdf$Rate[i], time=batdf$TBD[i],
mu3=batdf$mu3[i], mu4=batdf$mu4[i],
price=batdf$Opt.Price[i]))
if(inherits(root, "try-error")){
print(root)
print(paste("Inputs:", batdf$Fut[i], strike=batdf$Strike[i],
batdf$Rate[i], batdf$TBD[i], batdf$mu3[i], batdf$mu4[i],
batdf$Opt.Price[i], sep=' '))
print(paste('i=',i))
root <- list(root=-1)
}
sigma[i] <- root$root
}
return(sigma)
}
# Q14 56256
# V14 57151
# Z14 58056
# G15 58897
# M15 60635
# Z15 63594
#' Black Corrado-Su std. Deltas
#'
#' Finds and returns a dataframe with the strikes and implied
#' volatilites for the provided set of deltas or for the
#' default deltas.
#'
#' @param type Option type, "call" for calls or anything else for puts
#' @param future underlying future price at a given date
#' @param time time in years from a given date to a maturity date
#' @param rate interest free rate
#' @param sigma volatility of the stock price
#' @param mu3 skewness
#' @param mu4 kurtosis
#' @param deltas deltas for which strikes and vols should be calculated, 1-99. Defaults to usual
#' delta values.
#'
#' @return
#' Dataframe with columns delta, strike and impvol
#' @export
blackcs_deltas <- function(type, future, time, rate, sigma, mu3, mu4, deltas = DELTAS) {
eps <- 1e-8
strk_cs <- sapply(deltas/100, function(delta) {
uniroot(f=blackcs_strikes, interval=c(1, future*10), tol=eps,
delta=delta, type=type, future=future, time=time,
rate=rate, sigma=sigma, mu3=mu3, mu4=mu4)$root
})
impvol_cs <- sapply(strk_cs, function(strike) {
blackimpvol(blackcsprice(type, future, strike, time, rate, sigma, mu3, mu4),
type, future, strike, time, rate)
})
data.frame(delta=deltas, strike=strk_cs, impvol=impvol_cs)
}
blackcs_strikes <- function(x, delta, type, future, time, rate, sigma, mu3, mu4 ) {
csp <- blackcsprice(type, future, x, time, rate, sigma, mu3, mu4)
if (csp < 0) return(999)
varx <- tryCatch( { blackimpvol(csp, type, future, x, time, rate) },
error = function(e) {
1e-8
})
x - blackstrike(future, delta, varx, time)
}
#' Corrado-Su std. Deltas
#'
#' Finds and returns a dataframe with the strikes and implied
#' volatilites for the provided set of deltas or for the
#' default deltas.
#'
#' @param type Option type, "call" for calls or anything else for puts
#' @param spot underlying spot price at a given date
#' @param time time in years from a given date to a maturity date
#' @param rate interest free rate
#' @param yield dividend yield
#' @param sigma volatility of the stock price
#' @param mu3 skewness
#' @param mu4 kurtosis
#' @param deltas deltas for which strikes and vols should be calculated, 1-99. Defaults to usual
#' delta values.
#'
#' @return
#' Dataframe with columns delta, strike and impvol
#' @export
cs_deltas <- function(type, spot, time, rate, yield, sigma, mu3, mu4, deltas = DELTAS) {
ft <- spot * exp((rate - yield) * time)
eps <- 1e-8
strk_cs <- sapply(deltas/100, function(delta) {
uniroot(f=cs_strikes, interval=c(1, spot*10), tol=eps,
delta=delta, type=type, spot=spot, time=time,
rate=rate, yield=yield, sigma=sigma, mu3=mu3, mu4=mu4, ft=ft)$root
})
impvol_cs <-blackimpvol(csprice(type, spot, strk_cs, time, rate, yield, sigma, mu3, mu4),
type, ft, strk_cs, time, rate)
data.frame(delta=deltas, strike=strk_cs, impvol=impvol_cs)
}
cs_strikes <- function(x, delta, type, spot, strike, time, rate, yield, sigma, mu3, mu4, ft) {
csp <- csprice(type, spot, x, time, rate, yield, sigma, mu3, mu4)
if (csp < 0) return(999)
varx <- blackimpvol(csp, type, ft, x, time, rate)
x - blackstrike(ft, delta, varx, time)
}
var.garch <- function(rets, omega, alpha, beta) {
mu <- mean(rets)
e2 <- (rets - mu)^2
e2t <- omega + alpha*e2[c(-1,-length(e2))]
s2 <- stats::filter(e2t, beta, "recursive", init=mean(e2))
c(mean(e2), s2)
}
# source for student and ged formulas: https://hal.inria.fr/file/index/docid/270719/filename/B08027.pdf
.garch_likelihood <- function(rets, garchEnv, dist, scale=c(1, 1, 1)) {
.rets <- rets
.garchEnv <- garchEnv
llh_function <- switch(dist,
normal = {
function(...) {
x <- c(...)
omega <- x[1]/scale[1] # 1e-5
alpha <- x[2]/scale[2] # 1e-1
beta <- x[3]/scale[3]
garch <- var.garch(.rets, omega, alpha, beta)
# likelihood = 0.5 sum(-log(2 pi) - log(garch) - rets^2/garch)
# the implementation below is a simplification
if (any(garch < 0)) {
.llh <- get("llh", env=.garchEnv)
llh <- .llh + 0.1*(abs(.llh))
} else {
llh <- 0.5*sum(log(garch) + .rets[-1]^2/garch + log(2*pi))
}
assign("llh", llh, env=.garchEnv)
llh
}
},
student = {
function(...) {
x <- c(...)
omega <- x[1]/scale[1] # 1e-5
alpha <- x[2]/scale[2] # 1e-1
beta <- x[3]/scale[3]
v <- x[4]
n <- length(.rets)
garch <- var.garch(.rets, omega, alpha, beta)
s1 <- n*(lgamma((v + 1)/2) - lgamma(v/2) - (1/2)*log(pi*(v - 2)))
ll <- 1 + ((.rets[-1])^2/(garch * (v - 2)))
if (is.na(v) || any(garch < 0) || any(ll < 0)) {
.llh <- get("llh", env=.garchEnv)
llh <- .llh + 0.1*(abs(.llh))
} else {
llh <- - (s1 - 0.5*(sum(log(garch) + (v + 1)*log(ll))))
}
assign("llh", llh, env=.garchEnv)
llh
}
},
ged = {
function(...) {
x <- c(...)
omega <- x[1]/scale[1] # 1e-5
alpha <- x[2]/scale[2] # 1e-1
beta <- x[3]/scale[3]
v <- x[4]
n <- length(.rets)
yv <- sqrt((2^(-2/v)) * gamma(1/v) / gamma(3/v))
garch <- var.garch(.rets, omega, alpha, beta)
s1 <- n*(log(v/yv) - (1 + 1/v)*log(2) - lgamma(1/v))
if (is.na(v) || any(garch < 0)) {
.llh <- get("llh", env=.garchEnv)
llh <- .llh + 0.1*(abs(.llh))
} else {
llh <- - (s1 - 0.5*(sum(log(garch) + ((sqrt(garch))^(-v)) * ((abs(.rets[-1]/yv))^(v)))))
}
assign("llh", llh, env=.garchEnv)
llh
}
})
llh_function
}
garch_fit_1 <- function(rets, garchEnv, dist, bind_params) {
tiny <- 1e-3
alpha <- 0.1
beta <- 0.8
omega <- (1 - alpha - beta)*var(rets)
mu <- mean(rets)
v <- 1
x0 <- c(omega, alpha, beta)
# scaling and optimization bounds
small <- 1e-12
lo <- c(small, small, small)
hi <- c(2*var(rets), 1-small, 1-small)
# setting up likelihood function
garch_likelihood <- .garch_likelihood(rets, garchEnv, dist)
# optimization
eval_g0 <- function(x) {
c(-x[1], -x[2], -x[3], sum(x[2:3]) - (1 - tiny))
}
# student needs the degrees of freedom to be optimized too
if (dist == "student") {
v <- 3
x0 <- c(x0, v)
lo <- c(lo, 3)
hi <- c(hi, 10)
eval_g0 <- function(x) {
c(-x[1], -x[2], -x[3], sum(x[2:3]) - (1 - tiny), -x[4])
}
} else if (dist == "ged") { # ged needs nu to be estimated
x0 <- c(x0, v)
lo <- c(lo, 1)
hi <- c(hi, 10)
eval_g0 <- function(x) {
c(-x[1], -x[2], -x[3], sum(x[2:3]) - (1 - tiny), -x[4])
}
}
# optimization
if (bind_params) {
res <- nloptr::nloptr(x0=x0,
eval_f=garch_likelihood,
lb=lo,
ub=hi,
eval_g_ineq = eval_g0,
opts=list(algorithm="NLOPT_LN_COBYLA",
print_level = 0,
xtol_rel=1.0e-12, maxeval=2000))
} else {
res <- nloptr::nloptr(x0=x0,
eval_f=garch_likelihood,
lb=lo,
ub=hi,
opts=list(algorithm="NLOPT_LN_COBYLA",
print_level = 0,
xtol_rel=1.0e-12, maxeval=2000))
}
par <- res$solution
parms <- list(omega=par[1], alpha=par[2], beta=par[3])
var.g <- var.garch(rets, parms$omega, parms$alpha, parms$beta)
current_var <- sum(unlist(parms) * c(1, tail(rets, 1)^2, tail(var.g, 1)))
fit <- MyQuantTK::as.garchparms(c(parms, sigma_t=MyQuantTK::annualize(MyQuantTK::as.volatility(MyQuantTK::as.variance(current_var)))))
fit$shape <- par[4]
fit
}
.garch_likelihood_2 <- function(rets, garchEnv) {
.rets <- rets
.var <- as.numeric(var(rets))
.garchEnv <- garchEnv
function(...) {
x <- c(...)
omega <- .var*(1 - sum(x))
alpha <- x[1]
beta <- x[2]
garch <- var.garch(.rets, omega, alpha, beta)
# likelihood = 0.5 sum(-log(2 pi) - log(garch) - rets^2/garch)
# the implementation below is a simplification
if (any(garch < 0)) {
.llh <- get("llh", env=.garchEnv)
llh <- .llh + 0.1*(abs(.llh))
} else {
llh <- 0.5*sum(log(garch) + .rets[-1]^2/garch + log(2*pi))
}
assign("llh", llh, env=.garchEnv)
llh
}
}
garch_fit_2 <- function(rets, garchEnv, bind_params) {
tiny <- 1e-3
alpha <- 0.2
beta <- 0.7
mu <- mean(rets)
x0 <- c(alpha, beta)
# scaling and optimization bounds
small <- 1e-12
lo <- c(small, small)
hi <- c(1-small, 1-small)
# setting up likelihood function
garch_likelihood <- .garch_likelihood_2(rets, garchEnv)
# optimization
eval_g0 <- function(x) {
c(-x[1], -x[2], sum(x[1:2]) - (1 - tiny))
}
if (bind_params) {
res <- nloptr::nloptr(x0=x0,
eval_f=garch_likelihood,
lb=lo,
ub=hi,
eval_g_ineq = eval_g0,
opts=list(algorithm="NLOPT_LN_COBYLA",
print_level = 0,
xtol_rel=1.0e-12, maxeval=2000))
} else {
res <- nloptr::nloptr(x0=x0,
eval_f=garch_likelihood,
lb=lo,
ub=hi,
opts=list(algorithm="NLOPT_LN_COBYLA",
print_level = 0,
xtol_rel=1.0e-12, maxeval=2000))
}
par <- res$solution
parms <- list(omega=as.numeric(var(rets))*(1 - sum(par)), alpha=par[1], beta=par[2])
var.g <- var.garch(rets, parms$omega, parms$alpha, parms$beta)
current_var <- sum(unlist(parms) * c(1, tail(rets, 1)^2, tail(var.g, 1)))
fit <- MyQuantTK::as.garchparms(c(parms, sigma_t=MyQuantTK::annualize(MyQuantTK::as.volatility(MyQuantTK::as.variance(current_var)))))
fit$shape <- NA
fit
}
#' GARCH time series fitting
#'
#' Estimate parameters for a GARCH model, using Quasi-Maximum Likelihood Estimation
#' for optimization.
#'
#' @param rets log returns to be fitted
#' @param dist the conditional distribution to be used for estimation
#' @param bind_params if TRUE the fit will be bound by the alpha + beta < 1 restriction
#' @param force_asgard if TRUE will use asgard model instead of fair-garch
#'
#' @return
#' \code{"fit"} a list with the estimated omega, alpha and beta parameters,
#' and sigma, LTV, model, distribution and covariance matrix.
#'
#' @export
garch_fit <- function(rets, dist=c("normal", "student", "ged"), bind_params = TRUE, force_asgard = FALSE) {
dist <- match.arg(dist)
garchEnv <- new.env(hash = TRUE)
assign("llh", 1.0e99, env=garchEnv) # Keep the calculated likelihood function values to handle NaN
fit <- garch_fit_1(rets, garchEnv, dist, bind_params)
LTV <- MyQuantTK::ltv(fit)
model <- 'fair-garch'
if ((LTV < 0 || as.numeric(MyQuantTK::annualize(MyQuantTK::as.volatility(LTV))) > 500 ) || force_asgard) {
fit <- garch_fit_2(rets, garchEnv, bind_params)
model <- 'asgard'
}
LTV <- ltv(fit)
LTV_annual <- as.numeric(MyQuantTK::annualize(MyQuantTK::as.volatility(LTV)))
LTV <- as.numeric(LTV)
SIGMA_T <- as.numeric(fit$sigma_t)
fit$cvar <- cvar(rets, dist, fit$omega, fit$alpha, fit$beta, fit$shape)
ci <- conf_interval(fit$omega, fit$alpha, fit$beta, fit$cvar, LTV, LTV_annual)
list(omega = fit$omega, alpha = fit$alpha, beta = fit$beta, shape = fit$shape,
sigma_t = SIGMA_T, LTV = LTV, LTV_annual = LTV_annual,
model = model, dist = dist, cvar = fit$cvar, ci = ci)
}
cvar <- function(rets, dist, omega, alpha, beta, shape){
garchEnv <- new.env(hash = TRUE)
assign("llh", 1.0e99, env=garchEnv) # Keep the calculated likelihood function values to handle NaN
par <- c(omega, alpha, beta)
if(!is.na(shape))
par <- c(par, shape)
H <- hessian(.garch_likelihood(rets, garchEnv, dist), par,
method.args = list(eps = 1e-12))
H <- 0.5 * (H + t(H))
solve(H, tol = 1e-17)
}
conf_interval <- function(omega, alpha, beta, cvar, LTV, LTV_annual) {
dv_dw <- 1/(1 - alpha - beta)
dv_da <- omega/((1 - alpha - beta))^2
dv_db <- dv_da
var_vl <- (dv_dw ^ 2) * cvar[1,1] + (dv_da ^ 2) * cvar[2,2] +
(dv_db ^ 2) * cvar[3,3] + 2 * dv_dw * dv_da * cvar[1,2] +
2 * dv_dw * dv_db * cvar[1,3] + 2 * dv_da * dv_db * cvar[2,3]
.se_lt_var <- suppressWarnings(sqrt(var_vl))
volp <- MyQuantTK::as.volatility(LTV + .se_lt_var)[[1]]
volm <- MyQuantTK::as.volatility(LTV - .se_lt_var)[[1]]
if (!is.nan(volm) && !is.null(volm) && !is.na(volm) && volm < 0) volm <- 0
icmax <- sqrt(volp * 252)*100
icmin <- sqrt(volm * 252)*100
interv <- (icmax - icmin)/2
lo <- LTV_annual - interv
hi <- LTV_annual + interv
data.frame(Annualized_Volatility = LTV_annual,
CI_low = lo,
CI_high = hi,
In_CI = (LTV_annual <= hi && LTV_annual >= lo),
se_lt_var = .se_lt_var)
}
#' garchparms Class Creator
#'
#' Create list of class \code{"garchparms"}.
#'
#' @param omega Generalized AutoRegressive Conditional Heteroskedasticity
#' process paramater.
#' @param alpha GARCH process paramater.
#' @param beta GARCH process paramater.
#' @param sigma_t GARCH process paramater.
#'
#' @return
#' List with class \code{"garchparms"} from given arguments.
#'
#' @seealso
#' \code{\link{as.garchparms}}.
#'
#' @examples
#' garchparms(1, 1, 1, 1)
#' class(garchparms(1, 1, 1, 1))
#' typeof(garchparms(1, 1, 1, 1))
#'
#' @export
garchparms <- function(omega, alpha, beta, sigma_t) {
as.garchparms(list(
omega=omega,
alpha=alpha,
beta=beta,
sigma_t=sigma_t
))
}
#' garchparms Class Coercing
#'
#' Generic function for coercing to \code{"garchparms"}. There is no default
#' method, so only classes \code{"data.frame"}, \code{"numeric"}, \code{"list"}
#' are supported.
#'
#' @param x an object whose class will determine the method to be dispatched. Must
#' have the values for omega, alpha, beta, sigma_t.
#' @param ... further arguments to be passed to the next method.
#'
#' @return
#' List with class \code{garchparms} from given argument. When x has
#' \code{"data.frame"} class, idx = FALSE or equivalent will pass numeric(0) values
#' and numbers in idx will produce NA.
#'
#' @examples
#' class(garchparms(1, 1, 1, 1)) == class(list(omega=1, alpha=1, beta=1, sigma_t=1))
#' x = data.frame(omega=1, alpha=1, beta=1, sigma_t=1)
#' as.garchparms(x)
#' as.garchparms(x, 0)
#' as.garchparms(x, 2.579238)
#'
#' @name as.gachparms
#' @export
as.garchparms <- function(x, ...) {
UseMethod('as.garchparms', x)
}
#' @rdname as.gachparms
#'
#' @param idx additional parameter.
#'
#' @export
as.garchparms.data.frame <- function(x, idx=1, ...) {
as.garchparms.list(unclass(x[idx,]))
}
#' @rdname as.gachparms
#' @export
as.garchparms.numeric <- function(x, ...) {
as.garchparms.list(as.list(x))
}
#' @rdname as.gachparms
#' @export
as.garchparms.list <- function(x, ...) {
if (is.null(names(x)))
stop('names not defined')
aux <- c('omega', 'alpha', 'beta', 'sigma_t') %in% tolower(names(x))
if (!all(aux))
stop('names mismatch')
x$sigma_t <- as.volatility(x$sigma_t, annualized=T, dib=252)
structure(x, class='garchparms')
}
#' GARCH Information
#'
#' Gives relevant information about a GARCH model, such as parameters and
#' volatility. There is no default method, so only classes \code{"garch"},
#' \code{"fGARCH"}, \code{"uGARCHfit"} are supported.
#'
#' Methods for printing classes \code{"info.garch"}, \code{"info.fGARCH"},
#' \code{"info.uGARCHfit"} are also included.
#'
#' @param x a GARCH model.
#' @param ... further arguments to be passed to the next method.
#' @param dib days in one year, day count convention.
#'
#' @return
#' List with parameters, daily long term variance, annual long term variance, fit
#' of class "info.class(x)". Printing info classes will show daily and
#' annualized variance and volatility, plus parameters.
#'
#' @name info
#' @export
info <- function(x, ...) UseMethod('info')
#' @rdname info
#' @export
info.garch <- function(x, ..., dib=252) {
gamma <- ggamma(x)
lt_var <- coef(x)['omega'] / gamma
lt_vol <- sqrt(lt_var)
lt_vol_year <- lt_vol * sqrt(dib)
.vcov <- sqrt(diag(vcov(x)))
names(.vcov) <- c('omega', 'alpha', 'beta')
parms <- c(coef(x)['a0'], gamma, coef(x)['a1'], coef(x)['b1'])
names(parms) <- c('omega', 'gamma', 'alpha', 'beta')
.se_lt_var <- sqrt(sum(diag(vcov(x)) * c(1, lt_var ^ 2, lt_var ^ 2)) / (gamma ^ 2))
longterm <- c(lt_var, lt_vol, .se_lt_var)
names(longterm) <- c('variance', 'volatility', 'se')
longterm.annual <- c(lt_vol_year ^ 2, lt_vol_year)
names(longterm.annual) <- c('variance', 'volatility')
ans <- list(garch.parms=parms, longterm=longterm,
longterm.annual=longterm.annual,
fit=x, se=.vcov)
class(ans) <- 'info.garch'
ans
}
#' @rdname info
#' @export
print.info.garch <- function(x, ...) {
a <- x
class(a) <- NULL
cat('\nLong Term Info (%):', '\n')
print.default(a$longterm*rep(100, length(a$longterm)), digits=3,
print.gap=4)
cat('\nAnnualized Volatility (%):', '\n')
print.default(a$longterm.annual*rep(1, length(a$longterm.annual)),
digits=3, print.gap=4)
cat('\nGARCH parameters:', '\n')
print.default(a$garch.parms, digits=3, print.gap=4)
invisible(x)
}
#' @rdname info
#' @export
info.fGARCH <- function(x, ..., dib=252) {
# long term info
gamma <- ggamma(x)
lt_var <- ltv(x)
lt_vol <- as.volatility(lt_var)
lt_vol_year <- annualize(lt_vol, dib)
# se info
.vcov <- sqrt(diag(x@fit$cvar))
names(.vcov) <- c('omega', 'alpha', 'beta')
# parameters
parms <- c(coef(x)['omega'], gamma, coef(x)['alpha1'], coef(x)['beta1'])
names(parms) <- c('omega', 'gamma', 'alpha', 'beta')
# long term variance se
.se_lt_var <- sqrt(sum(diag(x@fit$cvar) * c(1, lt_var ^ 2, lt_var ^ 2)) / (gamma ^ 2))
longterm <- c(lt_var, lt_vol, .se_lt_var)
names(longterm) <- c('variance', 'volatility', 'se')
# annualized info
longterm.annual <- c(lt_vol_year ^ 2, lt_vol_year)
names(longterm.annual) <- c('variance', 'volatility')
# info structure
ans <- list(garch.parms=parms, longterm=longterm,
longterm.annual=longterm.annual,
fit=x, se=.vcov)
class(ans) <- 'info.garch'
ans
}
#' @rdname info
#' @export
print.info.fGARCH <- function(x, ...) {
print.info.garch(x, ...)
}
#' @rdname info
#' @export
info.uGARCHfit <- function(x, ..., dib=252) {
# long term info
gamma <- ggamma(x)
lt_var <- ltv(x)
lt_vol <- as.volatility(lt_var)
lt_vol_year <- annualize(lt_vol, dib)
# se info
.vcov <- sqrt(diag(x@fit$cvar))
names(.vcov) <- c('omega', 'alpha', 'beta')
# parameters
parms <- c(coef(x)['omega'], gamma, coef(x)['alpha1'], coef(x)['beta1'])
names(parms) <- c('omega', 'gamma', 'alpha', 'beta')
# long term variance se
.se_lt_var <- sqrt(sum(diag(x@fit$cvar) * c(1, lt_var ^ 2, lt_var ^ 2)) / (gamma ^ 2))
longterm <- c(lt_var, lt_vol, .se_lt_var)
names(longterm) <- c('variance', 'volatility', 'se')
# annualized info
longterm.annual <- c(lt_vol_year ^ 2, lt_vol_year)
names(longterm.annual) <- c('variance', 'volatility')
# info structure
ans <- list(garch.parms=parms, longterm=longterm,
longterm.annual=longterm.annual,
fit=x, se=.vcov)
class(ans) <- 'info.garch'
ans
}
#' @rdname info
#' @export
print.info.uGARCHfit <- function(x, ...) {
print.info.garch(x, ...)
}
#' GARCH Model Long Term Variance
#'
#' Calculates long term variance for GARCH models. There is no default
#' method, so only classes \code{"garch"}, \code{"fGARCH"}, \code{"uGARCHfit"},
#' \code{"garchparms"} are supported.
#'
#' @param x object from where omega, alpha and beta parameters will be extracted.
#'
#' @return
#' A structure with daily long term variance and class \code{"variance"}.
#'
#' @seealso
#' .
#'
#' @examples
#' ltv(garchparms(1, 2:4/20, .7, 1))
#'
#' @name ltv
#'
#' @export
ltv <- function(x) UseMethod('ltv')
#' @rdname ltv
#' @export
ltv.fGARCH <- function(x) as.variance(coef(x)['omega'] / ggamma(x))
#' @rdname ltv
#' @export
ltv.garch <- function(x) as.variance(coef(x)['a0'] / ggamma(x))
#' @rdname ltv
#' @export
ltv.garchparms <- function(x) as.variance(x$omega / ggamma(x))
#' @rdname ltv
#' @export
ltv.uGARCHfit <- function(x) as.variance(coef(x)['omega'] / ggamma(x))
#' GARCH Model Long Term Variance Decay
#'
#' Calculates long term variance decay for given alpha and beta. There is
#' no default method, so only classes \code{"fGARCH"}, \code{"garch"},
#' \code{"garchparms"}, \code{"uGARCHfit"} are supported.
#'
#' @param x object from where alpha and beta parameters will be extracted.
#'
#' @return
#' Numeric with long term variance decay for given parameters.
#'
#' @examples
#' decay(garchparms(1, 0:10/10, 1, 1))
#'
#' @name decay
#' @export
decay <- function(x) UseMethod('decay')
#' @rdname decay
#' @export
decay.fGARCH <- function(x) {
a <- log(1 / (coef(x)['alpha1'] + coef(x)['beta1']))
as.numeric(a)
}
#' @rdname decay
#' @export
decay.garch <- function(x) {
a <- log(1 / (coef(x)['a1'] + coef(x)['b1']))
as.numeric(a)
}
#' @rdname decay
#' @export
decay.garchparms <- function(x) {
a <- log(1 / (x$alpha + x$beta))
as.numeric(a)
}
#' @rdname decay
#' @export
decay.uGARCHfit <- function(x) {
a <- log(1 / (coef(x)['alpha1'] + coef(x)['beta1']))
as.numeric(a)
}
#' GARCH Model Gamma
#'
#' Computes gamma parameter for GARCH models. There is no default
#' method, so only classes \code{"garch"}, \code{"fGARCH"}, \code{"uGARCHfit"},
#' \code{"garchparms"} are supported.
#'
#' @param x an object whose class will determine the method to be dispatched.
#'
#' @return
#' Numeric with gamma calculated for given parameters.
#'
#' @seealso
#' .
#'
#' @examples
#' ggamma(garchparms(1, 2:4/20, .7, 1))
#'
#' @name ggamma
#' @export
ggamma <- function(x) UseMethod('ggamma')
#' @rdname ggamma
#' @export
ggamma.garch <- function(x) 1 - coef(x)['a1'] - coef(x)['b1']
#' @rdname ggamma
#' @export
ggamma.fGARCH <- function(x) 1 - coef(x)['alpha1'] - coef(x)['beta1']
#' @rdname ggamma
#' @export
ggamma.garchparms <- function(x) 1 - x$alpha - x$beta
#' @rdname ggamma
#' @export
ggamma.uGARCHfit <- function(x) 1 - coef(x)['alpha1'] - coef(x)['beta1']
#' GARCh Model Variance Term Structure
#'
#' Computes variance term structure for GARCH models. There is a default method,
#' where parameters for formula may be passed directly, and methods for classes
#' \code{"fGARCH"}, \code{"garchparms"}, \code{"uGARCHfit"}, where parameters are
#' calcuated from the GARCH models.
#'
#' @param x a GARCH model, GARCH paramters or ignored for default method.
#' @param ... further arguments to be passed to the next method.
#' @param v0 for default method, sigma_t converted to daily variance.
#' @param a for default method, long term variance decay.
#' @param vl for default method, long term variance.
#'
#' @return
#' Term structure of variance as a function of time.
#'
#' @seealso
#' \code{\link{decay}}, \code{\link{ltv}}.
#'
#' @examples
#' vts(garchparms(1, 1, 1, 1))(1)
#' vts("ignored", .5, .5, .3)(1)
#'
#' @name vts
#' @export
vts <- function(x, ...) UseMethod('vts')
#' @rdname vts
#' @export
vts.default <- function(x, v0, a, vl, ...) {
function(t) {
q <- (1 - exp(- a * t)) / (a * t)
as.variance(vl + q * (v0 - vl))
}
}
#' @rdname vts
#' @export
vts.fGARCH <- function(x, v0=NULL, ...) {
v0 <- if (is.null(v0)) tail(fBasics::volatility(x, type='h'), 1) else v0
a <- decay(x)
vl <- ltv(x)
vts.default(x, v0=v0, a=a, vl=vl)
}
#' @rdname vts
#' @export
vts.garchparms <- function(x, v0=NULL, ...) {
v0 <- if (is.null(v0)) daily(as.variance(x$sigma_t)) else v0
a <- decay(x)
vl <- ltv(x)
vts.default(x, v0=v0, a=a, vl=vl)
}
#' @rdname vts
#' @export
vts.uGARCHfit <- function(x, v0=NULL, ...) {
v0 <- if (is.null(v0)) daily(as.variance(x$sigma_t)) else v0
a <- decay(x)
vl <- ltv(x)
vts.default(x, v0=v0, a=a, vl=vl)
}
#' vts_update Volatility Term Structure Update
#'
#' Update volatility term structure given new return series data.
#'
#' @param garchdate current GARCH parameters estimation date.
#' @param refdate reference date for calculation.
#' @param returns return series to update vts.
#' @param omega omega GARCH parameter.
#' @param alpha alpha GARCH parameter.
#' @param beta beta GARCH parameter.
#' @param sigma_t sigma GARCH parameter.
#'
#' @return
#' Volatility Term Structure.
#'
#' @name vts_update
#' @export
vts_update <- function(garchdate, refdate, returns, omega, alpha, beta, sigma_t) {
if(garchdate < refdate) {
sigma_t <- sigma_t_update(returns, omega, alpha, beta, sigma_t)
sigma_t <- as.numeric(sigma_t)
}
vts(garchparms(omega, alpha, beta, sigma_t))
}
# sigma(t) ----
#' sigma_t Calculation for GARCH Models
#'
#' Calculates sigma_t for GARCH models. There is no default method, so only
#' classes \code{"fGARCH"}, \code{"uGARCHfit"}, are supported.
#'
#' @param x a GARCH model with class \code{"fGARCH"} or \code{"uGARCHfit"}.
#' @param ... further arguments to be passed to the next method.
#'
#' @return
#' sigma_t for GARCH model in annualized volatility.
#'
#' @name sigma_t
#' @export
sigma_t <- function(x, ...) UseMethod('sigma_t')
#' @rdname sigma_t
#' @export
sigma_t.fGARCH <- function(x, ...) {
last_ret <- tail(x@data, 1)
last_var <- tail(x@sigma.t, 1) ^ 2
current_var <- sum(coef(x) * c(1, last_ret ^ 2, last_var))
annualize(as.volatility(as.variance(current_var)))
}
# #' @rdname sigma_t
# #' @export
# sigma_t.uGARCHfit <- function(fit) {
# current_var <- rugarch::sigma(rugarch::ugarchforecast(fit, n.ahead = 1)) ^ 2
# annualize(as.volatility(as.variance(current_var)))
# }
#' sigma_t_update Update GARCH paramater Sigma
#'
#' Given an additional return series, update sigma GARCH parameter, keeping the
#' others.
#'
#' @param returns return series to update sigma.
#' @param omega omega Garch parameter.
#' @param alpha alpha Garch parameter.
#' @param beta beta Garch parameter.
#' @param sigma_t sigma Garch parameter previously estimated.
#'
#' @return
#' Annualized sigma_t.
#'
#' @name sigma_t_update
#' @export
sigma_t_update <- function(returns, omega, alpha, beta, sigma_t) {
sig <- daily(as.volatility(sigma_t, annualized = T, dib = 252)) ^ 2
x <- omega + alpha * returns ^ 2
if(length(x) == 1)
sig <- x + beta * sig
else
sig <- filter(x, beta, method = "recursive", init = sig)
sig <- sig[length(sig)]
annualize(as.volatility(as.variance(sig)))
}
# volatility ----
#' volatility_update Volatility Estimative Update
#'
#' Estimate new volatility value within initial date with known volatility
#' an desired final date.
#'
#' @param ticker underlying ticker identification.
#' @param refdate reference date for calculation.
#' @param matdate desired final date to witch update volatility.
#' @param garchdate current GARCH parameters estimation date.
#' @param omega omega GARCH parameter.
#' @param alpha alpha GARCH parameter.
#' @param beta beta GARCH parameter.
#' @param sigma_t sigma GARCH parameter.
#'
#' @return
#' Annualized volatility.
#'
#' @name volatility_update
#' @export
volatility_update <- function(ticker, refdate, matdate, garchdate, omega, alpha, beta, sigma_t) {
vts <- vts_update(ticker, garchdate, refdate, omega, alpha, beta, sigma_t)
bizdays <- bizdays::bizdays(refdate, matdate)
volatility <- if (! is.null(vts)) vts(bizdays) else NA
as.numeric(annualize(as.volatility(volatility))) / 100
}
#' instrate Continuous Compounded Interest rate interpolation
#'
#' Given an interest rate curve, find the continuous compounded interest
#' rate for an instant in time.
#'
#' @param curve yield curve.
#' @param refdate reference date for calculation.
#' @param matdate desired maturity for interest rate compounding.
#'
#' @return
#' Continuously compounded interest rate.
#'
#' @name instrate
#' @export
instrate <- function(curve, refdate, matdate) {
bizdays <- bizdays::bizdays(refdate, matdate)
rate <- curve[bizdays]
as.numeric(log(1 + rate / 100))
}
# #' garchparms_info Garch fitting parameters summary.
# #'
# #' Summarize parameters omega, alpha, beta, sigma and long-term volatility
# #' from input serie.
# #'
# #' @param .fit valid GARCH fitting result.
# #' @param series input series for fitted data.
# #'
# #' @return
# #' Dataframe with parameters omega, alpha, beta, sigma and ltv in this order.
# #'
# #' @name garchparms_info
# #' @export
# garchparms_info <- function(.fit, series) {
# if(is.null(series)) return(NULL)
# series <- series[!is.na(series)]
#
# fit <- tryCatch({ .fit(series) }, error=function(e) { NULL })
# if (is.null(fit)) {
# warning(ticker, ': nao foi possivel realizar o fit!')
# return(NULL)
# }
#
# fit.info <- info(fit)
# lt_vol <- as.numeric(annualize(as.volatility(ltv(fit))))
# if(is.infinite(lt_vol) | is.na(lt_vol)) warning(ticker, ': LTV nao converge!')
#
# l <- list(omega=fit.info$garch.parms['omega'],
# alpha=fit.info$garch.parms['alpha'],
# beta=fit.info$garch.parms['beta'],
# 'sigma_t'=as.numeric(sigma_t(fit)),
# 'LTV'=lt_vol)
# as.data.frame(l)
# }
#
# #' @export
# garchparms_calc <- function(ticker, from=NULL, to, gpack=c("fGarch", "rugarch")) {
# gpack <- match.arg(gpack)
# .fit <- switch(gpack,
# fGarch = function(x) {
# fGarch::garchFit(~garch(1,1), x, cond.dist='QMLE',
# include.mean=FALSE, trace=FALSE)
# },
# rugarch = function(x) {
# gspec.ru <- rugarch::ugarchspec(
# variance.model = list(model="sGARCH", garchOrder=c(1, 1)),
# mean.model=list(armaOrder=c(0,0), include.mean=F),
# distribution.model="norm")
# rugarch::ugarchfit(gspec.ru, x)
# }
# )
#
# to <- as.Date(bizdays::adjust.previous(to))
# if(is.null(from)) from <- to - lubridate::dyears(3)
# from <- as.Date(bizdays::adjust.next(from))
#
# df <- data.frame(ticker, from, to, stringsAsFactors = F)
# garch <- apply(df, 1, function(x, .fit) {
# garchparms_info(.fit, x['ticker'], x['from'], x['to'])
# }, .fit=.fit)
#
# if(!is.null(garch)) garch <- do.call(rbind, garch)
# row.names(garch) <- NULL
# garch
# }
#' gmoa.pricing
#'
#' BVMF pricing models package.
#'
#' @name gmoa.pricing-package
#' @import bizdays
#' @import dplyr
#' @importFrom numDeriv hessian
#' @importFrom fBasics volatility
#' @importFrom reshape melt
NULL
#' Computes european call option price via Heston Model
#'
#' Heston formula implementation, pricing european call options.
#'
#' @param S spot price
#' @param k the strike price.
#' @param tau annualized period between option reference date and maturity date
#' @param r interest free rate
#' @param q dividend yield rate
#' @param lambda speed of the reversion mean of the volatility
#' @param eta volatility of the volatility
#' @param rho correlation between the Brownian motion of St and vt.
#' @param vbar long term mean of volatility
#' @param v0 initial variance
#'
#' @return
#' Vector with price of european options, according to
#' Heston model.
#'
#' @export
heston_price <- function(S, K, tau, r, q, lambda, eta, rho, vbar, v0) {
f <- S * exp((r - q) * tau)
x <- log(f/K)
integral <- heston_phi_transform(tau, x, lambda, eta, rho, vbar, v0)
f - (sqrt(K*f)/(2*pi)) * integral
}
#' Computes european call option price via Heston Model
#'
#' Heston formula implementation, pricing european call futures options.
#'
#' @param F future price
#' @param k the strike price.
#' @param tau annualized period between option reference date and maturity date
#' @param lambda speed of the reversion mean of the volatility
#' @param eta volatility of the volatility
#' @param rho correlation between the Brownian motion of St and vt.
#' @param vbar long term mean of volatility
#' @param v0 initial variance
#'
#' @return
#' Vector with price of european options, according to
#' Heston model.
#'
#' @export
heston_pricef <- function(f, K, tau, lambda, eta, rho, vbar, v0) {
x <- log(f/K)
integral <- heston_phi_transform(tau, x, lambda, eta, rho, vbar, v0)
f - (sqrt(K*f)/(2*pi)) * integral
}
#' Heston model fit
#'
#' Fits a heston model to the option data provided and returns
#' the model parameters.
#'
#' @param forwards forward prices of the underlying
#' @param strikes strike prices of the options
#' @param prices options closing prices for the reference dates
#' @param terms annualized periods between option reference dates and maturity dates
#' @param initial_guess vector with the initial guess for the parameters lambda, eta, rho, vbar and v0
#' @param uncertainties price values uncertainties in \% for weighting
#' @param do_imp_vol if TRUE will minimize implied vol error (market imp vols must be passed in prices param)
#' @param do_global if TRUE does global optimization to find starting point for local optimization
#'
#' @return
#' List with heston model parameters lambda, eta, rho, vbar and v0
#'
#' @export
heston_fitf <- function(forwards, strikes, prices, terms,
initial_guess = NULL, uncertainties = NULL,
do_imp_vol = FALSE, do_global = TRUE) {
min_function <- heston_minsqf(forwards, strikes, prices, terms, uncertainties, do_imp_vol)
heston_optimize(min_function, initial_guess, do_global)
}
heston_minsqf <- function(forwards, strikes, prices, terms, uncertainties = NULL, do_imp_vol = FALSE) {
function(...) {
x <- c(...)
lambda <- x[1]
eta <- x[2]
rho <- x[3]
vbar <- x[4]
v0 <- x[5]
heston_prices <- numeric()
# Integration doesn't work well for vectors
for(i in 1 : length(forwards)) {
heston_prices[i] <- heston_pricef(forwards[i], strikes[i], terms[i],
lambda, eta, rho, vbar, v0)
}
if (do_imp_vol) {
heston_prices <- tryCatch({
gmoa.pricing::bsmimpvol(heston_prices, "call", spots, strikes, terms, rates, yields)
}, error = function(e) { 0 })
}
if (is.null(uncertainties)) {
sum(((heston_prices - prices)/prices)^2)
} else {
sum(((((heston_prices - prices)/prices)) * 1/((uncertainties/100)))^2)
}
}
}
#' Heston model fit
#'
#' Fits a heston model to the stock option data provided and returns
#' the model parameters.
#'
#' @param spots forward prices of the underlying
#' @param strikes strike prices of the options
#' @param prices options closing prices for the reference dates
#' @param rates interest free rates
#' @param yields dividend yields
#' @param terms annualized periods between option reference dates and maturity dates
#' @param initial_guess vector with the initial guess for the parameters lambda, eta, rho, vbar and v0
#' @param uncertainties price values uncertainties in \% for weighting
#' @param do_imp_vol if TRUE will minimize implied vol error (market imp vols must be passed in prices param)
#' @param do_global if TRUE does global optimization to find starting point for local optimization
#'
#' @return
#' List with heston model parameters lambda, eta, rho, vbar and v0
#'
#' @export
heston_fit <- function(spots, strikes, prices, rates, yields, terms,
initial_guess = NULL, uncertainties = NULL,
do_imp_vol = FALSE, do_global = TRUE) {
min_function <- heston_minsq(spots, strikes, prices, rates, yields, terms, uncertainties, do_imp_vol)
heston_optimize(min_function, initial_guess, do_global)
}
heston_minsq <- function(spots, strikes, prices, rates, yields, terms, uncertainties = NULL, do_imp_vol = FALSE) {
function(...) {
x <- c(...)
lambda <- x[1]
eta <- x[2]
rho <- x[3]
vbar <- x[4]
v0 <- x[5]
heston_prices <- numeric()
# Integration doesn't work well for vectors
for(i in 1 : length(spots)) {
heston_prices[i] <- heston_price(spots[i], strikes[i], terms[i], rates[i],
yields[i], lambda, eta, rho, vbar, v0)
}
if (do_imp_vol) {
heston_prices <- tryCatch({
gmoa.pricing::bsmimpvol(heston_prices, "call", spots, strikes, terms, rates, yields)
}, error = function(e) { 0 })
}
if (is.null(uncertainties)) {
sum(((heston_prices - prices)/prices)^2)
} else {
sum(((((heston_prices - prices)/prices)) * 1/((uncertainties/100)))^2)
}
}
}
heston_phi <- function(k, tau, lambda, eta, rho, vbar, v0) {
b <- lambda + complex (imaginary = rho * eta) * k
d <- sqrt(b^2 + (eta^2) * (k) * (k + complex(imaginary = -1)))
g <- (b - d)/(b + d)
T_m <- (b - d)/(eta ^ 2)
t <- T_m * ( 1 - exp(-d * tau) )/( 1 - g * exp(-d * tau) )
W <- lambda * vbar * (tau * T_m - 2 * log(( 1 - g*exp(-d * tau) )/( 1 - g ))/(eta ^ 2))
exp(W + v0*t)
}
heston_phi_transform <- function(tau, x, lambda, eta, rho, vbar, v0) {
integrand <- function(k) {
result <- 2 * Re(exp(complex(imaginary = -k * x)) * heston_phi(complex(real = k, imaginary = 0.5), tau,
lambda, eta, rho, vbar, v0))/(k ^ 2 + 1/4)
if (!all(is.numeric(result))) browser()
if (any(is.nan(result))) browser()
if (any(is.na(result))) browser()
result
}
integrate(integrand, 0, 100, stop.on.error = F)$value
}
heston_optimize <- function(objective_function, initial_guess = NULL, do_global = FALSE) {
if (is.null(initial_guess)) {
x0 <- c(1e-6, 1e-6, -1, 1e-6, 1e-6)
} else {
x0 <- initial_guess
}
lo <- c(1e-12, 1e-12, -1, 1e-12, 1e-12)
hi <- c(20, 5, 0, 1, 1)
# Feller's condition
# 2*lambda*vbar - eta^2 > 0
# lambda, eta, rho, vbar, v0
eval_g0 <- function(x) {
c(x[2]*x[2] - (2*x[1]*x[4]))
}
if (do_global) {
gl_fit <- nloptr::nloptr(x0 = x0,
eval_f = objective_function,
lb = lo,
ub = hi,
eval_g_ineq = eval_g0,
opts = list(algorithm="NLOPT_GN_ISRES",
print_level = 0,
xtol_rel=1.0e-6, maxeval = 500))
x0 <- gl_fit$solution
}
fit <- nloptr::nloptr(x0 = x0,
eval_f = objective_function,
lb = lo,
ub = hi,
eval_g_ineq = eval_g0,
opts = list(algorithm="NLOPT_LN_COBYLA",
print_level = 0,
xtol_rel=1.0e-6, maxeval = 500))
params <- fit$solution
list(lambda = params[1], eta = params[2], rho = params[3],
vbar = params[4] , v0 = params[5], objective = fit$objective)
}
#' Heston model std. Deltas
#'
#' Finds and returns a dataframe with the strikes and implied
#' volatilites for the provided set of deltas or for the
#' default deltas.
#'
#' @param fit a Heston model fit as returned by heston_fit
#' @param S underlying spot price at a given date
#' @param tau time in years from a given date to a maturity date
#' @param r interest free rate
#' @param q dividend yield
#' @param future underlying future price, can be provided instead of spot and yield
#' @param deltas deltas for which strikes and vols should be calculated. Defaults to usual
#' delta values.
#'
#' @return
#' Dataframe with columns delta, strike and impvol
#'
#' @export
heston_deltas <- function(fit, S = NULL, tau, r, q = NULL, future = NULL, deltas = DELTAS) {
if (is.null(future)) {
ft <- S * exp((r - q) * tau)
} else {
ft <- future
}
if (is.null(ft)) stop("Future price or spot, rate and yield must be provided.")
eps <- 1e-8
# heston price não funciona vetorialmente devido à integral,
# então as chamadas precisam ficar em apply
strk_heston <- sapply(deltas/100, function(delta) {
uniroot(f=heston_strikes, interval=c(1, ft*10), tol=eps,
t=tau, delta=delta, ft=ft, fit=fit, r=r)$root
})
impvol_heston <- sapply(strk_heston, function(strike) {
blackimpvol(heston_pricef(ft, strike, tau, fit$lambda, fit$eta, fit$rho, fit$vbar, fit$v0), "call", ft, strike, tau, r)
})
data.frame(delta=deltas, strike=strk_heston, impvol=impvol_heston)
}
heston_strikes <- function(x, delta, ft, t, fit, r) {
hp <- heston_pricef(ft, x, t, fit$lambda, fit$eta, fit$rho, fit$vbar, fit$v0)
if (hp < 0) return(999)
varx <- tryCatch( { blackimpvol(hp, "call", ft, x, t, r) },
error = function(e) {
1e-8
})
x - blackstrike(ft, delta, varx, t)
}
#' Computes implied volatility inverting Black-Scholes-Merton formula.
#'
#' Uses bisection method to calculate implied volatility, given other parameters
#' for Black-Scholes-Merton formula for pricing options on stocks.
#'
#' @param option_prices stock option price.
#' @param type 'call' for call option, any other value for put.
#' @param spot current stock price.
#' @param strike the strike price.
#' @param time time to option expiration in years.
#' @param rate the risk-free interest rate.
#' @param yield the dividends that are expected to be paid.
#' @param tolerance the tolerance for the approximation.
#' @param maxiter maximum number of iterations for bisection method.
#' @param check function for checking the convergence of all bisections.
#'
#' @section Recycle rule:
#'
#' These arguments handle the recycle rule so vectors can be provided
#' and once those vectors differs in length the recycle rule is applied.
#'
#' @return
#' An approximation for the implied volatility. If the algorithm converges before
#' \code{maxiter} is reached, Black-Scholes-Merton formula calculated with this
#' volatility should not differ from \code{option_prices} by more than
#' \code{tolerance}.
#'
#' @seealso
#' \code{\link{blackimpvol}} for options on futures, with Black-76 model.
#'
#' @examples
#' bsmimpvol(6, 'call', 50, 52, 1, 0.1, 0)
#' bsmimpvol(c(3, 4, 5, 6), 'call', 50, 52, 1, 0.1, 0)
#' bsmimpvol(1.5, 'put', 40, 38, 0.5, 0.15, 0, tolerance = 1e-8)
#'
#' @export
bsmimpvol <- function(option_prices, type, spot, strike, time, rate, yield,
tolerance=.Machine$double.eps, maxiter=100, check=any) {
f <- function(sigma, ...){
bsmprice(sigma=sigma, ...) - option_prices
}
res <- multiroot(func_=f, interval=c(1e-8, 10), tolerance=tolerance,
maxiter=maxiter, check=check, type=type, spot=spot,
strike=strike, rate=rate, time=time, yield=yield)
res$root
}
#' Computes implied volatility inverting Black-76 formula.
#'
#' Uses bisection method to calculate implied volatility, given other parameters
#' for Black-76 formula for pricing options on futures.
#'
#' @param option_prices future option price.
#' @param type 'call' for call option, any other value for put option.
#' @param future current future price.
#' @param strike the strike price.
#' @param time time to option expiration in years.
#' @param rate the risk-free interest rate.
#' @param tolerance the tolerance for the approximation.
#' @param maxiter maximum number of iterations for bisection method.
#' @param check function for checking the convergence of all bisections.
#'
#' @section Recycle rule:
#'
#' These arguments handle the recycle rule so vectors can be provided
#' and once those vectors differs in length the recycle rule is applied.
#'
#' @return
#' Vector with approximations implied volatility. If the algorithm converges
#' before \code{maxiter} is reached, Black-76 formula calculated with this
#' volatility should not differ from \code{option_prices} by more than
#' \code{tolerance}.
#'
#' @seealso
#' \code{\link{bsmimpvol}} for options on stocks, with Black-Scholes-Merton model.
#'
#' @examples
#' blackimpvol(c(3.66765, 5.441491), c('call', 'put'), 50, 52, 1, 0.12)
#' blackimpvol(c(2, 3, 4, 5, 6, 7), 'put', 50, 52, 1, 0.12)
#' blackimpvol(2000:6000/1000, 'call', 50, 52, 1, 0.12)
#'
#' @export
blackimpvol <- function(option_prices, type, future, strike, time, rate,
tolerance=.Machine$double.eps, maxiter=100, check=any) {
f <- function(sigma, ...){
blackprice(sigma=sigma, ...) - option_prices
}
res <- multiroot(func_=f, interval=c(1e-8, 10), tolerance=tolerance,
maxiter=maxiter, check=check, type=type, future=future,
strike=strike, rate=rate, time=time)
res$root
}
#' Vectorial bisection method varying parameters from one function.
#'
#' Searches the interval for a root (i.e., zero) of the function func_ with
#' respect to its first argument. Other arguments may be vectorial, so
#' \code{multiroot} will return a vector with roots.
#'
#' \code{multiroot} was created aiming implied volatility calculation for
#' several options.
#'
#' @param func_ objective function for root finding.
#' @param interval a vector containing the end-points of the interval to be
#' searched for all the roots.
#' @param ... additional named or unnamed arguments to be passed to func_,
#' may be vectorial, and this is the difference between multiroot and uniroot.
#' @param tolerance the desired accuracy (convergence tolerance).
#' @param maxiter the maximum number of iterations.
#' @param check function for checking convergence in \code{\link{any}} or
#' \code{\link{all}} of the roots. Since it is bisection method, they are
#' the same.
#'
#' @return
#' Vector with roots.
#'
#' @seealso
#' \code{\link{uniroot}} for R built-in one dimensional root finding.
#'
#' @examples
#' f <- function(x, y) x - y
#' multiroot(f, c(-3, 10), 1:3)
#' g <- function(sigma, premium, type, spot, strike, time, rate, yield)
#' bsmprice(type, spot, strike, time, rate, yield, sigma) - premium
#' multiroot(g, c(1e-8, 10), 5, 'call', 30, 31, c(1, 1.1, 1.2, 1.3), 0.15, 0)
#' premium <- blackprice('put', 10, 10, 0.5, 0.12, 1:100/100)
#' h <- function(sigma, premium, type, spot, strike, time, rate)
#' blackprice(type, spot, strike, time, rate, sigma) - premium
#' multiroot(h, c(1e-8, 10), premium, 'put', 10, 10, 0.5, 0.12)
#'
#' @export
multiroot <- function(func_, interval, ...,
tolerance=.Machine$double.eps, maxiter=100, check=any) {
s_lower <- sign(func_(interval[1], ...))
s_upper <- sign(func_(interval[2], ...))
if ( any(s_upper*s_lower != - 1) )
stop('function evaluated at upper and lower limits must have the opposite sign')
func <- function(a, ...) func_(a, ...) * s_upper
initial_guess <- sum(interval) / 2
x <- initial_guess * rep(1, length(s_upper))
lower <- interval[1] * rep(1, length(s_upper))
upper <- interval[2] * rep(1, length(s_upper))
iter <- 0
err <- func(x, ...)
while (check(abs(err) > tolerance) && iter < maxiter) {
idx.err <- err < 0
idx.err <- ifelse(is.na(idx.err), FALSE, idx.err)
lower[idx.err] <- x[idx.err]
x[idx.err] <- (upper[idx.err] + x[idx.err]) / 2
idx.err <- ! idx.err
upper[idx.err] <- x[idx.err]
x[idx.err] <- (lower[idx.err] + x[idx.err]) / 2
err <- func(x, ...)
iter <- iter + 1
}
res <- list(root=x, iter=iter, err=err)
structure(res, class='multiroot')
}
#' Print Bisection Method Result.
#'
#' Specific method for printing the multiroot class, created for multiroot
#' function, in a pleasing form.
#'
#' The multiroot class is a list with slots root, iter, err.
#'
#' @param x a list.
#' @param ... additional arguments, will be ignored.
#'
#' @return
#' \code{invisible(x)}.
#'
#' @seealso
#' \code{\link{multiroot}} for vectorial bisection method.
#'
#' @examples
#' mt <- structure(list(root=1, iter=20, err=1e-8), class='multiroot')
#' print(mt)
#'
#' @export
print.multiroot <- function(x, ...) {
cat('root:', x$root, '\n')
cat('iter:', x$iter, '\n')
cat('err:', x$err, '\n')
invisible(x)
}
#' z auxiliary function for SABR(Stochastic Alpha Beta Rho) volatility model.
#'
#' SABR model formula is broken in auxiliary functions, and z is one
#' of them.
#'
#' @param fut current future price.
#' @param strike the strike price.
#' @param alpha parameter from model.
#' @param beta parameter from model.
#' @param nu parameter from model.
#'
#' @section Recycle rule:
#'
#' These arguments handle the recycle rule so vectors can be provided
#' and once those vectors differs in length the recycle rule is applied.
#'
#' @return
#' Vector with the z formula values.
#'
#' @seealso
#' \code{\link{x.func}} for x auxiliary function from SABR model.
#' \code{\link{sabrimpvol}} for volatility fit with SABR model.
#'
#' @examples
#' z.func(10, 9, 0.5, 0.5, 0.5)
#' z.func(50:150/10, 9, 0.5, 0.5, 0.5)
#' z.func(50, 45, 0.4325, 0.65846, 1.5)
#'
#' @export
z.func <- function(fut, strike, alpha, beta, nu){
return((nu / alpha) * ((fut * strike) ^ (0.5 * (1 - beta))) * log(fut / strike))
}
#' x auxiliary function for SABR(Stochastic Alpha Beta Rho) volatility model.
#'
#' SABR model formula is separated in auxiliary functions, and x is one
#' of them.
#'
#' @param z other auxiliary function for SABR.
#' @param rho parameter from model.
#'
#' @section Recycle rule:
#'
#' These arguments handle the recycle rule so vectors can be provided
#' and once those vectors differs in length the recycle rule is applied.
#'
#' @return
#' Vector with the x formula values.
#'
#' @seealso
#' \code{\link{z.func}} for z auxiliary function from SABR model.
#' \code{\link{sabrimpvol}} for volatility fit with SABR model.
#'
#' @examples
#' x.func(z.func(10, 9, 0.5, 0.5, 0.5), 0.5)
#' x.func(z.func(50, 45, 0.4325, 0.65846, 1.5), -0.15678)
#' x.func(-100:100/100, 0)
#'
#' @export
x.func <- function(z, rho){
fct <- 1 - 2 * rho * z + z ^ 2
fct <- ifelse(fct < 0, 0, fct)
fct <- sqrt(fct)
#den <- 1 - rho
log.arg <- (fct + z - rho) / (1 - rho)
log.arg <- ifelse(log.arg < 0, 0, log.arg)
#TODO: verificar caso em que 1 - rho == 0
return( log(log.arg) )
}
#' Volatility fit with SABR(Stochastic Alpha Beta Rho) model.
#'
#' SABR model for volatility market data fit implementation.
#'
#' @param spot current stock price.
#' @param rate the risk-free interest rate.
#' @param strike the strike price.
#' @param bizdays time to option expiration in years, based in working days.
#' @param alpha parameter from model.
#' @param beta parameter from model.
#' @param rho parameter from model.
#' @param nu parameter from model.
#'
#' @section Recycle rule:
#'
#' These arguments handle the recycle rule so vectors can be provided
#' and once those vectors differs in length the recycle rule is applied.
#'
#' @return
#' Vector with fit volatility, according to SABR model.
#'
#' @seealso
#' \code{\link{z.func}} for z auxiliary function from SABR model.
#' \code{\link{x.func}} for x auxiliary function from SABR model.
#'
#' @examples
#' sabrimpvol(5, 0.16, 4.5, 1, 0.5, 0.5, 0.5, 0.5)
#' sabrimpvol(5, 0.16, 4.5, 1, 0.5, 0:100/100, 0.5, 0.5)
#' sabrimpvol(5, .16, 4.5, 1, .5, .5, -100:100/100, .5)
#'
#' @export
sabrimpvol <- function(spot = NULL, rate = NULL, fut = NULL, strike, bizdays, alpha, beta, rho, nu){
if (is.null(fut)) {
fut <- spot * exp(rate * bizdays)
}
z <- z.func(fut, strike, alpha, beta, nu)
x <- x.func(z, rho)
dn1 <- (fut * strike) ^ (0.5 * (1 - beta))
dn2 <- 1 + (1 / 24) * ((1 - beta) ^ 2) * ((log(fut / strike)) ^ 2) +
(1 / 1920) * ((1 - beta) ^ 4) * (log(fut / strike)) ^ 4
term1 <- (alpha / (dn1 * dn2)) * (z / x)
fct <- (1 / 24) * ((1 - beta) ^ 2) * (alpha ^ 2) / ((fut * strike) ^ (1 - beta)) +
(1 / 4) * (rho * beta * nu * alpha) / ((fut * strike) ^ (0.5 * (1 - beta))) +
(1 / 24) * (2 - 3 * (rho ^ 2)) * nu ^ 2
term2 <- 1 + (fct * bizdays)
impvol <- term1 * term2
if (any(is.nan(impvol))) impvol[is.nan(impvol)] <- 0
return(impvol)
}
#' Numerical derivative of implied volatility formula from SABR method with
#' respect to alpha.
#'
#' Symmetric Difference Quotient method of numerical differentiation applied
#' in the volatility formula from SABR method, with respect to alpha.
#'
#' @param spot current stock price.
#' @param rate the risk-free interest rate.
#' @param strike the strike price.
#' @param bizdays time to option expiration in years, based in working days.
#' @param alpha parameter from model.
#' @param beta parameter from model.
#' @param rho parameter from model.
#' @param nu parameter from model.
#'
#' @section Recycle rule:
#'
#' These arguments handle the recycle rule so vectors can be provided
#' and once those vectors differs in length the recycle rule is applied.
#'
#' @return
#' Vector with the derivatives.
#'
#' @seealso
#' \code{\link{sabrimpvol}} for SABR volatility formula.
#'
#' @examples
#' dsabrimpvol.dalpha(5, 0.16, 4.5, 1, 0.5, 0.5, 0.5, 0.5)
#' dsabrimpvol.dalpha(5, 0.16, 4.5, 1, 0.5, 0:100/100, 0.5, 0.5)
#' dsabrimpvol.dalpha(5, .16, 4.5, 1, .5, .5, -100:100/100, .5)
#'
#' @export
dsabrimpvol.dalpha <- function(spot, rate, strike, bizdays, alpha, beta, rho, nu){
d.alpha <- 1e-6
impvol.up <- sabrimpvol(spot, rate, strike, bizdays, alpha + d.alpha, beta, rho, nu)
impvol.down <- sabrimpvol(spot, rate, strike, bizdays, alpha - d.alpha, beta, rho, nu)
return((impvol.up - impvol.down) / (2 * d.alpha))
}
#' SABR model fit
#'
#' Finds and returns SABR model parameters alpha, beta, nu and rho for
#' a given set of strikes and sigmas.
#'
#' @param spot current stock price. If not provided will try to use the future parameter.
#' @param rate the risk-free interest rate. If not provided will try to use the future parameter.
#' @param future the future price. If not provided will calculate future from spot and rate.
#' @param tau time to option expiration in years, based in working days.
#' @param strikes options strikes
#' @param sigmas options sigmas
#' @param initial_guess named vector in order alpha, nu, rho, beta of initial parameters for optimization (optional)
#'
#' @return
#' List with fitted parameters alpha, beta, nu, rho
#'
#' @seealso
#' \code{\link{sabrimpvol}} for SABR volatility
#'
#' @export
sabr_fit <- function(future = NULL, strikes, tau, sigmas, spot = NULL, rate = NULL, initial_guess = NULL) {
if (is.null(future) && (is.null(spot) || is.null(rate))) stop("Either future price or spot and rate must be provided.")
if (is.null(future)) future <- spot * exp(rate * tau)
if (is.null(initial_guess)) initial_guess <- c(alpha=ALPHA, nu=NU, rho=RHO, beta=BETA)
fit <- nloptr::lbfgs(x0=initial_guess,
fn=sabr_optim(future, strikes, tau, sigmas),
lower=c(0.0001, 0.0001, -1, 0),
upper=c(20, Inf, 0.99, 1))
list(alpha = fit$par[1], nu = fit$par[2], rho = fit$par[3], beta = fit$par[4])
}
sabr_optim <- function(future, strikes, tau, sigmas) {
function (par) {
alpha <- par[1]
nu <- par[2]
rho <- par[3]
beta <- par[4]
sabr_sigs <- sapply(strikes, function(strike) {
sabrimpvol(fut = future, strike = strike, bizdays = tau,
alpha = alpha, beta = beta, rho = rho, nu = nu)
})
sum((sigmas - sabr_sigs)^2)
}
}
#' SABR model std. Deltas
#'
#' Finds and returns a dataframe with the strikes and implied
#' volatilites for the provided set of deltas or for the
#' default deltas.
#'
#' @param alpha a SABR model fit alpha parameter
#' @param beta a SABR model fit beta parameter
#' @param rho a SABR model fit rho parameter
#' @param nu a SABR model fit nu parameter
#' @param S underlying spot price at a given date
#' @param tau time in years from a given date to a maturity date
#' @param r interest free rate
#' @param q yield rate
#' @param future future price. Can be given instead of spot/rate/yield.
#' @param deltas deltas for which strikes and vols should be calculated. Defaults to usual
#' delta values.
#'
#' @return
#' Dataframe with columns delta, strike and impvol
#'
#' @export
sabr_deltas <- function(alpha, beta, rho, nu, S = NULL, tau, r = NULL,
future = NULL, q = NULL, deltas = DELTAS) {
eps <- 1e-8
if (is.null(future)) {
ft <- S * exp((r - q) * tau)
} else {
ft <- future
}
if (is.null(ft)) stop("Future price or spot, rate and yield must be provided.")
strk_sabr <- sapply(deltas/100, function(delta) {
uniroot(f=sabr_strikes, interval=c(1, ft*10), tol=eps,
t=tau, delta=delta, ft=ft, alpha=alpha,
beta=beta, rho=rho, nu=nu)$root
})
## multiroot não funciona bem, varx <- sabrimpvol(...) não usa vetor de deltas
# strk_sabr <- multiroot(func_ = sabr_strikes, interval = c(1, S*10), tolerance = eps,
# t = tau, delta = deltas, ft = ft, r = r, spot = S, alpha = alpha,
# beta = beta, rho = rho, nu = nu)$root
impvol_sabr <- sabrimpvol(fut = ft, strike = strk_sabr, bizdays = tau,
alpha = alpha, beta = beta, rho = rho, nu = nu)
data.frame(delta=deltas, strike=strk_sabr, impvol=impvol_sabr)
}
sabr_strikes <- function(x, delta, ft, t, alpha, beta, rho, nu) {
varx <- sabrimpvol(fut = ft, strike = x, bizdays = t,
alpha = alpha, beta = beta, rho = rho, nu = nu)
x - blackstrike(ft, delta, varx, t)
}
#' SVI Variance
#'
#' Calculates the variance as given by the SVI model for
#' the provided parameters.
#'
#' @param a 'a' in the SVI model (~~ instantaneous variance)
#' @param b 'b' in the SVI model (~~ speed of variance mean reversion)
#' @param m 'm' in the SVI model (~~ long term expected variance)
#' @param rho 'rho' in the SVI model (~~ correlation of variance and price)
#' @param x 'x' in the SVI model, given by ln(K/F) where K is an options strike price and F its underlying's future
#' @param sigma 'sigma' in the SVI model (~~ volatility of variance)
#'
#' @section Recycle rule:
#'
#' These arguments handle the recycle rule so vectors can be provided
#' and once those vectors differs in length the recycle rule is applied.
#'
#' @return
#' Volatility value as given by the SVI model
#'
#' @seealso
#' \code{\link{SVI_fit}} for SVI model fitting.
#' \code{\link{SVI_deltas}} for calculating strikes and imp. vols. for given deltas
#'
#' @export
SVI_var <- function(a, b, m, rho, x, sigma) {
a + b * (rho * (x - m) + sqrt((x - m) ^ 2 + sigma ^ 2))
}
#' SVI fit
#'
#' Fits a SVI model for the given smile as defined by strikes, futures
#' and their respective variances. Time to maturity should also be provided
#' for the non-arbitrage restriction. Returns the fitted parameters.
#'
#' @param strikes options strikes
#' @param future options underlying future If NULL will try to use spot and rate.
#' @param variances variances for the given strikes
#' @param ttm time to maturity in years
#' @param spot underlying spot priced, used in conjunction with rate if future price is not provided
#' @param rate risk free rate, used for calculating a future price in conjunction with spot if not provided
#'
#' @return
#' A list with the SVI model parameters a, b, x, m, rho and sigma.
#'
#' @seealso
#' \code{\link{SVI_var}} for SVI model variance calculation
#' \code{\link{SVI_deltas}} for calculating strikes and imp. vols. for given deltas
#'
#' @export
SVI_fit <- function(strikes, future = NULL, variances, ttm, spot = NULL, rate = NULL, initial_guess = NULL) {
if (is.null(future) && (is.null(spot) || is.null(rate))) stop("Either future price or spot and rate must be provided.")
if (is.null(future)) future <- spot * exp(rate * ttm)
x <- log(strikes/future)
tol <- length(variances) * min(variances) ^ 2
# SVI restrictions
# 0 <= a <= max(variances)
# 0 <= b <= 4/(ttm * (1 + abs(rho)))
# -1 <= rho <= 1
# min(x) <= m <= max(x)
# 0 < sigma < 10
# a, b, m, rho, sigma
eval_g0 <- function(par) {
c(par[2] - 4/(ttm * (1 + abs(par[4]))))
}
lo <- c(0, 0, min(x), -1, 1e-12)
hi <- c(max(c(variances, SVI.A)), 4/(ttm * (1 + 1e-4)), max(x), 1, 10)
if (is.null(initial_guess)) {
initial_guess <- c(a=SVI.A, b=SVI.B,
m=min(x), rho=SVI.RHO, sigma=SVI.SIGMA)
fit <- nloptr::nloptr(x0 = initial_guess,
eval_f = SVI_minsq(variances, x),
lb = lo,
ub = hi,
eval_g_ineq = eval_g0,
opts = list(algorithm = "NLOPT_GN_ISRES",
print_level = 0,
xtol_rel = 1e-4,
maxeval = 2000)
)
initial_guess <- fit$solution
}
fit <- nloptr::nloptr(x0 = initial_guess,
eval_f = SVI_minsq(variances, x),
lb = lo,
ub = hi,
eval_g_ineq = eval_g0,
opts = list(algorithm = "NLOPT_LN_COBYLA",
print_level = 0,
xtol_rel = 1e-4,
maxeval = 2000)
)
curr_param <- fit$solution
list(a = curr_param[1], b = curr_param[2], m = curr_param[3],
rho = curr_param[4], sigma = curr_param[5])
}
SVI_minsq <- function(variances, x) {
function (...) {
par <- c(...)
a <- par[1]
b <- par[2]
m <- par[3]
rho <- par[4]
sigma <- par[5]
SVI <- SVI_var(a, b, m, rho, x, sigma)
sum((variances - SVI)^2)
}
}
#' SVI fit std. Deltas
#'
#' Finds and returns a dataframe with the strikes and implied
#' volatilites for the provided set of deltas or for the
#' default deltas.
#'
#' @param a a SVI model fit a parameter
#' @param b a SVI model fit b parameter
#' @param m a SVI model fit m parameter
#' @param rho a SVI model fit rho parameter
#' @param sigma a SVI model fit sigma parameter
#' @param future future price
#' @param t time to maturity
#' @param deltas deltas for which strikes and vols should be calculated. Defaults to usual
#' delta values.
#'
#' @return
#' Dataframe with columns delta, strike and impvol
#'
#' @export
SVI_deltas <- function(a, b, m, rho, sigma, future, t, deltas = DELTAS) {
eps <- 1e-8
strk_svi <- sapply(deltas/100, function(delta) {
uniroot(f=svi_strikes, interval=c(1, future*10), tol=eps,
t = t, delta = delta, a = a, b = b,
m = m, rho = rho, sigma = sigma, ft = future)$root
})
impvol_svi <- sqrt(SVI_var(a, b, m, rho, log(strk_svi/future), sigma))
data.frame(delta=deltas, strike=strk_svi, impvol=impvol_svi)
}
svi_strikes <- function(x, delta, a, b, m, rho, sigma, ft, t) {
varx <- sqrt(SVI_var(a, b, m, rho, log(x/ft), sigma))
x - blackstrike(ft, delta, varx, t)
}
# calculate historical volatility for the period in
# ohlc_data using log returns
calc_hist_vol <- function (ohlc_data, year_biz_days) {
# ln(s_i+1 / s_i)
close_vals <- ohlc_data[, 1]
x <- sapply(2:nrow(ohlc_data), function(idx) {
log((close_vals[[idx]])/(close_vals[[idx - 1]]))
})
x_mean <- mean(x)
x_sum <- sum((x - x_mean)^2)
sqrt(x_sum * year_biz_days/(nrow(ohlc_data) - 2)) # no daily return for the first value
}
# Build volatility table for a given ohlc_data.
# Periods will be separated according to the number of
# months available in the data.
# Each row of the volatility table indicates when a period
# starts, and each column indicates the period length for
# which historical volatilities will be calculated.
build_vol_table <- function(ohlc_data) {
start_date <- as.Date(min(zoo::index(ohlc_data)))
end_date <- as.Date(max(zoo::index(ohlc_data)))
sample_size <- length(zoo::index(ohlc_data))
suppressWarnings(nmonth <- abs(interval(start_date, end_date)) %/% months(1))
days_by_period <- sample_size%/%nmonth
ymd_periods <- zoo::as.Date(sapply(0:(nmonth - 1), function(m) {zoo::index(ohlc_data[1 + m*days_by_period])}))
year_days <- bizdays(from=paste(year(start_date), '01', '01', sep='-'),
to=paste(year(start_date), '12', '31', sep='-'),
cal='Brazil/ANBIMA')
if(length(ymd_periods) > nmonth) ymd_periods <- ymd_periods[-length(ymd_periods)]
vol_table <- data.frame(row.names=ymd_periods)
for (m in 1:nmonth) {
vol_table[, as.character(m*days_by_period)] <- sapply(ymd_periods, function (st_date) {
st_date_index <- match(as.Date(st_date), zoo::index(ohlc_data))
ed_date_index <- st_date_index + days_by_period*m
if(ed_date_index <= (nmonth)*days_by_period + 1 && ed_date_index - st_date_index > 2) {
if (ed_date_index > sample_size) ed_date_index <- sample_size
tryCatch( {calc_hist_vol(ohlc_data[st_date_index:ed_date_index], year_days)}, error=function(e) {browser()})
} else NA
})
}
vol_table
}
# Calculate a confidence interval for a given alpha
confidence_interval <- function(alpha, n) {
lower <- sqrt((n - 1)/qchisq(p = alpha/2, df = n - 1))
higher <- sqrt((n - 1)/qchisq(p = (1 - alpha/2), df = n - 1))
c(lower, higher)
}
# Calculate implied volatilities for all options traded on ref_d
# Risk free rate is given by PRE curve at ref_d
# Implied volatility is calculated using bsm
calc_impl_vol <- function(options_data, ref_d) {
dividendYield <- 0
options <- options_data[!is.na(options_data$close), ]
options <- options[options$close != 0, ]
options <- options[options$ref_date==ref_d, ]
options <- transform(options, bznsdtexp = bizdays(ref_date, maturity_date, cal='Brazil/ANBIMA'))
options <- transform(options, dtexp = as.numeric(as.Date(maturity_date) - as.Date(ref_date)))
rate_curve <- get_curve('PRE', ref_d)
if(nrow(options) >= 1) {
impl_vol <- apply(options, 1, function(option) {
close <- as.numeric(option[['close']])
type <- option[['type']]
spot <- as.numeric(option[['spot']])
strike <- as.numeric(option[['strike']])
dtexp <- as.numeric(option[['dtexp']])
bzdtexp <- as.numeric(option[['bznsdtexp']])
riskFreeRate <- rate_curve[rate_curve$terms==dtexp, ]$value
i <- dtexp - 5
while (length(riskFreeRate) == 0) {
riskFreeRate <- rate_curve[rate_curve$terms==i, ]$value
i <- i + 1
}
ivol <- MyQuantTK::bsmimpvol(close, type, spot, strike, dtexp/365, riskFreeRate/100, dividendYield)
data.frame(bz_days_to_exp=bzdtexp, days_to_exp=dtexp, value=ivol, type=type)
})
impl_vol <- do.call(rbind, impl_vol)
} else {
impl_vol <- NULL
}
impl_vol
}
#' Volatility cone graph generator
#'
#' Generates a graph showing a volatility cone for the stock given
#' by ticker, calculated with data starting from historical_sample_start_date
#' to the most recent available date. It also calculates implied volatilities
#' for that stock's options that were traded on ref_date and plots it on
#' the same graph.
#' The volatility estimator for the cone is annualized standard deviation
#' of log returns. Its confidence interval is for a 5% alpha.
#' The implied volatilities are calculated using the bissection method
#' and the Black-Scholes model.
#'
#' @param ticker Stock to be graphed
#' @param ref_date Reference date for the options and impl. vol calculation
#' @param historical_sample_start_date Start date for the historical data to be used
#' to calculate the volatility cone
#'
#' @return
#' ggplot object containing the graph with the estimated volatility cone, its
#' confidence intervals and implied volatilities for options traded on ref_date
#'
#'
#' @export
gen_vol_cone <- function(ticker, ref_date, historical_sample_start_date, historical_sample_end_date=today()) {
options <- get_options(ticker, src='stock', from=ref_date, to=ref_date)
vals <- get_stocks(ticker, src='bloomberg', from=historical_sample_start_date, to=historical_sample_end_date)
vt <- build_vol_table(vals)
impl_vols <- calc_impl_vol(options, ref_date)
if(!is.null(impl_vols)) {
impl_vols$value <- impl_vols$value*100 # adjust to percentage values
# remove entries for which implied volatilities couldn't be found
# with the bissection method
impl_vols <- impl_vols[impl_vols$value!=0.000001, ]
}
# Adjust historical volatility data and create confidence interval
# with a 5% alpha
max_values <- apply(vt[, names(vt)], 2, max, na.rm = TRUE)*100
mean_values <- apply(vt[, names(vt)], 2, mean, na.rm = TRUE)*100
min_values <- apply(vt[, names(vt)], 2, min, na.rm = TRUE)*100
ci <- confidence_interval(0.05, length(vals))
maxcilow <- c(max_values*ci[1])
maxcihi <- c(max_values*ci[2])
meancilow <- c(mean_values*ci[1])
meancihi <- c(mean_values*ci[2])
mincilow <- c(min_values*ci[1])
mincihi <- c(min_values*ci[2])
cone <- melt(data.frame(days_to_exp=as.numeric(names(vt)),
max_values = max_values,
maxcilow = maxcilow,
maxcihi = maxcihi,
mean_values = mean_values,
meancilow = meancilow,
meancihi = meancihi,
min_values = min_values,
mincilow = mincilow,
mincihi = mincihi)
,id.vars = "days_to_exp")
len <- length(max_values)
cone$cgroup <- c(rep('Maximum', times=3*len), rep('Mean', times=3*len), rep('Minimum', times=3*len))
cone$type <- rep(c(rep('Estimate', times=len), rep('Confidence Interval', times=2*len)), times=3)
cone$type_detail <- rep(c(rep('Estimate', times=len), rep('Confidence Interval lo', times=len), rep('Confidence Interval hi', times=len)), times=3)
title <- paste(ticker, 'estimated vol. cone with impl. vols for', ref_date)
subtitle <- paste('Historical volatility calculated between', historical_sample_start_date, 'and', historical_sample_end_date)
plot <- ggplot(cone, aes(x=days_to_exp, y=value,
group=interaction(cgroup, type_detail),
colour=cgroup, linetype=type)) +
geom_line() +
geom_point(data = subset(cone, type == "Estimate")) +
scale_linetype_manual(values=c("dotted", "solid")) +
scale_x_continuous("Days to Expiry", breaks = c(0, as.numeric(names(vt)))) +
scale_y_continuous("Vol. (%)") +
coord_cartesian(ylim=c(max(c(min(mincihi - 5), 0)), max(maxcilow) + 5)) +
labs(shape='Option type', linetype="Line type", colour="Estimated historical vols.", title=title, subtitle=subtitle) +
theme(plot.title = element_text(hjust = 0.5))
if(!is.null(impl_vols)) {
plot <- plot + geom_point(data=impl_vols, aes(x=bz_days_to_exp, y=value, shape=type), inherit.aes=FALSE)
}
plot
}
#' volatility Class Coercing
#'
#' Generic function for coercing to \code{"volatility"}. There is a default
#' method and a method for \code{"variance"}.
#'
#' @param x an object whose class will determine the method to be dispatched.
#' Value is the volatility or variance.
#' @param ... further arguments to be passed to the next method, such as:
#' @param annualized for default method, TRUE if volatility value in x is annualized,
#' FALSE if volatility is daily.
#' @param dib days in one year, day count convention.
#'
#' @return
#' All values of x as volatility class, according to given daily or annualized type.
#'
#' @seealso
#' \code{\link{as.variance}}.
#'
#' @examples
#' as.volatility(1)
#' as.volatility(as.variance(.5))
#' as.volatility(0.2, TRUE, 252)
#' as.volatility(as.variance(10:30/100, TRUE))
#'
#' @name as.volatility
#' @export
as.volatility <- function(x, ...) UseMethod('as.volatility')
#' @rdname as.volatility
#' @export
as.volatility.default <- function(x, annualized=FALSE, dib=NULL, ...)
structure(x, annualized=annualized, dib=dib, class='volatility')
#' @rdname as.volatility
#' @export
as.volatility.variance <- function(x, ...) {
y <- unclass(x)
attributes(y) <- NULL
p <- if (attr(x, 'annualized')) 100 else 1
structure(sqrt(y) * p, annualized=attr(x, 'annualized'), dib=attr(x, 'dib'),
class='volatility')
}
#' variance Class Coercing
#'
#' Generic function for coercing to \code{"variance"}. There is a default
#' method and a method for \code{"volatility"}.
#'
#' @param x an object whose class will determine the method to be dispatched.
#' Value is the volatility or variance.
#' @param ... further arguments to be passed to the next method, such as:
#' @param annualized for default method, TRUE if variance value in x is annualized,
#' FALSE if variance is daily.
#' @param dib days in one year, day count convention.
#'
#' @return
#' All values of x as \code{"variance"} class, according to given daily or
#' annualized type.
#'
#' @seealso
#' \code{\link{as.volatility}}.
#'
#' @examples
#' as.variance(1)
#' as.variance(as.volatility(.5))
#' as.variance(0.2, TRUE, 252)
#' as.variance(as.volatility(100:300/100, TRUE))
#'
#' @name as.variance
#' @export
as.variance <- function(x, ...) UseMethod('as.variance')
#' @rdname as.variance
#' @export
as.variance.default <- function(x, annualized=FALSE, dib=NULL, ...)
structure(x, annualized=annualized, dib=dib, class='variance')
#' @rdname as.variance
#' @export
as.variance.volatility <- function(x, ...) {
y <- unclass(x)
attributes(y) <- NULL
p <- if (attr(x, 'annualized')) 100 else 1
structure((y / p) ^ 2, annualized=attr(x, 'annualized'), dib=attr(x, 'dib'),
class='variance')
}
#' Special print method for volatility class.
#'
#' Makes objects of class \code{"volatility"} be printed differently.
#'
#' @param x \code{"volatility"} class object to be printed.
#' @param ... further arguments to be passed to the next method.
#'
#' @return
#' invisible(x).
#'
#' @seealso
#' \code{\link{print.variance}}.
#'
#' @examples
#' a <- as.volatility(1)
#' print(a)
#' a <- annualize(a)
#' print(a)
#'
#' @export
print.volatility <- function(x, ...) {
cat(if (attr(x, "annualized")) "Annual" else "Daily",
"Volatility",
if (attr(x, "annualized")) paste("(%)", attr(x, "dib"), "days"),
"\n")
y <- unclass(x)
attributes(y) <- NULL
print(y)
invisible(x)
}
#' Special print method for variance class.
#'
#' Makes objects of class \code{"variance"} be printed differently.
#'
#' @param x \code{"variance"} class object to be printed.
#' @param ... further arguments to be passed to the next method.
#'
#' @return
#' invisible(x).
#'
#' @seealso
#' \code{\link{print.volatility}}.
#'
#' @examples
#' a <- as.variance(1)
#' print(a)
#' a <- annualize(a)
#' print(a)
#'
#' @export
print.variance <- function(x, ...) {
cat(if (attr(x, "annualized")) "Annual" else "Daily",
"Variance",
if (attr(x, "annualized")) paste(attr(x, "dib"), "days"),
"\n")
y <- unclass(x)
attributes(y) <- NULL
print(y)
invisible(x)
}
#' Annualize Volatility or Variance
#'
#' Transforms variance or volatility to annual. There is no default
#' method, so only classes \code{"volatility"}, \code{"variance"}, are supported.
#'
#' @param x an object whose class will determine the method to be dispatched.
#' Volatility or variance, will become annual.
#' @param ... further arguments to be passed to the next method.
#' @param dib days in one year, day count convention.
#'
#' @return
#' Same class with annual volatility or variance.
#'
#' @seealso
#' \code{\link{daily}} to transform variance or volatility to daily basis.
#'
#' @examples
#' annualize(as.variance(.5))
#' annualize(as.volatility(.25))
#'
#' @name annualize
#' @export
annualize <- function(x, ...) UseMethod('annualize')
#' @rdname annualize
#' @export
annualize.volatility <- function(x, dib=252, ...) {
y <- unclass(x)
attributes(y) <- NULL
if (attr(x, 'annualized'))
return(x)
else
y <- y * sqrt(dib) * 100
as.volatility(y, annualized=TRUE, dib=dib)
}
#' @rdname annualize
#' @export
annualize.variance <- function(x, dib=252, ...) {
y <- unclass(x)
attributes(y) <- NULL
if (attr(x, 'annualized'))
return(x)
else
y <- y * dib
as.variance(y, annualized=TRUE, dib=dib)
}
#' Daily Volatility or Variance
#'
#' Transforms variance or volatility to daily. There is no default
#' method, so only classes \code{"volatility"}, \code{"variance"}, are supported.
#'
#' @param x an object whose class will determine the method to be dispatched.
#' Volatility or variance, will become daily.
#' @param ... further arguments to be passed to the next method.
#'
#' @return
#' Same class with daily volatility or variance.
#'
#' @seealso
#' \code{\link{annualize}} to transform variance or volatility to annual basis.
#'
#' @examples
#' daily(annualize(as.variance(.5)))
#' daily(annualize(as.volatility(.25)))
#'
#' @name daily
#' @export
daily <- function(x, ...) UseMethod('daily')
#' @rdname daily
#' @export
daily.volatility <- function(x, ...) {
y <- unclass(x)
attributes(y) <- NULL
if (!attr(x, 'annualized'))
return(x)
else
y <- y / (sqrt(attr(x, 'dib')) * 100)
as.volatility(y, annualized=FALSE, dib=NULL)
}
#' @rdname daily
#' @export
daily.variance <- function(x, ...) {
y <- unclass(x)
attributes(y) <- NULL
if (! attr(x, 'annualized'))
return(x)
else
y <- y / attr(x, 'dib')
as.variance(y, annualized=FALSE, dib=NULL)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment