Arbitrage with an Option Pricing App— Part 1
In this blog post, we will explore how to create a Black-Scholes model to price options and compare it against the latest option price as a way to look for an arbitrage buying opportunity.
In Part 2 we are going to put this into a Dash App so we can check options for arbitrage opportunites.
ChatGPT defines black-scholes with the following :
“The Black-Scholes model is one of the most widely used models for pricing options. It was developed by Fischer Black and Myron Scholes in 1973, and has since become the standard for option pricing. The model takes into account factors such as the underlying asset’s price, time to expiration, volatility, interest rates, and strike price.“ ChatGPT
We are going to break this into a few steps over a couple blog posts.
Part 1
- Collect Stock Data
- Collect Interest Rate
- Write Black-Scholes Formula
- Price an Option
Part 2
- Create a Dash Layout
- Create the Callback for Live Prices
- Launch the App
We are going to focus on Part 1 in this post and a follow up post will show how to build the app.
1. Collect Stock & Options Data
We are going to pick up stock data from yahooquery with the code below.
from yahooquery import Ticker
import pandas as pd
def stockdata(ticker, sd, ed):
print("Grabbing stock prices for : " + ticker + " between : " + str(sd) + " and " + str(ed))
tickers_arg = Ticker(ticker)
df = tickers_arg.history(interval="1d", start=(sd), end=(ed)).reset_index()
print("Data picked up with : " + str(df['date'].count()) + " rows")
options_df = pd.DataFrame(tickers_arg.option_chain).reset_index()
call_options = options_df[options_df['optionType'] == 'calls']
put_options = options_df[options_df['optionType'] == 'puts']
return df, call_options, put_options
2. Pick up current Risk Free Interest Rate
We are going to define Risk Free Interest Rate as the 3-month US treasuary bill, and pick this up with the following function.
import pandas_datareader.data as pdr
def interest_rate():
#3 month treasury bill
data = pdr.get_data_fred('DTB3')
current_rate = data.iloc[-1]['DTB3']
print("Current risk free interest rate :", current_rate, "%")
return current_rate
cr = interest_rate()
3. Black Scholes Formula
Black Scholes formula is as follows
C = Call option Price
N = CDF of normal distribution
S = Spot price of an asset
K = Strike Price
r= risk free interest rate
t = time to maturity
σ = volitility of an asset
We writing this up as a python function as below, which includes one extra input of the option type — ‘call’ or ‘put’.
import numpy as np
from scipy.stats import norm
#Black Scholes - calcultion
def black_scholes(S, K, T, r, sigma, option_type):
d1 = (np.log(S / K) + (r + 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
d2 = (np.log(S / K) + (r - 0.5 * sigma ** 2) * T) / (sigma * np.sqrt(T))
if option_type.lower() == "call":
option_price = S * norm.cdf(d1) - K * np.exp(-r * T) * norm.cdf(d2)
elif option_type.lower() == "put":
option_price = K * np.exp(-r * T) * norm.cdf(-d2) - S * norm.cdf(-d1)
else:
raise ValueError("Invalid option type. Please use 'call' or 'put'.")
return option_price
4. Price an Option
Pick up the data
As an example we are going to look at the ‘meme’ stock AMC.
import datetime
import numpy as np
today = datetime.date.today()
ed = today.strftime("%Y-%m-%d")
sd = (today - datetime.timedelta(days=364)).strftime("%Y-%m-%d")
ticker = 'AMC'
df,df_call,df_put = stockdata(ticker, sd, ed)
The dataframes look as follows
Stock Price
Pick up the current stock price.
S = round(df["close"].iloc[-1],2)
print("Current stock price is : " + str(S))
Strike Price
The current strike price options can be seen below.
call['strike'].unique()
K = 7
Seelcting a strike price of $7.
Risk Free interest rate
Risk free interest rate using out function from above
r = interest_rate() / 100
Expiration Date
Can pick the Expiration dates from the call dataframe
list(set(call['expiration']))
t = (datetime.date(2023,9,15) - today).days / 365
We are going to select 15 September 2023 which should be just after an AMC earnings call.
Volitility of the asset
Calculate volitlity (sigma) with the following
sigma = df['adjclose'].pct_change().std() * np.sqrt(252)
print("Annualised volatility " + str(sigma))
Black Scholes Option Price
So we have all the vairables to price an option and are now going to feed this into the Black-Scholes function we created earlier.
scholes_call_price = round(black_scholes(S,K,T,r,sigma,'call'),2)
scholes_put_price = round(black_scholes(S,K,T,r,sigma,'put'),2)
print("European Call price : " + str(scholes_call_price))
print("European Put price : " + str(scholes_put_price))
Looking for pricing discripencies
So we are pricing a call option at $1.92 and a put option at $2.64.
The current asking price
call_lastprice = call['lastPrice'].loc[(call['strike'] == K) & (call['expiration'] == pd.to_datetime(exp_dt))].iloc[0]
put_lastprice = puts['lastPrice'].loc[(puts['strike'] == K) & (puts['expiration'] == pd.to_datetime(exp_dt))].iloc[0]
print("The last call price was : " + str(round(call_lastprice,3))), print("The last Put ask price was : " + str(round(put_lastprice,3)))
So Black Scholes model is pricing a call option more valuable than that of the most recent asking price.
Assuming the model assumptions hold true here is an arbitrage opportunity. We could purchase the option and then sell it immediately at the black scholes price.
And as ChatGPT wisely says
‘However, it is important to note that the Black-Scholes model is just a theoretical model and is based on a number of assumptions. In practice, the actual price of an option can be influenced by a number of different factors such as market conditions, supply and demand, and the underlying asset’s performance. Therefore, it is important to conduct thorough research and analysis before making any investment decisions based on the Black-Scholes model.’
I am going to conclude Part 1 today will make a follow up with Part 2 that creates an option pricing Dash app.