The Volatility of Volatility for Stocks & Crypto, Part 8

A hint of how to compute call option prices in this context

NTTP
11 min readJun 11, 2024
A more obsolete call “option.” Wow, no curly-cue cord! Photo by Quino Al on Unsplash

The Excel file for this example is at: https://github.com/diffent/excel/blob/main/volofvol7F.xlsm

There seems to be some alignment if not full agreement with those in-the-know that American style call options are valued/priced similarly (or the same) as European style call options, because it is purportedly not beneficial to exercise calls before their expiry… as American options are allowed to be exercised. To non-option trader mindsets, this assertion seems debatable. Take for example a recent article…

https://medium.com/@markvmorgan/real-estate-might-make-you-a-broke-millionaire-dd5f3ef64ab1

…on this site regarding the illiquidity of real estate. It seems that for risk reduction purposes, the ability to cash out a position at any time is a valuable characteristic for an investment to have. We will leave it to options trading experts to debate this in the comments so we all can learn, and here seems like a nice intro discussion on the topic:

https://www.quora.com/For-a-non-dividend-paying-stock-why-does-an-american-call-option-have-the-same-value-as-an-european-call-option

In the mean time, we can continue on our quixotic Excel macro quest and compute Euro style call option prices with the macro, as this is a much simpler computation than that for American style options. For this first pass we will ignore dividends, since a correction for dividends can be added to the model later, at least approximately:

https://www.investopedia.com/articles/active-trading/090115/understanding-how-dividends-affect-option-prices.asp

And, our GE stock example which we have been using has small-ish dividends at the moment, which is favorable to the approximation. GE currently has has an annual dividend yield of about 0.7%, which is << than the current risk free rate of 4+% per annum. Hence, including dividends would seemingly be a small adjustment. Further, our stochvol model as we will see is not tuned yet, so our model is probably off by more than 1% anyway. The Pareto rule applies to math modeling as well: Deal with the big problems first. Recall also that we had used an annual risk free rate of 4% as a placeholder rate.

The setup

We took the last close price of GE of $161.46 on June 3 2024 as our last known price, then we read off some call option prices at a $190 strike price out into the future from that date, reading from the options chain user interface at finance.yahoo.com [YF] for GE; and then saved them for later comparison with our model.

Yahoo Finance call options table example for one particular expiration date June 14 and many strike prices. Caution: This table was not generated on the same day that we pulled the data.

Historical options data is kind of tough to find sometimes according to some commenters, and may be expensive…

But wait! We just found some historical options data on Intrino! We like their tag line of “without breaking the bank.” Yes!

https://docs.intrinio.com/documentation/product/options-eod-delayed/api_v2/getting_started

For those of you who don’t know yet, the data provider IEX Cloud (used in our MCarloRisk3D and MVAR apps) is shutting down in August 2024. They are directing us to Intrinio to help fill the upcoming data gap with actual data:

https://www.reddit.com/r/algotrading/comments/1d542h5/iex_cloud_shutting_down_in_august/

They are doing us long-term IEX customers a “solid,” so take a look at their stuff!

… so you may have to re-run this experiment on whatever day you want and pull new options data for that day.

We just left the historical volatility of our model the same as we had it before (in our last article), since this will be a tunable parameter later anyway.

The [Quantstart] folks have some standard code to compute Euro option prices for one example day, and we put this code in our monte carlo forecast macro (translated into Excel VBA from C++) and ran it for every day forward for 100 days at the single strike price of $190. As a side note, one of the market expiry dates we checked did not have a $190 options price listed, so we averaged call prices at $185 and $195 strike or whatever were the bracketing prices around $190. This is just a coarse first test to see if our model is in the ballpark of what the market prices are telling us.

Call options computation code at a single strike price but for all forecasted days forward is added to the monte carlo macro [mcFaster], viz:

' strikePrice is set earlier like this: 
' Let strikePrice = Sheets("Sheet1").Cells(2, 7).Value

' optional put/call options pre-computations
' we will sum up MC generated price values over the strike price and
' under the strike price separately for each time step

' timesteps are in rows with (row)(col) indexing

' daily risk free rate same as drift for now

Let dailyRFR = Sheets("Sheet1").Cells(10, 5).Value

For irow = 2 To 2 + ntimesteps

Let callSum = 0
Let putSum = 0

For monte = 1 To nmonte
' simple distance above strike for call, distance below strike for put
' clipped at 0 so no negative prices get into our sum
callSum = callSum + WorksheetFunction.Max(mcBlockOuter(irow)(monte) - strikePrice, 0)
putSum = putSum + WorksheetFunction.Max(strikePrice - mcBlockOuter(irow)(monte), 0)
Next

Let avgForCall = callSum / nmonte
Let avgForPut = putSum / nmonte

Sheets("probability").Cells(irow, 205) = callSum / nmonte
Sheets("probability").Cells(irow, 206) = putSum / nmonte

' discount back to present value via risk free rate
Sheets("probability").Cells(irow, 207) = avgForCall * Exp(-dailyRFR * (irow - 2))
Sheets("probability").Cells(irow, 208) = avgForPut * Exp(-dailyRFR * (irow - 2))

Next
Payoff operator for each path from a [Quantstart] linked article, not directly in [Quantstart]. K = striKe price. S = Spot price. This is performed by the VBA code lines involving WorksheetFunction.Max above. We only do the full computation for the calls here, not for the puts; but we have the puts in the code for maybe later. As earlier articles here referenced note, American puts are valued differently than European puts, so let’s not compute them yet as this is the Euro style computation (“exercise at expiry only”)
MC options price from [Quantstart], with notable items highlighted that we duplicated in our Excel macro

This is identical to how Euro put and call option prices are computed in our MCarloRisk apps, but those apps and related models have a different empirical returns-based data set from which the MC price path generator algorithm samples.

These estimated option prices are stored in our sheet probability at columns GV and to the right.

Figure P: From sheet probability. This is for the case of assuming that the options model runs every day, not just every trading day. Note sequential calendar dates in column GV. HB was previously generated using the normal distribution assumption (no stochvol) then pasted in HB from GY.

For now, we delete the historical GE closing dates and prices that we had in columns GT and GU on sheet probability (reserving those columns for later), since we are projecting forward into the future beyond June 3, and are not interested in the past for this trial… except to estimate initial model parameters such as volatility.

For comparison, we run this Excel model at the “no stochvol” settings, and also again at our prior extreme stochvol settings that we employed to try to capture what was going on with the GE stock price recently in its “bull run”:

GE in recent months. Wow.

The “no stochvol” case should in theory give results similar to the Black-Scholes analytic formulas (within tolerance of the un-converged monte carlo results noise) if you used the same values we have here for mean, volatility, and risk free rate (and dividend if you want). It seems that this author has a nice spreadsheet for such computations, and there are other calculators of this type online and in apps.

We haven’t checked our monte carlo macro results yet versus the analytic (formula) method, but feel free to tell us all your findings in the comments if you try it!

In some of these analytic formulations, take care to note the units of the various parameters, as they may be different from what we use here. For example, we use days for time units rather than fractions of a year, along with daily returns, daily volatility, and so on.

Figure Q: Two models for call prices versus market data. The final dot on the right is beyond our 100 days worth of estimates so it’s kind of floating out there to the right of our model curves. But you can see that if we extrapolate the red and blue curves in the same direction they are going, the curves will miss that point too.

We get the familiar hockey-stick shaped plots of option price over time with a bit of a curve at the corner, which is encouraging. We only ran these for 5000 MC runs, so the curves are not smooth. You can crank up nmonte to a higher value in cell J1 on Sheet1 and re-run the monte to get smoother curves.

Call option theoretical from Wikipedia, hockey sticks. We are only computing Payoff here, not Profit, so the baseline is zero.

Time unit / step issues

There is a slight issue on what to use for a daily risk free rate. Since the model only generates results on trading days, you might want to use 252 (trading days per year) versus 365 for our annual to daily RFR computation (Sheet1 / G10). This brings up the differences among calendar days, trading days, and fractions of a year, which is a less trivial issue: Which of these time increments should we use for option price estimates? When we compare actual market prices to a model, should we let the model accrue price changes over non trading days (weekends, market holidays?), or freeze it in time during those non trading days? Should we use 365 in the annual to daily RFR conversion, or 252… the nominal count of trading days per year?

https://www.powercycletrading.com/do-options-lose-value-over-the-weekend/

https://www.reddit.com/r/thetagang/comments/nncok4/theta_decay_over_the_weekend/

The TastyTrade folks who specialize in retail options trading cover this in one of their videos, according to the above links. If we find the video, we will post it here. We are not yet concerned with the noted “theta decay,” because we haven’t even computed theta yet [this is a different theta than our stochvol theta, in an unfortunate overlap of variable names as sometimes happens], but the concept is the same… does the formulated options price keep changing over the weekend or not, even if you can’t trade it?

One reference seems to suggest: Just be consistent in your use of trading days versus calendar days in your modeling.

https://quant.stackexchange.com/questions/32037/when-pricing-options-which-day-counting-conventions-should-be-used-to-calculate

“No matter how you do it, the key is that you use the same one for the calibration of your model to market data and for pricing.” [from above link]

To start, in this article’s tests, we used calendar days for plotting from-market options prices to our model. This assumes that the model “works” and proceeds in time over the weekends and other non trading days. This is contrary to our prior application of this model in earlier articles where we only used it to forecast on trading days to compare to historical actual closing prices of the underlying asset. As such, this makes us a little queasy… with a resolution TBD. It seems that we should be using trading days only with this model, which is based upon closing prices from trading days, but…

Ignoring these qualms for the moment: With this setup, we see in Figure Q above that the normal distribution based “no stochvol” case is under-estimating call option prices versus the market when we start to go more than a month out, and our “cranked up” stochvol model is over-estimating them. This seems to be about right, because we only cranked up the stochvol parameters to see what would happen when doing so, and to see if we could capture historical behavior of GE a bit better (demonstrated in our prior articles). We haven’t tuned any of the stochvol parameters. Both of these models have the same nominal volatility.

There is also the additional data that we have available for option price analysis including bid/ask prices, which could be plotted on this type of graph to see how our models fit within those ranges. We wanted to get this edition of the article series out and get the code into the hands of our users before we had a chance to do this. However, such bid/ask pricing and other data may have some value in tuning and calibrating these types of models to reality.

Same options table as above but with bid/ask prices marked with arrows. Note that these are not entirely in agreement with the last traded price, sometimes by a significant amount. Note also the volume and open interest data… these could be weight factors that we apply if we want to tune our model.

What we really need is some way to pull this options data easily into our Excel sheet and then into the proper cells of it, overlay this data onto our model, and not have to hunt and peck and make sure we enter the data into proper sheet cells. To ease data entry for options prices from the market [from YF or any other source you prefer], we created column GV on sheet probability [see Figure P above] using Excel’s date increment function, so we could know on which row to enter the options price from YF.

A slice at a given time and a given strike

Using our probability sheet graph, we can examine a slice through the model at a given time step forward, with price on the X axis and probability on the Y axis to explain (with additional annotations) where this options pricing method comes from.

Base graph from our probplot sheet, with additional annotations and shading added.

We see (as we have shown in prior articles) the characteristic positively skewed bell curve shape of price / probabilities. If we draw a vertical line at X = $190 (our strike price which we chose arbitrarily for this article), we can shade in the area under the price/probability curve to the right of this strike price, indicating where the 5000 monte carlo paths have blipped above $190 at this 100 day forward example. This area indicates where the Euro call option will have postive value at that 100 day forward expiry, according to the monte carlo model. To compute the options price at this 100 days forward, all the code does is: a) subtracts the strike price (along the X axis), b) sums up those “in excess of strike-price” deltas, then c) divides by the number of monte carlo iterations… thereby averaging those results (again averaging along the X price axis).

Note that we divide by the total number of path simulations [5000 in this case], thereby counting also the cases where the monte carlo paths were below the strike price at this 100 day count forward and hence contributing zero to the sum. This is not intuitively obvious at first. It seems like you might want to divide by only the paths that are above the strike price at that day count forward, but this would yield a much larger number.

This forward in time average price is then projected backwards in time to the present by the standard accounting formula: the “present value from future value” relation, assuming a constant (risk free) daily interest rate over this time period of 100 days: PV = FV*EXP(-rate*time). This formula we see applied in the above Quantstart snapshot and in our above macro code. Note that rate and time need to be in the same units (e.g. rate has to be a daily rate and time in days, or rate has to be yearly and time in fractions of a year).

One thing that seems fairly trivial to do with this code is to compute option prices for multiple strike prices as the monte carlo code runs. Stay tuned to this Medium channel for more updates on this concept!

As usual, if you see any errors in this article or the Excel file, please let us know in the comments!

[Quantstart] Heston Stochastic Volatility Model with Euler Discretisation in C++, https://www.quantstart.com/articles/Heston-Stochastic-Volatility-Model-with-Euler-Discretisation-in-C/

--

--