Building a Dashboard for option pricing & Arbitrage Opportunities — Part 2

Mike Flynn
5 min readMar 14, 2023

In Part 1 we looked at pricing an option with the Black Scholes model which output an estimate of the price of a European style stock option.

Within the blog we picked up the current stock price, the option’s strike price, the time to expiration, the risk-free interest rate, and the stock’s volatility. We built a Black Scholes model function and found an arbitrage opportunity where black scholes priced a call higher than the most recent price.

Part 2 —Option Pricing App

In part 2 we are going to create a Python Dash web app which will allow us to select which stock’s options we would like to price.

Importing the functions we created from part 1 — this will allow us to always look at the most recent data — so every time we choose a stock our app will make a call to pick up the latest data. See part 1 here

https://medium.com/@flynn.michael.mf/looking-for-arbitrage-with-an-option-pricing-app-part-1-a1af8cbc3374


import stockdata, interest_rate, black_scholes, import_data, grab_dates

To build our dashapp we are going to be utalisaing the following packages.

import dash
from dash import callback, html, dcc
import dash_bootstrap_components as dbc
from jupyter_dash import JupyterDash

1. Create a Dash Layout

First we are going to set up a new app and give it SLATE theme.

Then we are going to design a Layout — below we have put a title in and a link to my website. On the left we have created 3 dropdown selections and a button. The dropdown selections will allow us to give options for which stock, strike price and expiry date we would like to price an option for. Finally we have a price option button to click once we have our inputs ready.

In the middle of the page we have an option for text output id=’Option-text’ — we going to utalise this in the callback to output calculation.


app = JupyterDash(external_stylesheets=[dbc.themes.SLATE])

app.layout = dbc.Container([
dbc.Row([dbc.Col(html.H1('Options Calculator' , className='text-center'), width=12)]),
dbc.Row(dbc.Col([dbc.NavLink("Data Providentia", href="https://www.data-providentia.com/", external_link=True,
target="_blank", className="text-center", id="link-tooltip"),
dbc.Tooltip("Visit Data Providentia", target="link-tooltip")
], width=12)
),

dbc.Row([
dbc.Col([
dbc.Card([
dbc.CardBody([
dcc.Markdown('**Select Stock**'),
dcc.Dropdown(
id='ticker-dropdown',
options=[{'label': name, 'value': ticker} for ticker,name in dftik[['ticker','name']].values],
value=dftik['ticker'][0],
style={'width': '100%'}
),
html.Br(),
dcc.Markdown('**Expiry Date**'),
dcc.Dropdown(
id='expiry-dropdown',
options=[],
style={'width': '100%'}
),
html.Br(),
dcc.Markdown('**Strike Price**'),
dcc.Dropdown(
id='strike-dropdown',
options=[],
#value=1,
style={'width': '100%'}
),
html.Br(),
html.Br(),
html.Button('Price Option', id='id-button')
])
])
],width=2),
dbc.Col([
dbc.Card([
dbc.CardBody([
html.P(id='option-text'),
])
])
],width=8)
], className='mx-3 my-3')
])

2. Callback function for Live Prices

The code for the call back function is below.

The function takes five inputs: the value of a ticker dropdown menu, the the current strike price, the current expiry date, and the current state of the strike and expiry dropdown menus.

The function calculates calculates the Black-Scholes call and put prices for the selected strike price and expiry date, using live stock data and an interest rate. Finally, it compares the calculated prices to the most recent purchase prices for the options and generates text to display to the user indicating whether the calculated prices are higher or lower than the purchase prices.


@app.callback(
[dash.dependencies.Output('expiry-dropdown', 'options'),
dash.dependencies.Output('strike-dropdown', 'options'),
dash.dependencies.Output('option-text', 'children')],
[dash.dependencies.Input('ticker-dropdown', 'value'),
dash.dependencies.Input('id-button', 'n_clicks')],
[dash.dependencies.State('strike-dropdown', 'value'),
dash.dependencies.State('expiry-dropdown', 'value')])
def update_options(tick, n_clicks, current_strike, current_expiry):
today, ed, sd = grab_dates()

ticker = tick
df, calls, puts = stockdata(ticker, sd, ed)


unq_expiry_options = pd.Series(calls['expiration']).unique()
expiry_options = [np.datetime_as_string(date, unit='D').split('T')[0] for date in unq_expiry_options]

try:
date_obj = datetime.datetime.strptime(current_expiry, '%Y-%m-%d').date()
Days_Expiry = (date_obj - today).days
except:
Days_Expiry = 0

strike_options = [{'label': str(strike), 'value': strike} for strike in calls['strike'].unique()]


try:
returns = df['adjclose'].pct_change()
cr = interest_rate()
S = df["adjclose"].iloc[-1]
K = current_strike # strike price
T = Days_Expiry / 365 # time to expiration (in days)
r = round(cr / 100,4) # risk-free interest rate
sigma = returns.std() * np.sqrt(252) # annualized volatility
call_price, put_price = black_scholes(S, K, T, r, sigma)
except:
call_price = 0
put_price = 0

try:
call_lastprice = calls['ask'].loc[(calls['strike'] == K) & (calls['expiration'] == pd.to_datetime(current_expiry))].iloc[0]
except:
call_lastprice = 0
try:
put_lastprice = puts['ask'].loc[(puts['strike'] == K) & (puts['expiration'] == pd.to_datetime(current_expiry))].iloc[0]
except:
put_lastprice = 0

if put_price > put_lastprice:
put_text = 'European Black Scholes calculates this Put option to worth more than the ask price.'
else:
put_text = ''
if call_price > call_lastprice:
call_text = 'European Black Scholes calculates this Call option to be worth more the ask price.'
else:
call_text = ''

opt_text = None
if current_expiry is not None and current_strike is not None:
opt_text = html.P([
dcc.Markdown(f'**Selected Ticker :** {ticker}'),
dcc.Markdown(f'**Expiry Date :** {current_expiry}'),
dcc.Markdown(f'**Strike Price :** {current_strike}'),
dcc.Markdown(f'**Days Expiry :** {str(Days_Expiry)}'),
dcc.Markdown(f'**Interest Rate:** {r:.2%}'),
dcc.Markdown(f'**European Black Scholes Put Price :** {str(round(put_price,3))}'),
dcc.Markdown(f'**European Black Scholes Call Price :** {str(round(call_price,3))}'),
dcc.Markdown(f'**Most recent Put purchase price was :** {round(put_lastprice,2)}'),
dcc.Markdown(f'**Most recent Call purchase price was :** {round(call_lastprice,2)}'),
dcc.Markdown(f'{put_text}'),
dcc.Markdown(f'{call_text}'),
])

if (current_strike == None) | (current_expiry == None) | (call_price == 0) | (Days_Expiry == 0):
opt_text = html.P([html.P("Input details to price the options price"),])
current_strike = None
current_expiry = None

return expiry_options, strike_options, opt_text

3. Launch the App

Once all the above is run we can launch the app as below.


if __name__ == '__main__':
app.run_server(mode='inline')

We can find it locally in our browser at

http://127.0.0.1:8050/

Below is a quick video of how it should look.

Looking for arbitrage

Within the video we are looking for an arbitrage opportunity, again we are going to look at AMC

Expiry Date : 2023–09–15

Strike Price : 6

Days Expiry : 185

Interest Rate: 4.83%

European Black Scholes Call Price : 1.538

Most recent Call purchase price was : 1.08

European Black Scholes calculates this Call option to be worth more the ask price. So we could buy the underpriced option at the asking price and then immediately sell it at the higher price predicted by the Black-Scholes model, earning a profit without taking on any risk. Note this is not financial advice and as ChatGPT wisely says.

‘The Black-Scholes model is a widely used financial model for pricing options contracts. It assumes certain market conditions, including the constant volatility of the underlying asset, which may not always hold true in reality. In practice, there may be market frictions or other factors that can make it difficult to execute an arbitrage strategy based on Black-Scholes pricing.

Furthermore, there are often transaction costs, such as brokerage fees, that must be taken into account when executing a trading strategy. These costs can significantly reduce or eliminate any potential profits from an arbitrage strategy.

In addition, even if an arbitrage opportunity does exist, it may be quickly identified and exploited by other traders, leading to market adjustments that eliminate the opportunity. Therefore, while the Black-Scholes model is useful for pricing options and understanding their value, it is not always reliable for identifying profitable arbitrage opportunities.’

Recap

So to recap we have created functions to pull the latest stock prices for any stock, the current risk free interest rate, and the current option prices for calls and puts and a Black Scholes function to price an option. From here we have created a Plotly Dash Dashboard that allows us to sleect a stock, strike price and expiry date of an option and then provide the black scholes pricing and whether this is a potential arbitrage opportunity.

BECOME a WRITER at MLearning.ai

--

--