├── README ├── mibian ├── __init__.py └── __init__.py.bak ├── performanceTests.py ├── setup.py └── tests.py /README: -------------------------------------------------------------------------------- 1 | 2 | MibianLib - Options Pricing Open Source Library - http://code.mibian.net/ 3 | Copyright (C) 2011 Yassine Maaroufi - 4 | Distributed under GPLv3 - http://www.gnu.org/copyleft/gpl.html 5 | 6 | 7 | 8 | Documentation 9 | ------------- 10 | BS - Black-Scholes Used for pricing European options on stocks without dividends 11 | BS([underlyingPrice, strikePrice, interestRate, daysToExpiration], volatility=x, callPrice=y, putPrice=z) 12 | 13 | eg: 14 | c = mibian.BS([1.4565, 1.45, 1, 30], volatility=20) 15 | c.callPrice Returns the call price 16 | c.putPrice Returns the put price 17 | c.callDelta Returns the call delta 18 | c.putDelta Returns the put delta 19 | c.callDelta2 Returns the call dual delta 20 | c.putDelta2 Returns the put dual delta 21 | c.callTheta Returns the call theta 22 | c.putTheta Returns the put theta 23 | c.callRho Returns the call rho 24 | c.putRho Returns the put rho 25 | c.vega Returns the option vega 26 | c.gamma Returns the option gamma 27 | 28 | c = mibian.BS([1.4565, 1.45, 1, 30], callPrice=0.0359) 29 | c.impliedVolatility Returns the implied volatility from the call price 30 | 31 | c = mibian.BS([1.4565, 1.45, 1, 30], putPrice=0.0306) 32 | c.impliedVolatility Returns the implied volatility from the put price 33 | 34 | c = mibian.BS([1.4565, 1.45, 1, 30], callPrice=0.0359, putPrice=0.0306) 35 | c.putCallParity Returns the put-call parity 36 | 37 | 38 | GK - Garman-Kohlhagen Used for pricing European options on currencies 39 | GK([underlyingPrice, strikePrice, domesticRate, foreignRate, daysToExpiration], volatility=x, callPrice=y, putPrice=z) 40 | 41 | eg: 42 | c = mibian.GK([1.4565, 1.45, 1, 2, 30], volatility=20) 43 | c.callPrice Returns the call price 44 | c.putPrice Returns the put price 45 | c.callDelta Returns the call delta 46 | c.putDelta Returns the put delta 47 | c.callDelta2 Returns the call dual delta 48 | c.putDelta2 Returns the put dual delta 49 | c.callTheta Returns the call theta 50 | c.putTheta Returns the put theta 51 | c.callRhoD Returns the call domestic rho 52 | c.putRhoD Returns the put domestic rho 53 | c.callRhoF Returns the call foreign rho 54 | c.putRhoF Returns the call foreign rho 55 | c.vega Returns the option vega 56 | c.gamma Returns the option gamma 57 | 58 | c = mibian.GK([1.4565, 1.45, 1, 2, 30], callPrice=0.0359) 59 | c.impliedVolatility Returns the implied volatility from the call price 60 | 61 | c = mibian.GK([1.4565, 1.45, 1, 2, 30], putPrice=0.03) 62 | c.impliedVolatility Returns the implied volatility from the put price 63 | 64 | c = mibian.GK([1.4565, 1.45, 1, 2, 30], callPrice=0.0359, putPrice=0.03) 65 | c.putCallParity Returns the put-call parity 66 | 67 | Me - Merton Used for pricing European options on stocks with dividends 68 | Me([underlyingPrice, strikePrice, interestRate, annualDividends, daysToExpiration], volatility=x, callPrice=y, putPrice=z) 69 | 70 | eg: 71 | c = mibian.Me([52, 50, 1, 1, 30], volatility=20) 72 | c.callPrice Returns the call price 73 | c.putPrice Returns the put price 74 | c.callDelta Returns the call delta 75 | c.putDelta Returns the put delta 76 | c.callDelta2 Returns the call dual delta 77 | c.putDelta2 Returns the put dual delta 78 | c.callTheta Returns the call theta 79 | c.putTheta Returns the put theta 80 | c.callRho Returns the call rho 81 | c.putRho Returns the put rho 82 | c.vega Returns the option vega 83 | c.gamma Returns the option gamma 84 | 85 | c = mibian.Me([52, 50, 1, 1, 30], callPrice=0.0359) 86 | c.impliedVolatility Returns the implied volatility from the call price 87 | 88 | c = mibian.Me([52, 50, 1, 1, 30], putPrice=0.0306) 89 | c.impliedVolatility Returns the implied volatility from the put price 90 | 91 | c = mibian.Me([52, 50, 1, 1, 30], callPrice=0.0359, putPrice=0.0306) 92 | c.putCallParity Returns the put-call parity 93 | 94 | 95 | 96 | Contributions: 97 | -------------- 98 | Contributions to MibianLib are welcome. Please send suggestions, critics, 99 | patches to yassinemaaroufi@mibian.net. Otherwise you can create a fork on 100 | github at https://github.com/yassinemaaroufi/MibianLib. 101 | 102 | 103 | 104 | Contributors List: 105 | ------------------ 106 | Yassine Maaroufi 107 | Jack Grahl 108 | Dmitry Vatolin 109 | https://github.com/smickles 110 | -------------------------------------------------------------------------------- /mibian/__init__.py: -------------------------------------------------------------------------------- 1 | ''' 2 | MibianLib - Options Pricing Open Source Library - http://code.mibian.net/ 3 | Copyright (C) 2011 Yassine Maaroufi - 4 | Distributed under GPLv3 - http://www.gnu.org/copyleft/gpl.html 5 | ''' 6 | 7 | from math import log, e 8 | try: 9 | from scipy.stats import norm 10 | except ImportError: 11 | print('Mibian requires scipy to work properly') 12 | 13 | # WARNING: All numbers should be floats -> x = 1.0 14 | 15 | def impliedVolatility(className, args, callPrice=None, putPrice=None, high=500.0, low=0.0): 16 | '''Returns the estimated implied volatility''' 17 | if callPrice: 18 | target = callPrice 19 | restimate = eval(className)(args, volatility=high, performance=True).callPrice 20 | if restimate < target: 21 | return high 22 | if args[0]>args[1] + callPrice: 23 | return 0.001 24 | if putPrice: 25 | target = putPrice 26 | restimate = eval(className)(args, volatility=high, performance=True).putPrice 27 | if restimate < target: 28 | return high 29 | if args[1]>args[0] + putPrice: 30 | return 0.001 31 | decimals = len(str(target).split('.')[1]) # Count decimals 32 | for i in range(10000): # To avoid infinite loops 33 | mid = (high + low) / 2 34 | if mid < 0.00001: 35 | mid = 0.00001 36 | if callPrice: 37 | estimate = eval(className)(args, volatility=mid, performance=True).callPrice 38 | if putPrice: 39 | estimate = eval(className)(args, volatility=mid, performance=True).putPrice 40 | if round(estimate, decimals) == target: 41 | break 42 | elif estimate > target: 43 | high = mid 44 | elif estimate < target: 45 | low = mid 46 | return mid 47 | 48 | class GK: 49 | '''Garman-Kohlhagen 50 | Used for pricing European options on currencies 51 | 52 | GK([underlyingPrice, strikePrice, domesticRate, foreignRate, \ 53 | daysToExpiration], volatility=x, callPrice=y, putPrice=z) 54 | 55 | eg: 56 | c = mibian.GK([1.4565, 1.45, 1, 2, 30], volatility=20) 57 | c.callPrice # Returns the call price 58 | c.putPrice # Returns the put price 59 | c.callDelta # Returns the call delta 60 | c.putDelta # Returns the put delta 61 | c.callDelta2 # Returns the call dual delta 62 | c.putDelta2 # Returns the put dual delta 63 | c.callTheta # Returns the call theta 64 | c.putTheta # Returns the put theta 65 | c.callRhoD # Returns the call domestic rho 66 | c.putRhoD # Returns the put domestic rho 67 | c.callRhoF # Returns the call foreign rho 68 | c.putRhoF # Returns the call foreign rho 69 | c.vega # Returns the option vega 70 | c.gamma # Returns the option gamma 71 | 72 | c = mibian.GK([1.4565, 1.45, 1, 2, 30], callPrice=0.0359) 73 | c.impliedVolatility # Returns the implied volatility from the call price 74 | 75 | c = mibian.GK([1.4565, 1.45, 1, 2, 30], putPrice=0.03) 76 | c.impliedVolatility # Returns the implied volatility from the put price 77 | 78 | c = mibian.GK([1.4565, 1.45, 1, 2, 30], callPrice=0.0359, putPrice=0.03) 79 | c.putCallParity # Returns the put-call parity 80 | ''' 81 | 82 | def __init__(self, args, volatility=None, callPrice=None, putPrice=None, \ 83 | performance=None): 84 | self.underlyingPrice = float(args[0]) 85 | self.strikePrice = float(args[1]) 86 | self.domesticRate = float(args[2]) / 100 87 | self.foreignRate = float(args[3]) / 100 88 | self.daysToExpiration = float(args[4]) / 365 89 | 90 | for i in ['callPrice', 'putPrice', 'callDelta', 'putDelta', \ 91 | 'callDelta2', 'putDelta2', 'callTheta', 'putTheta', \ 92 | 'callRhoD', 'putRhoD', 'callRhoF', 'callRhoF', 'vega', \ 93 | 'gamma', 'impliedVolatility', 'putCallParity']: 94 | self.__dict__[i] = None 95 | 96 | if volatility: 97 | self.volatility = float(volatility) / 100 98 | 99 | self._a_ = self.volatility * self.daysToExpiration**0.5 100 | self._d1_ = (log(self.underlyingPrice / self.strikePrice) + \ 101 | (self.domesticRate - self.foreignRate + \ 102 | (self.volatility**2)/2) * self.daysToExpiration) / self._a_ 103 | self._d2_ = self._d1_ - self._a_ 104 | # Reduces performance overhead when computing implied volatility 105 | if performance: 106 | [self.callPrice, self.putPrice] = self._price() 107 | else: 108 | [self.callPrice, self.putPrice] = self._price() 109 | [self.callDelta, self.putDelta] = self._delta() 110 | [self.callDelta2, self.putDelta2] = self._delta2() 111 | [self.callTheta, self.putTheta] = self._theta() 112 | [self.callRhoD, self.putRhoD] = self._rhod() 113 | [self.callRhoF, self.putRhoF] = self._rhof() 114 | self.vega = self._vega() 115 | self.gamma = self._gamma() 116 | self.exerciceProbability = norm.cdf(self._d2_) 117 | if callPrice: 118 | self.callPrice = round(float(callPrice), 6) 119 | self.impliedVolatility = impliedVolatility(\ 120 | self.__class__.__name__, args, callPrice=self.callPrice) 121 | if putPrice and not callPrice: 122 | self.putPrice = round(float(putPrice), 6) 123 | self.impliedVolatility = impliedVolatility(\ 124 | self.__class__.__name__, args, putPrice=self.putPrice) 125 | if callPrice and putPrice: 126 | self.callPrice = float(callPrice) 127 | self.putPrice = float(putPrice) 128 | self.putCallParity = self._parity() 129 | 130 | def _price(self): 131 | '''Returns the option price: [Call price, Put price]''' 132 | if self.volatility == 0 or self.daysToExpiration == 0: 133 | call = max(0.0, self.underlyingPrice - self.strikePrice) 134 | put = max(0.0, self.strikePrice - self.underlyingPrice) 135 | if self.strikePrice == 0: 136 | raise ZeroDivisionError('The strike price cannot be zero') 137 | else: 138 | call = e**(-self.foreignRate * self.daysToExpiration) * \ 139 | self.underlyingPrice * norm.cdf(self._d1_) - \ 140 | e**(-self.domesticRate * self.daysToExpiration) * \ 141 | self.strikePrice * norm.cdf(self._d2_) 142 | put = e**(-self.domesticRate * self.daysToExpiration) * \ 143 | self.strikePrice * norm.cdf(-self._d2_) - \ 144 | e**(-self.foreignRate * self.daysToExpiration) * \ 145 | self.underlyingPrice * norm.cdf(-self._d1_) 146 | return [call, put] 147 | 148 | def _delta(self): 149 | '''Returns the option delta: [Call delta, Put delta]''' 150 | if self.volatility == 0 or self.daysToExpiration == 0: 151 | call = 1.0 if self.underlyingPrice > self.strikePrice else 0.0 152 | put = -1.0 if self.underlyingPrice < self.strikePrice else 0.0 153 | if self.strikePrice == 0: 154 | raise ZeroDivisionError('The strike price cannot be zero') 155 | else: 156 | _b_ = e**-(self.foreignRate * self.daysToExpiration) 157 | call = norm.cdf(self._d1_) * _b_ 158 | put = -norm.cdf(-self._d1_) * _b_ 159 | return [call, put] 160 | 161 | def _delta2(self): 162 | '''Returns the dual delta: [Call dual delta, Put dual delta]''' 163 | if self.volatility == 0 or self.daysToExpiration == 0: 164 | call = -1.0 if self.underlyingPrice > self.strikePrice else 0.0 165 | put = 1.0 if self.underlyingPrice < self.strikePrice else 0.0 166 | if self.strikePrice == 0: 167 | raise ZeroDivisionError('The strike price cannot be zero') 168 | else: 169 | _b_ = e**-(self.domesticRate * self.daysToExpiration) 170 | call = -norm.cdf(self._d2_) * _b_ 171 | put = norm.cdf(-self._d2_) * _b_ 172 | return [call, put] 173 | 174 | def _vega(self): 175 | '''Returns the option vega''' 176 | if self.volatility == 0 or self.daysToExpiration == 0: 177 | return 0.0 178 | if self.strikePrice == 0: 179 | raise ZeroDivisionError('The strike price cannot be zero') 180 | else: 181 | return self.underlyingPrice * e**-(self.foreignRate * \ 182 | self.daysToExpiration) * norm.pdf(self._d1_) * \ 183 | self.daysToExpiration**0.5 184 | 185 | def _theta(self): 186 | '''Returns the option theta: [Call theta, Put theta]''' 187 | _b_ = e**-(self.foreignRate * self.daysToExpiration) 188 | call = -self.underlyingPrice * _b_ * norm.pdf(self._d1_) * \ 189 | self.volatility / (2 * self.daysToExpiration**0.5) + \ 190 | self.foreignRate * self.underlyingPrice * _b_ * \ 191 | norm.cdf(self._d1_) - self.domesticRate * self.strikePrice * \ 192 | _b_ * norm.cdf(self._d2_) 193 | put = -self.underlyingPrice * _b_ * norm.pdf(self._d1_) * \ 194 | self.volatility / (2 * self.daysToExpiration**0.5) - \ 195 | self.foreignRate * self.underlyingPrice * _b_ * \ 196 | norm.cdf(-self._d1_) + self.domesticRate * self.strikePrice * \ 197 | _b_ * norm.cdf(-self._d2_) 198 | return [call / 365, put / 365] 199 | 200 | def _rhod(self): 201 | '''Returns the option domestic rho: [Call rho, Put rho]''' 202 | call = self.strikePrice * self.daysToExpiration * \ 203 | e**(-self.domesticRate * self.daysToExpiration) * \ 204 | norm.cdf(self._d2_) / 100 205 | put = -self.strikePrice * self.daysToExpiration * \ 206 | e**(-self.domesticRate * self.daysToExpiration) * \ 207 | norm.cdf(-self._d2_) / 100 208 | return [call, put] 209 | 210 | def _rhof(self): 211 | '''Returns the option foreign rho: [Call rho, Put rho]''' 212 | call = -self.underlyingPrice * self.daysToExpiration * \ 213 | e**(-self.foreignRate * self.daysToExpiration) * \ 214 | norm.cdf(self._d1_) / 100 215 | put = self.underlyingPrice * self.daysToExpiration * \ 216 | e**(-self.foreignRate * self.daysToExpiration) * \ 217 | norm.cdf(-self._d1_) / 100 218 | return [call, put] 219 | 220 | def _gamma(self): 221 | '''Returns the option gamma''' 222 | return (norm.pdf(self._d1_) * e**-(self.foreignRate * \ 223 | self.daysToExpiration)) / (self.underlyingPrice * self._a_) 224 | 225 | def _parity(self): 226 | '''Returns the put-call parity''' 227 | return self.callPrice - self.putPrice - (self.underlyingPrice / \ 228 | ((1 + self.foreignRate)**self.daysToExpiration)) + \ 229 | (self.strikePrice / \ 230 | ((1 + self.domesticRate)**self.daysToExpiration)) 231 | 232 | class BS: 233 | '''Black-Scholes 234 | Used for pricing European options on stocks without dividends 235 | 236 | BS([underlyingPrice, strikePrice, interestRate, daysToExpiration], \ 237 | volatility=x, callPrice=y, putPrice=z) 238 | 239 | eg: 240 | c = mibian.BS([1.4565, 1.45, 1, 30], volatility=20) 241 | c.callPrice # Returns the call price 242 | c.putPrice # Returns the put price 243 | c.callDelta # Returns the call delta 244 | c.putDelta # Returns the put delta 245 | c.callDelta2 # Returns the call dual delta 246 | c.putDelta2 # Returns the put dual delta 247 | c.callTheta # Returns the call theta 248 | c.putTheta # Returns the put theta 249 | c.callRho # Returns the call rho 250 | c.putRho # Returns the put rho 251 | c.vega # Returns the option vega 252 | c.gamma # Returns the option gamma 253 | 254 | c = mibian.BS([1.4565, 1.45, 1, 30], callPrice=0.0359) 255 | c.impliedVolatility # Returns the implied volatility from the call price 256 | 257 | c = mibian.BS([1.4565, 1.45, 1, 30], putPrice=0.0306) 258 | c.impliedVolatility # Returns the implied volatility from the put price 259 | 260 | c = mibian.BS([1.4565, 1.45, 1, 30], callPrice=0.0359, putPrice=0.0306) 261 | c.putCallParity # Returns the put-call parity 262 | ''' 263 | 264 | def __init__(self, args, volatility=None, callPrice=None, putPrice=None, \ 265 | performance=None): 266 | self.underlyingPrice = float(args[0]) 267 | self.strikePrice = float(args[1]) 268 | self.interestRate = float(args[2]) / 100 269 | self.daysToExpiration = float(args[3]) / 365 270 | 271 | for i in ['callPrice', 'putPrice', 'callDelta', 'putDelta', \ 272 | 'callDelta2', 'putDelta2', 'callTheta', 'putTheta', \ 273 | 'callRho', 'putRho', 'vega', 'gamma', 'impliedVolatility', \ 274 | 'putCallParity']: 275 | self.__dict__[i] = None 276 | 277 | if volatility: 278 | self.volatility = float(volatility) / 100 279 | 280 | self._a_ = self.volatility * self.daysToExpiration**0.5 281 | self._d1_ = (log(self.underlyingPrice / self.strikePrice) + \ 282 | (self.interestRate + (self.volatility**2) / 2) * \ 283 | self.daysToExpiration) / self._a_ 284 | self._d2_ = self._d1_ - self._a_ 285 | if performance: 286 | [self.callPrice, self.putPrice] = self._price() 287 | else: 288 | [self.callPrice, self.putPrice] = self._price() 289 | [self.callDelta, self.putDelta] = self._delta() 290 | [self.callDelta2, self.putDelta2] = self._delta2() 291 | [self.callTheta, self.putTheta] = self._theta() 292 | [self.callRho, self.putRho] = self._rho() 293 | self.vega = self._vega() 294 | self.gamma = self._gamma() 295 | self.exerciceProbability = norm.cdf(self._d2_) 296 | if callPrice: 297 | self.callPrice = round(float(callPrice), 6) 298 | self.impliedVolatility = impliedVolatility(\ 299 | self.__class__.__name__, args, callPrice=self.callPrice) 300 | if putPrice and not callPrice: 301 | self.putPrice = round(float(putPrice), 6) 302 | self.impliedVolatility = impliedVolatility(\ 303 | self.__class__.__name__, args, putPrice=self.putPrice) 304 | if callPrice and putPrice: 305 | self.callPrice = float(callPrice) 306 | self.putPrice = float(putPrice) 307 | self.putCallParity = self._parity() 308 | 309 | def _price(self): 310 | '''Returns the option price: [Call price, Put price]''' 311 | if self.volatility == 0 or self.daysToExpiration == 0: 312 | call = max(0.0, self.underlyingPrice - self.strikePrice) 313 | put = max(0.0, self.strikePrice - self.underlyingPrice) 314 | if self.strikePrice == 0: 315 | raise ZeroDivisionError('The strike price cannot be zero') 316 | else: 317 | call = self.underlyingPrice * norm.cdf(self._d1_) - \ 318 | self.strikePrice * e**(-self.interestRate * \ 319 | self.daysToExpiration) * norm.cdf(self._d2_) 320 | put = self.strikePrice * e**(-self.interestRate * \ 321 | self.daysToExpiration) * norm.cdf(-self._d2_) - \ 322 | self.underlyingPrice * norm.cdf(-self._d1_) 323 | return [call, put] 324 | 325 | def _delta(self): 326 | '''Returns the option delta: [Call delta, Put delta]''' 327 | if self.volatility == 0 or self.daysToExpiration == 0: 328 | call = 1.0 if self.underlyingPrice > self.strikePrice else 0.0 329 | put = -1.0 if self.underlyingPrice < self.strikePrice else 0.0 330 | if self.strikePrice == 0: 331 | raise ZeroDivisionError('The strike price cannot be zero') 332 | else: 333 | call = norm.cdf(self._d1_) 334 | put = -norm.cdf(-self._d1_) 335 | return [call, put] 336 | 337 | def _delta2(self): 338 | '''Returns the dual delta: [Call dual delta, Put dual delta]''' 339 | if self.volatility == 0 or self.daysToExpiration == 0: 340 | call = -1.0 if self.underlyingPrice > self.strikePrice else 0.0 341 | put = 1.0 if self.underlyingPrice < self.strikePrice else 0.0 342 | if self.strikePrice == 0: 343 | raise ZeroDivisionError('The strike price cannot be zero') 344 | else: 345 | _b_ = e**-(self.interestRate * self.daysToExpiration) 346 | call = -norm.cdf(self._d2_) * _b_ 347 | put = norm.cdf(-self._d2_) * _b_ 348 | return [call, put] 349 | 350 | def _vega(self): 351 | '''Returns the option vega''' 352 | if self.volatility == 0 or self.daysToExpiration == 0: 353 | return 0.0 354 | if self.strikePrice == 0: 355 | raise ZeroDivisionError('The strike price cannot be zero') 356 | else: 357 | return self.underlyingPrice * norm.pdf(self._d1_) * \ 358 | self.daysToExpiration**0.5 / 100 359 | 360 | def _theta(self): 361 | '''Returns the option theta: [Call theta, Put theta]''' 362 | _b_ = e**-(self.interestRate * self.daysToExpiration) 363 | call = -self.underlyingPrice * norm.pdf(self._d1_) * self.volatility / \ 364 | (2 * self.daysToExpiration**0.5) - self.interestRate * \ 365 | self.strikePrice * _b_ * norm.cdf(self._d2_) 366 | put = -self.underlyingPrice * norm.pdf(self._d1_) * self.volatility / \ 367 | (2 * self.daysToExpiration**0.5) + self.interestRate * \ 368 | self.strikePrice * _b_ * norm.cdf(-self._d2_) 369 | return [call / 365, put / 365] 370 | 371 | def _rho(self): 372 | '''Returns the option rho: [Call rho, Put rho]''' 373 | _b_ = e**-(self.interestRate * self.daysToExpiration) 374 | call = self.strikePrice * self.daysToExpiration * _b_ * \ 375 | norm.cdf(self._d2_) / 100 376 | put = -self.strikePrice * self.daysToExpiration * _b_ * \ 377 | norm.cdf(-self._d2_) / 100 378 | return [call, put] 379 | 380 | def _gamma(self): 381 | '''Returns the option gamma''' 382 | return norm.pdf(self._d1_) / (self.underlyingPrice * self._a_) 383 | 384 | def _parity(self): 385 | '''Put-Call Parity''' 386 | return self.callPrice - self.putPrice - self.underlyingPrice + \ 387 | (self.strikePrice / \ 388 | ((1 + self.interestRate)**self.daysToExpiration)) 389 | 390 | class Me: 391 | '''Merton 392 | Used for pricing European options on stocks with dividends 393 | 394 | Me([underlyingPrice, strikePrice, interestRate, annualDividends, \ 395 | daysToExpiration], volatility=x, callPrice=y, putPrice=z) 396 | 397 | eg: 398 | c = mibian.Me([52, 50, 1, 1, 30], volatility=20) 399 | c.callPrice # Returns the call price 400 | c.putPrice # Returns the put price 401 | c.callDelta # Returns the call delta 402 | c.putDelta # Returns the put delta 403 | c.callDelta2 # Returns the call dual delta 404 | c.putDelta2 # Returns the put dual delta 405 | c.callTheta # Returns the call theta 406 | c.putTheta # Returns the put theta 407 | c.callRho # Returns the call rho 408 | c.putRho # Returns the put rho 409 | c.vega # Returns the option vega 410 | c.gamma # Returns the option gamma 411 | 412 | c = mibian.Me([52, 50, 1, 1, 30], callPrice=0.0359) 413 | c.impliedVolatility # Returns the implied volatility from the call price 414 | 415 | c = mibian.Me([52, 50, 1, 1, 30], putPrice=0.0306) 416 | c.impliedVolatility # Returns the implied volatility from the put price 417 | 418 | c = mibian.Me([52, 50, 1, 1, 30], callPrice=0.0359, putPrice=0.0306) 419 | c.putCallParity # Returns the put-call parity 420 | ''' 421 | 422 | def __init__(self, args, volatility=None, callPrice=None, putPrice=None, \ 423 | performance=None): 424 | self.underlyingPrice = float(args[0]) 425 | self.strikePrice = float(args[1]) 426 | self.interestRate = float(args[2]) / 100 427 | self.dividend = float(args[3]) 428 | self.dividendYield = self.dividend / self.underlyingPrice 429 | self.daysToExpiration = float(args[4]) / 365 430 | 431 | for i in ['callPrice', 'putPrice', 'callDelta', 'putDelta', \ 432 | 'callDelta2', 'putDelta2', 'callTheta', 'putTheta', \ 433 | 'callRho', 'putRho', 'vega', 'gamma', 'impliedVolatility', \ 434 | 'putCallParity']: 435 | self.__dict__[i] = None 436 | 437 | if volatility: 438 | self.volatility = float(volatility) / 100 439 | 440 | self._a_ = self.volatility * self.daysToExpiration**0.5 441 | self._d1_ = (log(self.underlyingPrice / self.strikePrice) + \ 442 | (self.interestRate - self.dividendYield + \ 443 | (self.volatility**2) / 2) * self.daysToExpiration) / \ 444 | self._a_ 445 | self._d2_ = self._d1_ - self._a_ 446 | if performance: 447 | [self.callPrice, self.putPrice] = self._price() 448 | else: 449 | [self.callPrice, self.putPrice] = self._price() 450 | [self.callDelta, self.putDelta] = self._delta() 451 | [self.callDelta2, self.putDelta2] = self._delta2() 452 | [self.callTheta, self.putTheta] = self._theta() 453 | [self.callRho, self.putRho] = self._rho() 454 | self.vega = self._vega() 455 | self.gamma = self._gamma() 456 | self.exerciceProbability = norm.cdf(self._d2_) 457 | if callPrice: 458 | self.callPrice = round(float(callPrice), 6) 459 | self.impliedVolatility = impliedVolatility(\ 460 | self.__class__.__name__, args, self.callPrice) 461 | if putPrice and not callPrice: 462 | self.putPrice = round(float(putPrice), 6) 463 | self.impliedVolatility = impliedVolatility(\ 464 | self.__class__.__name__, args, putPrice=self.putPrice) 465 | if callPrice and putPrice: 466 | self.callPrice = float(callPrice) 467 | self.putPrice = float(putPrice) 468 | self.putCallParity = self._parity() 469 | 470 | def _price(self): 471 | '''Returns the option price: [Call price, Put price]''' 472 | if self.volatility == 0 or self.daysToExpiration == 0: 473 | call = max(0.0, self.underlyingPrice - self.strikePrice) 474 | put = max(0.0, self.strikePrice - self.underlyingPrice) 475 | if self.strikePrice == 0: 476 | raise ZeroDivisionError('The strike price cannot be zero') 477 | else: 478 | call = self.underlyingPrice * e**(-self.dividendYield * \ 479 | self.daysToExpiration) * norm.cdf(self._d1_) - \ 480 | self.strikePrice * e**(-self.interestRate * \ 481 | self.daysToExpiration) * norm.cdf(self._d2_) 482 | put = self.strikePrice * e**(-self.interestRate * \ 483 | self.daysToExpiration) * norm.cdf(-self._d2_) - \ 484 | self.underlyingPrice * e**(-self.dividendYield * \ 485 | self.daysToExpiration) * norm.cdf(-self._d1_) 486 | return [call, put] 487 | 488 | def _delta(self): 489 | '''Returns the option delta: [Call delta, Put delta]''' 490 | if self.volatility == 0 or self.daysToExpiration == 0: 491 | call = 1.0 if self.underlyingPrice > self.strikePrice else 0.0 492 | put = -1.0 if self.underlyingPrice < self.strikePrice else 0.0 493 | if self.strikePrice == 0: 494 | raise ZeroDivisionError('The strike price cannot be zero') 495 | else: 496 | _b_ = e**(-self.dividendYield * self.daysToExpiration) 497 | call = _b_ * norm.cdf(self._d1_) 498 | put = _b_ * (norm.cdf(self._d1_) - 1) 499 | return [call, put] 500 | 501 | # Verify 502 | def _delta2(self): 503 | '''Returns the dual delta: [Call dual delta, Put dual delta]''' 504 | if self.volatility == 0 or self.daysToExpiration == 0: 505 | call = -1.0 if self.underlyingPrice > self.strikePrice else 0.0 506 | put = 1.0 if self.underlyingPrice < self.strikePrice else 0.0 507 | if self.strikePrice == 0: 508 | raise ZeroDivisionError('The strike price cannot be zero') 509 | else: 510 | _b_ = e**-(self.interestRate * self.daysToExpiration) 511 | call = -norm.cdf(self._d2_) * _b_ 512 | put = norm.cdf(-self._d2_) * _b_ 513 | return [call, put] 514 | 515 | def _vega(self): 516 | '''Returns the option vega''' 517 | if self.volatility == 0 or self.daysToExpiration == 0: 518 | return 0.0 519 | if self.strikePrice == 0: 520 | raise ZeroDivisionError('The strike price cannot be zero') 521 | else: 522 | return self.underlyingPrice * e**(-self.dividendYield * \ 523 | self.daysToExpiration) * norm.pdf(self._d1_) * \ 524 | self.daysToExpiration**0.5 / 100 525 | 526 | def _theta(self): 527 | '''Returns the option theta: [Call theta, Put theta]''' 528 | _b_ = e**-(self.interestRate * self.daysToExpiration) 529 | _d_ = e**(-self.dividendYield * self.daysToExpiration) 530 | call = -self.underlyingPrice * _d_ * norm.pdf(self._d1_) * \ 531 | self.volatility / (2 * self.daysToExpiration**0.5) + \ 532 | self.dividendYield * self.underlyingPrice * _d_ * \ 533 | norm.cdf(self._d1_) - self.interestRate * \ 534 | self.strikePrice * _b_ * norm.cdf(self._d2_) 535 | put = -self.underlyingPrice * _d_ * norm.pdf(self._d1_) * \ 536 | self.volatility / (2 * self.daysToExpiration**0.5) - \ 537 | self.dividendYield * self.underlyingPrice * _d_ * \ 538 | norm.cdf(-self._d1_) + self.interestRate * \ 539 | self.strikePrice * _b_ * norm.cdf(-self._d2_) 540 | return [call / 365, put / 365] 541 | 542 | def _rho(self): 543 | '''Returns the option rho: [Call rho, Put rho]''' 544 | _b_ = e**-(self.interestRate * self.daysToExpiration) 545 | call = self.strikePrice * self.daysToExpiration * _b_ * \ 546 | norm.cdf(self._d2_) / 100 547 | put = -self.strikePrice * self.daysToExpiration * _b_ * \ 548 | norm.cdf(-self._d2_) / 100 549 | return [call, put] 550 | 551 | def _gamma(self): 552 | '''Returns the option gamma''' 553 | return e**(-self.dividendYield * self.daysToExpiration) * \ 554 | norm.pdf(self._d1_) / (self.underlyingPrice * self._a_) 555 | 556 | # Verify 557 | def _parity(self): 558 | '''Put-Call Parity''' 559 | return self.callPrice - self.putPrice - self.underlyingPrice + \ 560 | (self.strikePrice / \ 561 | ((1 + self.interestRate)**self.daysToExpiration)) 562 | 563 | -------------------------------------------------------------------------------- /mibian/__init__.py.bak: -------------------------------------------------------------------------------- 1 | ''' 2 | MibianLib - Options Pricing Open Source Library - http://code.mibian.net/ 3 | Copyright (C) 2011 Yassine Maaroufi - 4 | Distributed under GPLv3 - http://www.gnu.org/copyleft/gpl.html 5 | ''' 6 | 7 | from math import log, e 8 | try: 9 | from scipy.stats import norm 10 | except ImportError: 11 | print 'Mibian requires scipy to work properly' 12 | 13 | # WARNING: All numbers should be floats -> x = 1.0 14 | 15 | def impliedVolatility(className, args, callPrice=None, putPrice=None, high=500.0, low=0.0): 16 | '''Returns the estimated implied volatility''' 17 | if callPrice: 18 | target = callPrice 19 | if putPrice: 20 | target = putPrice 21 | decimals = len(str(target).split('.')[1]) # Count decimals 22 | for i in range(10000): # To avoid infinite loops 23 | mid = (high + low) / 2 24 | if mid < 0.00001: 25 | mid = 0.00001 26 | if callPrice: 27 | estimate = eval(className)(args, volatility=mid, performance=True).callPrice 28 | if putPrice: 29 | estimate = eval(className)(args, volatility=mid, performance=True).putPrice 30 | if round(estimate, decimals) == target: 31 | break 32 | elif estimate > target: 33 | high = mid 34 | elif estimate < target: 35 | low = mid 36 | return mid 37 | 38 | class GK: 39 | '''Garman-Kohlhagen 40 | Used for pricing European options on currencies 41 | 42 | GK([underlyingPrice, strikePrice, domesticRate, foreignRate, \ 43 | daysToExpiration], volatility=x, callPrice=y, putPrice=z) 44 | 45 | eg: 46 | c = mibian.GK([1.4565, 1.45, 1, 2, 30], volatility=20) 47 | c.callPrice # Returns the call price 48 | c.putPrice # Returns the put price 49 | c.callDelta # Returns the call delta 50 | c.putDelta # Returns the put delta 51 | c.callDelta2 # Returns the call dual delta 52 | c.putDelta2 # Returns the put dual delta 53 | c.callTheta # Returns the call theta 54 | c.putTheta # Returns the put theta 55 | c.callRhoD # Returns the call domestic rho 56 | c.putRhoD # Returns the put domestic rho 57 | c.callRhoF # Returns the call foreign rho 58 | c.putRhoF # Returns the call foreign rho 59 | c.vega # Returns the option vega 60 | c.gamma # Returns the option gamma 61 | 62 | c = mibian.GK([1.4565, 1.45, 1, 2, 30], callPrice=0.0359) 63 | c.impliedVolatility # Returns the implied volatility from the call price 64 | 65 | c = mibian.GK([1.4565, 1.45, 1, 2, 30], putPrice=0.03) 66 | c.impliedVolatility # Returns the implied volatility from the put price 67 | 68 | c = mibian.GK([1.4565, 1.45, 1, 2, 30], callPrice=0.0359, putPrice=0.03) 69 | c.putCallParity # Returns the put-call parity 70 | ''' 71 | 72 | def __init__(self, args, volatility=None, callPrice=None, putPrice=None, \ 73 | performance=None): 74 | self.underlyingPrice = float(args[0]) 75 | self.strikePrice = float(args[1]) 76 | self.domesticRate = float(args[2]) / 100 77 | self.foreignRate = float(args[3]) / 100 78 | self.daysToExpiration = float(args[4]) / 365 79 | 80 | for i in ['callPrice', 'putPrice', 'callDelta', 'putDelta', \ 81 | 'callDelta2', 'putDelta2', 'callTheta', 'putTheta', \ 82 | 'callRhoD', 'putRhoD', 'callRhoF', 'callRhoF', 'vega', \ 83 | 'gamma', 'impliedVolatility', 'putCallParity']: 84 | self.__dict__[i] = None 85 | 86 | if volatility: 87 | self.volatility = float(volatility) / 100 88 | 89 | self._a_ = self.volatility * self.daysToExpiration**0.5 90 | self._d1_ = (log(self.underlyingPrice / self.strikePrice) + \ 91 | (self.domesticRate - self.foreignRate + \ 92 | (self.volatility**2)/2) * self.daysToExpiration) / self._a_ 93 | self._d2_ = self._d1_ - self._a_ 94 | # Reduces performance overhead when computing implied volatility 95 | if performance: 96 | [self.callPrice, self.putPrice] = self._price() 97 | else: 98 | [self.callPrice, self.putPrice] = self._price() 99 | [self.callDelta, self.putDelta] = self._delta() 100 | [self.callDelta2, self.putDelta2] = self._delta2() 101 | [self.callTheta, self.putTheta] = self._theta() 102 | [self.callRhoD, self.putRhoD] = self._rhod() 103 | [self.callRhoF, self.putRhoF] = self._rhof() 104 | self.vega = self._vega() 105 | self.gamma = self._gamma() 106 | self.exerciceProbability = norm.cdf(self._d2_) 107 | if callPrice: 108 | self.callPrice = round(float(callPrice), 6) 109 | self.impliedVolatility = impliedVolatility(\ 110 | self.__class__.__name__, args, callPrice=self.callPrice) 111 | if putPrice and not callPrice: 112 | self.putPrice = round(float(putPrice), 6) 113 | self.impliedVolatility = impliedVolatility(\ 114 | self.__class__.__name__, args, putPrice=self.putPrice) 115 | if callPrice and putPrice: 116 | self.callPrice = float(callPrice) 117 | self.putPrice = float(putPrice) 118 | self.putCallParity = self._parity() 119 | 120 | def _price(self): 121 | '''Returns the option price: [Call price, Put price]''' 122 | if self.volatility == 0 or self.daysToExpiration == 0: 123 | call = max(0.0, self.underlyingPrice - self.strikePrice) 124 | put = max(0.0, self.strikePrice - self.underlyingPrice) 125 | if self.strikePrice == 0: 126 | raise ZeroDivisionError('The strike price cannot be zero') 127 | else: 128 | call = e**(-self.foreignRate * self.daysToExpiration) * \ 129 | self.underlyingPrice * norm.cdf(self._d1_) - \ 130 | e**(-self.domesticRate * self.daysToExpiration) * \ 131 | self.strikePrice * norm.cdf(self._d2_) 132 | put = e**(-self.domesticRate * self.daysToExpiration) * \ 133 | self.strikePrice * norm.cdf(-self._d2_) - \ 134 | e**(-self.foreignRate * self.daysToExpiration) * \ 135 | self.underlyingPrice * norm.cdf(-self._d1_) 136 | return [call, put] 137 | 138 | def _delta(self): 139 | '''Returns the option delta: [Call delta, Put delta]''' 140 | if self.volatility == 0 or self.daysToExpiration == 0: 141 | call = 1.0 if self.underlyingPrice > self.strikePrice else 0.0 142 | put = -1.0 if self.underlyingPrice < self.strikePrice else 0.0 143 | if self.strikePrice == 0: 144 | raise ZeroDivisionError('The strike price cannot be zero') 145 | else: 146 | _b_ = e**-(self.foreignRate * self.daysToExpiration) 147 | call = norm.cdf(self._d1_) * _b_ 148 | put = -norm.cdf(-self._d1_) * _b_ 149 | return [call, put] 150 | 151 | def _delta2(self): 152 | '''Returns the dual delta: [Call dual delta, Put dual delta]''' 153 | if self.volatility == 0 or self.daysToExpiration == 0: 154 | call = -1.0 if self.underlyingPrice > self.strikePrice else 0.0 155 | put = 1.0 if self.underlyingPrice < self.strikePrice else 0.0 156 | if self.strikePrice == 0: 157 | raise ZeroDivisionError('The strike price cannot be zero') 158 | else: 159 | _b_ = e**-(self.domesticRate * self.daysToExpiration) 160 | call = -norm.cdf(self._d2_) * _b_ 161 | put = norm.cdf(-self._d2_) * _b_ 162 | return [call, put] 163 | 164 | def _vega(self): 165 | '''Returns the option vega''' 166 | if self.volatility == 0 or self.daysToExpiration == 0: 167 | return 0.0 168 | if self.strikePrice == 0: 169 | raise ZeroDivisionError('The strike price cannot be zero') 170 | else: 171 | return self.underlyingPrice * e**-(self.foreignRate * \ 172 | self.daysToExpiration) * norm.pdf(self._d1_) * \ 173 | self.daysToExpiration**0.5 174 | 175 | def _theta(self): 176 | '''Returns the option theta: [Call theta, Put theta]''' 177 | _b_ = e**-(self.foreignRate * self.daysToExpiration) 178 | call = -self.underlyingPrice * _b_ * norm.pdf(self._d1_) * \ 179 | self.volatility / (2 * self.daysToExpiration**0.5) + \ 180 | self.foreignRate * self.underlyingPrice * _b_ * \ 181 | norm.cdf(self._d1_) - self.domesticRate * self.strikePrice * \ 182 | _b_ * norm.cdf(self._d2_) 183 | put = -self.underlyingPrice * _b_ * norm.pdf(self._d1_) * \ 184 | self.volatility / (2 * self.daysToExpiration**0.5) - \ 185 | self.foreignRate * self.underlyingPrice * _b_ * \ 186 | norm.cdf(-self._d1_) + self.domesticRate * self.strikePrice * \ 187 | _b_ * norm.cdf(-self._d2_) 188 | return [call / 365, put / 365] 189 | 190 | def _rhod(self): 191 | '''Returns the option domestic rho: [Call rho, Put rho]''' 192 | call = self.strikePrice * self.daysToExpiration * \ 193 | e**(-self.domesticRate * self.daysToExpiration) * \ 194 | norm.cdf(self._d2_) / 100 195 | put = -self.strikePrice * self.daysToExpiration * \ 196 | e**(-self.domesticRate * self.daysToExpiration) * \ 197 | norm.cdf(-self._d2_) / 100 198 | return [call, put] 199 | 200 | def _rhof(self): 201 | '''Returns the option foreign rho: [Call rho, Put rho]''' 202 | call = -self.underlyingPrice * self.daysToExpiration * \ 203 | e**(-self.foreignRate * self.daysToExpiration) * \ 204 | norm.cdf(self._d1_) / 100 205 | put = self.underlyingPrice * self.daysToExpiration * \ 206 | e**(-self.foreignRate * self.daysToExpiration) * \ 207 | norm.cdf(-self._d1_) / 100 208 | return [call, put] 209 | 210 | def _gamma(self): 211 | '''Returns the option gamma''' 212 | return (norm.pdf(self._d1_) * e**-(self.foreignRate * \ 213 | self.daysToExpiration)) / (self.underlyingPrice * self._a_) 214 | 215 | def _parity(self): 216 | '''Returns the put-call parity''' 217 | return self.callPrice - self.putPrice - (self.underlyingPrice / \ 218 | ((1 + self.foreignRate)**self.daysToExpiration)) + \ 219 | (self.strikePrice / \ 220 | ((1 + self.domesticRate)**self.daysToExpiration)) 221 | 222 | class BS: 223 | '''Black-Scholes 224 | Used for pricing European options on stocks without dividends 225 | 226 | BS([underlyingPrice, strikePrice, interestRate, daysToExpiration], \ 227 | volatility=x, callPrice=y, putPrice=z) 228 | 229 | eg: 230 | c = mibian.BS([1.4565, 1.45, 1, 30], volatility=20) 231 | c.callPrice # Returns the call price 232 | c.putPrice # Returns the put price 233 | c.callDelta # Returns the call delta 234 | c.putDelta # Returns the put delta 235 | c.callDelta2 # Returns the call dual delta 236 | c.putDelta2 # Returns the put dual delta 237 | c.callTheta # Returns the call theta 238 | c.putTheta # Returns the put theta 239 | c.callRho # Returns the call rho 240 | c.putRho # Returns the put rho 241 | c.vega # Returns the option vega 242 | c.gamma # Returns the option gamma 243 | 244 | c = mibian.BS([1.4565, 1.45, 1, 30], callPrice=0.0359) 245 | c.impliedVolatility # Returns the implied volatility from the call price 246 | 247 | c = mibian.BS([1.4565, 1.45, 1, 30], putPrice=0.0306) 248 | c.impliedVolatility # Returns the implied volatility from the put price 249 | 250 | c = mibian.BS([1.4565, 1.45, 1, 30], callPrice=0.0359, putPrice=0.0306) 251 | c.putCallParity # Returns the put-call parity 252 | ''' 253 | 254 | def __init__(self, args, volatility=None, callPrice=None, putPrice=None, \ 255 | performance=None): 256 | self.underlyingPrice = float(args[0]) 257 | self.strikePrice = float(args[1]) 258 | self.interestRate = float(args[2]) / 100 259 | self.daysToExpiration = float(args[3]) / 365 260 | 261 | for i in ['callPrice', 'putPrice', 'callDelta', 'putDelta', \ 262 | 'callDelta2', 'putDelta2', 'callTheta', 'putTheta', \ 263 | 'callRho', 'putRho', 'vega', 'gamma', 'impliedVolatility', \ 264 | 'putCallParity']: 265 | self.__dict__[i] = None 266 | 267 | if volatility: 268 | self.volatility = float(volatility) / 100 269 | 270 | self._a_ = self.volatility * self.daysToExpiration**0.5 271 | self._d1_ = (log(self.underlyingPrice / self.strikePrice) + \ 272 | (self.interestRate + (self.volatility**2) / 2) * \ 273 | self.daysToExpiration) / self._a_ 274 | self._d2_ = self._d1_ - self._a_ 275 | if performance: 276 | [self.callPrice, self.putPrice] = self._price() 277 | else: 278 | [self.callPrice, self.putPrice] = self._price() 279 | [self.callDelta, self.putDelta] = self._delta() 280 | [self.callDelta2, self.putDelta2] = self._delta2() 281 | [self.callTheta, self.putTheta] = self._theta() 282 | [self.callRho, self.putRho] = self._rho() 283 | self.vega = self._vega() 284 | self.gamma = self._gamma() 285 | self.exerciceProbability = norm.cdf(self._d2_) 286 | if callPrice: 287 | self.callPrice = round(float(callPrice), 6) 288 | self.impliedVolatility = impliedVolatility(\ 289 | self.__class__.__name__, args, callPrice=self.callPrice) 290 | if putPrice and not callPrice: 291 | self.putPrice = round(float(putPrice), 6) 292 | self.impliedVolatility = impliedVolatility(\ 293 | self.__class__.__name__, args, putPrice=self.putPrice) 294 | if callPrice and putPrice: 295 | self.callPrice = float(callPrice) 296 | self.putPrice = float(putPrice) 297 | self.putCallParity = self._parity() 298 | 299 | def _price(self): 300 | '''Returns the option price: [Call price, Put price]''' 301 | if self.volatility == 0 or self.daysToExpiration == 0: 302 | call = max(0.0, self.underlyingPrice - self.strikePrice) 303 | put = max(0.0, self.strikePrice - self.underlyingPrice) 304 | if self.strikePrice == 0: 305 | raise ZeroDivisionError('The strike price cannot be zero') 306 | else: 307 | call = self.underlyingPrice * norm.cdf(self._d1_) - \ 308 | self.strikePrice * e**(-self.interestRate * \ 309 | self.daysToExpiration) * norm.cdf(self._d2_) 310 | put = self.strikePrice * e**(-self.interestRate * \ 311 | self.daysToExpiration) * norm.cdf(-self._d2_) - \ 312 | self.underlyingPrice * norm.cdf(-self._d1_) 313 | return [call, put] 314 | 315 | def _delta(self): 316 | '''Returns the option delta: [Call delta, Put delta]''' 317 | if self.volatility == 0 or self.daysToExpiration == 0: 318 | call = 1.0 if self.underlyingPrice > self.strikePrice else 0.0 319 | put = -1.0 if self.underlyingPrice < self.strikePrice else 0.0 320 | if self.strikePrice == 0: 321 | raise ZeroDivisionError('The strike price cannot be zero') 322 | else: 323 | call = norm.cdf(self._d1_) 324 | put = -norm.cdf(-self._d1_) 325 | return [call, put] 326 | 327 | def _delta2(self): 328 | '''Returns the dual delta: [Call dual delta, Put dual delta]''' 329 | if self.volatility == 0 or self.daysToExpiration == 0: 330 | call = -1.0 if self.underlyingPrice > self.strikePrice else 0.0 331 | put = 1.0 if self.underlyingPrice < self.strikePrice else 0.0 332 | if self.strikePrice == 0: 333 | raise ZeroDivisionError('The strike price cannot be zero') 334 | else: 335 | _b_ = e**-(self.interestRate * self.daysToExpiration) 336 | call = -norm.cdf(self._d2_) * _b_ 337 | put = norm.cdf(-self._d2_) * _b_ 338 | return [call, put] 339 | 340 | def _vega(self): 341 | '''Returns the option vega''' 342 | if self.volatility == 0 or self.daysToExpiration == 0: 343 | return 0.0 344 | if self.strikePrice == 0: 345 | raise ZeroDivisionError('The strike price cannot be zero') 346 | else: 347 | return self.underlyingPrice * norm.pdf(self._d1_) * \ 348 | self.daysToExpiration**0.5 / 100 349 | 350 | def _theta(self): 351 | '''Returns the option theta: [Call theta, Put theta]''' 352 | _b_ = e**-(self.interestRate * self.daysToExpiration) 353 | call = -self.underlyingPrice * norm.pdf(self._d1_) * self.volatility / \ 354 | (2 * self.daysToExpiration**0.5) - self.interestRate * \ 355 | self.strikePrice * _b_ * norm.cdf(self._d2_) 356 | put = -self.underlyingPrice * norm.pdf(self._d1_) * self.volatility / \ 357 | (2 * self.daysToExpiration**0.5) + self.interestRate * \ 358 | self.strikePrice * _b_ * norm.cdf(-self._d2_) 359 | return [call / 365, put / 365] 360 | 361 | def _rho(self): 362 | '''Returns the option rho: [Call rho, Put rho]''' 363 | _b_ = e**-(self.interestRate * self.daysToExpiration) 364 | call = self.strikePrice * self.daysToExpiration * _b_ * \ 365 | norm.cdf(self._d2_) / 100 366 | put = -self.strikePrice * self.daysToExpiration * _b_ * \ 367 | norm.cdf(-self._d2_) / 100 368 | return [call, put] 369 | 370 | def _gamma(self): 371 | '''Returns the option gamma''' 372 | return norm.pdf(self._d1_) / (self.underlyingPrice * self._a_) 373 | 374 | def _parity(self): 375 | '''Put-Call Parity''' 376 | return self.callPrice - self.putPrice - self.underlyingPrice + \ 377 | (self.strikePrice / \ 378 | ((1 + self.interestRate)**self.daysToExpiration)) 379 | 380 | class Me: 381 | '''Merton 382 | Used for pricing European options on stocks with dividends 383 | 384 | Me([underlyingPrice, strikePrice, interestRate, annualDividends, \ 385 | daysToExpiration], volatility=x, callPrice=y, putPrice=z) 386 | 387 | eg: 388 | c = mibian.Me([52, 50, 1, 1, 30], volatility=20) 389 | c.callPrice # Returns the call price 390 | c.putPrice # Returns the put price 391 | c.callDelta # Returns the call delta 392 | c.putDelta # Returns the put delta 393 | c.callDelta2 # Returns the call dual delta 394 | c.putDelta2 # Returns the put dual delta 395 | c.callTheta # Returns the call theta 396 | c.putTheta # Returns the put theta 397 | c.callRho # Returns the call rho 398 | c.putRho # Returns the put rho 399 | c.vega # Returns the option vega 400 | c.gamma # Returns the option gamma 401 | 402 | c = mibian.Me([52, 50, 1, 1, 30], callPrice=0.0359) 403 | c.impliedVolatility # Returns the implied volatility from the call price 404 | 405 | c = mibian.Me([52, 50, 1, 1, 30], putPrice=0.0306) 406 | c.impliedVolatility # Returns the implied volatility from the put price 407 | 408 | c = mibian.Me([52, 50, 1, 1, 30], callPrice=0.0359, putPrice=0.0306) 409 | c.putCallParity # Returns the put-call parity 410 | ''' 411 | 412 | def __init__(self, args, volatility=None, callPrice=None, putPrice=None, \ 413 | performance=None): 414 | self.underlyingPrice = float(args[0]) 415 | self.strikePrice = float(args[1]) 416 | self.interestRate = float(args[2]) / 100 417 | self.dividend = float(args[3]) 418 | self.dividendYield = self.dividend / self.underlyingPrice 419 | self.daysToExpiration = float(args[4]) / 365 420 | 421 | for i in ['callPrice', 'putPrice', 'callDelta', 'putDelta', \ 422 | 'callDelta2', 'putDelta2', 'callTheta', 'putTheta', \ 423 | 'callRho', 'putRho', 'vega', 'gamma', 'impliedVolatility', \ 424 | 'putCallParity']: 425 | self.__dict__[i] = None 426 | 427 | if volatility: 428 | self.volatility = float(volatility) / 100 429 | 430 | self._a_ = self.volatility * self.daysToExpiration**0.5 431 | self._d1_ = (log(self.underlyingPrice / self.strikePrice) + \ 432 | (self.interestRate - self.dividendYield + \ 433 | (self.volatility**2) / 2) * self.daysToExpiration) / \ 434 | self._a_ 435 | self._d2_ = self._d1_ - self._a_ 436 | if performance: 437 | [self.callPrice, self.putPrice] = self._price() 438 | else: 439 | [self.callPrice, self.putPrice] = self._price() 440 | [self.callDelta, self.putDelta] = self._delta() 441 | [self.callDelta2, self.putDelta2] = self._delta2() 442 | [self.callTheta, self.putTheta] = self._theta() 443 | [self.callRho, self.putRho] = self._rho() 444 | self.vega = self._vega() 445 | self.gamma = self._gamma() 446 | self.exerciceProbability = norm.cdf(self._d2_) 447 | if callPrice: 448 | self.callPrice = round(float(callPrice), 6) 449 | self.impliedVolatility = impliedVolatility(\ 450 | self.__class__.__name__, args, self.callPrice) 451 | if putPrice and not callPrice: 452 | self.putPrice = round(float(putPrice), 6) 453 | self.impliedVolatility = impliedVolatility(\ 454 | self.__class__.__name__, args, putPrice=self.putPrice) 455 | if callPrice and putPrice: 456 | self.callPrice = float(callPrice) 457 | self.putPrice = float(putPrice) 458 | self.putCallParity = self._parity() 459 | 460 | def _price(self): 461 | '''Returns the option price: [Call price, Put price]''' 462 | if self.volatility == 0 or self.daysToExpiration == 0: 463 | call = max(0.0, self.underlyingPrice - self.strikePrice) 464 | put = max(0.0, self.strikePrice - self.underlyingPrice) 465 | if self.strikePrice == 0: 466 | raise ZeroDivisionError('The strike price cannot be zero') 467 | else: 468 | call = self.underlyingPrice * e**(-self.dividendYield * \ 469 | self.daysToExpiration) * norm.cdf(self._d1_) - \ 470 | self.strikePrice * e**(-self.interestRate * \ 471 | self.daysToExpiration) * norm.cdf(self._d2_) 472 | put = self.strikePrice * e**(-self.interestRate * \ 473 | self.daysToExpiration) * norm.cdf(-self._d2_) - \ 474 | self.underlyingPrice * e**(-self.dividendYield * \ 475 | self.daysToExpiration) * norm.cdf(-self._d1_) 476 | return [call, put] 477 | 478 | def _delta(self): 479 | '''Returns the option delta: [Call delta, Put delta]''' 480 | if self.volatility == 0 or self.daysToExpiration == 0: 481 | call = 1.0 if self.underlyingPrice > self.strikePrice else 0.0 482 | put = -1.0 if self.underlyingPrice < self.strikePrice else 0.0 483 | if self.strikePrice == 0: 484 | raise ZeroDivisionError('The strike price cannot be zero') 485 | else: 486 | _b_ = e**(-self.dividendYield * self.daysToExpiration) 487 | call = _b_ * norm.cdf(self._d1_) 488 | put = _b_ * (norm.cdf(self._d1_) - 1) 489 | return [call, put] 490 | 491 | # Verify 492 | def _delta2(self): 493 | '''Returns the dual delta: [Call dual delta, Put dual delta]''' 494 | if self.volatility == 0 or self.daysToExpiration == 0: 495 | call = -1.0 if self.underlyingPrice > self.strikePrice else 0.0 496 | put = 1.0 if self.underlyingPrice < self.strikePrice else 0.0 497 | if self.strikePrice == 0: 498 | raise ZeroDivisionError('The strike price cannot be zero') 499 | else: 500 | _b_ = e**-(self.interestRate * self.daysToExpiration) 501 | call = -norm.cdf(self._d2_) * _b_ 502 | put = norm.cdf(-self._d2_) * _b_ 503 | return [call, put] 504 | 505 | def _vega(self): 506 | '''Returns the option vega''' 507 | if self.volatility == 0 or self.daysToExpiration == 0: 508 | return 0.0 509 | if self.strikePrice == 0: 510 | raise ZeroDivisionError('The strike price cannot be zero') 511 | else: 512 | return self.underlyingPrice * e**(-self.dividendYield * \ 513 | self.daysToExpiration) * norm.pdf(self._d1_) * \ 514 | self.daysToExpiration**0.5 / 100 515 | 516 | def _theta(self): 517 | '''Returns the option theta: [Call theta, Put theta]''' 518 | _b_ = e**-(self.interestRate * self.daysToExpiration) 519 | _d_ = e**(-self.dividendYield * self.daysToExpiration) 520 | call = -self.underlyingPrice * _d_ * norm.pdf(self._d1_) * \ 521 | self.volatility / (2 * self.daysToExpiration**0.5) + \ 522 | self.dividendYield * self.underlyingPrice * _d_ * \ 523 | norm.cdf(self._d1_) - self.interestRate * \ 524 | self.strikePrice * _b_ * norm.cdf(self._d2_) 525 | put = -self.underlyingPrice * _d_ * norm.pdf(self._d1_) * \ 526 | self.volatility / (2 * self.daysToExpiration**0.5) - \ 527 | self.dividendYield * self.underlyingPrice * _d_ * \ 528 | norm.cdf(-self._d1_) + self.interestRate * \ 529 | self.strikePrice * _b_ * norm.cdf(-self._d2_) 530 | return [call / 365, put / 365] 531 | 532 | def _rho(self): 533 | '''Returns the option rho: [Call rho, Put rho]''' 534 | _b_ = e**-(self.interestRate * self.daysToExpiration) 535 | call = self.strikePrice * self.daysToExpiration * _b_ * \ 536 | norm.cdf(self._d2_) / 100 537 | put = -self.strikePrice * self.daysToExpiration * _b_ * \ 538 | norm.cdf(-self._d2_) / 100 539 | return [call, put] 540 | 541 | def _gamma(self): 542 | '''Returns the option gamma''' 543 | return e**(-self.dividendYield * self.daysToExpiration) * \ 544 | norm.pdf(self._d1_) / (self.underlyingPrice * self._a_) 545 | 546 | # Verify 547 | def _parity(self): 548 | '''Put-Call Parity''' 549 | return self.callPrice - self.putPrice - self.underlyingPrice + \ 550 | (self.strikePrice / \ 551 | ((1 + self.interestRate)**self.daysToExpiration)) 552 | 553 | -------------------------------------------------------------------------------- /performanceTests.py: -------------------------------------------------------------------------------- 1 | ''' 2 | MibianLib - Options Pricing Open Source Library - http://code.mibian.net/ 3 | Copyright (C) 2011 Yassine Maaroufi - 4 | Distributed under GPLv3 - http://www.gnu.org/copyleft/gpl.html 5 | 6 | MibianLib Performance Tests 7 | ''' 8 | 9 | #from time import time 10 | from timeit import Timer 11 | #import mibian 12 | 13 | # Implied volatility performance 14 | print 'Implied volatility: ', 15 | #t = time() 16 | #test = mibian.GK([1.4565, 1.45, 1, 2, 30], callPrice=0.021) 17 | #print time() - t 18 | 19 | print Timer('mibian.GK([1.4565, 1.45, 1, 2, 30], callPrice=0.021)', 'import mibian').timeit(number=1) 20 | #print t.timeit(number=1) 21 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from distutils.core import setup 2 | from os import walk 3 | 4 | def getPackages(): 5 | return [a.replace('/','.') for a, b, c in walk('mibian')] 6 | 7 | setup( 8 | name='mibian', 9 | version='0.1.2', 10 | description='Options Pricing Library', 11 | long_description='MibianLib is an options pricing library implementing ' 12 | + 'the Garman-Kohlhagen, Black-Scholes and Merton pricing models ' 13 | + 'for European options on currencies and stocks.', 14 | author='Yassine Maaroufi', 15 | author_email='yassinemaaroufi@mibian.net', 16 | url='http://code.mibian.net/', 17 | license='GPL', 18 | packages=getPackages(), 19 | requires=['scipy'], 20 | classifiers = [ 21 | 'Intended Audience :: Education', 22 | 'Intended Audience :: Financial and Insurance Industry', 23 | 'License :: OSI Approved :: GNU General Public License (GPL)' 24 | ] 25 | ) 26 | -------------------------------------------------------------------------------- /tests.py: -------------------------------------------------------------------------------- 1 | ''' 2 | MibianLib - Options Pricing Open Source Library - http://code.mibian.net/ 3 | Copyright (C) 2011 Yassine Maaroufi - 4 | Distributed under GPLv3 - http://www.gnu.org/copyleft/gpl.html 5 | 6 | MibianLib Unit Tests 7 | ''' 8 | 9 | import unittest 10 | 11 | import mibian 12 | 13 | class UnitTesting(unittest.TestCase): 14 | '''Unit tests for MibianLib''' 15 | def testGK(self): 16 | '''Garman-Kohlhagen model tests''' 17 | test = mibian.GK([1.4565, 1.45, 1, 2, 30], volatility=20) 18 | self.assertEqual([test.callPrice, test.putPrice], [0.03591379198404554, 19 | 0.030614780580200285]) 20 | self.assertEqual([test.callDelta, test.putDelta], [0.53590471276326945, 21 | -0.46245280197803584]) 22 | self.assertEqual([test.callDelta2, test.putDelta2], 23 | [-0.51353891183148714, 0.48563950804221351]) 24 | self.assertEqual([test.callTheta, test.putTheta], [-0.00052962585114210519, 25 | -0.00056964220851379096]) 26 | self.assertEqual([test.callRhoD, test.putRhoD], [0.00061202582642930648, 27 | -0.00057877585205030923]) 28 | self.assertEqual([test.callRhoF, test.putRhoF], 29 | [-0.00064154401162167267, 0.00055361301869671985]) 30 | self.assertEqual(test.vega, 0.16560340559332973) 31 | self.assertEqual(test.gamma, 4.7488658326126272) 32 | 33 | test = mibian.GK([1.4565, 1.45, 1, 2, 30], callPrice=0.021) 34 | self.assertEqual(test.impliedVolatility, 10.7421875) 35 | 36 | test = mibian.GK([1.4565, 1.45, 1, 2, 30], putPrice=0.0306) 37 | self.assertEqual(test.impliedVolatility, 20.01953125) 38 | 39 | test = mibian.GK([1.4565, 1.45, 1, 2, 30], 40 | callPrice=0.036133685584059827, putPrice=0.030851333789832069) 41 | self.assertEqual(test.putCallParity, -3.433431599675352e-05) 42 | 43 | def testBS(self): 44 | '''Black-Scholes model tests''' 45 | test = mibian.BS([81, 80, 6, 60], volatility=30) 46 | self.assertEqual([test.callPrice, test.putPrice], [4.8422936422068901, 47 | 3.0571309465072147]) 48 | self.assertEqual([test.callDelta, test.putDelta], [0.5963986247019829, 49 | -0.4036013752980171]) 50 | self.assertEqual([test.callDelta2, test.putDelta2], 51 | [-0.54332493698317152, 0.4468605293205825]) 52 | self.assertEqual([test.callTheta, test.putTheta], [-0.038938157820841104, 53 | -0.025916540729723249]) 54 | self.assertEqual([test.callRho, test.putRho], [0.07145095061696502, 55 | -0.05876522029421359]) 56 | self.assertEqual(test.vega, 0.12717225103657845) 57 | self.assertEqual(test.gamma, 0.039304536595328565) 58 | 59 | test = mibian.BS([52, 60, 5, 30], callPrice=3) 60 | self.assertEqual(test.impliedVolatility, 95.703125) 61 | 62 | test = mibian.BS([52, 60, 5, 30], putPrice=7.86) 63 | self.assertEqual(test.impliedVolatility, 29.78515625) 64 | 65 | test = mibian.BS([81, 80, 6, 60], callPrice=4.8422936422068901, 66 | putPrice=3.0571309465072147) 67 | self.assertEqual(test.putCallParity, 0.02254482311879258) 68 | 69 | def testMe(self): 70 | '''Merton model tests''' 71 | test = mibian.Me([52, 50, 1, 1, 30], volatility=30) 72 | self.assertEqual([test.callPrice, test.putPrice], [2.8984579845404852, 73 | 0.93950583663422549]) 74 | self.assertEqual([test.callDelta, test.putDelta], [0.68691662012467536, 75 | -0.31150401721361859]) 76 | #self.assertEqual([test.callDelta2, test.putDelta2], 77 | # [-0.54332493698317152, 0.4468605293205825]) 78 | self.assertEqual([test.callTheta, test.putTheta], [-0.025346097581119695, 79 | -0.026712759026055157]) 80 | self.assertEqual([test.callRho, test.putRho], [0.026976333913925447, 81 | -0.014085792930199226]) 82 | self.assertEqual(test.vega, 0.052657699586925684) 83 | self.assertEqual(test.gamma, 0.07897789426868787) 84 | 85 | test = mibian.Me([52, 50, 1, 1, 30], callPrice=3) 86 | self.assertEqual(test.impliedVolatility, 31.25) 87 | 88 | test = mibian.Me([52, 50, 1, 1, 30], putPrice=0.84) 89 | self.assertEqual(test.impliedVolatility, 28.076171875) 90 | 91 | # test = mibian.Me([52, 50, 1, 1, 30], callPrice=4.8422936422068901, 92 | # putPrice=3.0571309465072147) 93 | # self.assertEqual(test.putCallParity, 0.02254482311879258) 94 | 95 | if __name__ == '__main__': 96 | unittest.main() 97 | 98 | --------------------------------------------------------------------------------