Implied Volatility
Implied Volatility
The implied volatility (IV) of an option contract is that value of the volatility of the underlying instrument which, when input in an option pricing model (such as Black–Scholes), will return a theoretical value equal to the current market price of that option. The VIX , in contrast, is a model-free estimate of Implied Volatility. The latter is viewed as being important because it represents a measure of risk for the underlying asset. Elevated Implied Volatility suggests that risks to underlying are also elevated. Ordinarily, to estimate implied volatility we rely upon Black-Scholes (1973). This implies that we are prepared to accept the assumptions of Black Scholes (1973). To estimate Implied Volatility we need, first and foremost, to set up a fully functional Option Pricing model. The simplest way to do this is to work with excel. A good way to way to acquaint one self with the steps involved in implementing Black Scholes is set up the estimation in excel .
Implied Volatility estimated with Goal Seek
Implied Volatility is important to data scientists because because it provides an ex ante measure of expected risk - which can be different diverge from historical time series projections gleaned from GARCH style models. Again, I use excel here to set out a simplified treatment invoking goal-seek. Please follow link.
Excel VBA code for Black Scholes
To estimate implied volatility for a number of options goal seek can become a bit clunky. To automate that estimation it is worth setting out VBA code for the Black Schole Call and couple this with a bisection function to run to invoke a trial and error iterative search for the Implied Volatility consistent with the market price of the option.
Function BSC(S, K, r, q, sigma, T)
Dim dOne, dTwo, Nd1, Nd2
dOne = (Log(S / K) + (r - q + 0.5 * sigma ^ 2) * T) / (sigma * Sqr(T))
dTwo = dOne - sigma * Sqr(T)
Nd1 = Application.NormSDist(dOne)
Nd2 = Application.NormSDist(dTwo)
BSC = Exp(-q * T) * S * Nd1 - Exp(-r * T) * K * Nd2
End Function
to then estimate implied volatility we need the Bisection Function
Function BSCImVol(S, K, r, q, T, callmktprice)
H = 5
L = 0
Do While (H - L) > 0.00000001
If BSC(S, K, r, q, (H + L) / 2, T) > callmktprice Then
H = (H + L) / 2
Else: L = (H + L) / 2
End If
Loop
BSCImVol = (H + L) / 2
End Function
Please see video below.
C++ code for Implied Volatility
To implement Implied Volatility in C++ please use the following code:
//Implied Volatility using Black Scholes with Dividend
#include <iostream>
#include <cmath>
#include <vector>
using namespace std;
// N(0,1) density
double
f (double x)
{
double pi = 4.0 * atan (1.0);
return exp (-x * x * 0.5) / sqrt (2 * pi);
}
// Boole's Rule
double
Boole (double StartPoint, double EndPoint, int n)
{
vector < double >X (n + 1, 0.0);
vector < double >Y (n + 1, 0.0);
double delta_x = (EndPoint - StartPoint) / double (n);
for (int i = 0; i <= n; i++)
{
X[i] = StartPoint + i * delta_x;
Y[i] = f (X[i]);
}
double sum = 0;
for (int t = 0; t <= (n - 1) / 4; t++)
{
int ind = 4 * t;
sum +=
(1 / 45.0) * (14 * Y[ind] + 64 * Y[ind + 1] + 24 * Y[ind + 2] +
64 * Y[ind + 3] + 14 * Y[ind + 4]) * delta_x;
}
return sum;
}
// N(0,1) cdf by Boole's Rule
double
N (double x)
{
return Boole (-10.0, x, 240);
}
// Black-Scholes Call Price
double
BSPrice (double S, double K, double r, double T, double q, double v, char PutCall)
{
double d = (log (S / K) + T * (r - q + 0.5 * v * v)) / (v * sqrt (T));
double call = S *exp(-q*T)* N (d) - exp (-r * T) * K * N (d - v * sqrt (T));
if (PutCall == 'C')
return call;
else
// Put Parity
return call - S*exp (-q * T) + K * exp (-r * T);
}
// Bisection Algorithm
double BisecBSV(double S, double K, double r, double T, double q,
double a, double b, double MktPrice, char PutCall) {
const int MaxIter = 50000;
double Tol = 0.0000001;
double midP = 0.0, midCdif;
double lowCdif = MktPrice - BSPrice(S, K, r, T, q, a, PutCall);
double highCdif = MktPrice - BSPrice(S, K, r, T, q, b, PutCall);
if (lowCdif*highCdif > 0)
return -1;
else
for (int i = 0; i <= MaxIter; i++) {
midP = (a + b) / 2.0;
midCdif = MktPrice - BSPrice(S, K, r, T, q, midP, PutCall);
if (abs(midCdif)<Tol) goto LastLine;
else {
if (midCdif>0) a = midP;
else b = midP;
}
}
LastLine:
return midP;
}
// Main program
int main() {
double S = 100; // Spot Price
double K = 100; // Exercise
double r = 0.05; // Interest Rate
double T = 1; // Maturity in Years
double q = 0.0;
double a = 0.00000001; // Bisection algorithm starting value for lower volatility
double b = 7.0; // Bisection algorithm starting value for higher volatility
double MktPrice = 10.45;
char PutCall = 'C'; // C
double IV = BisecBSV(S, K, r, T, q, a, b, MktPrice, PutCall);
cout << " Implied Volatility = " << IV << " " << endl;
system("PAUSE");
}
Javascript Code for Black Scholes and Implied Volatility
And for good measure I have included the following Javacsript code for Black Scholes and Implied Volatility. Please check below video for estimation.
/* Returns probability of occuring below and above target price. */
function probability(price, target, days, volatility) {
var p = price;
var q = target;
var t = days / 365;
var v = volatility;
var vt = v*Math.sqrt(t);
var lnpq = Math.log(q/p);
var d1 = lnpq / vt;
var y = Math.floor(1/(1+.2316419*Math.abs(d1))*100000)/100000;
var z = Math.floor(.3989423*Math.exp(-((d1*d1)/2))*100000)/100000;
var y5 = 1.330274*Math.pow(y,5);
var y4 = 1.821256*Math.pow(y,4);
var y3 = 1.781478*Math.pow(y,3);
var y2 = 0.356538*Math.pow(y,2);
var y1 = 0.3193815*y;
var x = 1-z*(y5-y4+y3-y2+y1);
x = Math.floor(x*100000)/100000;
if (d1<0) {x=1-x};
var pabove = Math.floor(x*1000)/10;
var pbelow = Math.floor((1-x)*1000)/10;
return [[pbelow],[pabove]]
}
// JavaScript adopted from Bernt Arne Odegaard's Financial Numerical Recipes
// http://finance.bi.no/~bernt/gcc_prog/algoritms/algoritms/algoritms.html
// by Steve Derezinski, CXWeb, Inc. http://www.cxweb.com
// Copyright (C) 1998 Steve Derezinski, Bernt Arne Odegaard
//
// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// http://www.fsf.org/copyleft/gpl.html
function ndist(z) {
return (1.0/(Math.sqrt(2*Math.PI)))*Math.exp(-0.5*z);
//?? Math.exp(-0.5*z*z)
}
function N(z) {
b1 = 0.31938153;
b2 = -0.356563782;
b3 = 1.781477937;
b4 = -1.821255978;
b5 = 1.330274429;
p = 0.2316419;
c2 = 0.3989423;
a=Math.abs(z);
if (a>6.0) {return 1.0;}
t = 1.0/(1.0+a*p);
b = c2*Math.exp((-z)*(z/2.0));
n = ((((b5*t+b4)*t+b3)*t+b2)*t+b1)*t;
n = 1.0-b*n;
if (z < 0.0) {n = 1.0 - n;}
return n;
}
function fraction(z) {
// given a decimal number z, return a string with whole number + fractional string
// i.e. z = 4.375, return "4 3/8"
var whole = Math.floor(z);
var fract = z - whole;
var thirtytwos = Math.round(fract*32);
if (thirtytwos == 0) {return whole + " ";} //(if fraction is < 1/64)
if (thirtytwos == 32) {return whole + 1;} //(if fraction is > 63/64)
//32's non-trivial denominators: 2,4,8,16
if (thirtytwos/16 == 1) { return whole + " 1/2";}
if (thirtytwos/8 == 1) { return whole + " 1/4";}
if (thirtytwos/8 == 3) { return whole + " 3/4";}
if (thirtytwos/4 == Math.floor(thirtytwos/4)) {return whole + " " + thirtytwos/4 + "/8";}
if (thirtytwos/2 == Math.floor(thirtytwos/2)) {return whole + " " + thirtytwos/2 + "/16";}
else return whole + " " + thirtytwos + "/32";
} //end function
function black_scholes(call,S,X,r,v,t) {
// call = Boolean (to calc call, call=True, put: call=false)
// S = stock prics, X = strike price, r = no-risk interest rate
// v = volitility (1 std dev of S for (1 yr? 1 month?, you pick)
// t = time to maturity
// define some temp vars, to minimize function calls
var sqt = Math.sqrt(t);
var Nd2; //N(d2), used often
var nd1; //n(d1), also used often
var ert; //e(-rt), ditto
var delta; //The delta of the option
d1 = (Math.log(S/X) + r*t)/(v*sqt) + 0.5*(v*sqt);
d2 = d1 - (v*sqt);
if (call) {
delta = N(d1);
Nd2 = N(d2);
} else { //put
delta = -N(-d1);
Nd2 = -N(-d2);
}
ert = Math.exp(-r*t);
nd1 = ndist(d1);
gamma = nd1/(S*v*sqt);
vega = S*sqt*nd1;
theta = -(S*v*nd1)/(2*sqt) - r*X*ert*Nd2;
rho = X*t*ert*Nd2;
return ( S*delta-X*ert *Nd2);
} //end of black_scholes
function option_implied_volatility(call,S,X,r,t,o) {
// call = Boolean (to calc call, call=True, put: call=false)
// S = stock prics, X = strike price, r = no-risk interest rate
// t = time to maturity
// o = option price
// define some temp vars, to minimize function calls
sqt = Math.sqrt(t);
MAX_ITER = 100;
ACC = 0.0001;
sigma = (o/S)/(0.398*sqt);
for (i=0;i<MAX_ITER;i++) {
price = black_scholes(call,S,X,r,sigma,t);
diff = o-price;
if (Math.abs(diff) < ACC) return sigma;
d1 = (Math.log(S/X) + r*t)/(sigma*sqt) + 0.5*sigma*sqt;
vega = S*sqt*ndist(d1);
sigma = sigma+diff/vega;
}
return "Error, failed to converge";
} //end of option_implied_volatility
//function call_iv(s,x,r,t,o) { return option_implied_volatility(true,s,x,r/100,t/365,o) }
Python Code for Black Scholes Implied Volatility Calls and Puts
The code below I obtained from Kevin Mooney's github. I made a few changes for dividends. Kevin does not use a bisection technique here. Rather he uses a root finding approach. Please check out Kevin's youtube channel. When selecting parameters please ensure that values used satisfy option properties. Also some parameter values may not produce convergence - no matter how many iterations are applied.
# This Python code can be used to calculate the value for Implied
# Volatility for a European call
from math import sqrt, exp, log, pi
from scipy.stats import norm
# Function to calculate the values of d1 and d2 as well as the call
# price. To extend to puts, one could just add a function that
# calculates the put price, or combine calls and puts into a single
# function that takes an argument specifying which type of contract one
# is dealing with.
def d(sigma, S, K, r, q, t):
d1 = 1 / (sigma * sqrt(t)) * ( log(S/K) + (r - q + sigma**2/2) * t)
d2 = d1 - sigma * sqrt(t)
return d1, d2
def call_price(sigma, S, K, r, q, t, d1, d2):
C = norm.cdf(d1) * S * exp(-q * t)- norm.cdf(d2) * K * exp(-r * t)
return C
# Option parameters
S = 100.0
K = 100
t = 1
r = 0.05
q = 0.05
C0 =10.45
# Tolerances
tol = 1e-3
epsilon = 1
# Variables to log and manage number of iterations
count = 0
max_iter = 1000
# We need to provide an initial guess for the root of our function
vol = 0.50
while epsilon > tol:
# Count how many iterations and make sure while loop doesn't run away
count += 1
if count >= max_iter:
print('Breaking on count')
break;
# Log the value previously calculated to computer percent change
# between iterations
orig_vol = vol
# Calculate the vale of the call price
d1, d2 = d(vol, S, K, r,q, t)
function_value = call_price(vol, S, K, r, q, t, d1, d2) - C0
# Calculate vega, the derivative of the price with respect to
# volatility
vega = S * norm.pdf(d1) * sqrt(t)* exp(-q * t)
# Update for value of the volatility
vol = -function_value / vega + vol
# Check the percent change between current and last iteration
epsilon = abs( (vol - orig_vol) / orig_vol )
# Print out the results
print('Sigma = ', vol)
print('Code took ', count, ' iterations')
The Python code below follows Kevin's code above. I just make a few changes that invoke put call parity.
# This Python code can be used to calculate the value for Implied
# Volatility for a European put
from math import sqrt, exp, log, pi
from scipy.stats import norm
# Function to calculate the values of d1 and d2 as well as the call
# price. To extend to puts, define the put from put call parity
def d(sigma, S, K, r, q, t):
d1 = 1 / (sigma * sqrt(t)) * ( log(S/K) + (r - q + sigma**2/2) * t)
d2 = d1 - sigma * sqrt(t)
return d1, d2
def call_price(sigma, S, K, r, q, t, d1, d2):
C = norm.cdf(d1) * S * exp(-q * t)- norm.cdf(d2) * K * exp(-r * t)
return C
# From Put call Prity
def put_price(sigma, S, K, r, q, t, d1, d2):
P = - S * exp(-q * t) + K * exp(-r * t) + call_price(sigma, S, K, r, q, t, d1, d2)
return P
# Option parameters
S = 100.0
K = 100
t = 1
r = 0.05
q = 0.0
P0 =5.57
# Tolerances
tol = 1e-3
epsilon = 1
# Variables to log and manage number of iterations
count = 0
max_iter = 1000
# We need to provide an initial guess for the root of our function
vol = 0.50
while epsilon > tol:
# Count how many iterations and make sure while loop doesn't run away
count += 1
if count >= max_iter:
print('Breaking on count')
break;
# Log the value previously calculated to computer percent change
# between iterations
orig_vol = vol
# Calculate the vale of the call price
d1, d2 = d(vol, S, K, r,q, t)
function_value = put_price(vol, S, K, r, q, t, d1, d2) - P0
# Calculate vega, the derivative of the price with respect to
# volatility
vega = S * norm.pdf(d1) * sqrt(t)* exp(-q * t)
# Update for value of the volatility
vol = -function_value / vega + vol
# Check the percent change between current and last iteration
epsilon = abs( (vol - orig_vol) / orig_vol )
# Print out the results
print('Sigma = ', vol)
print('Code took ', count, ' iterations')
The video below outlines the how to run code snippets above in Anaconda Spyder. We then compare results in Python OnlineGBD and then test results against C++ code that we know is correct. In that way we test the robustness of the Python Code.
The video below uses Amer Lu's Python code to estimate Black Scholes/Implied Volatility/Greeks in a Jupyter Notebook
Python code for Black Scholes Implied Volatility using Bisection
The code below can be used to estimate implied volatility using Bisection.
# https://github.com/YuChenAmberLu?tab=repositories
from math import log, sqrt, pi, exp
from scipy.stats import norm
## define two functions, d1 and d2 in Black-Scholes model
# change sigma*sqrt(T) to (sigma*sqrt(T))
def d1(S,K,T,r,q,sigma):
return(log(S/K)+(r - q +sigma**2/2.)*T)/(sigma*sqrt(T))
def d2(S,K,T,r,q,sigma):
return d1(S,K,T,r,q,sigma)-sigma*sqrt(T)
## define the call options price function
def bs_call(S,K,T,r,q,sigma):
return S*exp(-q*T)*norm.cdf(d1(S,K,T,r,q,sigma))-K*exp(-r*T)*norm.cdf(d2(S,K,T,r,q,sigma))
## define the put options price function
def bs_put(S,K,T,r,q,sigma):
return K*exp(-r*T)-S*exp(-q*T)+bs_call(S,K,T,r,q,sigma)
# Implied Volatility using bisection
# quantconnect.com/forum/discussion/2269/generate-volatility-surface-plot-by-interpolation/p1
def implied_vol(option_type, option_price, S, K, r, T, q):
# apply bisection method to get the implied volatility by solving the BSM function
precision = 0.00001
upper_vol = 50.0
max_vol = 50.0
min_vol = 0.0001
lower_vol = 0.0001
iteration = 0
while 1:
iteration +=1
mid_vol = (upper_vol + lower_vol)/2.0
if option_type == 'c':
price = bs_call(S,K,T,r,q,mid_vol)
lower_price = bs_call(S,K,T,r,q,lower_vol)
if (lower_price - option_price) * (price - option_price) > 0:
lower_vol = mid_vol
else:
upper_vol = mid_vol
if abs(price - option_price) < precision: break
if mid_vol > max_vol - 5 :
mid_vol = 0.0001
break
elif option_type == 'p':
price = bs_put(S,K,T,r,q,mid_vol)
upper_price = bs_put(S,K,T,r,q,upper_vol)
if (upper_price - option_price) * (price - option_price) > 0:
upper_vol = mid_vol
else:
lower_vol = mid_vol
if abs(price - option_price) < precision: break
if iteration > 100: break
return mid_vol
print (implied_vol('p', 5.57, 100, 100, 0.05, 1, 0))