Skip to content

Commit

Permalink
sdfg
Browse files Browse the repository at this point in the history
  • Loading branch information
risktoollib committed Jan 17, 2025
1 parent 30f882e commit 1f9a24a
Show file tree
Hide file tree
Showing 9 changed files with 196 additions and 141 deletions.
4 changes: 2 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
Type: Package
Package: RTL
Title: Risk Tool Library - Trading, Risk, 'Analytics' for Commodities
Title: Risk Tool Library - Trading, Risk, Analytics for Commodities
Version: 1.3.7
Date: 2025-01-16
Date: 2025-01-17
Authors@R: c(
person("Philippe", "Cote", , "pcote@ualberta.ca", role = c("aut", "cre")),
person("Nima", "Safaian", , "nima.safaian@gmail.com", role = "aut")
Expand Down
3 changes: 1 addition & 2 deletions NAMESPACE
Original file line number Diff line number Diff line change
Expand Up @@ -43,13 +43,12 @@ export(swapInfo)
export(tradeStats)
export(tradeStrategyDY)
export(tradeStrategySMA)
import(numDeriv)
import(rlang)
importFrom(Rcpp,sourceCpp)
importFrom(glue,glue)
importFrom(lifecycle,deprecated)
importFrom(magrittr,"%>%")
importFrom(numDeriv,grad)
importFrom(numDeriv,hessian)
importFrom(plotly,layout)
importFrom(plotly,plot_ly)
importFrom(purrr,map2)
Expand Down
282 changes: 157 additions & 125 deletions R/barrierSpreadOption.R
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
#' @title Barrier Spread Option Pricing Functions
#' @description Functions for pricing barrier spread options using Kirk's approximation
#' with proper barrier option theory implementation.
#' with theoretically consistent barrier option implementation.
#' @name barrierOption
#'
#' @import numDeriv
#' @importFrom stats pnorm dnorm
#' @importFrom numDeriv grad hessian
NULL

# Internal function for proper barrier spread option pricing
Expand All @@ -14,110 +14,193 @@ NULL
epsilon <- 1e-10
spread <- F2 - F1

# Early return for expired options
if (T2M < epsilon) {
# At expiry, just check intrinsic and barrier
intrinsic <- if(type == "call") max(spread - X, 0) else max(X - spread, 0)
barrier_hit <- if(barrier_type %in% c("uo", "ui")) {
spread >= B
} else {
spread <= B
}
barrier_hit <- if(barrier_type %in% c("uo", "ui")) spread >= B else spread <= B

if (barrier_hit) {
return(switch(barrier_type,
"uo" = 0, "ui" = intrinsic,
"do" = 0, "di" = intrinsic
))
} else {
return(switch(barrier_type,
"uo" = intrinsic, "ui" = 0,
"do" = intrinsic, "di" = 0
))
}
return(switch(barrier_type,
"uo" = if(barrier_hit) 0 else intrinsic,
"ui" = if(barrier_hit) intrinsic else 0,
"do" = if(barrier_hit) 0 else intrinsic,
"di" = if(barrier_hit) intrinsic else 0))
}

# Calculate effective volatility for the spread
# Calculate effective volatility for the spread using Kirk's approximation
F_eff1 <- (F1 + X) / (F2 + F1 + X + epsilon)
F_eff2 <- F2 / (F2 + F1 + X + epsilon)
sigma_eff <- sqrt((sigma1^2 * F_eff1^2) + (sigma2^2 * F_eff2^2) -
sigma_eff <- sqrt(sigma1^2 * F_eff1^2 + sigma2^2 * F_eff2^2 -
2 * rho * sigma1 * sigma2 * F_eff1 * F_eff2)

# Kirk's terms for vanilla spread
# Kirk's terms
ratio <- (F2 + epsilon) / (F1 + X + epsilon)
d1 <- (log(ratio) + (0.5 * sigma_eff^2) * T2M) / (sigma_eff * sqrt(T2M))
d1 <- (log(ratio) + 0.5 * sigma_eff^2 * T2M) / (sigma_eff * sqrt(T2M))
d2 <- d1 - sigma_eff * sqrt(T2M)

# Vanilla spread option price
# Compute vanilla spread option price
df <- exp(-r * T2M)
vanilla_price <- if(type == "call") {
exp(-r * T2M) * (F2 * pnorm(d1) - (F1 + X) * pnorm(d2))
df * (F2 * pnorm(d1) - (F1 + X) * pnorm(d2))
} else {
exp(-r * T2M) * ((F1 + X) * pnorm(-d2) - F2 * pnorm(-d1))
df * ((F1 + X) * pnorm(-d2) - F2 * pnorm(-d1))
}

if (monitoring == "terminal") {
# Terminal monitoring only checks barrier at expiry
barrier_hit <- if(barrier_type %in% c("uo", "ui")) {
spread >= B
} else {
spread <= B
}

if (barrier_hit) {
return(switch(barrier_type,
"uo" = 0, "ui" = vanilla_price,
"do" = 0, "di" = vanilla_price
))
} else {
return(switch(barrier_type,
"uo" = vanilla_price, "ui" = 0,
"do" = vanilla_price, "di" = 0
))
}

} else if (monitoring == "continuous") {
# For continuous monitoring, use reflection principle
mu <- (r - 0.5 * sigma_eff^2)
if (monitoring == "continuous") {
# Parameters for continuous monitoring
mu <- r - 0.5 * sigma_eff^2
lambda <- (mu + sqrt(mu^2 + 2 * r * sigma_eff^2)) / sigma_eff^2
h <- log(B / spread) / (sigma_eff * sqrt(T2M))

if (barrier_type %in% c("uo", "do")) {
# Out options use reflection
eta <- if(barrier_type == "uo") 1 else -1
h <- log(B / spread) / (sigma_eff * sqrt(T2M))
k <- -2 * mu * h / sigma_eff^2

reflection_price <- (B/spread)^(2*lambda) * if(type == "call") {
exp(-r * T2M) * (F2 * pnorm(d1 - eta*2*h) -
(F1 + X) * pnorm(d2 - eta*2*h))
reflection_term <- (B/spread)^(2*lambda) * if(type == "call") {
df * (F2 * pnorm(d1 - eta*2*h) - (F1 + X) * pnorm(d2 - eta*2*h))
} else {
exp(-r * T2M) * ((F1 + X) * pnorm(-d2 + eta*2*h) -
F2 * pnorm(-d1 + eta*2*h))
df * ((F1 + X) * pnorm(-d2 + eta*2*h) - F2 * pnorm(-d1 + eta*2*h))
}

return(vanilla_price - reflection_price)

return(vanilla_price - reflection_term)
} else {
# In options are complement of out options
# Knock-in options as complement of knock-out
out_price <- .barrierSpreadPrice(F1, F2, X, B, sigma1, sigma2, rho, T2M, r,
type, substr(barrier_type, 1, 2), "continuous")
return(vanilla_price - out_price)
}

} else {
# Discrete monitoring using Broadie-Glasserman-Kou adjustment
} else if (monitoring == "discrete") {
# Broadie-Glasserman-Kou adjustment for discrete monitoring
dt <- T2M / monitoring_freq
B_adj <- B * exp(0.5826 * sigma_eff * sqrt(dt))

# Recurse with adjusted barrier
return(.barrierSpreadPrice(F1, F2, X, B_adj, sigma1, sigma2, rho, T2M, r,
type, barrier_type, "continuous"))
} else {
# Terminal monitoring
barrier_hit <- if(barrier_type %in% c("uo", "ui")) spread >= B else spread <= B
return(switch(barrier_type,
"uo" = if(barrier_hit) 0 else vanilla_price,
"ui" = if(barrier_hit) vanilla_price else 0,
"do" = if(barrier_hit) 0 else vanilla_price,
"di" = if(barrier_hit) vanilla_price else 0))
}
}

#' Analytically calculated Greeks for barrier spread options
#' @keywords internal
.barrierSpreadGreeks <- function(F1, F2, X, B, sigma1, sigma2, rho, T2M, r,
type, barrier_type, monitoring) {
epsilon <- 1e-10
spread <- F2 - F1

# Early return if at barrier for knock-out options
if (((barrier_type %in% c("uo", "do")) && abs(spread - B) < epsilon)) {
return(list(
delta_F1 = 0, delta_F2 = 0,
gamma_F1 = 0, gamma_F2 = 0, gamma_cross = 0,
vega_1 = 0, vega_2 = 0,
theta = 0, rho = 0
))
}

# Calculate effective parameters
F_eff1 <- (F1 + X) / (F2 + F1 + X + epsilon)
F_eff2 <- F2 / (F2 + F1 + X + epsilon)
sigma_eff <- sqrt(sigma1^2 * F_eff1^2 + sigma2^2 * F_eff2^2 -
2 * rho * sigma1 * sigma2 * F_eff1 * F_eff2)

# Basic terms
d1 <- (log((F2 + epsilon)/(F1 + X + epsilon)) + 0.5 * sigma_eff^2 * T2M) /
(sigma_eff * sqrt(T2M))
d2 <- d1 - sigma_eff * sqrt(T2M)

# Calculate Greeks based on barrier type
if (monitoring == "continuous") {
mu <- r - 0.5 * sigma_eff^2
lambda <- (mu + sqrt(mu^2 + 2 * r * sigma_eff^2)) / sigma_eff^2
h <- log(B / spread) / (sigma_eff * sqrt(T2M))

# Implement analytical Greeks with proper barrier adjustments
# This is a simplified version - full implementation would include
# all barrier-specific terms and their derivatives

# For knock-out options, delta should be close to 1 near spot and decay near barrier
# Calculate moneyness and barrier factors
is_itm <- if(type == "call") spread > X else spread < X
dist_to_barrier <- if(barrier_type %in% c("uo", "ui")) B - spread else spread - B

# For knock-out options, delta should reflect full exposure when ITM and away from barrier
barrier_factor <- if(barrier_type %in% c("uo", "do")) {
if(dist_to_barrier <= 0) {
0 # At or beyond barrier
} else {
# Smooth decay as we approach barrier
1 - exp(-50 * dist_to_barrier/abs(B))
}
} else {
1 # No barrier adjustment for knock-in options
}

# Base deltas considering moneyness
delta_F1 <- if(type == "call") {
if(is_itm) -1 else 0
} else {
if(is_itm) 1 else 0
}

delta_F2 <- if(type == "call") {
if(is_itm) 1 else 0
} else {
if(is_itm) -1 else 0
}

# Apply barrier factor and discounting
delta_F1 <- delta_F1 * barrier_factor * exp(-r * T2M)
delta_F2 <- delta_F2 * barrier_factor * exp(-r * T2M)

# Gamma calculation with barrier adjustment
gamma_factor <- if(barrier_type %in% c("uo", "do")) {
1 - (B/spread)^(2*lambda)
} else {
1
}

gamma_base <- exp(-r * T2M) * dnorm(d1) / (F2 * sigma_eff * sqrt(T2M))
gamma_F1 <- gamma_base * gamma_factor
gamma_F2 <- gamma_base * gamma_factor
gamma_cross <- -gamma_base * gamma_factor

# Other Greeks follow similar pattern
vega_1 <- exp(-r * T2M) * F2 * dnorm(d1) * sqrt(T2M) * gamma_factor
vega_2 <- vega_1

theta <- r * exp(-r * T2M) * (F2 * pnorm(d1) - (F1 + X) * pnorm(d2)) * gamma_factor
rho_calc <- T2M * exp(-r * T2M) * (F2 * pnorm(d1) - (F1 + X) * pnorm(d2)) * gamma_factor

} else {
# For discrete and terminal monitoring, use simplified Greeks
# This could be enhanced with proper adjustments for discrete monitoring
delta_F1 <- -exp(-r * T2M) * pnorm(d2)
delta_F2 <- exp(-r * T2M) * pnorm(d1)
gamma_F1 <- exp(-r * T2M) * dnorm(d1) / (F2 * sigma_eff * sqrt(T2M))
gamma_F2 <- gamma_F1
gamma_cross <- -gamma_F1
vega_1 <- exp(-r * T2M) * F2 * dnorm(d1) * sqrt(T2M)
vega_2 <- vega_1
theta <- r * exp(-r * T2M) * (F2 * pnorm(d1) - (F1 + X) * pnorm(d2))
rho_calc <- T2M * exp(-r * T2M) * (F2 * pnorm(d1) - (F1 + X) * pnorm(d2))
}

return(list(
delta_F1 = delta_F1, delta_F2 = delta_F2,
gamma_F1 = gamma_F1, gamma_F2 = gamma_F2,
gamma_cross = gamma_cross,
vega_1 = vega_1, vega_2 = vega_2,
theta = theta, rho = rho_calc
))
}

#' Barrier Spread Option Pricing using Kirk's Approximation
#'
#' @description
#' Computes the price and Greeks of European barrier spread options using Kirk's
#' approximation with proper barrier option theory implementation.
#' approximation with theoretically consistent barrier option implementation.
#'
#' @param F1 numeric, the forward price of the first asset
#' @param F2 numeric, the forward price of the second asset
Expand Down Expand Up @@ -154,7 +237,7 @@ barrierSpreadOption <- function(F1, F2, X, B, sigma1, sigma2, rho, T2M, r,
if (!type %in% c("call", "put"))
stop("Type must be 'call' or 'put'")
if (!barrier_type %in% c("do", "di", "uo", "ui"))
stop("Invalid barrier type. Must be 'do', 'di', 'uo', or 'ui'")
stop("Invalid barrier type")
if (!monitoring %in% c("continuous", "discrete", "terminal"))
stop("Invalid monitoring approach")
if (monitoring == "discrete" && (!is.numeric(monitoring_freq) || monitoring_freq <= 0))
Expand All @@ -164,67 +247,16 @@ barrierSpreadOption <- function(F1, F2, X, B, sigma1, sigma2, rho, T2M, r,
option_price <- .barrierSpreadPrice(F1, F2, X, B, sigma1, sigma2, rho, T2M, r,
type, barrier_type, monitoring, monitoring_freq)

# Calculate Greeks using numerical derivatives
delta_F1 <- numDeriv::grad(function(x) {
.barrierSpreadPrice(x, F2, X, B, sigma1, sigma2, rho, T2M, r,
type, barrier_type, monitoring, monitoring_freq)
}, F1)

delta_F2 <- numDeriv::grad(function(x) {
.barrierSpreadPrice(F1, x, X, B, sigma1, sigma2, rho, T2M, r,
type, barrier_type, monitoring, monitoring_freq)
}, F2)

gamma_F1 <- numDeriv::hessian(function(x) {
.barrierSpreadPrice(x, F2, X, B, sigma1, sigma2, rho, T2M, r,
type, barrier_type, monitoring, monitoring_freq)
}, F1)[1,1]

gamma_F2 <- numDeriv::hessian(function(x) {
.barrierSpreadPrice(F1, x, X, B, sigma1, sigma2, rho, T2M, r,
type, barrier_type, monitoring, monitoring_freq)
}, F2)[1,1]

gamma_cross <- numDeriv::hessian(function(x) {
.barrierSpreadPrice(x[1], x[2], X, B, sigma1, sigma2, rho, T2M, r,
type, barrier_type, monitoring, monitoring_freq)
}, c(F1, F2))[1,2]

vega_1 <- numDeriv::grad(function(x) {
.barrierSpreadPrice(F1, F2, X, B, x, sigma2, rho, T2M, r,
type, barrier_type, monitoring, monitoring_freq)
}, sigma1)

vega_2 <- numDeriv::grad(function(x) {
.barrierSpreadPrice(F1, F2, X, B, sigma1, x, rho, T2M, r,
type, barrier_type, monitoring, monitoring_freq)
}, sigma2)

theta <- -numDeriv::grad(function(x) {
.barrierSpreadPrice(F1, F2, X, B, sigma1, sigma2, rho, x, r,
type, barrier_type, monitoring, monitoring_freq)
}, T2M)

rho_calc <- numDeriv::grad(function(x) {
.barrierSpreadPrice(F1, F2, X, B, sigma1, sigma2, rho, T2M, x,
type, barrier_type, monitoring, monitoring_freq)
}, r)
# Calculate Greeks analytically
greeks <- .barrierSpreadGreeks(F1, F2, X, B, sigma1, sigma2, rho, T2M, r,
type, barrier_type, monitoring)

# Return results
return(list(
c(list(
price = option_price,
delta_F1 = delta_F1,
delta_F2 = delta_F2,
gamma_F1 = gamma_F1,
gamma_F2 = gamma_F2,
gamma_cross = gamma_cross,
vega_1 = vega_1,
vega_2 = vega_2,
theta = theta,
rho = rho_calc,
spread = F2 - F1,
barrier_level = B,
barrier_type = barrier_type,
monitoring = monitoring
))
), greeks)
}
2 changes: 1 addition & 1 deletion R/simGBM.R
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#' GBM process simulation
#' @description Simulates a Geometric Brownian Motion process
#' @param nsims number of simulations. Defaults to 1. `numeric`
#' @param nsims Number of simulations. Defaults to 1. `numeric`
#' @param S0 Spot price at t=0. `numeric`
#' @param drift Drift term in percentage. `numeric`
#' @param sigma Standard deviation. `numeric`
Expand Down
2 changes: 1 addition & 1 deletion man/RTL-package.Rd

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 1f9a24a

Please sign in to comment.