Aplying the BlackScholes formula we can relatively easily calculate the different greeks of the options.
Options greeks are the parameters that are going to tell us how the option prices is going to performance in relation to the changes in the underlying price and others like time to the expiry date or volatility.
One of the most important parameters to get is the implied volatility. Given the market price of the option and the rest of parameters (time to expiry date, strike, interest) we can calculate the volatility with which this market option price was calculated. And see if it makes sense.
Although IB gives all of this information, I have decided to calculate them in my own just for checking and for doing further analysis.
The tricky one is the implied volatility, as we have to iterate in order to estimate its value.
As I intend to do massive calculations, I also have created a class for processing multiple options.
Here it is the code:
""" This module contains backscholes calculations of prices and greeks for options """ #=============================================================================== # LIBRARIES #=============================================================================== import math from scipy.stats import norm import pandas as pd from datetime import date import numpy as np #=============================================================================== # CLASS OPTION #=============================================================================== class Option: """ This class will group the different black-shcoles calculations for an opion """ def __init__(self, right, s, k, eval_date, exp_date, price = None, rf = 0.01, vol = 0.3, div = 0): self.k = float(k) self.s = float(s) self.rf = float(rf) self.vol = float(vol) self.eval_date = eval_date self.exp_date = exp_date self.t = self.calculate_t() if self.t == 0: self.t = 0.000001 ## Case valuation in expiration date self.price = price self.right = right ## 'C' or 'P' self.div = div def calculate_t(self): if isinstance(self.eval_date, basestring): if '/' in self.eval_date: (day, month, year) = self.eval_date.split('/') else: (day, month, year) = self.eval_date[6:8], self.eval_date[4:6], self.eval_date[0:4] d0 = date(int(year), int(month), int(day)) elif type(self.eval_date)==float or type(self.eval_date)==long or type(self.eval_date)==np.float64: (day, month, year) = (str(self.eval_date)[6:8], str(self.eval_date)[4:6], str(self.eval_date)[0:4]) d0 = date(int(year), int(month), int(day)) else: d0 = self.eval_date if isinstance(self.exp_date, basestring): if '/' in self.exp_date: (day, month, year) = self.exp_date.split('/') else: (day, month, year) = self.exp_date[6:8], self.exp_date[4:6], self.exp_date[0:4] d1 = date(int(year), int(month), int(day)) elif type(self.exp_date)==float or type(self.exp_date)==long or type(self.exp_date)==np.float64: (day, month, year) = (str(self.exp_date)[6:8], str(self.exp_date)[4:6], str(self.exp_date)[0:4]) d1 = date(int(year), int(month), int(day)) else: d1 = self.exp_date return (d1 - d0).days / 365.0 def get_price_delta(self): d1 = ( math.log(self.s/self.k) + ( self.rf + self.div + math.pow( self.vol, 2)/2 ) * self.t ) / ( self.vol * math.sqrt(self.t) ) d2 = d1 - self.vol * math.sqrt(self.t) if self.right == 'C': self.calc_price = ( norm.cdf(d1) * self.s * math.exp(-self.div*self.t) - norm.cdf(d2) * self.k * math.exp( -self.rf * self.t ) ) self.delta = norm.cdf(d1) elif self.right == 'P': self.calc_price = ( -norm.cdf(-d1) * self.s * math.exp(-self.div*self.t) + norm.cdf(-d2) * self.k * math.exp( -self.rf * self.t ) ) self.delta = -norm.cdf(-d1) def get_call(self): d1 = ( math.log(self.s/self.k) + ( self.rf + math.pow( self.vol, 2)/2 ) * self.t ) / ( self.vol * math.sqrt(self.t) ) d2 = d1 - self.vol * math.sqrt(self.t) self.call = ( norm.cdf(d1) * self.s - norm.cdf(d2) * self.k * math.exp( -self.rf * self.t ) ) #put = ( -norm.cdf(-d1) * self.s + norm.cdf(-d2) * self.k * math.exp( -self.rf * self.t ) ) self.call_delta = norm.cdf(d1) def get_put(self): d1 = ( math.log(self.s/self.k) + ( self.rf + math.pow( self.vol, 2)/2 ) * self.t ) / ( self.vol * math.sqrt(self.t) ) d2 = d1 - self.vol * math.sqrt(self.t) #call = ( norm.cdf(d1) * self.s - norm.cdf(d2) * self.k * math.exp( -self.rf * self.t ) ) self.put = ( -norm.cdf(-d1) * self.s + norm.cdf(-d2) * self.k * math.exp( -self.rf * self.t ) ) self.put_delta = -norm.cdf(-d1) def get_theta(self, dt = 0.0027777): self.t += dt self.get_price_delta() after_price = self.calc_price self.t -= dt self.get_price_delta() orig_price = self.calc_price self.theta = (after_price - orig_price) * (-1) def get_gamma(self, ds = 0.01): self.s += ds self.get_price_delta() after_delta = self.delta self.s -= ds self.get_price_delta() orig_delta = self.delta self.gamma = (after_delta - orig_delta) / ds def get_all(self): self.get_price_delta() self.get_theta() self.get_gamma() return self.calc_price, self.delta, self.theta, self.gamma def get_impl_vol(self): """ This function will iterate until finding the implied volatility """ ITERATIONS = 100 ACCURACY = 0.05 low_vol = 0 high_vol = 1 self.vol = 0.5 ## It will try mid point and then choose new interval self.get_price_delta() for i in range(ITERATIONS): if self.calc_price > self.price + ACCURACY: high_vol = self.vol elif self.calc_price < self.price - ACCURACY: low_vol = self.vol else: break self.vol = low_vol + (high_vol - low_vol)/2.0 self.get_price_delta() return self.vol def get_price_by_binomial_tree(self): """ This function will make the same calculation but by Binomial Tree """ n=30 deltaT=self.t/n u = math.exp(self.vol*math.sqrt(deltaT)) d=1.0/u # Initialize our f_{i,j} tree with zeros fs = [[0.0 for j in xrange(i+1)] for i in xrange(n+1)] a = math.exp(self.rf*deltaT) p = (a-d)/(u-d) oneMinusP = 1.0-p # Compute the leaves, f_{N,j} for j in xrange(i+1): fs[n][j]=max(self.s * u**j * d**(n-j) - self.k, 0.0) print fs for i in xrange(n-1, -1, -1): for j in xrange(i+1): fs[i][j]=math.exp(-self.rf * deltaT) * (p * fs[i+1][j+1] + oneMinusP * fs[i+1][j]) print fs return fs[0][0] #=============================================================================== # CLASS OPTIONS STRATEGY #=============================================================================== class Options_strategy: """ This class will calculate greeks for a group of options (called Options Strategy) """ def __init__(self, df_options): self.df_options = df_options #It will store the different options in a pandas dataframe def get_greeks(self): """ For analysis underlying (option chain format) """ self.delta = 0 self.gamma = 0 self.theta = 0 for k,v in self.df_options.iterrows(): ## Case stock or future if v['m_secType']=='STK': self.delta += float(v['position']) * 1 ## Case option elif v['m_secType']=='OPT': opt = Option(s=v['underlying_price'], k=v['m_strike'], eval_date=date.today(), # We want greeks for today exp_date=v['m_expiry'], rf = v['interest'], vol = v['volatility'], right = v['m_right']) price, delta, theta, gamma = opt.get_all() self.delta += float(v['position']) * delta self.gamma += float(v['position']) * gamma self.theta += float(v['position']) * theta else: print "ERROR: Not known type" return self.delta, self.gamma, self.theta def get_greeks2(self): """ For analysis_options_strategy """ self.delta = 0 self.gamma = 0 self.theta = 0 for k,v in self.df_options.iterrows(): ## Case stock or future if v['m_secType']=='STK': self.delta += float(v['position']) * 1 ## Case option elif v['m_secType']=='OPT': opt = Option(s=v['underlying_price'], k=v['m_strike'], eval_date=date.today(), # We want greeks for today exp_date=v['m_expiry'], rf = v['interest'], vol = v['volatility'], right = v['m_right']) price, delta, theta, gamma = opt.get_all() if v['m_side']=='BOT': position = float(v['position']) else: position = - float(v['position']) self.delta += position * delta self.gamma += position * gamma self.theta += position * theta else: print "ERROR: Not known type" return self.delta, self.gamma, self.theta if __name__ == '__main__': #=========================================================================== # TO CHECK OPTION CALCULATIONS #=========================================================================== s = 56.37 k = 60 exp_date = '20171215' eval_date = '20140528' rf = 0.01 vol = 0.2074 div = 0.014 right = 'C' opt = Option(s=s, k=k, eval_date=eval_date, exp_date=exp_date, rf=rf, vol=vol, right=right, div = div) price, delta, theta, gamma = opt.get_all() print "-------------- FIRST OPTION -------------------" print "Price CALL: " + str(price) # 2.97869320042 print "Delta CALL: " + str(delta) # 0.664877358932 print "Theta CALL: " + str(theta) # 0.000645545628288 print "Gamma CALL:" + str(gamma) # 0.021127937082 price = opt.get_price_by_binomial_tree() print "Price by BT:" + str(price) s = 110.41 k = 112 exp_date = '20160115' eval_date = '20140429' rf = 0.01 vol = 0.11925 right = 'C' opt = Option(s=s, k=k, eval_date=eval_date, exp_date=exp_date, rf=rf, vol=vol, right=right) price, delta, theta, gamma = opt.get_all() print "-------------- SECOND OPTION -------------------" print "Price CALL: " + str(price) # 7.02049813137 print "Delta CALL: " + str(delta) # 0.53837898036 print "Theta CALL: " + str(theta) # -0.00699852931575 print "Gamma CALL:" + str(gamma) # 0.0230279263655 #=========================================================================== # TO CHECK OPTIONS STRATEGIES CALCULATIONS #=========================================================================== d_option1 = {'m_secType': 'OPT', 'm_expiry': '20150116', 'm_right': 'C', 'm_symbol': 'TLT', 'm_strike': '115', 'm_multiplier': '100', 'position': '-2', 'trade_price': '3.69', 'comission': '0', 'eval_date': '20140422', 'interest': '0.01', 'volatility': '0.12353', 'underlying_price': '109.96'} d_option2 = {'m_secType': 'OPT', 'm_expiry': '20150116', 'm_right': 'C', 'm_symbol': 'TLT', 'm_strike': '135', 'm_multiplier': '100', 'position': '2', 'trade_price': '0.86', 'comission': '0', 'eval_date': '20140422', 'interest': '0.01', 'volatility': '0.12353', 'underlying_price': '109.96'} df_options = pd.DataFrame([d_option1, d_option2]) opt_strat = Options_strategy(df_options) delta, gamma, theta = opt_strat.get_greeks() print "-------- OPTIONS STRATEGY --------------" print "Delta: " + str(delta) print "Gamma: " + str(gamma) print "Theta: " + str(theta) #=========================================================================== # TO CHECK OPTION IMPLIED VOLATILITY CALCULATION #=========================================================================== s = 110.63 k = 115 exp_date = '20150116' eval_date = '20140424' rf = 0.01 price = 3.18 ## Calculated for a vol = 0.12353 right = 'C' opt = Option(s=s, k=k, eval_date=eval_date, exp_date=exp_date, rf=rf, price=price, right=right) ivol = opt.get_impl_vol() print "-------------- FIRST OPTION -------------------" print "Implied Volatility: " + str(ivol) |
I ran into a couple of problems with your python code to calculate the greeks.
I am a beginning python programmer, so I probably made some ignorant mistakes.
1. I tried to run the code in Python 3.4 and I got some errors. I was able to resolve most of them by changing the code to be 3.4 compliant, but in the binomial computation the following line gave me the error that “i” was not defined.
fs = [[0.0 for j in xrange(i+1)] for i in xrange(n+1)]
I had to change xrange to range, so that may have been a problem. I could not figure out from the code what the initial value of i should be, probably 0, so I could not declare it earlier and set it to 0.
So I spun up a 2.7 environment and tried again.
2. The first option test did not come out with the expected values. I get.
————– FIRST OPTION ——————-
Price CALL: 6.39548850488
Delta CALL: 0.600248022046
Theta CALL: -0.00247525485112
Gamma CALL:0.0175249946303
Price by BT:8.17598850134 although the test did not have a comment for this value.
The second option was right on the money.
————– SECOND OPTION ——————-
Price CALL: 7.02049813137
Delta CALL: 0.53837898036
Theta CALL: -0.00699852931575
Gamma CALL:0.0230279263655
3. The option strategy gave me a problem because i am running this on 7/7/2016 and you wrote it over 2 years ago. In the option strategies calculation you set m_expiry to ‘20150116’ but get_greeks(2) sets eval_date=date.today() in the Option(…) constructor. I had to change the ‘m_expiry’: ‘20170113’ in order to get it to give me a positive days to expiration.
I realize that it has been a while, but I thought I would share my experience so other people reading this excellent blog post
Many thanks Nicholas, I have not been working lately in this subject (sorry for late reply)