from . import dbconn from quantlib.indexes.api import UsdLiborSwapIsdaFixAm from quantlib.quotes import SimpleQuote from quantlib.time.api import Date, Period, Years, pydate_from_qldate from quantlib.instruments.api import MakeSwaption from quantlib.instruments.swap import SwapType from quantlib.pricingengines.api import BlackSwaptionEngine from scipy.optimize import brentq from yieldcurve import YC class IRSwaption: """ adapter class for the QuantLib code""" def __init__( self, swap_index, option_tenor, strike, option_type="payer", direction="Long", notional=10_000_000, yc=None, ): self._qloption = ( MakeSwaption(swap_index, option_tenor, strike) .with_nominal(notional) .with_underlying_type(SwapType[option_type.title()])() ) if type(direction) is bool: self._direction = 2 * direction - 1 else: self.direction = direction self._yc = yc or swap_index.forwarding_term_structure self._sigma = SimpleQuote(0.218) self._qloption.set_pricing_engine(BlackSwaptionEngine(self._yc, self._sigma)) @property def direction(self): if self._direction == 1.0: return "Long" else: return "Short" @direction.setter def direction(self, d): if d == "Long": self._direction = 1.0 elif d == "Short": self._direction = -1.0 else: raise ValueError("Direction needs to be either 'Long' or 'Short'") @property def pv(self): return self._direction * self._qloption.npv @pv.setter def pv(self, val): def handle(x): self.sigma = x return self._direction * (self.pv - val) eta = 1.1 a = 0.1 b = a * eta while True: if handle(b) > 0: break b *= eta self.sigma = brentq(handle, a, b) @property def sigma(self): return self._sigma.value @sigma.setter def sigma(self, s): self._sigma.value = s def from_tradeid(trade_id): with dbconn("dawndb") as conn: with conn.cursor() as c: c.execute("SELECT * from swaptions " "WHERE id = %s", (trade_id,)) rec = c.fetchone() yc = YC(evaluation_date=rec.trade_date, fixed=True, extrapolation=True) p = Period(int(rec.security_id.replace("USISDA", "")), Years) swap_index = UsdLiborSwapIsdaFixAm(p, yc) instance = IRSwaption( swap_index, Date.from_datetime(rec.expiration_date), rec.strike, rec.option_type, rec.buysell, rec.notional, ) instance.pv = rec.price / 100 * rec.notional * instance._direction return instance @property def value_date(self): return pydate_from_qldate(self._qloption.valuation_date) @value_date.setter def value_date(self, d): self.yc.link_to(YC(evaluation_date=d, fixed=True)) @property def strike(self): return self._qloption.underlying_swap().fixed_rate