├── .gitignore ├── bfc ├── BinomialTree.py ├── __init__.py ├── binomial.py ├── bs.py ├── pfv.py └── portf.py ├── hw1.py ├── hw2.py ├── hw3.py ├── hw4.py ├── hw5.py ├── hw6.py ├── hw9.py └── pdot.py /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | *.pyc 3 | -------------------------------------------------------------------------------- /bfc/BinomialTree.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Binomial Tree structure 3 | ======================================== 4 | ''' 5 | import numpy as np 6 | 7 | class BinomialTree(object): 8 | ''' Binomial Tree Structure 9 | ''' 10 | def __init__(self,n): 11 | ''' 12 | initialize the binomial tree 13 | 14 | :param n: number of levels ( equals time step+1) 15 | :returns: bt binomial tree 16 | ''' 17 | self.n = n 18 | self.bt = np.zeros(n*(n+1)/2) 19 | 20 | def getNode(self,i,j): 21 | ''' 22 | return the value of given node 23 | 24 | :param i: at level i 25 | :param j: at note j in i level 26 | ''' 27 | return self.bt[i*(i+1)/2+j] 28 | 29 | def setNode(self,i,j,v): 30 | ''' set the value of given node 31 | 32 | :param i: at level i 33 | :param j: at note j in i level 34 | :param v: the assigned value 35 | ''' 36 | self.bt[i*(i+1)/2+j] = v 37 | 38 | def showData(self,pfile,form='{0:f}',coef=1): 39 | ''' visualize the data 40 | 41 | :param pfile: opened file pointer 42 | :param form: set format of output 43 | :param coef: set the scale factor for output 44 | ''' 45 | pfile.write('digraph tree{\n size="10,10";\n ratio=compress;') 46 | pfile.write(" rankdir=LR; rotate=90\n") 47 | for i in range(self.n): 48 | for j in range(i+1): 49 | if i==0: 50 | line = ' node'+str(i)+str(j)+';\n' 51 | else: 52 | if j==0: 53 | line = ' node'+str(i-1)+str(j)+'-> node'+str(i)+str(j)+';\n' 54 | elif j==i: 55 | line = ' node'+str(i-1)+str(j-1)+'-> node'+str(i)+str(j)+';\n' 56 | elif j!=0 and j!=i: 57 | line = ' node'+str(i-1)+str(j)+'-> node'+str(i)+str(j)+';\n' 58 | line += ' node'+str(i-1)+str(j-1)+'-> node'+str(i)+str(j)+';\n' 59 | pfile.write(line) 60 | for i in range(self.n): 61 | levelline='' 62 | for j in range(i+1): 63 | line = ' node'+str(i)+str(j)+'[label = "'+form.format(self.getNode(i,j)*coef)+'"];\n' 64 | pfile.write(line) 65 | levelline += 'node'+str(i)+str(j)+' ' 66 | line = ' {rank=same; '+levelline+'}\n' 67 | pfile.write(line) 68 | pfile.write('}') 69 | 70 | -------------------------------------------------------------------------------- /bfc/__init__.py: -------------------------------------------------------------------------------- 1 | __all__ = ['pfv','binomial'] 2 | -------------------------------------------------------------------------------- /bfc/binomial.py: -------------------------------------------------------------------------------- 1 | ''' 2 | .. module:: bfc.binomial 3 | 4 | Option pricing 5 | --------------------- 6 | 7 | ''' 8 | 9 | class Binomial: 10 | ''' 11 | Binomial model for option pricing 12 | 13 | ''' 14 | def __init__(self): # create default model 15 | self.setup(100,3,1.07,0.01,100.,'european','call',0) 16 | 17 | def display(self): 18 | ''' display basic information 19 | ''' 20 | print "####################################" 21 | print "u/d: {0:4}/{1:4}".format(self.u,self.d) 22 | print "price/strike: {0:5}/{1:5}".format(self.price,self.strike) 23 | print "period: {0:d}".format(self.n) 24 | print "interest: {0:5}%".format((self.R-1)*100) 25 | print "coupon/dividend ratio {0:4}%".format(self.c*100) 26 | print self.type 27 | print "q: {0:6}".format(self.q) 28 | print "####################################" 29 | 30 | def setup(self,p,n,u,r,s,ae,cp,c): 31 | ''' set up the basic information for binomial model 32 | 33 | :param p: current price 34 | :param n: number of period 35 | :param u: price for up / current price 36 | :param r: cash interest rate 37 | :param s: strike price 38 | :param ae: option type "american" / "european" 39 | :param cp: option type "call" / "put" 40 | :param c: coupon rate 41 | ''' 42 | self.u=u 43 | self.d=1./u 44 | self.price=p 45 | self.n=n 46 | self.R=1+r 47 | self.strike=s 48 | self.c = c # coupon rate / dividend 49 | self.type=[ae,cp] 50 | self.checkArbitrage() 51 | self.setRNP() 52 | 53 | 54 | def calcOptionPrice(self): 55 | ''' driver to calculate the option price 56 | ''' 57 | self.display() 58 | if self.type[1] == 'call' and self.type[0]=='european': 59 | return self.calcCall() 60 | if self.type[1] == 'call' and self.type[0]=='american': 61 | return self.calcCall() 62 | if self.type[1] == 'put' and self.type[0]=='american': 63 | return self.calcAmericanPut() 64 | if self.type[1] == 'put' and self.type[0]=='european': 65 | return self.calcEuropeanPut() 66 | 67 | def checkArbitrage(self): 68 | ''' check up price and interest rate prevent arbitrage oppotunity 69 | ''' 70 | if self.u < 1.: 71 | print "!! no price up is wrong!!",self.u 72 | if self.u < self.R: 73 | print "!! arbitrage by short sell stock !!",self.u,self.R 74 | if self.d > self.R: 75 | print "!! arbitrage by borrow cash to buy !!",self.d,self.R 76 | 77 | def setRNP(self): 78 | ''' return risk neutral probabilities 79 | ''' 80 | self.checkArbitrage() 81 | self.q = (self.R-self.d-self.c)/(self.u-self.d) 82 | 83 | def setStockPrice(self,t): 84 | ''' return stock price at time t 85 | 86 | :param t: time step t 87 | ''' 88 | return np.array([(self.u)**i*(self.d)**(t-i)\ 89 | for i in range(t,-1,-1)])*self.price 90 | 91 | def calcCall(self): 92 | ''' calculate call option price 93 | the prices for american and european are same 94 | ''' 95 | Cf = self.setStockPrice(self.n)-self.strike 96 | Cf = np.maximum(Cf,np.zeros_like(Cf)) 97 | qv = [(self.q)**i*(1-self.q)**(self.n-i)*comb(self.n,i)\ 98 | for i in range(self.n,-1,-1)] 99 | return np.dot(Cf,qv)/self.R**self.n 100 | 101 | def calcEuropeanPut(self): 102 | Cf = self.strike-self.setStockPrice(self.n) 103 | Cf = np.maximum(Cf,np.zeros_like(Cf)) 104 | qv = [(self.q)**i*(1-self.q)**(self.n-i)*comb(self.n,i)\ 105 | for i in range(self.n,-1,-1)] 106 | return np.dot(Cf,qv)/self.R**self.n 107 | 108 | def calcAmericanPut(self): 109 | ''' return the American put option price 110 | ''' 111 | for i in range(self.n,0,-1): 112 | print "time step {0}/{1}".format(i,self.n) 113 | if i==self.n: 114 | Cf = self.strike-self.setStockPrice(i) 115 | Cf = np.maximum(Cf,np.zeros_like(Cf)) 116 | else: 117 | Cf = self.strike-self.setStockPrice(i) 118 | Cft = np.maximum(Cf,np.zeros_like(Cf)) 119 | Cf = np.maximum(Cft,pCf) 120 | if not (pCf == Cf).all(): 121 | print Cft 122 | print np.array(pCf) 123 | pCf = [onePeriodPrice(Cf[j],Cf[j+1],self.q,(self.R-1)) \ 124 | for j in range(0,i)] 125 | return max(self.strike-self.price,pCf[0]) 126 | 127 | def calcForwards(self): 128 | ''' return the forwards price 129 | ''' 130 | Cf = self.setStockPrice(self.n)-self.strike 131 | qv = [(self.q)**i*(1-self.q)**(self.n-i)*calcPerm(self.n,i)\ 132 | for i in range(self.n,-1,-1)] 133 | return np.dot(Cf,qv)/self.R**self.n 134 | 135 | def setFromBS(self,T,r,n,c,sigma,p,s,ae,cp): 136 | ''' set parameters by Black Scholes Model 137 | 138 | ''' 139 | self.R = exp(r*T/n) 140 | self.c = self.R-exp((r-c)*T/n) 141 | self.u = exp(sigma*sqrt(T/n)) 142 | self.d=1./self.u 143 | self.price = p 144 | self.n = n 145 | self.strike = s 146 | self.type=[ae,cp] 147 | self.checkArbitrage() 148 | self.setRNP() 149 | 150 | ''' os.system("dot -Tps "+filename+".dot -o temp.ps") 151 | os.system("ps2pdf temp.ps") 152 | os.system("mv temp.pdf "+filename+".pdf") 153 | ''' 154 | 155 | 156 | ''' 157 | 158 | Security pricing related 159 | ------------------------ 160 | ''' 161 | 162 | import bfc.BinomialTree as BT 163 | 164 | def setShortRateLattice(r0,u,d,n): 165 | ''' set short rate lattice 166 | 167 | :param r0: interest rate at time 0 168 | :param u: up move of interest rate 169 | :param d: down move of interest rate 170 | :param n: total period n 171 | 172 | ''' 173 | bt = BT.BinomialTree(n+1) 174 | for i in range(n+1): 175 | for j in range(i+1): 176 | bt.setNode(i,j,r0*u**(i-j)*d**j) 177 | return bt 178 | 179 | def setZCB(srl,p=100,qu=0.5,n=-1): 180 | ''' calculate the price structure of Zero-Coupon-Bond 181 | 182 | :param srl: short rate lattice 183 | :param p: strike price 184 | :param qu: up move probability 185 | :param n: expiration time 186 | :returns: the zero coupon bond lattice 187 | ''' 188 | if n == -1: 189 | n = srl.n 190 | elif n >= srl.n: 191 | print "! wrong expiration time n!" 192 | exit() 193 | else: 194 | n=n+1 195 | qd = 1-qu 196 | bt = BT.BinomialTree(n) 197 | # last time step 198 | for i in range(n): 199 | bt.setNode(n-1,i,p) 200 | for i in range(n-2,-1,-1): 201 | for j in range(i+1): 202 | bt.setNode(i,j,(qu*bt.getNode(i+1,j+1)+qd*bt.getNode(i+1,j))/(1+srl.getNode(i,j))) 203 | return bt 204 | 205 | def setCouponBond(srl,p=100,c=0.1,qu=0.5,n=-1): 206 | ''' calculate the price structure of Coupon-Bearing Bond 207 | 208 | :param srl: short rate lattice 209 | :param p: strike price 210 | :param c: coupon rate 211 | :param qu: up move probability 212 | :param n: expiration time 213 | :returns: the coupon-bearing bond lattice 214 | ''' 215 | if n == -1: 216 | n = srl.n 217 | elif n > srl.n: 218 | print "! wrong expiration time n!" 219 | exit() 220 | else: 221 | n=n+1 222 | qd = 1-qu 223 | bt = BT.BinomialTree(n) 224 | # last time step 225 | for i in range(n): 226 | bt.setNode(n-1,i,p+p*c) 227 | for i in range(n-2,-1,-1): 228 | for j in range(i+1): 229 | bt.setNode(i,j,(qu*bt.getNode(i+1,j+1)+qd*bt.getNode(i+1,j))/(1+srl.getNode(i,j))+p*c) 230 | bt.setNode(0,0,bt.getNode(0,0)-p*c) # correct last step - no coupon at time 0 231 | return bt 232 | 233 | def calcR(srl,n,qu=0.5): 234 | ''' calculate the cash account price for 1 dollar at time n 235 | 236 | :param srl: short rate lattice 237 | :param n: time n 238 | :returns: E[1/Bn] 239 | ''' 240 | n=n+1 241 | if n > srl.n: 242 | print "! wrong expiration time n!" 243 | exit() 244 | qd = 1-qu 245 | bt = BT.BinomialTree(n) 246 | # last time step 247 | for i in range(n): 248 | bt.setNode(n-1,i,1.) 249 | for i in range(n-2,-1,-1): 250 | for j in range(i+1): 251 | bt.setNode(i,j,(qu*bt.getNode(i+1,j+1)+qd*bt.getNode(i+1,j))/(1+srl.getNode(i,j))) 252 | return bt.getNode(0,0) 253 | 254 | def callBond(strike,expire,bond,srl,qu=0.5,outputfile=''): 255 | ''' calculate the Call option on the Binomial Bond 256 | 257 | :param strike: strike price 258 | :param expire: time for expiration 259 | :param zcb: underline zero coupon bond 260 | :param srl: underline short rate lattice 261 | ''' 262 | n = expire+1 263 | if n > srl.n or n > bond.n: 264 | print "! wrong expiration time n!" 265 | exit() 266 | qd = 1-qu 267 | bt = BT.BinomialTree(n) 268 | # last time step 269 | for i in range(n): 270 | bt.setNode(expire,i,max(0,bond.getNode(expire,i)-strike)) 271 | for i in range(n-2,-1,-1): 272 | for j in range(i+1): 273 | bt.setNode(i,j,(qu*bt.getNode(i+1,j+1)+qd*bt.getNode(i+1,j))/(1+srl.getNode(i,j))) 274 | if outputfile: 275 | with open(outputfile,'w') as f: 276 | bt.showData(f,form='{0:.2f}') 277 | return bt.getNode(0,0) 278 | 279 | def forwardBond(bond,expire,srl,qu=0.5,c=0.): 280 | ''' calculate the price of forward on Bond 281 | 282 | :param bond: underline bond 283 | :param expire: forward mature time 284 | :param srl: short rate lattice 285 | :param qu: up move probability 286 | :param c: coupon rate 287 | ''' 288 | n = expire+1 289 | if n > srl.n or n > bond.n: 290 | print "! wrong expiration time n!" 291 | exit() 292 | coupon = bond.getNode(bond.n-1,1) / (1+c) *c # calculate the coupon 293 | qd = 1-qu 294 | bt = BT.BinomialTree(n) 295 | # last time step 296 | for i in range(n): 297 | bt.setNode(expire,i,bond.getNode(expire,i)-coupon) # remove the coupon at expiration date 298 | for i in range(n-2,-1,-1): 299 | for j in range(i+1): 300 | bt.setNode(i,j,(qu*bt.getNode(i+1,j+1)+qd*bt.getNode(i+1,j))/(1+srl.getNode(i,j))) 301 | return bt.getNode(0,0)/calcR(srl,expire) 302 | 303 | 304 | def futureBond(bond,expire,srl,qu=0.5,c=0.): 305 | ''' calculate the price of forward on Bond 306 | 307 | :param bond: underline bond 308 | :param expire: forward mature time 309 | :param srl: short rate lattice 310 | :param qu: up move probability 311 | :param c: coupon rate 312 | ''' 313 | n = expire+1 314 | if n > srl.n or n > bond.n: 315 | print "! wrong expiration time n!" 316 | exit() 317 | coupon = bond.getNode(bond.n-1,1) / (1+c) *c # calculate the coupon 318 | qd = 1-qu 319 | bt = BT.BinomialTree(n) 320 | # last time step 321 | for i in range(n): 322 | bt.setNode(expire,i,bond.getNode(expire,i)-coupon) # remove the coupon at expiration date 323 | for i in range(n-2,-1,-1): 324 | for j in range(i+1): 325 | bt.setNode(i,j,(qu*bt.getNode(i+1,j+1)+qd*bt.getNode(i+1,j))) 326 | return bt.getNode(0,0) 327 | 328 | def bondlet(expire,srl,strike,qu=0.5,type='cap'): 329 | ''' calculate the price of caplet/floorlet 330 | 331 | :param expire: expire time (arrear settle) 332 | :param srl: short rate lattice 333 | :param strike: strike rate 334 | :param qu: up move probability 335 | :param type: cap/floor 336 | :returns: caplet price 337 | ''' 338 | if type == 'cap': 339 | coef=1 340 | else: # 'floor' 341 | coef=-1 342 | if expire > srl.n: 343 | print "! wrong expiration time n!" 344 | exit() 345 | n = expire 346 | qd = 1-qu 347 | bt = BT.BinomialTree(n) 348 | # last time step 349 | for i in range(n): 350 | bt.setNode(n-1,i,max(coef*(srl.getNode(n-1,i)-strike),0)/(1+srl.getNode(n-1,i))) 351 | for i in range(n-2,-1,-1): 352 | for j in range(i+1): 353 | bt.setNode(i,j,(qu*bt.getNode(i+1,j+1)+qd*bt.getNode(i+1,j))/(1+srl.getNode(i,j))) 354 | return bt.getNode(0,0) 355 | 356 | def swap(expire,srl,fr,qu=0.5,pay='fix',start=0): 357 | ''' calculate the price of Swaps 358 | 359 | :param expire: expire time (arrear settle) 360 | :param srl: short rate lattice 361 | :param fr: fix rate 362 | :param qu: up move probability 363 | :returns: swaps lattice 364 | ''' 365 | if expire >= srl.n: 366 | print "! wrong expiration time n!" 367 | exit() 368 | if pay == 'fix': 369 | coef = 1 370 | else: 371 | coef = -1 # pay float receive fix 372 | n = expire+1 373 | qd = 1-qu 374 | bt = BT.BinomialTree(n) 375 | # set initial payment 376 | for i in range(n): 377 | bt.setNode(n-1,i,(coef*(srl.getNode(n-1,i)-fr)/(1+srl.getNode(n-1,i)))) 378 | # construct backwards 379 | for i in range(n-2,start-1,-1): 380 | for j in range(i+1): 381 | bt.setNode(i,j,((coef*(srl.getNode(i,j)-fr)+qu*bt.getNode(i+1,j+1)+qd*bt.getNode(i+1,j))/(1+srl.getNode(i,j)))) 382 | for i in range(start-1,-1,-1): 383 | for j in range(i+1): 384 | bt.setNode(i,j,((qu*bt.getNode(i+1,j+1)+qd*bt.getNode(i+1,j))/(1+srl.getNode(i,j)))) 385 | return bt 386 | 387 | def swaption(swap,srl,strike,expire,qu=0.5): 388 | ''' calculate the price of Swaption 389 | 390 | :param swap: underline swap 391 | :param srl: short rate lattice 392 | :param strike: strike rate 393 | :param expire: expire time 394 | :param qu: up move probability 395 | :returns: swapion price at time 0 396 | ''' 397 | n = expire+1 398 | if n>swap.n or n>srl.n: 399 | print "! wrong expiration time n!" 400 | exit() 401 | qd = 1-qu 402 | bt = BT.BinomialTree(n) 403 | # last time step 404 | for i in range(n): 405 | bt.setNode(n-1,i,max(swap.getNode(n-1,i)-strike,0)) 406 | for i in range(n-2,-1,-1): 407 | for j in range(i+1): 408 | bt.setNode(i,j,(qu*bt.getNode(i+1,j+1)+qd*bt.getNode(i+1,j))/(1+srl.getNode(i,j))) 409 | return bt.getNode(0,0) 410 | 411 | 412 | def elementarySecurity(srl,qu=0.5): 413 | ''' build elementary security lattice 414 | 415 | :param srl: short rate lattice 416 | :returns: state price lattice 417 | ''' 418 | n = srl.n 419 | bt = BT.BinomialTree(n) 420 | qd = 1-qu 421 | bt.setNode(0,0,1) 422 | for i in range(1,n): 423 | # j=0 case 424 | bt.setNode(i,0,bt.getNode(i-1,0)/2./(1+srl.getNode(i-1,0))) 425 | for j in range(1,i): 426 | bt.setNode(i,j,0.5*(bt.getNode(i-1,j-1)/(1+srl.getNode(i-1,j-1))+bt.getNode(i-1,j)/(1+srl.getNode(i-1,j)))) 427 | # j=i case 428 | bt.setNode(i,i,0.5*(bt.getNode(i-1,i-1)/(1+srl.getNode(i-1,i-1)))) 429 | return bt 430 | 431 | 432 | 433 | 434 | if __name__=="__main__": 435 | import os 436 | a = setShortRateLattice(0.06,1.25,0.9,6) 437 | 438 | #b = setCouponBond(a,c=0.1) 439 | #print forwardBond(b,4,a,c=0.1) 440 | #print futureBond(b,4,a,c=0.1) 441 | #print bondlet(6,a,0.02,type='floor') 442 | c = swap(5,a,0.05) 443 | print c.getNode(0,0) 444 | #print swaption(c,a,0.,3) 445 | #callZCB(84,2,b,a,outputfile='test.dot') 446 | d=elementarySecurity(a) 447 | price = 0 448 | for i in range(2,4): 449 | s = [(0.07-a.getNode(i-1,j))/(1+a.getNode(i-1,j))*d.getNode(i-1,j) for j in range(i)] 450 | price += sum(s) 451 | print price*1000000 452 | #with open('test.dot','w') as f: 453 | #a.showData(f,form='{0:.2f}%',coef=100) 454 | #b.showData(f,form='{0:.2f}') 455 | ''' 456 | os.system("dot -Tps test.dot -o test.ps") 457 | os.system("ps2pdf test.ps") 458 | os.system("mv test.pdf test.pdf") 459 | ''' 460 | -------------------------------------------------------------------------------- /bfc/bs.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Black Scholes Model 3 | ------------------- 4 | ''' 5 | from math import erf,sqrt,log,exp,pi 6 | version = '1.0' 7 | 8 | def calcN(x): 9 | ''' return P(N(0,1)=1: 62 | print "!! q value is wrong !!" 63 | return (q*Cu+(1-q)*Cd)/(1+r) 64 | 65 | 66 | 67 | class cashflow: 68 | ''' Cash Flow Class 69 | ''' 70 | def __init__(self,n,r): 71 | ''' initialize a n-period cash flow with fixed interest rate r for each period 72 | ''' 73 | self.cf = np.zeros(n) 74 | self.r = r 75 | 76 | def pv(self): 77 | ''' return the present value of a cash flow 78 | ''' 79 | return np.sum([calcPV(c,i,self.r) for i,c in enumerate(self.cf)]) 80 | -------------------------------------------------------------------------------- /bfc/portf.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Portfolios related calculation 3 | ============================== 4 | ''' 5 | 6 | import numpy as np 7 | from math import * 8 | 9 | def var_risk_asset(r,mu,sig): 10 | ''' calculate the minimum variance portfolio 11 | 12 | minimize L = sum_i,sum_j(sig_ij*xi*xj) - v*[sum_i(mu_i*xi)-r) - u*[sum_i(xi)-1] 13 | --> 2 sum_j(sig_ij*xi)-v*mu_i-u=0 14 | 15 | :param r: required return rate 16 | :param mu: mean return for assets 17 | :type mu: numpy array 18 | :param sig: covariance matrix for assets 19 | :type sig: numpy matrix 20 | :returns: minimum variance portfolio 21 | ''' 22 | n = len(mu) 23 | A = np.zeros((n+2,n+2)) 24 | A[:n,:n]=sig*2 25 | A[:n,n]=0-mu 26 | A[n,:n]=mu 27 | A[n+1,:n]=1 28 | A[:n,n+1]=-1 29 | 30 | b = np.zeros(n+2) 31 | b[n:] = [r,1] 32 | 33 | x = np.array(np.dot(np.matrix(A).I,b)) 34 | x.shape=-1 35 | return x[:n] 36 | 37 | def var_riskfree_asset(mu,sig,rf): 38 | ''' calculate the max return portfolio with risk-free asset (rf) 39 | 40 | max rf+sum_i[(mu_i-rf)*xi]-tau*[sum_i,sum_j(sig_ij*xi*xj)] 41 | --> x = V^-1 mu' / 2tau 42 | V = sig, mu'=mu-rf 43 | 44 | :param mu: mean return for assets 45 | :type mu: numpy array 46 | :param sig: covariance matrix for assets 47 | :type sig: numpy matrix 48 | :param rf: risk-free return rate 49 | :returns: max return portfolio (with normalized to 1) 50 | ''' 51 | x = np.array(np.dot(sig.I,mu-rf)) 52 | x.shape=-1 53 | return x/np.sum(x) 54 | 55 | def min_var_risk_asset(mu,sig): 56 | ''' calculate the minimum variance of given asset 57 | min sum_i,sum_j(sig_ij*xi*xj) under sum_i xi=1 58 | sum_j (sig_ij*xj)-u=0 59 | sum_j xj = 1 60 | 61 | :param mu: mean return for assets 62 | :type mu: numpy array 63 | :param sig: covariance matrix for assets 64 | :type sig: numpy matrix 65 | ''' 66 | n = len(mu) 67 | A = np.zeros([n+1,n+1]) 68 | A[:n,:n] = sig 69 | A[:n,n] = -1 70 | A[n,:n] = 1 71 | b = np.zeros(n+1) 72 | b[n]=1 73 | x = np.array(np.dot(np.matrix(A).I,b)) 74 | x.shape=-1 75 | return x[:n] 76 | 77 | 78 | class portf: 79 | def __init__(self): 80 | x = [] # array 81 | sig = np.matrix([]) 82 | mu = [] 83 | rf = 0. 84 | 85 | def selfcheck(self): 86 | nx = len(self.x) 87 | if nx != len(self.mu) or (nx,nx != sig.shape): 88 | print "!the shape of portfolio's return/variance is wrong!" 89 | 90 | def calcReturn(self): 91 | ''' calculate the return of the portfolio 92 | ''' 93 | return np.sum(self.x*self.mu) 94 | 95 | def calcVolatility(self): 96 | ''' calculate the volatility of the portfolio 97 | ''' 98 | return sqrt(np.dot(self.x,np.dot(self.sig,self.x).T)[0,0]) 99 | -------------------------------------------------------------------------------- /hw1.py: -------------------------------------------------------------------------------- 1 | ''' 2 | homework 1 3 | ========== 4 | ''' 5 | def calcPV(p,n,r): 6 | return p*(1-r**(-n))/(1-1/r) 7 | 8 | def calcFV(p,n,r): 9 | return p*(r**n-1)/(r-1) 10 | 11 | def calcDis(r,t): 12 | return 1/r**t 13 | 14 | ''' 15 | Lottery payments 16 | 17 | A major lottery advertises that it pays the winner $10 million. However this prize money is paid at the rate of $500,000 each year (with the first payment being immediate) for a total of 20 payments. What is the present value of this prize at 10% interest compounded annually? Report your answer in millions, rounded to 2 decimal places. 18 | ''' 19 | def qs1(): 20 | print '%5.2f' % calcPV(0.5,20,1.1) 21 | 22 | 23 | ''' 24 | Sunk Costs (Exercise 2.6 in Luenberger): Part I 25 | 26 | Questions 2 and 3 are two parts of one problem. 27 | 28 | A young couple has already put down a deposit of the first month's rent (equal to $1,000) on a 6-month apartment lease, but have still not paid the first month's rent. The deposit is refundable at the end of six months if they take the apartment. The next day the couple finds a different apartment that they like just as well, but its monthly rent is only $900. And they would again have to put down a deposit of $900 refundable at the end of 6 months. The couple wants to decide whether to stay in the $1000 apartment or switch to the cheaper apartment and forego the deposit. They will do so by comparing the present value of the (future) cash flows associated with the two apartment leases. 29 | 30 | What is the present value of the (future) cash flows associated with the $1,000 apartment? Assume an interest rate of 12% per month compounded monthly. Round your answer to the nearest integer. 31 | 32 | Assume that the rent for each month is paid at the beginning of the month in advance, and the deposit is returned at the end of six months. Also, your answer should turn out to be a negative number since the rent payment is a cash outflow for the couple. 33 | ''' 34 | def qs2(): 35 | r = 1.12 36 | n = 6 37 | # case 1 38 | p = 1000. 39 | pv1 = calcPV(p,n,r)-p/r**n 40 | print '%5.1f' % -pv1 41 | ''' 42 | Sunk Costs (Exercise 2.6 in Luenberger): Part II 43 | 44 | Recall the situation described in Question 2 where a couple is deciding between a $1000 apartment and a $900 apartment. 45 | 46 | What is the present value of the cash flows associated with the $900 apartment? Assume an interest rate of 12% per month compounded monthly. Round your answer to the nearest integer. 47 | 48 | Assume that the rent for each month is paid at the beginning of the month in advance, and the deposit is returned at the end of six months. Also, your answer should turn out to be a negative number since the rent payment is a cash outflow for the couple. 49 | ''' 50 | def qs3(): 51 | r = 1.12 52 | n = 6 53 | # case 2 54 | p = 900. 55 | pv2 = p+calcPV(p,n,r)-p/r**n 56 | print '%5.1f' % -pv2 57 | 58 | ''' 59 | Relation between spot and discount rates 60 | 61 | Suppose the spot rates for 1 and 2 years are s1=6.3% and s2=6.9% with annual compounding. Recall that in this course interest rates are always quoted on an annual basis unless otherwise specified. What is the discount rate d(0,2)? 62 | ''' 63 | def qs4(): 64 | print '%5.3f' % (1/1.069**2) 65 | 66 | ''' 67 | Relation between spot and forward rates 68 | 69 | Suppose the spot rates for 1 and 2 years are s1=6.3% and s2=6.9% with annual compounding. Recall that in this course interest rates are always quoted on an annual basis unless otherwise specified. What is the forward rate, f1,2 assuming annual compounding? Round your answer to 3 decimal points (in decimal form, not in percentage). 70 | ''' 71 | def qs5(): 72 | print '%5.3f' % (1.069**2/1.063-1) 73 | 74 | ''' 75 | Forward contract on a stock 76 | 77 | The current price of a stock is $400 per share and it pays no dividends. Assuming a constant interest rate of 8% per year compounded quarterly, what is the stock's theoretical forward price for delivery in 9 months? Round your answer to 2 decimal points. 78 | ''' 79 | def qs6(): 80 | p = 400 81 | r = 1.02 82 | n = 3 83 | print '%6.2f' % (p*r**n) 84 | 85 | ''' 86 | Term structure of interest rates and swap valuation 87 | 88 | Suppose the current term structure of interest rates, assuming annual compounding, is as follows: 89 | 90 | s1 s2 s3 s4 s5 s6 91 | 7.0% 7.3% 7.7% 8.1% 8.4% 8.8% 92 | 93 | Recall that interest rates are always quoted on an annual basis unless stated otherwise. 94 | 95 | Suppose a 6-year swap with a notional principal of $10 million is being configured. What is the fixed rate of interest that will make the value of the swap equal to zero? Round your answer to 3 decimal points (in decimal form, not in percentage). 96 | ''' 97 | def qs7(): 98 | r=[7.,7.3,7.7,8.1,8.4,8.8] 99 | r = [1+i/100. for i in r] 100 | d = [calcDis(ir,i+1) for i,ir in enumerate(r)] 101 | x = (1-d[-1])/sum(d) 102 | print '%5.3f' % x 103 | 104 | if __name__=="__main__": 105 | qs1() 106 | qs2() 107 | qs3() 108 | qs4() 109 | qs5() 110 | qs6() 111 | qs7() 112 | -------------------------------------------------------------------------------- /hw2.py: -------------------------------------------------------------------------------- 1 | ''' 2 | homework 2 3 | ========== 4 | ''' 5 | from bfc import * 6 | 7 | def calcAmericanCall(sel): 8 | for i in range(sel.n,0,-1): 9 | print "time step {0}/{1}".format(i,sel.n) 10 | if i==sel.n: 11 | Cf = sel.setStockPrice(i)-sel.strike 12 | Cf = np.maximum(Cf,np.zeros_like(Cf)) 13 | else: 14 | Cf = sel.setStockPrice(i)-sel.strike 15 | Cft = np.maximum(Cf,np.zeros_like(Cf)) 16 | Cf = np.maximum(Cft,pCf) 17 | if (Cf-pCf<0.01).any(): 18 | print Cft 19 | print np.array(pCf) 20 | pCf = [onePeriodPrice(Cf[j],Cf[j+1],sel.q,(sel.R-1)) \ 21 | for j in range(0,i)] 22 | return max(sel.strike-sel.price,pCf[0]) 23 | 24 | if __name__ == '__main__': 25 | q = Binomial() 26 | 27 | q.setFromBS(0.25,0.02,15,0.01,0.3,100,110,'american','call') 28 | #print "Q1: option price: {0:4}".format(q.calcOptionPrice()) 29 | 30 | q2 = q 31 | q2.type[1]='put' 32 | print "Q2: option price: {0:4}".format(q2.calcOptionPrice()) 33 | 34 | q6 = q 35 | q6.setFromBS(0.25,0.02,15,0.01,0.3,100,110,'american','call') 36 | q6.n = 10 37 | print "Q6: option price: {0:4}".format(calcAmericanCall(q6)) 38 | 39 | q8a = Binomial() 40 | q8a.setFromBS(0.25,0.02,15,0.01,0.3,100,100,'european','call') 41 | q8b = Binomial() 42 | q8b.setFromBS(0.25,0.02,15,0.01,0.3,100,100,'european','put') 43 | 44 | Cfcall = q8a.setStockPrice(15)-q8a.strike 45 | Cfcall = np.maximum(Cfcall,np.zeros_like(Cfcall)) 46 | Cfput = q8b.strike - q8b.setStockPrice(15) 47 | Cfput = np.maximum(Cfput,np.zeros_like(Cfput)) 48 | for i in range(15,0,-1): 49 | print "time step {0}/15".format(i) 50 | if i > 10: 51 | Cfcall = [onePeriodPrice(Cfcall[j],Cfcall[j+1],q8a.q,(q8a.R-1)) \ 52 | for j in range(0,i)] 53 | Cfput = [onePeriodPrice(Cfput[j],Cfput[j+1],q8b.q,(q8b.R-1)) \ 54 | for j in range(0,i)] 55 | if i == 10: 56 | Cf = np.maximum(Cfcall,Cfput) 57 | print Cf 58 | if i <= 10: 59 | Cf = [onePeriodPrice(Cf[j],Cf[j+1],q8a.q,(q8a.R-1)) \ 60 | for j in range(0,i)] 61 | print Cf 62 | 63 | -------------------------------------------------------------------------------- /hw3.py: -------------------------------------------------------------------------------- 1 | ''' 2 | homework 3 3 | ========== 4 | ''' 5 | 6 | import numpy as np 7 | from bfc.portf import * 8 | 9 | np.set_printoptions(precision=4) 10 | 11 | mu = np.array([6,2,4])/100. 12 | V = np.matrix("8 -2 4; -2 2 -2; 4 -2 8")*1e-3 13 | rf = 0.01 14 | 15 | if __name__=="__main__": 16 | por = portf() 17 | por.mu = mu 18 | por.sig = V 19 | por.rf = rf 20 | 21 | # qs1 22 | por.x = np.array([1,1,1])/3. 23 | print por.calcReturn()*100 24 | 25 | # qs2 26 | print por.calcVolatility()*100 27 | 28 | # qs3 29 | por.x = min_var_risk_asset(por.mu,por.sig) 30 | print por.calcReturn()*100 31 | 32 | # qs4 33 | por.x = var_riskfree_asset(por.mu,por.sig,por.rf) 34 | mus =por.calcReturn()*100 35 | print mus 36 | 37 | # qs5 38 | sigs = por.calcVolatility()*100 39 | print sigs 40 | 41 | # qs6 42 | cml = (mus-por.rf*100)/sigs 43 | print cml 44 | 45 | # qs7 46 | req = cml*5+por.rf*100 47 | print req 48 | -------------------------------------------------------------------------------- /hw4.py: -------------------------------------------------------------------------------- 1 | from scipy.misc import comb 2 | import numpy as np 3 | 4 | def qs5(): 5 | n = 15 6 | r = 12 7 | p = 0.5 8 | px = [ comb(n,i)*p**i*(1-p)**(n-i) for i in range(r,n+1)] 9 | print np.sum(px) 10 | 11 | def qs6(): 12 | n = 15 13 | r = 14 14 | p = 0.5 15 | px = [comb(n,i)*p**i*(1-p)**(n-i) for i in range(r,n+1)] 16 | px = np.sum(px) 17 | print px 18 | pv = 1-(1-px)**100 19 | print pv 20 | 21 | if __name__=="__main__": 22 | qs5() 23 | 24 | -------------------------------------------------------------------------------- /hw5.py: -------------------------------------------------------------------------------- 1 | # solution for homework 5 2 | 3 | import bfc.binomial as bi 4 | 5 | if __name__ == "__main__": 6 | srl = bi.setShortRateLattice(0.05,1.1,0.9,10) 7 | zcb = bi.setZCB(srl) 8 | 9 | # question 1 10 | print zcb.getNode(0,0) 11 | 12 | # question 2 13 | print bi.forwardBond(zcb,4,srl) 14 | 15 | # question 3 16 | print bi.futureBond(zcb,4,srl) 17 | 18 | # question 4 19 | print bi.callBond(80,6,zcb,srl) 20 | 21 | # question 5 22 | d = bi.elementarySecurity(srl) 23 | price = 0 24 | for i in range(2,12): 25 | s = [(srl.getNode(i-1,j)-0.045)/(1+srl.getNode(i-1,j))*d.getNode(i-1,j) for j in range(i)] 26 | price += sum(s) 27 | print price*1000000 28 | 29 | # question 6 30 | e = bi.swap(10,srl,fr=0.045,start=1) 31 | print bi.swaption(e,srl,0,5)*1000000 32 | 33 | # another way 34 | import bfc.BinomialTree as BT 35 | n = srl.n 36 | expire = 5 37 | bt = BT.BinomialTree(n) 38 | qu = 0.5 39 | qd = 1-qu 40 | fr = 0.045 41 | bt.setNode(0,0,1) 42 | for i in range(1,n): 43 | # j=0 case 44 | bt.setNode(i,0,bt.getNode(i-1,0)/2./(1+srl.getNode(i-1,0))) 45 | for j in range(1,i): 46 | bt.setNode(i,j,0.5*(bt.getNode(i-1,j-1)/(1+srl.getNode(i-1,j-1))+bt.getNode(i-1,j)/(1+srl.getNode(i-1,j)))) 47 | # j=i case 48 | bt.setNode(i,i,0.5*(bt.getNode(i-1,i-1)/(1+srl.getNode(i-1,i-1)))) 49 | # check option excised 50 | if i == expire: 51 | for j in range(i+1): 52 | if srl.getNode(i,j) < fr: 53 | bt.setNode(i,j,0.) 54 | 55 | #pay = BT.BinomialTree(11) 56 | price = 0 57 | for i in range(expire,11): 58 | s = [(srl.getNode(i,j)-fr)/(1+srl.getNode(i,j))*bt.getNode(i,j) for j in range(i+1)] 59 | #for j in range(i+1): 60 | #pay.setNode(i,j,(srl.getNode(i,j)-0.045)/(1+srl.getNode(i,j))) #payment lattice 61 | price += sum(s) 62 | print price*1000000 63 | #with open('test.dot','w') as fi: 64 | #pay.showData(fi,form='{0:.2f}',coef=100) 65 | 66 | 67 | -------------------------------------------------------------------------------- /hw6.py: -------------------------------------------------------------------------------- 1 | import bfc.BinomialTree as BT 2 | import bfc.binomial as bino 3 | 4 | if __name__ == "__main__": 5 | n = 10 6 | u = 1.1 7 | d = 0.9 8 | r0 = 0.05 9 | a = 0.01 10 | b = 1.01 11 | rec = 0.2 12 | F = 100 13 | 14 | qu = 0.5 15 | qd = 1-0.5 16 | 17 | # set up short rate lattice 18 | rlat = bino.setShortRateLattice(0.05,1.1,0.9,n) 19 | 20 | # set up default probability 21 | hlat = BT.BinomialTree(n+1) 22 | for i in range(n+1): 23 | for j in range(i+1): 24 | hlat.setNode(i,j,a*b**(j-i/2.)) 25 | 26 | 27 | # set up price 28 | z = BT.BinomialTree(n+1) 29 | i = n 30 | for j in range(i+1): 31 | z.setNode(i,j,F) 32 | 33 | for i in range(n-1,-1,-1): 34 | for j in range(i+1): 35 | nondef = (1-hlat.getNode(i,j))*(qu*z.getNode(i+1,j+1)+qd*z.getNode(i+1,j)) 36 | defau = hlat.getNode(i,j)*F*rec 37 | z.setNode(i,j,(nondef+defau)/(1+rlat.getNode(i,j))) 38 | 39 | print z.getNode(0,0) 40 | 41 | with open('hlat.dot','w') as f: 42 | z.showData(f,form='{0:.2f}',coef=1) 43 | -------------------------------------------------------------------------------- /hw9.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | # default rate of individual portfolio 4 | ppd = np.array([0.2,0.2,0.06,0.3,0.4,0.65,0.3,0.23,0.02,0.12,0.134,0.21,0.08,0.1,0.1,0.02,0.3,0.015,0.2,0.03]) 5 | 6 | # probability with i credit default 7 | n = len(ppd) 8 | 9 | pd = np.zeros(n+1) 10 | pd[0] = 1-ppd[0] 11 | pd[1] = ppd[0] 12 | for i in range(1,n): 13 | for j in range(i+1,0,-1): 14 | pd[j] = pd[j-1]*ppd[i] + pd[j]*(1-ppd[i]) 15 | pd[0] = pd[0]*(1-ppd[i]) 16 | 17 | # question 1 18 | print "%.3f" % pd[3] 19 | 20 | # question 2 21 | meandefault = np.sum(np.arange(n+1)*pd) 22 | print "%.2f" % meandefault 23 | 24 | # question 3 25 | variancedefault = np.sum((np.arange(n+1)-meandefault)**2*pd) 26 | print "%.2f" % variancedefault 27 | 28 | # question 4 29 | loss = np.arange(n+1) 30 | id = np.where(loss > 2)[0] 31 | loss[id] = 2 32 | print "%.2f" % np.sum(loss*pd) 33 | 34 | # question 5 35 | loss = np.arange(n+1) 36 | id = np.where(loss > 4)[0] 37 | loss[id] = 4 38 | loss -= 2 39 | id = np.where(loss < 0)[0] 40 | loss[id] = 0 41 | print "%.2f" % np.sum(loss*pd) 42 | 43 | # question 6 44 | loss = np.arange(n+1) 45 | loss -= 4 46 | id = np.where(loss < 0)[0] 47 | loss[id] = 0 48 | print "%.2f" % np.sum(loss*pd) 49 | -------------------------------------------------------------------------------- /pdot.py: -------------------------------------------------------------------------------- 1 | ''' 2 | Visulization tools 3 | ================== 4 | ''' 5 | 6 | import sys 7 | import os 8 | import numpy as np 9 | 10 | def plotTree(filename,data): 11 | n = len(data) 12 | with open(filename+'.dot','w') as f: 13 | f.write("digraph tree{\n") 14 | f.write(" rankdir=LR;") 15 | for i in range(n): 16 | for j in range(i+1): 17 | if i==0: 18 | line = ' node'+str(i)+str(j)+';\n' 19 | else: 20 | if j==0: 21 | line = ' node'+str(i-1)+str(j)+'-> node'+str(i)+str(j)+';\n' 22 | if j==i: 23 | line = ' node'+str(i-1)+str(j-1)+'-> node'+str(i)+str(j)+';\n' 24 | if j!=0 and j!=i: 25 | line = ' node'+str(i-1)+str(j)+'-> node'+str(i)+str(j)+';\n' 26 | line += ' node'+str(i-1)+str(j-1)+'-> node'+str(i)+str(j)+';\n' 27 | f.write(line) 28 | for i in range(n): 29 | levelline='' 30 | for j in range(i+1): 31 | line = ' node'+str(i)+str(j)+'[label = "'+str(data[i,j])+'"];\n' 32 | f.write(line) 33 | levelline += 'node'+str(i)+str(j)+' ' 34 | line = ' {rank=same; '+levelline+'}\n' 35 | f.write(line) 36 | f.write('}') 37 | os.system("dot -Tps "+filename+".dot -o temp.ps") 38 | os.system("ps2pdf temp.ps") 39 | os.system("mv temp.pdf "+filename+".pdf") 40 | 41 | if __name__ == "__main__": 42 | data = np.random.randint(5,size=(4,4)) 43 | plotTree('test',data) 44 | 45 | 46 | --------------------------------------------------------------------------------