@@ -308,7 +308,7 @@ def price_barrier(self, strike, barrier, spot, texp, cp=1, io=-1):
308308 return p
309309
310310
311- class BsmDisp (smile .OptSmileABC ):
311+ class BsmDisp (smile .OptSmileABC , Bsm ):
312312 """
313313 Displaced Black-Scholes-Merton model for option pricing. Displace price,
314314
@@ -321,17 +321,19 @@ class BsmDisp(smile.OptSmileABC):
321321 >>> import pyfeng as pf
322322 >>> m = pf.BsmDisp(sigma=0.2, beta=0.5, pivot=100, intr=0.05, divr=0.1)
323323 >>> m.price(np.arange(80, 121, 10), 100, 1.2)
324- >>> sigma = np.array([0.2, 0.3, 0.5])[:, None]
325- >>> m = pf.BsmDisp(sigma, beta=0.5, pivot=100, intr=0.05, divr=0.1) # sigma in axis=0
326- >>> m.price(np.array([90, 100, 110]), 100, 1.2, cp=np.array([-1,1,1]))
327- array([[ 5.75927238, 5.52948546, 2.94558338],
328- [ 9.4592961 , 9.3881245 , 6.45745004],
329- [16.812035 , 17.10541288, 14.10354768]])
324+ array([15.9543935 , 9.7886658 , 5.4274197 , 2.71430505, 1.22740381])
325+ >>> beta = np.array([0.2, 0.6, 1])[:, None]
326+ >>> m = pf.BsmDisp(0.2, beta=beta, pivot=100) # beta in axis=0
327+ >>> m.vol_smile(np.arange(80, 121, 10), 100, 1.2)
328+ array([[0.21915778, 0.20904587, 0.20038559, 0.19286293, 0.18625174],
329+ [0.20977955, 0.20461468, 0.20025691, 0.19652101, 0.19327567],
330+ [0.2 , 0.2 , 0.2 , 0.2 , 0.2 ]])
330331 """
331332
332- pivot = None
333333 beta = 1 # equivalent to Black-Scholes
334- bsm_model = None
334+ pivot = 0
335+ sigma_disp = None
336+ #_m_bsm = None
335337
336338 def __init__ (self , sigma , beta = 1 , pivot = 0 , * args , ** kwargs ):
337339 """
@@ -343,53 +345,80 @@ def __init__(self, sigma, beta=1, pivot=0, *args, **kwargs):
343345 divr: dividend/convenience yield (foreign interest rate)
344346 is_fwd: if True, treat `spot` as forward price. False by default.
345347 """
346- super ().__init__ (sigma , * args , ** kwargs )
347348 self .pivot = pivot
348349 self .beta = beta
349- self .bsm_model = Bsm (sigma = beta * sigma )
350+ #self._m_bsm = Bsm(sigma=beta*sigma, *args, **kwargs)
351+ super ().__init__ (sigma , * args , ** kwargs )
350352
351- '''
352353 @property
353354 def sigma (self ):
354- return self.sigma
355+ return self .sigma_disp * self . beta
355356
356357 @sigma .setter
357358 def sigma (self , sigma ):
358- self.sigma = sigma
359- self.bsm_model.sigma = self.beta*sigma
360- '''
359+ self .sigma_disp = sigma
361360
362- def disp (self , x ):
363- return self .beta * x + (1 - self .beta )* self .pivot
361+ def disp_spot (self , spot ):
362+ """
363+ Displaced spot
364364
365- def price (self , strike , spot , * args , ** kwargs ):
366- return (1 / self .beta )* self .bsm_model .price (
367- self .disp (strike ), self .disp (spot ), * args , ** kwargs )
365+ Args:
366+ spot: spot (or forward) price
368367
369- def delta (self , strike , spot , * args , ** kwargs ):
370- return self .bsm_model .delta (
371- self .disp (strike ), self .disp (spot ), * args , ** kwargs )
368+ Returns:
369+ Displaces spot
370+ """
371+ return self .beta * spot + (1 - self .beta )* self .pivot
372372
373- def cdf (self , strike , spot , * args , ** kwargs ):
374- return self . bsm_model . cdf (
375- self . disp ( strike ), self . disp ( spot ), * args , ** kwargs )
373+ def disp_strike (self , strike , texp ):
374+ """
375+ Displaced strike
376376
377- def vega ( self , strike , spot , * args , ** kwargs ) :
378- return self . bsm_model . vega (
379- self . disp ( strike ), self . disp ( spot ), * args , ** kwargs )
377+ Args :
378+ strike: strike price
379+ texp: time to expiry
380380
381- def gamma (self , strike , spot , * args , ** kwargs ):
381+ Returns:
382+ Displace strike
383+ """
384+ return self .beta * strike + (1 - self .beta )* self .forward (self .pivot , texp )
385+
386+ def price (self , strike , spot , texp , cp = 1 ):
387+ spot = self .disp_spot (spot )
388+ strike = self .disp_strike (strike , texp )
389+ return (1 / self .beta ) * super ().price (strike , spot , texp , cp = cp )
390+
391+ def delta (self , strike , spot , texp , cp = 1 ):
392+ spot = self .disp_spot (spot )
393+ strike = self .disp_strike (strike , texp )
394+ return super ().delta (strike , spot , texp , cp = cp )
395+
396+ def cdf (self , strike , spot , texp , cp = 1 ):
397+ spot = self .disp_spot (spot )
398+ strike = self .disp_strike (strike , texp )
399+ return super ().cdf (strike , spot , texp , cp = cp )
400+
401+ def vega (self , strike , spot , texp , cp = 1 ):
402+ spot = self .disp_spot (spot )
403+ strike = self .disp_strike (strike , texp )
404+ return super ().vega (strike , spot , texp , cp = cp )
405+
406+ def gamma (self , strike , spot , texp , cp = 1 ):
382407 # need to mutiply beta because of (beta*sigma) appearing in the denominator of the bsm gamma
383- return self .beta * self .bsm_model .gamma (
384- self .disp (strike ), self .disp (spot ), * args , ** kwargs )
408+ spot = self .disp_spot (spot )
409+ strike = self .disp_strike (strike , texp )
410+ return self .beta * super ().gamma (strike , spot , texp , cp = cp )
385411
386- def theta (self , strike , spot , * args , ** kwargs ):
387- return (1 / self .beta )* self .bsm_model .theta (
388- self .disp (strike ), self .disp (spot ), * args , ** kwargs )
412+ def theta (self , strike , spot , texp , cp = 1 ):
413+ spot = self .disp_spot (spot )
414+ strike = self .disp_strike (strike , texp )
415+ return (1 / self .beta )* super ().theta (
416+ strike , spot , texp , cp = cp )
389417
390418 def impvol (self , price_in , strike , spot , texp , cp = 1 , setval = False ):
391- sigma = (1 / self .beta )* self .bsm_model .impvol (
392- self .beta * price_in , self .disp (strike ), self .disp (spot ), texp , cp = cp , setval = setval )
419+ spot = self .disp_spot (spot )
420+ strike = self .disp_strike (strike , texp )
421+ sigma = (1 / self .beta )* super ().impvol (self .beta * price_in , strike , spot , texp , cp = cp , setval = False )
393422 if setval :
394423 self .sigma = sigma
395424 return sigma
@@ -409,28 +438,29 @@ def vol_smile(self, strike, spot, texp, cp=1, model='bsm'):
409438 volatility smile under the specified model
410439 """
411440 if model .lower () == 'norm-approx' :
412- fwd , _ , _ = self ._fwd_factor ( spot , texp )
413- kkd = self .disp (strike ) / self . disp ( fwd )
441+ fwdd = self .forward ( self . disp_spot ( spot ) , texp )
442+ kkd = self .disp_strike (strike , texp ) / fwdd
414443 lnkd = np .log (kkd )
415- sig_beta = self .beta * self .sigma
416- vol = self .sigma * self .disp (fwd ) * np .sqrt (kkd ) * (1 + lnkd ** 2 / 24 ) / \
417- (1 + sig_beta ** 2 * texp / 24 )
444+ # self.sigma actually means self.beta * self._sigma
445+ vol = self .sigma_disp * fwdd * np .sqrt (kkd ) * (1 + lnkd ** 2 / 24 ) / (1 + self .sigma ** 2 * texp / 24 )
418446 elif model .lower () == 'bsm-approx' :
419- fwd , _ , _ = self ._fwd_factor (spot , texp )
447+ fwd = self .forward (spot , texp )
420448 kk = strike / fwd
421449 lnk = np .log (kk )
422- fwdd = self .disp (fwd )
423- kkd = self .disp (strike ) / fwdd
450+
451+ fwdd = self .forward (self .disp_spot (spot ), texp )
452+ kkd = self .disp_strike (strike , texp ) / fwdd
424453 lnkd = np .log (kkd )
425- sig_beta = self . beta * self . sigma
426- vol = self .sigma * ( fwdd / fwd ) * np . sqrt ( kkd / kk )
427- vol *= ( 1 + lnkd ** 2 / 24 ) / ( 1 + lnk ** 2 / 24 ) * ( 1 + vol ** 2 * texp / 24 ) / \
428- (1 + sig_beta ** 2 * texp / 24 )
454+
455+ # self.sigma actually means self.beta * self.sigma_disp
456+ vol = self . sigma_disp * ( fwdd / fwd ) * np . sqrt ( kkd / kk )
457+ vol *= ( 1 + lnkd ** 2 / 24 ) / (1 + lnk ** 2 / 24 ) * ( 1 + vol ** 2 * texp / 24 ) / ( 1 + self . sigma ** 2 * texp / 24 )
429458 else :
430459 vol = super ().vol_smile (strike , spot , texp , model = model , cp = cp )
431460
432461 return vol
433462
434- def price_barrier (self , strike , barrier , spot , * args , ** kwargs ):
435- return (1 / self .beta )* self .bsm_model .price_barrier (
436- self .disp (strike ), self .disp (barrier ), self .disp (spot ), * args , ** kwargs )
463+ def price_barrier (self , strike , barrier , spot , texp , cp = 1 , io = - 1 ):
464+ fwd = self .forward (spot , texp )
465+ return (1 / self .beta )* super ().price_barrier (
466+ self .disp_spot (strike ), self .disp_strike (barrier , strike ), self .disp_spot (fwd ), texp , cp = cp , io = io )
0 commit comments