
For option market makers, this is a very important subject.
As I pointed out earlier, one can wonder about the need of a pricer for options whose prices are liquid and tight in the market.
One use is for the greeks: knowing the price of an option doesn't tell me how such price will change when times passes or when the underlying moves. With the pricer and the appropriate volatility, I can calculate such sensitivity. One should keep in mind that such derivatives are model dependant.
But here I have a problem: in order to get the derivative, I need to know the volatility of the option (all other parameters are given: time, rate, stock value, strike). But all I know is the price, and maybe my own opinion about the volatility of the stock (in the future).
The implied volatility is the volatility I should use to have the same price than the one I see in the market. So I need to inverse my pricing function. If one checks the formulas, that's not possible. So we use an iterative procedure:
We guess a volatility, get a price, compare with the observed price and change the volatility until we get a price close enough.
One good aspect is that we know the price as f($\sigma$) but also the derivative of the price with respect to the volatility $df/d\sigma$. So schematically what we do is:
-Guess $\sigma$
-get a price $f(\sigma)$ and the derivative $Vega=df/d\sigma$
-update $\sigma=\sigma+(market price-f(\sigma))/Vega$
-until market price and $f(\sigma)$ are close enough.
This is an application of the Newton-Raphson method.
To have a smart guess for $\sigma$ I use the following approximation:
\[
Stra= S \sigma \frac{ \sqrt{days} }{24} \]
with Strad the price of the straddle (call +put with same strike) and $days$ the number of days until option expiration. Such approximation mostly works for ATM options with very low risk free rate. But that will be better than nothing to start.
Find here the code for the BlackAndScholesPricer class.
I also added an empty method GetImpled in the OptionPricer class
- def GetImplied(self,eps):
- def phi(z):
- return np.exp(-.5 * z * z) / (np.sqrt( 2 * np.pi ))
- S0 = self._PricingParameters._S0
- r = self._PricingParameters._r
- T = self._PricingParameters._T
- if self._DividendHandling == 'Hull':
- S0 = S0-self._PricingParameters._pvdividends
- #we start with calls
- KVec = []
- SigVec = []
- TargetPriceVec = []
- #get prices and strikes and a first guess for volatility
- for optionContract in self._contractList:
- if isinstance(optionContract, OptionContractCall):
- KVec.append(optionContract._K)
- #SigVec.append(optionContract._Sigma)
- put=-S0+optionContract._K
- SigVec.append((put+2*optionContract.call.price)*24/(S0*np.sqrt(T*365.0)))
- TargetPriceVec.append(optionContract.call.price)
- if self._DividendHandling == 'Forward':
- KVec = KVec+self._PricingParameters._fvdividends
- Kv = np.array(KVec[:])
- Sv = np.array(SigVec[:])
- PriceTargetv=np.array(TargetPriceVec[:])
- Pricev=0*PriceTargetv
- if len(Kv)>0:
- while True:
- #get vega
- Vegav= S0*phi(BlackAndScholesPricer.d1(S0,Kv, r, Sv, T))*np.sqrt(T)
- #get current price
- Pricev=BlackAndScholesPricer.price('C', S0, Kv, r, Sv, T)
- #update vola
- Sv=Sv+(PriceTargetv-Pricev)/Vegav
- if max((PriceTargetv-Pricev)**2) <eps:
- break
- idC = 0
- for optionContract in self._contractList:
- if isinstance(optionContract, OptionContractCall):
- optionContract.call.impliedVol = Sv[idC]
- idC = idC+1
- #Then Do it again for puts
- KVec = []
- SigVec = []
- TargetPriceVec = []
- #get prices and strikes and a first guess for volatility
- for optionContract in self._contractList:
- if isinstance(optionContract, OptionContractPut):
- KVec.append(optionContract._K)
- #SigVec.append(optionContract._Sigma)
- call=S0-optionContract._K
- SigVec.append((call+2*optionContract.put.price)*24/(S0*np.sqrt(T*365.0)))
- TargetPriceVec.append(optionContract.put.price)
- if self._DividendHandling == 'Forward':
- KVec = KVec+self._PricingParameters._fvdividends
- Kv = np.array(KVec[:])
- Sv = np.array(SigVec[:])
- PriceTargetv=np.array(TargetPriceVec[:])
- Pricev=0*PriceTargetv
- if len(Kv)>0:
- while True:
- #get vega
- Vegav= S0*phi(BlackAndScholesPricer.d1(S0,Kv, r, Sv, T))*np.sqrt(T)
- #get current price
- Pricev=BlackAndScholesPricer.price('P', S0, Kv, r, Sv, T)
- #update vola
- Sv=Sv+(PriceTargetv-Pricev)/Vegav
- if max((PriceTargetv-Pricev)**2) <eps:
- break
- idC = 0
- for optionContract in self._contractList:
- if isinstance(optionContract, OptionContractPut):
- optionContract.put.impliedVol = Sv[idC]
- idC = idC+1
A couple of comments on the code:
-It is vectorized: a list of call is made, in one function call we get their vega, and in a second function call we get their price.
-Then we treat the puts. If the pricing model is right and the parameters as well (rate, dividends), we should get the same implied for the puts but it might not be the case.
-I added an attribute to store the implied volatility in the class OptionGreeks.
-EDIT: I added the support for the BS dividend model. Without it, the implied of call and put would not match, even for european options, when pricing with dividends.
eps is the square of the maximum deviation allowed between prices in the market and our prices. Since it is vectorized, most of the strikes will have a lower error.
Using the following code, one can see interesting things:
- from BlackAndScholesPricer import *
- from BinomialTreePricer import *
- from PDEPricer import *
- from MCPricer import *
- from OptionContracts import *
- from FourierPricer import *
- import matplotlib.pyplot as plt
- pricer = BlackAndScholesPricer ()
- pricer2 = BinomialTreePricer ()
- pricer3 = PDEPricer ()
- pricer4 = MCPricer ()
- pricer5 = FourierPricer ()
- pricerlist=[pricer,pricer2]
- try:
- pp = PricingParameters (100, 3.0, [[1.5,5], [0.04,0.04]], [[], []])
- for pr in pricerlist:
- pr.SetPricingParameters(pp)
- pricer.SetPricerParameters()
- pricer2.SetPricerParameters(250,True,True,True)
- pricer3.SetPricerParameters(850,100,True,True)
- pricer4.SetPricerParameters(132500,5,True,True,True)
- pricer5.SetPricerParameters(500,10,True)
- contrat1 = OptionContractDuoUS(90, 0.4)
- contrat2 = OptionContractDuoUS(80, 0.42)
- contrat3 = OptionContractDuoUS(100, 0.43)
- contrat4 = OptionContractDuoUS(110, 0.45)
- contrat5 = OptionContractDuoUS(130, 0.50)
- contractlist=[contrat1,contrat2,contrat3,contrat4,contrat5]
- for pr in pricerlist:
- for co in contractlist:
- pr.AddContract(co)
- """
- fig=plt.figure()
- fig2=plt.figure()
- ax=fig.add_subplot(111)
- ax2=fig2.add_subplot(111)
- legende=[]
- for pr in pricerlist:
- price,time=pr.TestConvergence(500, 3000)
- legende.append(pr._pricerId)
- ax.plot(price)
- ax2.plot(time)
- leg=ax.legend(legende,'upper center')
- leg=ax2.legend(legende,'upper left')
- plt.show()
- """
- pricer2.Calculate()
- pricer.GetImplied(0.01)
- pricer.PrintSelf()
- except PricerError as e:
- print e._PricerStr, ":", e._Msg
Note that only one object per contract is created so both pricer points toward the same contracts.
The binomial tree is used to price these american options. Then BS is used to get the implied volatility.
The results show, for example, that contract5 has an implied vol of 55.18% for the put and 50% for the call. The premium for american option is responsible for that and is large (+10% of the implied vol). It makes sense as the put is quite in the money so the early exercise is quite possible (for some stock paths in the future, not now).
For contract1, the put is initially out of the money (strike 90, spot 100) but its implied volatility is 42% instead of 40%. not a neglectable difference.
Aucun commentaire:
Enregistrer un commentaire