Why Equity Option Prices Need American Pricing and Volatility Surfaces
Summary
The document examines why a basic Black–Scholes calculation may produce theoretical call prices outside observed bid–ask quotes. It points out that the example omits the continuous dividend yield in the pricing formula, but argues that correcting this alone is not enough to produce reliable fair values. The options are American-style, implied volatility varies across strikes, and interest rates and dividend assumptions also affect valuations, particularly at longer maturities.
The answer describes the broader workflow used to build an equity option pricing framework: estimate implied dividends from options while accounting for discrete dividend dates, use suitable interest rate curves, and convert American option prices to European equivalents before applying European methods. It also notes that sparse or illiquid maturities may require extrapolation and that volatility surfaces are often fitted with methods such as SVI. These are practical considerations rather than a complete calibration recipe; the document offers no fitted surface, corrected prices, or demonstration that model prices will fall between each market bid and ask.
Key ideas
- The call pricing formula should include a continuous dividend yield when rates and dividends are modeled continuously.
- Listed equity options may be American-style, so European pricing formulas may not directly apply.
- Implied volatility can vary by strike, making a single historical volatility an inadequate surface assumption.
- Dividend schedules and interest rate curves influence theoretical prices and must be estimated appropriately.
- Building a usable surface may require de-Americanization and extrapolation for less liquid maturities.
Tags
Full text
# Option Pricing - Incorrect price outcome for Out of the Money (OTM) calls
# Option Pricing - Incorrect price outcome for Out of the Money (OTM) calls
I have the options data for a stock -
```
structure(list(Curr_Date = structure(c(18904L, 18904L, 18904L,
18904L, 18904L, 18904L, 18904L, 18904L, 18904L), class = c("IDate",
"Date")), ticker = c("GOLD", "GOLD", "GOLD", "GOLD", "GOLD",
"GOLD", "GOLD", "GOLD", "GOLD"), ExpDate = structure(c(18915L,
18915L, 19013L, 19013L, 19013L, 19377L, 19377L, 19377L, 19377L
), class = c("IDate", "Date")), Strike = c(18, 30, 10, 30, 40,
10, 18, 30, 40), Option_Type = c("calls", "calls", "calls", "calls",
"calls", "calls", "calls", "calls", "calls"), OI = c(3570L, 341L,
723L, 68772L, 26302L, 1731L, 15662L, 37274L, 13215L), Vol = c(1L,
1L, 5L, 40L, 1L, 1L, 2L, 4L, 5L), ask = c(0.56, 0.01, 8.6, 0.07,
0.04, 10, 2.8, 0.51, 0.21), bid = c(0.54, 0, 8.2, 0.06, 0.03,
8.05, 2.58, 0.48, 0.2), StockPrice = c(18.23, 18.23, 18.23, 18.23,
18.23, 18.23, 18.23, 18.23, 18.23), days2exp = c(0.0301369863013699,
0.0301369863013699, 0.298630136986301, 0.298630136986301, 0.298630136986301,
1.2958904109589, 1.2958904109589, 1.2958904109589, 1.2958904109589
), calculated_price = c(0.53, 0, 8.27, 0, 0, 8.5, 2.87, 0.36,
0.06)), row.names = c(NA, -9L), class = c("data.table", "data.frame"
), .internal.selfref = <pointer: 0x562667c9eac0>)
```
which looks like -
```
Curr_Date ticker ExpDate Strike Option_Type OI Vol ask bid StockPrice days2exp calculated_price
1: 2021-10-04 GOLD 2021-10-15 18 calls 3570 1 0.56 0.54 18.23 0.03013699 0.53
2: 2021-10-04 GOLD 2021-10-15 30 calls 341 1 0.01 0.00 18.23 0.03013699 0.00
3: 2021-10-04 GOLD 2022-01-21 10 calls 723 5 8.60 8.20 18.23 0.29863014 8.27
4: 2021-10-04 GOLD 2022-01-21 30 calls 68772 40 0.07 0.06 18.23 0.29863014 0.00
5: 2021-10-04 GOLD 2022-01-21 40 calls 26302 1 0.04 0.03 18.23 0.29863014 0.00
6: 2021-10-04 GOLD 2023-01-20 10 calls 1731 1 10.00 8.05 18.23 1.29589041 8.50
7: 2021-10-04 GOLD 2023-01-20 18 calls 15662 2 2.80 2.58 18.23 1.29589041 2.87
8: 2021-10-04 GOLD 2023-01-20 30 calls 37274 4 0.51 0.48 18.23 1.29589041 0.36
9: 2021-10-04 GOLD 2023-01-20 40 calls 13215 5 0.21 0.20 18.23 1.29589041 0.06
```
As it can be seen in column `calculated_price`, the OTM (out of the money) calls are hugely underpriced compared to bid or ask price. I have used the below formulas to calculate the expected price of an option.
```
r = 0.0148 # Risk free rate
v = 0.3188 # Historic volatility of 252 trading days
b = 0.02 # TTM yeild
dt[, d1 := ((log(StockPrice/Strike)) + (r - b + (v^2)/2) * days2exp)/(v * (sqrt(days2exp)))]
dt[, d2 := d1 - v * (sqrt(days2exp))]
dt[, calculated_price := round(StockPrice * pnorm(d1) - Strike*exp(-r * days2exp)*pnorm(d2), 2)]
```
Can someone point me out what is wrong with these formulas and how to correct the mispricing?
The expected result is - The values in column `calculated_price` should be between the market price in `bid` and `ask` columns.
Thanks!
## Answer by AKdemy (score 2, accepted)
https://quant.stackexchange.com/a/68238
Your end goal of obtaining "fair" theoretical option prices will unfortunately require a lot of effort if you want to get this done properly on your own.
Here are a few reasons why:
- All Nasdaq marketplace stock options are American-style
- IVOL exhibits a skew
- You need to have reliable interest rates
- Dividend assumptions will influence your pricing for longer tenors a fair bit
The forward price is not directly quoted for listed options markets. Futures may be quoted but maturities frequently do not coincide with (all) option maturities. Interpolation is not trivial because future dividends (even times of payments) are usually unknown. Therefore, most practitioners use vanilla equity options to back out (implied) dividends.
The problem hereby is that put-call parity for American-Style options does not hold. Even for European-Style options, there are frequently issues with different trading times and erratic option prices etc. Since dividend payments are discrete in nature, you require a dividend schedule with dates and amounts to derive an implied dividend curve. This curve is usually noisier than one would hope for and commonly smoothed (via Kalman filter of the like).
For American options, one needs to first compute the European equivalent via a process called de-Americanization. For tenors that are not liquid, extrapolation may also be needed.
For interest rates, it is customary to use stripped interest rate curves. These used to be Libor 3m based but are now mainly SOFR curves, which are fundamentally built in a similar way.
Lastly, your main task will be building a reliable vol surface. SVI is frequently used. Voladynamics provides some interesting ideas and examples.
Your formula seems to exclude $e^{-q}$. As Wikipedia correctly states, it should be $${\displaystyle Se^{-q\tau }\Phi (d_{1})-e^{-r\tau }K\Phi (d_{2})\,}$$ This formula also requires rates and dividends to be continuous.
This link shows data sources (also IVOL). I am not familiar with any of these to be honest, but potentially they will help (be reliable). Many vendors like Bloomberg also offer, at a cost, Vol surfaces as part of their standard product offering.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.