Pages

lundi 25 mars 2013

Refactoring the code

As you have noticed, there is a lot of code, multiple versions of similar pricers. This is become a bit complicated.

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:
  1. import numpy as np  
  2. import scipy.stats as ss  
  3.   
  4.   
  5. #base  
  6. class OptionGreek:  
  7.     price = 0  
  8.     delta = 0  
  9.     gamma = 0  
  10.     theta = 0  
  11.     def __init__(self):  
  12.         pass  
  13.   
  14. class OptionContract(object):  
  15.     _K = 0.0  
  16.     _Sigma = 0.0  
  17.     def __init__(self, k, sigma):  
  18.         self._K = float(k)  
  19.         self._Sigma = float(sigma)  
  20.   
  21.     def printSelf(self):  
  22.         print "sigma\tVolatility:", self._Sigma  
  23.         print "K\tStrike:", self._K  
  24.           
  25.       
  26. #vanilla european options  
  27. class OptionContractCall(OptionContract):  
  28.     def expiC(self, S ):  
  29.         return np.maximum(S-self._K, 0.0)  
  30.     def d1(self, S0, K, r, sigma, T):  
  31.         return (np.log(S0 / K) + (r + sigma**2 / 2) * T) / (sigma * np.sqrt(T))  
  32.     def d2(self, S0, K, r, sigma, T):  
  33.         return (np.log(S0 / K) + (r - sigma**2 / 2) * T) / (sigma * np.sqrt(T))  
  34.     def expiCR(self, S0, r =0, sigma =0, T =1):  
  35.         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))          
  36.      
  37.     call=OptionGreek()  
  38.       
  39.     def printSelf(self):  
  40.         super(OptionContractCall, self).printSelf()  
  41.         print "call price:", self.call.price  
  42.       
  43.     def __init__(self, K, sigma):  
  44.         self.call=OptionGreek()  
  45.         super(OptionContractCall, self).__init__(K, sigma)  
  46.   
  47.   
  48.   
  49. class OptionContractPut(OptionContract):  
  50.     def expiP(self, S):  
  51.         return np.maximum(-S+self._K, 0.0)  
  52.     def d1(self, S0, K, r, sigma, T):  
  53.         return (np.log(S0 / K) + (r + sigma**2 / 2) * T) / (sigma * np.sqrt(T))  
  54.     def d2(self, S0, K, r, sigma, T):  
  55.         return (np.log(S0 / K) + (r - sigma**2 / 2) * T) / (sigma * np.sqrt(T))  
  56.     def expiPR(self, S0, r=0, sigma=0, T=1):  
  57.         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))  
  58.           
  59.     def printSelf(self):  
  60.         super(OptionContractPut, self).printSelf()  
  61.         print "put price:", self.put.price  
  62.   
  63.     put=OptionGreek()  
  64.      
  65.     def __init__(self, K, sigma):  
  66.         self.put=OptionGreek()  
  67.         super(OptionContractPut, self).__init__(K, sigma)  
  68.   
  69.   
  70. class OptionContractDuo(OptionContractPut, OptionContractCall):  
  71.     def printSelf(self):  
  72.         super(OptionContractDuo, self).printSelf()  
  73.   
  74.     def __init__(self, K, sigma):  
  75.         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


  1. #US vanilla  
  2. class OptionContractUS(OptionContract):  
  3.     pass      
  4.   
  5. class OptionContractCallUS(OptionContractCall, OptionContractUS):  
  6.     def earlyC(self, O, S):  
  7.         return np.maximum(S-self._K, O)  
  8.       
  9. class OptionContractPutUS(OptionContractPut, OptionContractUS):  
  10.     def earlyP(self, O, S):  
  11.         return np.maximum(-S+self._K, O)  
  12.           
  13. class OptionContractDuoUS(OptionContractDuo, OptionContractPutUS, OptionContractCallUS):  
  14.     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.


  1. #Cash or nothing euro options  
  2. class OptionContractCashNothing(OptionContract):  
  3.     pass  
  4.   
  5. class OptionContractCashNothingCall(OptionContractCall, OptionContractCashNothing):  
  6.     def expiC(self, S):  
  7.         #this will give 0 if S<K, 1 is S>=K  
  8.         return np.sign(np.sign(S-self._K)+1)  
  9.     def expiCR(self, S0, r=0, sigma=0, T=1):  
  10.         return np.exp(-r * T) * ss.norm.cdf(self.d2(S0, self._K, r, sigma, T))  
  11.     def printSelf(self):  
  12.         super(OptionContractCall, self).printSelf()  
  13.         print "Cash or Nothing call price:", self.call.price  
  14.        
  15.   
  16. class OptionContractCashNothingPut(OptionContractPut, OptionContractCashNothing):  
  17.     def expiP(self, S):  
  18.         #this will give 0 if S<K, 1 is S>=K  
  19.         return np.sign(np.sign(-S+self._K)+1)  
  20.     def expiPR(self, S0, r=0, sigma=0, T=1):  
  21.        return np.exp(-r * T) * ss.norm.cdf(-self.d2(S0, self._K, r, sigma, T))  
  22.     def printSelf(self):  
  23.         super(OptionContractPut, self).printSelf()  
  24.         print "Cash or Nothing put price:", self.put.price  
  25.        
  26. class OptionContractCashNothingDuo(OptionContractCashNothingPut, OptionContractCashNothingCall, OptionContractDuo):  
  27.     def printSelf(self):  
  28.         super(OptionContractCall, self).printSelf()  
  29.         print "Cash or Nothing call price:", self.call.price  
  30.         print "Cash or Nothing put price:", self.put.price  
  31.        

I saved all this is in OptionContracts.py.
I also created a Pricer class

  1. import numpy as np  
  2.   
  3. from OptionContracts import *  
  4.   
  5.   
  6. class Error(Exception):  
  7.     pass  
  8.    
  9. class PricerError(Error):  
  10.     _PricerStr = ""  
  11.     _Msg = ""  
  12.     def __init__(self,  pricerStr,  msg):  
  13.         self._PricerStr  =  pricerStr  
  14.         self._Msg  =  msg  
  15.   
  16.   
  17. class OptionPricer(object):  
  18.     _pricerId = ""  
  19.     _contractList = []  
  20.     _PricingParameters = []  
  21.       
  22.     def __init__(self):  
  23.         self._contractList = [ ]  
  24.         self._PricingParameters = []  
  25.           
  26.       
  27.     def SetPricerId(self, pricerId):  
  28.         self._pricerId = pricerId  
  29.       
  30.     def SetPricingParameters(self, pricingParameters):  
  31.         self._PricingParameters = pricingParameters  
  32.       
  33.     def Calculate(self):  
  34.         for optionContract in self._contractList:  
  35.             if isinstance(optionContract, OptionContract):  
  36.                 self.CalculateContract(optionContract)  
  37.               
  38.           
  39.     def CalculateContract(self, optionContract):  
  40.         pass  
  41.   
  42.     def SetPricerParameters(self):  
  43.         pass  
  44.       
  45.     def AddContract(self, optionContract):  
  46.         if not isinstance(optionContract, OptionContract):  
  47.             raise PricerError("OptionPricer",  "Not a proper Option Contract")  
  48.         self._contractList.append(optionContract)  
  49.   
  50.     def RemoveContract(self, optionContract):  
  51.         if optionContract not in self._contractList:  
  52.             raise PricerError("OptionPricer",  "Cannot remove contract,  it is not in the list")  
  53.         else:  
  54.             self._contractList.remove(optionContract)  
  55.              
  56.     def PrintSelf(self):  
  57.         print self._pricerId  
  58.         self._PricingParameters.printSelf()  
  59.         for optionContract in self._contractList:  
  60.             optionContract.printSelf()  
  61.   
  62.    
  63. class PricingParameters:  
  64.     _div = [[], []]  
  65.     _r = 0  
  66.     _T = 0  
  67.     _S0 = 0  
  68.     _r2 = [[], []]  
  69.       
  70.     _pvdividends = 0  
  71.     _fvdividends = 0  
  72.       
  73.     def __init__(self, S0, T, r, divi = "", r2 = ""):  
  74.         self._S0 = float(S0)  
  75.         self._T = float(T)  
  76.         self._r = float(r)  
  77.         self._r2 = r2  
  78.           
  79.         #we don't store dividends past T  
  80.         if (np.size(divi)>0 and divi[0][0]<T) :  
  81.             lastdiv = np.nonzero(np.array(divi[0][:])<= T)[0][-1]  
  82.             self._div[0] = divi[0][:lastdiv+1]          
  83.             self._div[1] = divi[1][:lastdiv+1]  
  84.               
  85.         #for the two dividend handling methods we need present and forward value of the dividend      
  86.         if np.size(self._div)>0:  
  87.             for index, dv in enumerate(list(self._div[1])):  
  88.                 self._pvdividends = self._pvdividends+dv*np.exp(self._r*(-self._div[0][index]))  
  89.                 self._fvdividends = self._fvdividends+dv*np.exp(self._r*(self._T-self._div[0][index]))  
  90.      
  91.     def printSelf(self):       
  92.         print "S0\tstock price at time 0:",  self._S0  
  93.         print "r\tcontinuously compounded risk-free rate:",  self._r  
  94.         print "T\ttime to maturity in trading years:",  self._T  
  95.         print "D\tDividends:", self._div   
  96.       

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:


  1. from OptionPricer import *  
  2.   
  3. class BlackAndScholesPricer(OptionPricer):  
  4.     _DividendHandling  =  'None'   
  5.     _AllowedMethod = ['None',  'Forward', 'Hull']  
  6.       
  7.     @staticmethod  
  8.     def d1(S0,  K,  r,  sigma,  T):  
  9.         return (np.log(S0 / K) + (r + sigma**2 / 2) * T) / (sigma * np.sqrt(T))  
  10.    
  11.     @staticmethod  
  12.     def d2(S0,  K,  r,  sigma,  T):  
  13.         return (np.log(S0 / K) + (r - sigma**2 / 2) * T) / (sigma * np.sqrt(T))  
  14.    
  15.     @staticmethod  
  16.     def price(optionType,  S0,  K,  r,  sigma,  T):  
  17.         if optionType == 'C':  
  18.             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))  
  19.         else:  
  20.             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))  
  21.     @staticmethod  
  22.     def priceCN(optionType,  S0,  K,  r,  sigma,  T):  
  23.         if optionType == 'C':  
  24.             return np.exp(-r * T) * ss.norm.cdf(BlackAndScholesPricer.d2(S0, K,  r,  sigma,  T))  
  25.         else:  
  26.             return np.exp(-r * T) * ss.norm.cdf(-BlackAndScholesPricer.d2(S0,  K,  r,  sigma,  T))  
  27.     
  28.     def __init__(self):  
  29.         self.SetPricerId("BlackAndScholes")    
  30.         super(BlackAndScholesPricer, self).__init__()          
  31.           
  32.    
  33.     def SetDividendHandling(self, method):  
  34.         if method not in self._AllowedMethod:  
  35.             raise PricerError("BlackAndScholesPricer",  'This dividend method is not in the list of supported methods:'+string.join(self._AllowedMethod))  
  36.         else:  
  37.             self._DividendHandling = method  
  38.               
  39.     def PrintSelf(self):  
  40.         print self._pricerId  
  41.         print "Dividend Handling method: ", self._DividendHandling  
  42.           
  43.         self._PricingParameters.printSelf()  
  44.           
  45.         for optionContract in self._contractList:  
  46.             optionContract.printSelf()  
  47.   
  48.     def CalculateContract(self, optionContract):  
  49.         # Check input parameters  
  50.         """         
  51.         if self._PricingParameters._HasDividends  ==  'true': 
  52.             raise PricerError("BlackAndScholesPricer",  "Dividends not supported by pricing algorithm") 
  53.         if self._PricingParameters._FixedR  ==  'false': 
  54.             raise PricerError("BlackAndScholesPricer",  "Only fixed interest supported by pricing algorithm") 
  55.         """  
  56.         S0  =  self._PricingParameters._S0  
  57.         r  =  self._PricingParameters._r  
  58.         T  =  self._PricingParameters._T         
  59.         sigma  =  optionContract._Sigma  
  60.         K  =  optionContract._K  
  61.   
  62.         if self._DividendHandling == 'Forward':  
  63.             K = K+self._PricingParameters._fvdividends  
  64.         elif self._DividendHandling == 'Hull':  
  65.             S0 = S0-self._PricingParameters._pvdividends  
  66.               
  67.         if isinstance(optionContract, OptionContractUS):  
  68.             raise PricerError("BlackAndScholesPricer",  "American options not supported by pricing algorithm")  
  69.         if isinstance(optionContract, OptionContractCashNothing):  
  70.             if isinstance(optionContract, OptionContractCall):  
  71.                 optionContract.call.price = BlackAndScholesPricer.priceCN('C', S0, K, r, sigma, T)  
  72.             if isinstance(optionContract, OptionContractPut):  
  73.                 optionContract.put.price = BlackAndScholesPricer.priceCN('P', S0, K, r, sigma, T)  
  74.         else:  
  75.             if isinstance(optionContract, OptionContractCall):  
  76.                 optionContract.call.price = BlackAndScholesPricer.price('C', S0, K, r, sigma, T)  
  77.             if isinstance(optionContract, OptionContractPut):  
  78.                 optionContract.put.price = BlackAndScholesPricer.price('P', S0, K, r, sigma, T)  
  79.                   

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:


  1. from BlackAndScholesPricer import *  
  2.   
  3. from OptionContracts import *  
  4.   
  5.   
  6. pricer = BlackAndScholesPricer ()  
  7.   
  8.   
  9. try:  
  10.     pp = PricingParameters (100, 3.0, 0.0, [[2.9], [0]], [[5], [0.0]])  
  11.     pricer.SetPricingParameters(pp)  
  12.       
  13.     contrat1 = OptionContractCashNothingDuo(120, 0.4)  
  14.     contrat2 = OptionContractPut(110, 0.4)  
  15.     contrat3 = OptionContractCall(100, 0.4)  
  16.     contrat4 = OptionContractDuo(130, 0.4)  
  17.     contrat5 = OptionContractDuoUS(130, 0.4)  
  18.       
  19.     pricer.AddContract(contrat1)  
  20.     pricer.AddContract(contrat2)  
  21.     pricer.AddContract(contrat3)  
  22.     pricer.AddContract(contrat4)  
  23.      
  24.   
  25.     pricer.SetDividendHandling('Forward')  
  26.     pricer.Calculate()  
  27.     pricer.PrintSelf()  
  28.      
  29. except PricerError as e:  
  30.     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