Debugging a Trinomial Uncertain Volatility Option Pricing Scheme
Summary
The document presents an attempted trinomial-tree implementation of an uncertain volatility model for pricing European calls between minimum and maximum volatility bounds. It lays out a backward-induction scheme, defines a discrete second-difference term from neighboring option values, and uses its sign to select the volatility contribution for upper and lower prices. The questioner reports that the implementation returns identical minimum and maximum prices.
The reply identifies a direct coding issue: the implemented upper and lower neighboring-value terms are always zero, so the volatility-selection logic cannot work as intended. The supplied code repeats the same neighboring value in places where the finite-difference expression requires values from adjacent states, and its boundary handling therefore needs careful review. The document offers a diagnosis rather than a corrected implementation or numerical validation. Its equations and code also contain indexing inconsistencies, so the tree conventions and boundary conditions should be reconciled before relying on the resulting price bounds.
Key ideas
- The uncertain volatility method uses backward induction to compute upper and lower option value bounds.
- The finite-difference term depends on option values at neighboring tree states.
- The implementation’s neighboring-value expressions make the upper and lower terms zero.
- When those terms are zero, the volatility-selection logic cannot distinguish the price bounds.
- Tree indexing and boundary conditions should be checked against the intended trinomial equations.
Tags
Full text
# Uncertain Volatility Model - Option Pricing R code help
# Uncertain Volatility Model - Option Pricing R code help
I am trying to price the following call option using the UVM method in R.
The code I wrote below keeps producing the same price for the min and max volatilities, which is wrong, however, I can't seem to fix this error.
```
# Market Data
strike_call <- c(150,152.5,155,157.5,160,165,170,172.5,175,177.5,180,182.5,185,187.5,190,192.5,195,197.5,200,202.5,205,207.5,210,212.5,215,217.5,220,222.5,225,227.5,230,232.5,235,245,250)
price_market_call <- c(59.9,57.55,55.05,52.55,50.1,45.1,40.15,37.65,35.15,32.7,30.25,27.6,25.15,22.75,20.35,17.95,15.6,13.3,11.2,9.2,7.45,5.85,4.4,3.2,2.2,1.45,0.92,0.56,0.36,0.23,0.15,0.11,0.07,0.02,0.01)
# UVM Price
UVM_price_call <- function(S, K, T, N, r, sigma_min, sigma_max, upper_price=TRUE) {
delta_t <- T / N
# Initialize WT matrix
WT <- matrix(0, nrow=N+1, ncol=N+1)
for (j in 1:(N + 1)) {
WT[N+1, j] <- max(S - K, 0) # Payoff at maturity
}
# f function as defined
f <- function(x, y, z) {
(1 - sigma_max * sqrt(delta_t)/2) * y + (1 + sigma_max * sqrt(delta_t)/2) * z - 2 * x
}
for (i in N:1) {
for (j in 1:(N+1)) {
L_T_plus <- ifelse(j < N+1, f(WT[i+1, j], WT[i+1, j+1], WT[i+1, j+1]), 0)
L_T_minus <- ifelse(j > 1, f(WT[i+1, j], WT[i+1, j-1], WT[i+1, j-1]), 0)
if (upper_price) {
# UVM Max logic
sigma_used <- ifelse(L_T_plus > 0, sigma_max, ifelse(L_T_minus < 0, sigma_max, sigma_min))
} else {
# UVM Min logic
sigma_used <- ifelse(L_T_plus < 0, sigma_min, ifelse(L_T_minus > 0, sigma_min, sigma_max))
}
WT[i, j] <- WT[i+1, j] + exp(-r * delta_t) * sigma_used * (L_T_plus + L_T_minus)/2
}
}
return(WT[1, 2]) # First time step, second state
}
# Given parameters
sigma_min <- 0.1
sigma_max <- 1.7
N <- 1
S <- 209.68
T <- 4/365
r <- 0.07
# Calculate UVM max and min prices
UVM_max_prices <- sapply(strike_call, function(K) {
UVM_price_call(S, K, T, N, r, sigma_min, sigma_max, upper_price=TRUE)
})
UVM_min_prices <- sapply(strike_call, function(K) {
UVM_price_call(S, K, T, N, r, sigma_min, sigma_max, upper_price=FALSE)
})
# Plot
plot(strike_call, price_market_call, type="l", col="blue", ylab="Option Price",
xlab="Strike", main="Option Price vs. Strike")
lines(strike_call, UVM_max_prices, col="red", lty=2)
lines(strike_call, UVM_min_prices, col="pink", lty=2)
legend("topright", legend=c("Market Price", "UVM Max", "UVM Min"),
col=c("blue", "red", "green"), lty=c(1, 2, 2), cex=0.8)
```
The approach above is based on the following finite difference approach.
The price of a stock is determined by:
\begin{equation} S(j, n)=S_{0} \mathrm{e}^{(j-n) \cdot \sigma_{\max } \sqrt{\Delta t}+(n-1) \cdot r \Delta t} \end{equation}
When examining an European option with value function ( F(x) ), for a given node ( (j, n) ), the option value is: \begin{equation} F_{n}^{j} = F_{n}(S(j, n)) \end{equation} Using the UVM, the worst-case scenario price for this can be represented as: \begin{equation} W_{n}^{ \pm, j} = \text{Sup or Inf E}\left[ \sum_{k=j+1}^{N} e^{-r(t_{k}-t_{n})} F_{k}(S_{k}) \right] \end{equation} For a trinomial tree structure, the formula is: \begin{equation} W_{n}^{+, j}=F_{n}^{j}+\mathrm{e}^{-r \Delta t} \times \operatorname{Sup}_{p}\left[P_{U}(p) W_{n+1}^{+, j+1}+P_{M}(p) W_{n+1}^{+, j}+P_{D}(p) W_{n+1}^{+, j+1}\right] \end{equation} \begin{equation} W_{n}^{-, j}=F_{n}^{j}+\mathrm{e}^{-r \Delta t} \times \operatorname{Inf}_{p}\left[P_{U}(p) W_{n+1}^{-, j+1}+P_{M}(p) W_{n+1}^{-, j}+P_{D}(p) W_{n+1}^{-, j+1}\right] \end{equation} This is equivalent to the result according to and the BSB equation solutions are represented by $W_{n}^{ \pm, j}$. Therefore, we find $W_{n}$ as: $$ \begin{aligned} & W_{n}^{+, j}=F_{n}^{j}+\mathrm{e}^{-r \Delta t} \begin{cases}W_{n+1}^{+, j}+\frac{1}{2} L_{n+1}^{+, j} & \text { if } L_{n+1}^{+, j} \geqslant 0 \\ W_{n+1}^{+, j}+\frac{\sigma_{\min }^{2}}{2 \sigma_{\max }^{2}} L_{n+1}^{+, j} & \text { if } L_{n+1}^{+, j}<0\end{cases} \\ & W_{n}^{-, j}=F_{n}^{j}+\mathrm{e}^{-r \Delta t}\left\{\begin{array}{cc} W_{n+1}^{-j}+\frac{1}{2} L_{n+1}^{-j} & \text { if } L_{n+1}^{-j}<0 \\ W_{n+1}^{-, j}+\frac{\sigma_{\min }^{2}}{2 \sigma_{\max }^{2}} L_{n+1}^{-j} & \text { if } L_{n+1}^{-, j} \geqslant 0 \end{array}\right. \end{aligned} $$
The parameters are defined as: \begin{aligned} & U=\mathrm{e}^{\sigma_{\max } \sqrt{\Delta t}+r \Delta t} \\ & M=\mathrm{e}^{r \Delta t} \\ & D=\mathrm{e}^{-\sigma_{\max } \sqrt{\Delta t}+r \Delta t} \end{aligned}
The pricing probabilities associated with a singular parameter are expressed as:
\begin{aligned} & P_{U}(p)=p \cdot\left(1-\frac{\sigma_{\max } \sqrt{\Delta t}}{2}\right) \\ & P_{M}(p)=1-2 p \\ & P_{D}(p)=p \cdot\left(\frac{1+\sigma_{\max } \sqrt{\Delta} t}{2}\right) \\ & \frac{\sigma_{\min }^{2}}{2 \sigma_{\max }^{2}} \leqslant p \leqslant 1 / 2 \end{aligned}
By utilising the finite difference approach, we get: \begin{equation} L_{n+1}^{j}=\left(1-\frac{\sigma_{\max } \sqrt{\Delta t}}{2}\right) W_{n+1}^{j+1}+\left(1+\frac{\sigma_{\max } \sqrt{\Delta t}}{2}\right) W_{n+1}^{j-1}-2 W_{n+1}^{j} \end{equation}
The variables $L_{n+1}^{j}$ and $W_{n+1}$ can be either $L_{n+1}^{-, j}$ or $L_{n+1}^{+, j}$, and $W_{n+1}^{-, .}$ or $W_{n+1}^{+, .}$ respectively.
To determine the price range of the portfolio bounded by $W_{0}^{+, 0}$ and $W_{0}^{-, 0}$, we use the method of backward induction. This means that in each step, the value of $W_{n}^{+, j}$ is derived from the values $W_{n+1}^{+, j-1}$, $W_{n+1}^{+, j}$, $W_{n+1}^{+, j+1}$, and other known variables at the time point $t_{n}$. This logic is also applicable for determining $W_{0}^{-, 0}$.
It would be much appreciated if someone could tell me where I have gone wrong.
## Answer by Alex D (score 0)
https://quant.stackexchange.com/a/76530
Looks like your `L_T_plus` & `L_T_minus` are always zero. Check your implementation.Shown in full with attribution under the source's licence. Licence: CC BY-SA 4.0 (Stack Exchange)
This summary was written by Stratmill's research agent from the original; it is not a copy of the source.