
Now it is the turn of the MC pricer to get a face lift. Note that I only have two versions of the computing code. One for solo, one for duo. The code is smart enough to directly jump to expiration if the option is european without dividend. If not the brownian bridge is used to generate the paths.
In case the option is american, LSM is used to check for early exercise. I included the class Sobol in the file but one still need the ltqnorm.py file that I provided earlier.
When one initialize the MC pricer, a value, depth, is used to generate the bridge so depth=10 means 2^10=1024 time steps.
- import scipy.interpolate as si
- from OptionPricer import *
- from ltqnorm import *
- class SobolSequence:
- "From the Gnu Scientific Library"
- def __init__(self,dim):
- self.max_dim = 40
- self.bit_count = 30
- self.primitive_polynomials = [
- 1, 3, 7, 11, 13, 19, 25, 37, 59, 47,
- 61, 55, 41, 67, 97, 91, 109, 103, 115, 131,
- 193, 137, 145, 143, 241, 157, 185, 167, 229, 171,
- 213, 191, 253, 203, 211, 239, 247, 285, 369, 299
- ]
- self.degree_table = [
- 0, 1, 2, 3, 3, 4, 4, 5, 5, 5,
- 5, 5, 5, 6, 6, 6, 6, 6, 6, 7,
- 7, 7, 7, 7, 7, 7, 7, 7, 7, 7,
- 7, 7, 7, 7, 7, 7, 7, 8, 8, 8
- ]
- self.v_init = [
- [0, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
- 1, 1, 1, 1, 1, 1, 1, 1, 1, 1],
- [0, 0, 1, 3, 1, 3, 1, 3, 3, 1,
- 3, 1, 3, 1, 3, 1, 1, 3, 1, 3,
- 1, 3, 1, 3, 3, 1, 3, 1, 3, 1,
- 3, 1, 1, 3, 1, 3, 1, 3, 1, 3],
- [0, 0, 0, 7, 5, 1, 3, 3, 7, 5,
- 5, 7, 7, 1, 3, 3, 7, 5, 1, 1,
- 5, 3, 3, 1, 7, 5, 1, 3, 3, 7,
- 5, 1, 1, 5, 7, 7, 5, 1, 3, 3],
- [0, 0, 0, 0, 0, 1, 7, 9, 13, 11,
- 1, 3, 7, 9, 5, 13, 13, 11, 3, 15,
- 5, 3, 15, 7, 9, 13, 9, 1, 11, 7,
- 5, 15, 1, 15, 11, 5, 3, 1, 7, 9],
- [0, 0, 0, 0, 0, 0, 0, 9, 3, 27,
- 15, 29, 21, 23, 19, 11, 25, 7, 13, 17,
- 1, 25, 29, 3, 31, 11, 5, 23, 27, 19,
- 21, 5, 1, 17, 13, 7, 15, 9, 31, 9],
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 37, 33, 7, 5, 11, 39, 63,
- 27, 17, 15, 23, 29, 3, 21, 13, 31, 25,
- 9, 49, 33, 19, 29, 11, 19, 27, 15, 25],
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 13,
- 33, 115, 41, 79, 17, 29, 119, 75, 73, 105,
- 7, 59, 65, 21, 3, 113, 61, 89, 45, 107],
- [0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
- 0, 0, 0, 0, 0, 0, 0, 7, 23, 39]
- ] # end of self.v_init
- self.dim = dim
- #self.v_direction = [[0]*self.bit_count]*self.max_dim
- #self.v_direction = [[0]*self.max_dim]*self.bit_count #??
- self.v_direction = []
- for i in range(self.bit_count):
- array = []
- for j in range(self.max_dim):
- array.append(0)
- self.v_direction.append(array)
- # Initialize direction table in dimension 0.
- for k in range(self.bit_count): self.v_direction[k][0] = 1
- # Initialize in remaining dimensions.
- for i_dim in range(1,self.dim):
- poly_index = i_dim
- degree_i = self.degree_table[poly_index]
- includ = [0]*8
- # Expand the polynomial bit pattern to separate
- # components of the logical array includ[].
- p_i = self.primitive_polynomials[poly_index]
- for k in range(degree_i-1,-1,-1):
- includ[k] = ((p_i % 2) == 1)
- p_i /= 2
- # Leading elements for dimension i come from v_init[][].
- for j in range(degree_i):
- self.v_direction[j][i_dim] = self.v_init[j][i_dim]
- # Calculate remaining elements for this dimension,
- # as explained in Bratley+Fox, section 2.
- for j in range(degree_i, self.bit_count):
- newv = self.v_direction[j-degree_i][i_dim]
- ell = 1
- for k in range(degree_i):
- ell *= 2
- if includ[k]:
- newv = newv ^ (ell*self.v_direction[j-k-1][i_dim])
- self.v_direction[j][i_dim] = newv
- # done with for i_dim in range(1,self.dim)
- # Multiply columns of v by appropriate power of 2
- ell = 1
- for j in range(self.bit_count-2,-1,-1):
- ell *= 2
- for i_dim in range(self.dim):
- self.v_direction[j][i_dim] = self.v_direction[j][i_dim]*ell
- # 1/(common denom of the elements in v_direction)
- self.last_denominator_inv = 1.0 /(2.0 * ell)
- # final setup
- self.sequence_count = 0
- self.last_numerator_vec = [0]*self.dim
- return
- def __call__(self):
- ell = 0
- c = self.sequence_count
- while 1:
- ell += 1
- if c % 2 == 1:
- c/=2
- else:
- break
- if ell > self.bit_count:
- raise "Sobol failed for %d" % self.sequence_count
- v = [0]*self.dim
- for i_dimension in range(self.dim):
- direction_i = self.v_direction[ell-1][i_dimension]
- old_numerator_i = self.last_numerator_vec[i_dimension]
- new_numerator_i = old_numerator_i ^ direction_i
- self.last_numerator_vec[i_dimension] = new_numerator_i
- v[i_dimension] = new_numerator_i*self.last_denominator_inv
- self.sequence_count += 1
- return v
- class MCPricer(OptionPricer):
- _NumberPath = 0
- _StepAdapt = False
- _Regul = False
- _AP = False
- _MM = True
- _Depth = 1
- @staticmethod
- def RNG(I, ap, mm):
- if ap:
- ran = np.random.standard_normal (I/2)
- ran = np.concatenate (( ran ,- ran ))
- else :
- ran = np.random.standard_normal (I)
- if mm:
- ran = ran - np.mean ( ran)
- ran = ran / np.std ( ran )
- return ran
- @staticmethod
- def SobolRNG(I, ap, mm):
- #we get a sobol sequence of proper size
- #if we use it only for values at expiration, dimension1 is fine
- ss = SobolSequence(1)
- ss()
- if ap:
- ran = np.array([ltqnorm(ss()[0]) for i in xrange(I/2)])
- ran = np.concatenate (( ran ,- ran ))
- else :
- ran = np.array([ltqnorm(ss()[0]) for i in xrange(I)])
- if mm:
- ran = ran - np.mean ( ran)
- ran = ran / np.std ( ran )
- return ran
- @staticmethod
- def Bridge(x, depth, depthmax, I, sigma, ap, mm):
- #we create path with the brownian bridge
- if depth == depthmax:
- return x
- xmid = np.average(x,0)+0.5*MCPricer.RNG(I, ap, mm)*np.sqrt(sigma/(2**(depth-1)))
- y = MCPricer.Bridge([x[0][:], xmid[:]], depth+1, depthmax, I, sigma, ap, mm)
- z = MCPricer.Bridge([xmid[:], x[-1][:]], depth+1, depthmax, I, sigma, ap, mm)
- x = np.concatenate((y[:-1][:], z[:][:]))
- return x
- @staticmethod
- def LSM(hC,VC,S,NP,df,t):
- deg = 2
- #get the ITM call
- dummy = np.greater(hC[t,:], 0)
- relevant = np.nonzero(dummy)
- relevantS = np.compress(dummy, S[t,:])
- p = len(relevantS)
- #if any
- if p==0:
- contvalues = np.zeros((NP))
- else:
- relevantV = np.compress(dummy, VC[t+1,:]*df)
- #rg is an approximation of the call price as a function of the stock
- #only in the ITM region
- #to fit that function we discount call prices from thenext time step (we go backward in time)
- rg = np.polyfit(relevantS[:], relevantV[:], deg)
- contvalues = np.polyval(rg, relevantS[:])
- #for each call we check if exercise now is higher than approximate value of keeping it
- erg = np.zeros((NP))
- np.put(erg,relevant,contvalues)
- return erg
- @staticmethod
- def MCDuo(pricingParameters, optionContract, depth, NP, ap, mm, stepAdapt,american):
- #parameters
- S0 = pricingParameters._S0
- r2 = pricingParameters._r2
- T = pricingParameters._T
- sigma = optionContract._Sigma
- divi = pricingParameters._div
- NTS=2**depth
- dt=float(T)/NTS
- np.random.seed
- dividends,dividendsStep=PricingParameters.preparedivs(divi,T,dt)
- r,rArray,rateChangeStep,DFArray=PricingParameters.preparerates(r2,T,dt,NTS)
- #we directly compute the final value of the brownian motion
- ran=np.asarray(MCPricer.SobolRNG(NP, ap, mm))*sigma*np.sqrt(T)
- #if we get the proper rate, we can directly get the european value
- rate=pricingParameters._r
- # generate stock price paths
- SEuro= np.zeros ((NP),'d') # index level matrix
- rr=r[1][1]
- SEuro[:]=(S0)*np.exp((rate-0.5*(sigma**2))* T+ ran[:])
- if (american or pricingParameters._pvdividends>0):
- # generate stock price paths
- S = np.zeros ((NTS,NP),'d') # index level matrix
- S[0 ,:] = S0 # initial values
- #we then proceed to generate intermediate values
- ranBB = (np.concatenate(([ran-ran],[ran])))
- ranBB = MCPricer.Bridge(ranBB,1,depth+1,ranBB.shape[1],sigma*sigma*T,ap,mm)
- incBB = ranBB[1:][:]-ranBB[:-1][:]
- for t in range (1,NTS): # loop over the days
- if (t in rateChangeStep[:]):
- rr = (r[1][np.nonzero(rateChangeStep==(t))[0]+1])
- S[t ,:] = S[t-1 ,:]* np.exp ((rr-0.5*sigma **2)* dt+incBB[t][:])
- if (t in dividendsStep):
- div = dividends[1][np.nonzero(dividendsStep==(t))[0]]
- S[t,:] = np.maximum(S[t,:]-div,0)
- if american:
- #apply LMS method
- VC = optionContract.earlyC(S,0.0)
- VP = optionContract.earlyP(S,0.0)
- hC = VC
- hP = VP
- for t in range(NTS-2,-1,-1):
- df = DFArray[t]
- erg = MCPricer.LSM(hC,VC,S,NP,df,t)
- VC[t,:] = np.where(hC[t,:]>erg,hC[t,:],VC[t+1,:]*df)
- erg = MCPricer.LSM(hP,VP,S,NP,df,t)
- VP[t,:] = np.where(hP[t,:]>erg,hP[t,:],VP[t+1,:]*df)
- optionContract.call.price = sum(VC[0,:])/NP
- optionContract.put.price = sum(VP[0,:])/NP
- else:
- df = np.exp(-rate*T)
- VC = optionContract.expiC(S[-1,:])
- optionContract.call.price = df*sum(VC)/NP
- VP = optionContract.expiP(S[-1,:])
- optionContract.put.price = df*sum(VP)/NP
- else:
- #print 'euro pricing'
- df = np.exp(-rate*T)
- VC = optionContract.expiC(SEuro[:])
- optionContract.call.price = df*sum(VC)/NP
- VP = optionContract.expiP(SEuro[:])
- optionContract.put.price = df*sum(VP)/NP
- @staticmethod
- def MCSolo(pricingParameters, optionContract, depth, NP, ap, mm, stepAdapt,american,option,payoff,early):
- #parameters
- S0 = pricingParameters._S0
- r2 = pricingParameters._r2
- T = pricingParameters._T
- sigma = optionContract._Sigma
- divi = pricingParameters._div
- NTS=2**depth
- dt=float(T)/NTS
- np.random.seed
- dividends,dividendsStep=PricingParameters.preparedivs(divi,T,dt)
- r,rArray,rateChangeStep,DFArray=PricingParameters.preparerates(r2,T,dt,NTS)
- #we directly compute the final value of the brownian motion
- ran=np.asarray(MCPricer.SobolRNG(NP, ap, mm))*sigma*np.sqrt(T)
- #if we get the proper rate, we can directly get the european value
- rate=pricingParameters._r
- # generate stock price paths
- SEuro= np.zeros ((NP),'d') # index level matrix
- rr=r[1][1]
- SEuro[:]=(S0)*np.exp((rate-0.5*(sigma**2))* T+ ran[:])
- if (american or pricingParameters._pvdividends>0):
- # generate stock price paths
- S = np.zeros ((NTS,NP),'d') # index level matrix
- S[0 ,:] = S0 # initial values
- #we then proceed to generate intermediate values
- ranBB = (np.concatenate(([ran-ran],[ran])))
- ranBB = MCPricer.Bridge(ranBB,1,depth+1,ranBB.shape[1],sigma*sigma*T,ap,mm)
- incBB = ranBB[1:][:]-ranBB[:-1][:]
- for t in range (1,NTS): # loop over the days
- if (t in rateChangeStep[:]):
- rr = (r[1][np.nonzero(rateChangeStep==(t))[0]+1])
- S[t ,:] = S[t-1 ,:]* np.exp ((rr-0.5*sigma **2)* dt+incBB[t][:])
- if (t in dividendsStep):
- div = dividends[1][np.nonzero(dividendsStep==(t))[0]]
- S[t,:] = np.maximum(S[t,:]-div,0)
- if american:
- #apply LMS method
- VC = early(0.0)
- hC = VC
- for t in range(NTS-2,-1,-1):
- df = DFArray[t]
- erg = MCPricer.LSM(hC,VC,S,NP,df,t)
- VC[t,:] = np.where(hC[t,:]>erg,hC[t,:],VC[t+1,:]*df)
- option.price = sum(VC[0,:])/NP
- else:
- df = np.exp(-rate*T)
- VC = payoff(S[-1,:])
- option.price = df*sum(VC)/NP
- else:
- #print 'euro pricing'
- df = np.exp(-rate*T)
- VC = payoff(SEuro[:])
- option.price = df*sum(VC)/NP
- def __init__(self):
- self.SetPricerId("MC")
- super(MCPricer, self).__init__()
- def SetPricerParameters(self, NP, depth = 1, ap = False, mm = False, StepAdapt = False):
- self._StepAdapt = StepAdapt
- self._AP=ap
- self._MM=mm
- self._NumberPath=NP
- self._Depth=depth
- self._NumberSteps=2**depth
- def PrintSelf(self):
- print self._pricerId
- print "Number of time steps: ", self._NumberSteps
- print "Number of path: ", self._NumberPath
- print "StepAdapt on: ", self._StepAdapt
- print "MM on: ", self._MM
- print "AP on: ", self._AP
- self._PricingParameters.printSelf()
- for optionContract in self._contractList:
- optionContract.printSelf()
- def CalculateContract(self, optionContract):
- # Check input parameters
- if isinstance(optionContract, OptionContractUS):
- if isinstance(optionContract, OptionContractDuo):
- MCPricer.MCDuo(self._PricingParameters, optionContract, self._Depth, self._NumberPath, self._AP, self._MM, self._StepAdapt,True)
- elif isinstance(optionContract, OptionContractCall):
- MCPricer.MCSolo(self._PricingParameters, optionContract, self._Depth, self._NumberPath, self._AP, self._MM, self._StepAdapt,True, optionContract.call, optionContract.expiC, optionContract.earlyC)
- elif isinstance(optionContract, OptionContractPut):
- MCPricer.MCSolo(self._PricingParameters, optionContract, self._Depth, self._NumberPath, self._AP, self._MM, self._StepAdapt,True, optionContract.put, optionContract.expiP, optionContract.earlyP)
- else:
- if isinstance(optionContract, OptionContractDuo):
- MCPricer.MCDuo(self._PricingParameters, optionContract, self._Depth, self._NumberPath, self._AP, self._MM, self._StepAdapt,False)
- elif isinstance(optionContract, OptionContractCall):
- MCPricer.MCSolo(self._PricingParameters, optionContract, self._Depth, self._NumberPath, self._AP, self._MM, self._StepAdapt,False, optionContract.call, optionContract.expiC,"")
- elif isinstance(optionContract, OptionContractPut):
- MCPricer.MCSolo(self._PricingParameters, optionContract, self._Depth, self._NumberPath, self._AP, self._MM, self._StepAdapt,False, optionContract.put, optionContract.expiP,"")
- else:
- raise PricerError("MCPricer", "Contract not yet supported by pricing algorithm")
Aucun commentaire:
Enregistrer un commentaire