I'll use this opportunity to rewrite the code using an object oriented approach.
I'll start with objects for the options contracts. I basically have calls, puts and duo. Duo should be used when we want to price calls and puts with the same strike and volatility in one function call. Duos should be faster for pricers that allow it but transparent for other pricers.
Find the code below:
- import numpy as np
- import scipy.stats as ss
- #base
- class OptionGreek:
- price = 0
- delta = 0
- gamma = 0
- theta = 0
- def __init__(self):
- pass
- class OptionContract(object):
- _K = 0.0
- _Sigma = 0.0
- def __init__(self, k, sigma):
- self._K = float(k)
- self._Sigma = float(sigma)
- def printSelf(self):
- print "sigma\tVolatility:", self._Sigma
- print "K\tStrike:", self._K
- #vanilla european options
- class OptionContractCall(OptionContract):
- def expiC(self, S ):
- return np.maximum(S-self._K, 0.0)
- def d1(self, S0, K, r, sigma, T):
- return (np.log(S0 / K) + (r + sigma**2 / 2) * T) / (sigma * np.sqrt(T))
- def d2(self, S0, K, r, sigma, T):
- return (np.log(S0 / K) + (r - sigma**2 / 2) * T) / (sigma * np.sqrt(T))
- def expiCR(self, S0, r =0, sigma =0, T =1):
- return S0*ss.norm.cdf(self.d1(S0, self._K, r, sigma, T)) - self._K * np.exp(-r * T) * ss.norm.cdf(self.d2(S0, self._K, r, sigma, T))
- call=OptionGreek()
- def printSelf(self):
- super(OptionContractCall, self).printSelf()
- print "call price:", self.call.price
- def __init__(self, K, sigma):
- self.call=OptionGreek()
- super(OptionContractCall, self).__init__(K, sigma)
- class OptionContractPut(OptionContract):
- def expiP(self, S):
- return np.maximum(-S+self._K, 0.0)
- def d1(self, S0, K, r, sigma, T):
- return (np.log(S0 / K) + (r + sigma**2 / 2) * T) / (sigma * np.sqrt(T))
- def d2(self, S0, K, r, sigma, T):
- return (np.log(S0 / K) + (r - sigma**2 / 2) * T) / (sigma * np.sqrt(T))
- def expiPR(self, S0, r=0, sigma=0, T=1):
- return self._K * np.exp(-r * T) * ss.norm.cdf(-self.d2(S0, self._K, r, sigma, T)) - S0 * ss.norm.cdf(-self.d1(S0, self._K, r, sigma, T))
- def printSelf(self):
- super(OptionContractPut, self).printSelf()
- print "put price:", self.put.price
- put=OptionGreek()
- def __init__(self, K, sigma):
- self.put=OptionGreek()
- super(OptionContractPut, self).__init__(K, sigma)
- class OptionContractDuo(OptionContractPut, OptionContractCall):
- def printSelf(self):
- super(OptionContractDuo, self).printSelf()
- def __init__(self, K, sigma):
- super(OptionContractDuo, self).__init__(K, sigma)
Since duo inherit from calls and puts, they have call and put attributes and methods. Methods expiC, expiP and expiCR and expiPR are used depending if it is a call or a put and if we regularize or not.
Method printSelf is used to display informations and the price.
We can go further and also handle american options
- #US vanilla
- class OptionContractUS(OptionContract):
- pass
- class OptionContractCallUS(OptionContractCall, OptionContractUS):
- def earlyC(self, O, S):
- return np.maximum(S-self._K, O)
- class OptionContractPutUS(OptionContractPut, OptionContractUS):
- def earlyP(self, O, S):
- return np.maximum(-S+self._K, O)
- class OptionContractDuoUS(OptionContractDuo, OptionContractPutUS, OptionContractCallUS):
- pass
Note that american options inherit from the payoffs of EU options and also of the printSelf method. One must make sure that both payoff and early exercise function can handle vectors as it is used in the pricing code.
For fun I added some more exotic options: cash or nothings. A cash or nothing option pays 1 if ITM at expiration, 0 otherwise.
- #Cash or nothing euro options
- class OptionContractCashNothing(OptionContract):
- pass
- class OptionContractCashNothingCall(OptionContractCall, OptionContractCashNothing):
- def expiC(self, S):
- #this will give 0 if S<K, 1 is S>=K
- return np.sign(np.sign(S-self._K)+1)
- def expiCR(self, S0, r=0, sigma=0, T=1):
- return np.exp(-r * T) * ss.norm.cdf(self.d2(S0, self._K, r, sigma, T))
- def printSelf(self):
- super(OptionContractCall, self).printSelf()
- print "Cash or Nothing call price:", self.call.price
- class OptionContractCashNothingPut(OptionContractPut, OptionContractCashNothing):
- def expiP(self, S):
- #this will give 0 if S<K, 1 is S>=K
- return np.sign(np.sign(-S+self._K)+1)
- def expiPR(self, S0, r=0, sigma=0, T=1):
- return np.exp(-r * T) * ss.norm.cdf(-self.d2(S0, self._K, r, sigma, T))
- def printSelf(self):
- super(OptionContractPut, self).printSelf()
- print "Cash or Nothing put price:", self.put.price
- class OptionContractCashNothingDuo(OptionContractCashNothingPut, OptionContractCashNothingCall, OptionContractDuo):
- def printSelf(self):
- super(OptionContractCall, self).printSelf()
- print "Cash or Nothing call price:", self.call.price
- print "Cash or Nothing put price:", self.put.price
I saved all this is in OptionContracts.py.
I also created a Pricer class
- import numpy as np
- from OptionContracts import *
- class Error(Exception):
- pass
- class PricerError(Error):
- _PricerStr = ""
- _Msg = ""
- def __init__(self, pricerStr, msg):
- self._PricerStr = pricerStr
- self._Msg = msg
- class OptionPricer(object):
- _pricerId = ""
- _contractList = []
- _PricingParameters = []
- def __init__(self):
- self._contractList = [ ]
- self._PricingParameters = []
- def SetPricerId(self, pricerId):
- self._pricerId = pricerId
- def SetPricingParameters(self, pricingParameters):
- self._PricingParameters = pricingParameters
- def Calculate(self):
- for optionContract in self._contractList:
- if isinstance(optionContract, OptionContract):
- self.CalculateContract(optionContract)
- def CalculateContract(self, optionContract):
- pass
- def SetPricerParameters(self):
- pass
- def AddContract(self, optionContract):
- if not isinstance(optionContract, OptionContract):
- raise PricerError("OptionPricer", "Not a proper Option Contract")
- self._contractList.append(optionContract)
- def RemoveContract(self, optionContract):
- if optionContract not in self._contractList:
- raise PricerError("OptionPricer", "Cannot remove contract, it is not in the list")
- else:
- self._contractList.remove(optionContract)
- def PrintSelf(self):
- print self._pricerId
- self._PricingParameters.printSelf()
- for optionContract in self._contractList:
- optionContract.printSelf()
- class PricingParameters:
- _div = [[], []]
- _r = 0
- _T = 0
- _S0 = 0
- _r2 = [[], []]
- _pvdividends = 0
- _fvdividends = 0
- def __init__(self, S0, T, r, divi = "", r2 = ""):
- self._S0 = float(S0)
- self._T = float(T)
- self._r = float(r)
- self._r2 = r2
- #we don't store dividends past T
- if (np.size(divi)>0 and divi[0][0]<T) :
- lastdiv = np.nonzero(np.array(divi[0][:])<= T)[0][-1]
- self._div[0] = divi[0][:lastdiv+1]
- self._div[1] = divi[1][:lastdiv+1]
- #for the two dividend handling methods we need present and forward value of the dividend
- if np.size(self._div)>0:
- for index, dv in enumerate(list(self._div[1])):
- self._pvdividends = self._pvdividends+dv*np.exp(self._r*(-self._div[0][index]))
- self._fvdividends = self._fvdividends+dv*np.exp(self._r*(self._T-self._div[0][index]))
- def printSelf(self):
- print "S0\tstock price at time 0:", self._S0
- print "r\tcontinuously compounded risk-free rate:", self._r
- print "T\ttime to maturity in trading years:", self._T
- print "D\tDividends:", self._div
It defines some methods for all my pricers, calculate present and forward value of the dividends. It also has a printSelf method which will be used if a specific pricer doesn't have one. I saved this as OptionPricer.py
Then I created an Black and Scholes option pricer, that inherits from option pricer:
- from OptionPricer import *
- class BlackAndScholesPricer(OptionPricer):
- _DividendHandling = 'None'
- _AllowedMethod = ['None', 'Forward', 'Hull']
- @staticmethod
- def d1(S0, K, r, sigma, T):
- return (np.log(S0 / K) + (r + sigma**2 / 2) * T) / (sigma * np.sqrt(T))
- @staticmethod
- def d2(S0, K, r, sigma, T):
- return (np.log(S0 / K) + (r - sigma**2 / 2) * T) / (sigma * np.sqrt(T))
- @staticmethod
- def price(optionType, S0, K, r, sigma, T):
- if optionType == 'C':
- return S0 * ss.norm.cdf(BlackAndScholesPricer.d1(S0, K, r, sigma, T)) - K * np.exp(-r * T) * ss.norm.cdf(BlackAndScholesPricer.d2(S0, K, r, sigma, T))
- else:
- return K * np.exp(-r * T) * ss.norm.cdf(-BlackAndScholesPricer.d2(S0, K, r, sigma, T)) - S0 * ss.norm.cdf(-BlackAndScholesPricer.d1(S0, K, r, sigma, T))
- @staticmethod
- def priceCN(optionType, S0, K, r, sigma, T):
- if optionType == 'C':
- return np.exp(-r * T) * ss.norm.cdf(BlackAndScholesPricer.d2(S0, K, r, sigma, T))
- else:
- return np.exp(-r * T) * ss.norm.cdf(-BlackAndScholesPricer.d2(S0, K, r, sigma, T))
- def __init__(self):
- self.SetPricerId("BlackAndScholes")
- super(BlackAndScholesPricer, self).__init__()
- def SetDividendHandling(self, method):
- if method not in self._AllowedMethod:
- raise PricerError("BlackAndScholesPricer", 'This dividend method is not in the list of supported methods:'+string.join(self._AllowedMethod))
- else:
- self._DividendHandling = method
- def PrintSelf(self):
- print self._pricerId
- print "Dividend Handling method: ", self._DividendHandling
- self._PricingParameters.printSelf()
- for optionContract in self._contractList:
- optionContract.printSelf()
- def CalculateContract(self, optionContract):
- # Check input parameters
- """
- if self._PricingParameters._HasDividends == 'true':
- raise PricerError("BlackAndScholesPricer", "Dividends not supported by pricing algorithm")
- if self._PricingParameters._FixedR == 'false':
- raise PricerError("BlackAndScholesPricer", "Only fixed interest supported by pricing algorithm")
- """
- S0 = self._PricingParameters._S0
- r = self._PricingParameters._r
- T = self._PricingParameters._T
- sigma = optionContract._Sigma
- K = optionContract._K
- if self._DividendHandling == 'Forward':
- K = K+self._PricingParameters._fvdividends
- elif self._DividendHandling == 'Hull':
- S0 = S0-self._PricingParameters._pvdividends
- if isinstance(optionContract, OptionContractUS):
- raise PricerError("BlackAndScholesPricer", "American options not supported by pricing algorithm")
- if isinstance(optionContract, OptionContractCashNothing):
- if isinstance(optionContract, OptionContractCall):
- optionContract.call.price = BlackAndScholesPricer.priceCN('C', S0, K, r, sigma, T)
- if isinstance(optionContract, OptionContractPut):
- optionContract.put.price = BlackAndScholesPricer.priceCN('P', S0, K, r, sigma, T)
- else:
- if isinstance(optionContract, OptionContractCall):
- optionContract.call.price = BlackAndScholesPricer.price('C', S0, K, r, sigma, T)
- if isinstance(optionContract, OptionContractPut):
- optionContract.put.price = BlackAndScholesPricer.price('P', S0, K, r, sigma, T)
As one can see there is some eror checking in comment, it is not finished yet but uses the exception we defined earlier. See the BS pricer doesn't handle US options and an exception is raised in that case. The two dividend handling methods are there and the third one ('None') just ignores them.
One should note that based on the class of the option contract, different pricers are called. The class function (defined with a @staticmethod decorator) pricer prices vanilla option and pricerCN prices cash or nothing options. So one should be careful when defining new types of contracts about their inheritance.
Note that this pricer doesn't handle Duo contracts but since they are both calls and puts, it gets called twice and the call and then the put get priced.
This is saved in a BlackAndScholesPricer.py file.
If one wants to use it:
- from BlackAndScholesPricer import *
- from OptionContracts import *
- pricer = BlackAndScholesPricer ()
- try:
- pp = PricingParameters (100, 3.0, 0.0, [[2.9], [0]], [[5], [0.0]])
- pricer.SetPricingParameters(pp)
- contrat1 = OptionContractCashNothingDuo(120, 0.4)
- contrat2 = OptionContractPut(110, 0.4)
- contrat3 = OptionContractCall(100, 0.4)
- contrat4 = OptionContractDuo(130, 0.4)
- contrat5 = OptionContractDuoUS(130, 0.4)
- pricer.AddContract(contrat1)
- pricer.AddContract(contrat2)
- pricer.AddContract(contrat3)
- pricer.AddContract(contrat4)
- pricer.SetDividendHandling('Forward')
- pricer.Calculate()
- pricer.PrintSelf()
- except PricerError as e:
- print e._PricerStr, ":", e._Msg
This script creates several contracts, use BS to price them and prints results.
If one adds the contract5 to the pricer contract list, an exception is raised as it cannot price US options.
Aucun commentaire:
Enregistrer un commentaire