How to compute options theta using QuantLib

How to compute options theta using QuantLib
Calculating the Greeks like theta for American options is tough because they can be exercised before they expire. Instead of a single formula like Black-Scholes, we need to use numerical methods. Today weâll look at theta which is critical for options traders as youâll see below.
The good news?
QuantLib can do it in a few lines of Python code. Itâs a library that offers a suite of tools for pricing options using many different methods. And in todayâs newsletter, weâre going to walk through it step-by-step.
Letâs go!
How to compute options theta using QuantLib
Options theta represents the sensitivity of the option's price to the passage of time. Itâs expressed as the change in the option's price for a one-day decrease in the time to expiration. In financial terms, theta can be considered the "time decay" of an option's value.
Theta is a critical concept for options traders to understand.
For traders holding long positions in options, a negative theta signifies a loss in value with each passing day. This loss in value can erode potential profits. This is especially true as the option approaches expiration.
If youâre short, theta can work in your favor.
For those holding short positions, a negative theta is beneficial. It can make it cheaper to close out a position or allow the option to expire worthless. In both cases maximizing the income received from the initial sale of the option.
Letâs see how to compute options theta with QuantLib.
Imports and set up
Weâll start with importing NumPy for some math, OpenBB for data, and QuantLib to calculate theta.
1import numpy as np
2import QuantLib as ql
3from openbb import obb
4import warnings
5warnings.filterwarnings("ignore")
6obb.user.preferences.output_type = "dataframe"Using OpenBB, grab the options chains to find at-the-money (ATM) strikes, expirations, and historical price data.
1symbol = "AAPL"
2chains = obb.derivatives.options.chains(symbol=symbol)
3prices = obb.equity.price.historical(symbol=symbol, provider="yfinance")
4expiration = chains.expiration.unique()[5]
5strikes = chains.query("`expiration` == @expiration").strike.to_frame()Note weâre picking the 6th nearest expiration date. You can pick whichever expiration youâd like.
Finally, letâs set up our variables for QuantLib using the market data.
1underlying_price = prices.close.iat[-1]
2strike_price = (
3 strikes
4 .loc[
5 (strikes-underlying_price)
6 .abs()
7 .sort_values("strike")
8 .index[0]
9 ].strike
10)
11volatility = prices.close.pct_change().std() * np.sqrt(252)
12maturity = ql.Date(
13 expiration.day,
14 expiration.month,
15 expiration.year,
16)
17dividend_yield = 0.0056
18risk_free_rate = 0.05
19calculation_date = ql.Date.todaysDate()
20ql.Settings.instance().evaluationDate = calculation_dateWe use pandas to find the strike price thatâs closest to the last traded price. Then we find the last closing price and the annualized volatility. QuantLib expects itâs own date objects which we create using the expiration we selected. Finally, we hard code the dividend yield, risk free rate, and set the evaluation date to today.
Market environment set up
QuantLib works by first establishing the market environment. While this might seem overkill for a simple vanilla call option, it is necessary for more advanced option types. QuantLib expects four âhandlesâ to compute the options values.
First, we create a handle to a simple quote object which takes the underlying asset's price for use in option pricing models.
1spot_handle = ql.QuoteHandle(
2 ql.SimpleQuote(underlying_price)
3)Next, we build the yield term structure which in this case is a flat forward curve using the risk free rate we defined above.
1yield_handle = ql.YieldTermStructureHandle(
2 ql.FlatForward(
3 calculation_date,
4 risk_free_rate,
5 ql.Actual365Fixed()
6 )
7)We do the same for the dividend yield. QuantLib also supports discrete dividend dates.
1dividend_handle = ql.YieldTermStructureHandle(
2 ql.FlatForward(
3 calculation_date,
4 dividend_yield,
5 ql.Actual365Fixed()
6 )
7)Finally, we build a handle to the the volatility surface. In our example we assume a constant volatility over time and across strike prices.
1volatility_handle = ql.BlackVolTermStructureHandle(
2 ql.BlackConstantVol(
3 calculation_date,
4 ql.NullCalendar(),
5 volatility,
6 ql.Actual365Fixed()
7 )
8)Set up the options pricing engine
We can now build the Black-Scholes-Merton process which models the dynamics of the underlying asset.
1bs_process = ql.BlackScholesMertonProcess(
2 spot_handle,
3 dividend_handle,
4 yield_handle,
5 volatility_handle
6)Using the Black-Scholes-Merton process, we can build our option pricer.
1engine = ql.BinomialVanillaEngine(bs_process, "crr", steps=1000)This code sets up a binomial tree pricing engine for our vanilla option using the Cox-Ross-Rubinstein (CRR) model with 1000 time steps. The engine is based on the specified Black-Scholes-Merton process.
Value the option
Weâre (finally) ready to define our options and get the Greeks.
1exercise = ql.AmericanExercise(calculation_date, maturity)
2call_option = ql.VanillaOption(
3 ql.PlainVanillaPayoff(ql.Option.Call, strike_price),
4 exercise
5)
6call_option.setPricingEngine(engine)
7
8call_theta = call_option.theta() / 365We define the American style options exercise and use it to initialize a vanilla option with a plain vanilla payoff. This setup creates the call option. We then set the pricing engine we created above and get the theta.
Note QuantLib returns the annualized theta so to get the theta per day, we need to divide by 365.
Next steps
While this example values a plain vanilla option using constant volatility, QuantLib supports many features to better reflect reality. Try using a BlackVarianceCurve if you want to specify the at-the-money volatility with respect to time or BlackVarianceSurface if you want to specify the smile as well.
