diff options
Diffstat (limited to 'python/analytics/option.py')
| -rw-r--r-- | python/analytics/option.py | 130 |
1 files changed, 64 insertions, 66 deletions
diff --git a/python/analytics/option.py b/python/analytics/option.py index 4a61e90f..d3012470 100644 --- a/python/analytics/option.py +++ b/python/analytics/option.py @@ -78,7 +78,7 @@ def ATMstrike(index, exercise_date): b *= eta return brentq(closure, a, b) -class Swaption(ForwardIndex): +class BlackSwaption(ForwardIndex): """Swaption class""" def __init__(self, index, exercise_date, strike, option_type="payer"): ForwardIndex.__init__(self, index, exercise_date) @@ -143,68 +143,6 @@ class Swaption(ForwardIndex): @property @memoize def pv(self): - T = self.T - tilt = np.exp(-self.sigma**2/2 * T + self.sigma * self._Z * math.sqrt(T)) - args = (self.forward_pv, self.exercise_date, self.exercise_date_settle, - self.index, self._forward_yc, tilt, self._w) - eta = 1.05 - a = self.index.spread * 0.99 - b = a * eta - while True: - if calib(*((b,) + args)) > 0: - break - b *= eta - - S0 = brentq(calib, a, b, args) - if T == 0: - return self.notional * self.intrinsic - ## Zstar solves S_0 exp(-\sigma^2/2 * T + sigma * Z^\star\sqrt{T}) = strike - Zstar = (math.log(self._strike/S0) + self.sigma**2/2 * T) / \ - (self.sigma * math.sqrt(T)) - - if self.option_type == "payer": - Z = Zstar + np.logspace(0, math.log(4 / (self.sigma * math.sqrt(T)), 10), 300) - 1 - elif self.option_type == "receiver": - Z = Zstar - np.logspace(0, math.log(4 / (self.sigma * math.sqrt(T)), 10), 300) + 1 - else: - raise ValueError("option_type needs to be either 'payer' or 'receiver'") - S = S0 * np.exp(-self.sigma**2/2 * T + self.sigma * Z * math.sqrt(T)) - r = pv_vec(S * 1e-4, self._forward_yc, self.exercise_date, - self.exercise_date_settle, self.index.start_date, - self.index.end_date, self.index.recovery, self.index.fixed_rate * 1e-4) - val = (r - self._G) * 1/math.sqrt(2*math.pi) * np.exp(-Z**2/2) - return self.notional * simps(val, Z) * self.df - - @pv.setter - def pv(self, val): - if np.isnan(val): - raise ValueError("val is nan") - if val < self.intrinsic_value: - raise ValueError("{}: is less than intrinsic value: {}". - format(val, self.intrinsic_value)) - elif val == self.intrinsic_value: - self.sigma = 0 - return - def handle(x): - self.sigma = x - return self.pv - val - # use sigma_black as a starting point - self.pv_black = val - eta = 1.1 - a = self.sigma - while True: - if handle(a) < 0: - break - a /= eta - b = a * eta - while True: - if handle(b) > 0: - break - b *= eta - self.sigma = brentq(handle, a, b) - - @property - def pv_black(self): """compute pv using black-scholes formula""" if self.sigma == 0: return self.intrinsic_value @@ -215,13 +153,17 @@ class Swaption(ForwardIndex): self.T, self.sigma, self.option_type) * self.notional - @pv_black.setter - def pv_black(self, val): + @pv.setter + def pv(self, val): if np.isnan(val): raise ValueError("val is nan") if val < self.intrinsic_value: raise ValueError("{}: is less than intrinsic value: {}". format(val, self.intrinsic_value)) + elif val == self.intrinsic_value: + self.sigma = 0 + return + def handle(x): self.sigma = x return self.pv_black - val @@ -261,7 +203,7 @@ class Swaption(ForwardIndex): old_delta = self.delta self.index.spread -= 10 self._update() - gamma = abs(self.delta- old_delta) + gamma = abs(self.delta - old_delta) self.index.spread += 5 self._update() return gamma @@ -367,6 +309,62 @@ class Swaption(ForwardIndex): ] return "\n".join(s) +class Swaption(BlackSwaption): + @property + @memoize + def pv(self): + T = self.T + tilt = np.exp(-self.sigma**2/2 * T + self.sigma * self._Z * math.sqrt(T)) + args = (self.forward_pv, self.exercise_date, self.exercise_date_settle, + self.index, self._forward_yc, tilt, self._w) + eta = 1.05 + a = self.index.spread * 0.99 + b = a * eta + while True: + if calib(*((b,) + args)) > 0: + break + b *= eta + + S0 = brentq(calib, a, b, args) + if T == 0: + return self.notional * self.intrinsic + ## Zstar solves S_0 exp(-\sigma^2/2 * T + sigma * Z^\star\sqrt{T}) = strike + Zstar = (math.log(self._strike/S0) + self.sigma**2/2 * T) / \ + (self.sigma * math.sqrt(T)) + + if self.option_type == "payer": + Z = Zstar + np.logspace(0, math.log(4 / (self.sigma * math.sqrt(T)), 10), 300) - 1 + elif self.option_type == "receiver": + Z = Zstar - np.logspace(0, math.log(4 / (self.sigma * math.sqrt(T)), 10), 300) + 1 + else: + raise ValueError("option_type needs to be either 'payer' or 'receiver'") + S = S0 * np.exp(-self.sigma**2/2 * T + self.sigma * Z * math.sqrt(T)) + r = pv_vec(S * 1e-4, self._forward_yc, self.exercise_date, + self.exercise_date_settle, self.index.start_date, + self.index.end_date, self.index.recovery, self.index.fixed_rate * 1e-4) + val = (r - self._G) * 1/math.sqrt(2*math.pi) * np.exp(-Z**2/2) + return self.notional * simps(val, Z) * self.df + + @pv.setter + def pv(self, val): + # use sigma_black as a starting point + super().pv = val + def handle(x): + self.sigma = x + return self.pv - val + eta = 1.1 + a = self.sigma + while True: + if handle(a) < 0: + break + a /= eta + b = a * eta + while True: + if handle(b) > 0: + break + b *= eta + self.sigma = brentq(handle, a, b) + def compute_vol(option, strike, mid, pv_type): option.strike = strike try: |
