Skip to content

Instantly share code, notes, and snippets.

@yhilpisch
Last active July 29, 2023 19:32
Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save yhilpisch/5c3c7cac0b55ebe5aef9a15e639afeb8 to your computer and use it in GitHub Desktop.
Save yhilpisch/5c3c7cac0b55ebe5aef9a15e639afeb8 to your computer and use it in GitHub Desktop.

Market-Based Valuation of Equity Options

CQF Lecture, 09. April 2018, London

Dr. Yves J. Hilpisch, The Python Quants GmbH

Resources

Short link to this Gist:

General resources:

Abstract

This lecture covers numerical methods for the market-based valuation of equity options. The lecture is mainly based on the book Derivatives Analytics with Python (http://dawp.tpq.io).

Slides

You find the slides under http://tpq.io/p/cqf_lecture_april_2018.html

Python

This Gist contains the files needed to replicate all the results shown during the lecture. The code base has been updated to Python 3.6.

Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Display the source blob
Display the rendered blob
Raw
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
#
# Valuation of European Call Options in BSM Model
# and Numerical Derivation of Implied Volatility
# 03_stf/BSM_imp_vol.py
#
# (c) Dr. Yves J. Hilpisch
# from Hilpisch, Yves (2014): Python for Finance, O'Reilly.
#
from math import log, sqrt, exp
from scipy import stats
from scipy.optimize import fsolve
class call_option(object):
''' Class for European call options in BSM Model.
Attributes
==========
S0 : float
initial stock/index level
K : float
strike price
t : datetime/Timestamp object
pricing date
M : datetime/Timestamp object
maturity date
r : float
constant risk-free short rate
sigma : float
volatility factor in diffusion term
Methods
=======
value : float
return present value of call option
vega : float
return vega of call option
imp_vol : float
return implied volatility given option quote
'''
def __init__(self, S0, K, t, M, r, sigma):
self.S0 = float(S0)
self.K = K
self.t = t
self.M = M
self.r = r
self.sigma = sigma
def update_ttm(self):
''' Updates time-to-maturity self.T. '''
if self.t > self.M:
raise ValueError("Pricing date later than maturity.")
self.T = (self.M - self.t).days / 365.
def d1(self):
''' Helper function. '''
d1 = ((log(self.S0 / self.K)
+ (self.r + 0.5 * self.sigma ** 2) * self.T)
/ (self.sigma * sqrt(self.T)))
return d1
def value(self):
''' Return option value. '''
self.update_ttm()
d1 = self.d1()
d2 = ((log(self.S0 / self.K)
+ (self.r - 0.5 * self.sigma ** 2) * self.T)
/ (self.sigma * sqrt(self.T)))
value = (self.S0 * stats.norm.cdf(d1, 0.0, 1.0)
- self.K * exp(-self.r * self.T) * stats.norm.cdf(d2, 0.0, 1.0))
return value
def vega(self):
''' Return Vega of option. '''
self.update_ttm()
d1 = self.d1()
vega = self.S0 * stats.norm.pdf(d1, 0.0, 1.0) * sqrt(self.T)
return vega
def imp_vol(self, C0, sigma_est=0.2):
''' Return implied volatility given option price. '''
option = call_option(self.S0, self.K, self.t, self.M,
self.r, sigma_est)
option.update_ttm()
def difference(sigma):
option.sigma = sigma
return option.value() - C0
iv = fsolve(difference, sigma_est)[0]
return iv
#
# Black-Scholes-Merton Implied Volatilities of
# Call Options on the EURO STOXX 50
# Option Quotes from 30. September 2014
# Source: www.eurexchange.com, www.stoxx.com
# 03_stf/ES50_imp_vol.py
#
# (c) Dr. Yves J. Hilpisch
# Derivatives Analytics with Python
#
import numpy as np
import pandas as pd
from BSM_imp_vol import call_option
import matplotlib as mpl
import matplotlib.pyplot as plt
mpl.rcParams['font.family'] = 'serif'
# Pricing Data
pdate = pd.Timestamp('30-09-2014')
#
# EURO STOXX 50 index data
#
# URL of data file
es_url = 'http://www.stoxx.com/download/historical_values/hbrbcpe.txt'
# column names to be used
cols = ['Date', 'SX5P', 'SX5E', 'SXXP', 'SXXE',
'SXXF', 'SXXA', 'DK5F', 'DKXF', 'DEL']
# reading the data with pandas
es = pd.read_csv(es_url, # filename
header=None, # ignore column names
index_col=0, # index column (dates)
parse_dates=True, # parse these dates
dayfirst=True, # format of dates
skiprows=4, # ignore these rows
sep=';', # data separator
names=cols) # use these column names
# deleting the helper column
del es['DEL']
S0 = es['SX5E']['30-09-2014']
r = -0.05
#
# Option Data
#
data = pd.read_csv('http://hilpisch.com/es50_option_data.csv', index_col=0)
data['Date'] = data['Date'].apply(lambda x: pd.Timestamp(x))
data['Maturity'] = data['Maturity'].apply(lambda x: pd.Timestamp(x))
#
# BSM Implied Volatilities
#
def calculate_imp_vols(data):
''' Calculate all implied volatilities for the European call options
given the tolerance level for moneyness of the option.'''
data['Imp_Vol'] = 0.0
tol = 0.30 # tolerance for moneyness
for row in data.index:
t = data['Date'][row]
T = data['Maturity'][row]
ttm = (T - t).days / 365.
forward = np.exp(r * ttm) * S0
if (abs(data['Strike'][row] - forward) / forward) < tol:
call = call_option(S0, data['Strike'][row], t, T, r, 0.2)
data['Imp_Vol'][row] = call.imp_vol(data['Call'][row])
return data
#
# Graphical Output
#
markers = ['.', 'o', '^', 'v', 'x', 'D', 'd', '>', '<']
def plot_imp_vols(data):
''' Plot the implied volatilites. '''
maturities = sorted(set(data['Maturity']))
plt.figure(figsize=(10, 6))
for i, mat in enumerate(maturities):
dat = data[(data['Maturity'] == mat) & (data['Imp_Vol'] > 0)]
plt.plot(dat['Strike'].values, dat['Imp_Vol'].values,
'b%s' % markers[i], label=str(mat)[:10])
plt.grid(True)
plt.legend()
plt.xlabel('strike')
plt.ylabel('implied volatility')
plt.show()
#
# Analyzing Returns from Geometric Brownian Motion
# 03_stf/GBM_returns.py
#
# (c) Dr. Yves J. Hilpisch
# Derivatives Analytics with Python
#
import math
import numpy as np
import pandas as pd
import scipy.stats as scs
import statsmodels.api as sm
import matplotlib as mpl
import matplotlib.pyplot as plt
plt.style.use('seaborn')
mpl.rcParams['font.family'] = 'serif'
#
# Helper Function
#
def dN(x, mu, sigma):
''' Probability density function of a normal random variable x.
Parameters
==========
mu : float
expected value
sigma : float
standard deviation
Returns
=======
pdf : float
value of probability density function
'''
z = (x - mu) / sigma
pdf = np.exp(-0.5 * z ** 2) / math.sqrt(2 * math.pi * sigma ** 2)
return pdf
# Return Sample Statistics and Normality Tests
def print_statistics(data):
print("RETURN SAMPLE STATISTICS")
print("---------------------------------------------")
print("Mean of Daily Log Returns %9.6f" % np.mean(data['returns']))
print("Std of Daily Log Returns %9.6f" % np.std(data['returns']))
print("Mean of Annua. Log Returns %9.6f" % (np.mean(data['returns']) * 252))
print("Std of Annua. Log Returns %9.6f" % \
(np.std(data['returns']) * math.sqrt(252)))
print("---------------------------------------------")
print("Skew of Sample Log Returns %9.6f" % scs.skew(data['returns']))
print("Skew Normal Test p-value %9.6f" % scs.skewtest(data['returns'])[1])
print("---------------------------------------------")
print("Kurt of Sample Log Returns %9.6f" % scs.kurtosis(data['returns']))
print("Kurt Normal Test p-value %9.6f" % \
scs.kurtosistest(data['returns'])[1])
print("---------------------------------------------")
print("Normal Test p-value %9.6f" % \
scs.normaltest(data['returns'])[1])
print("---------------------------------------------")
print("Realized Volatility %9.6f" % data['rea_vol'].iloc[-1])
print("Realized Variance %9.6f" % data['rea_var'].iloc[-1])
#
# Graphical Output
#
# daily quotes and log returns
def quotes_returns(data):
''' Plots quotes and returns. '''
plt.figure(figsize=(10, 6))
plt.subplot(211)
data['index'].plot()
plt.ylabel('daily quotes')
plt.grid(True)
plt.axis('tight')
plt.subplot(212)
data['returns'].plot()
plt.ylabel('daily log returns')
plt.grid(True)
plt.axis('tight')
# histogram of annualized daily log returns
def return_histogram(data):
''' Plots a histogram of the returns. '''
plt.figure(figsize=(10, 6))
x = np.linspace(min(data['returns']), max(data['returns']), 100)
plt.hist(np.array(data['returns']), bins=50, normed=True)
y = dN(x, np.mean(data['returns']), np.std(data['returns']))
plt.plot(x, y, linewidth=2)
plt.xlabel('log returns')
plt.ylabel('frequency/probability')
plt.grid(True)
# Q-Q plot of annualized daily log returns
def return_qqplot(data):
''' Generates a Q-Q plot of the returns.'''
plt.figure(figsize=(10, 6))
sm.qqplot(data['returns'], line='s')
plt.grid(True)
plt.xlabel('theoretical quantiles')
plt.ylabel('sample quantiles')
# realized volatility
def realized_volatility(data):
''' Plots the realized volatility. '''
plt.figure(figsize=(10, 6))
data['rea_vol'].plot()
plt.ylabel('realized volatility')
plt.grid(True)
# mean return, volatility and correlation (252 days moving = 1 year)
def rolling_statistics(data):
''' Calculates and plots rolling statistics (mean, std, correlation). '''
plt.figure(figsize=(11, 8))
plt.subplot(311)
mr = pd.rolling_mean(data['returns'], 252) * 252
mr.plot()
plt.grid(True)
plt.ylabel('returns (252d)')
plt.axhline(mr.mean(), color='r', ls='dashed', lw=1.5)
plt.subplot(312)
vo = pd.rolling_std(data['returns'], 252) * math.sqrt(252)
vo.plot()
plt.grid(True)
plt.ylabel('volatility (252d)')
plt.axhline(vo.mean(), color='r', ls='dashed', lw=1.5)
vx = plt.axis()
plt.subplot(313)
co = pd.rolling_corr(mr, vo, 252)
co.plot()
plt.grid(True)
plt.ylabel('correlation (252d)')
cx = plt.axis()
plt.axis([vx[0], vx[1], cx[2], cx[3]])
plt.axhline(co.mean(), color='r', ls='dashed', lw=1.5)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment