diff options
| -rw-r--r-- | python/swaption.py | 119 |
1 files changed, 102 insertions, 17 deletions
diff --git a/python/swaption.py b/python/swaption.py index 6a1174a7..305e7e51 100644 --- a/python/swaption.py +++ b/python/swaption.py @@ -1,9 +1,10 @@ from pyisda.legs import ContingentLeg, FeeLeg from pyisda.flat_hazard import strike_vec from pyisda.curve import YieldCurve, BadDay, SpreadCurve -from pyisda.utils import build_yc +from yieldcurve import YC, ql_to_jp, roll_yc, rate_helpers from pyisda.cdsone import upfront_charge - +from quantlib.settings import Settings +from quantlib.time.api import Date import array import math from scipy.optimize import brentq @@ -17,6 +18,8 @@ from db import dbconn from psycopg2 import DataError from dates import prev_immdate +from scipy.stats import norm +from termcolor import colored serenitasdb = dbconn('serenitasdb') class Index(): @@ -65,7 +68,7 @@ class Index(): self._default_leg = ContingentLeg(self.start_date, d, 1) self._end_date = d - def forward_pv(self, exercise_date): + def forward_annuity(self, exercise_date): step_in_date = exercise_date + datetime.timedelta(days=1) value_date = (pd.Timestamp(exercise_date) + 3* BDay()).date() a = self._fee_leg.pv(self.trade_date, step_in_date, self._value_date, @@ -76,14 +79,27 @@ class Index(): q = math.exp(-self.flat_hazard * year_frac(self._step_in_date, exercise_date)) else: q = 1 + return a - Delta * df * q + + def forward_pv(self, exercise_date): + """This is default adjusted forward price at time exercise_date""" + step_in_date = exercise_date + datetime.timedelta(days=1) + value_date = (pd.Timestamp(exercise_date) + 3* BDay()).date() + a = self._fee_leg.pv(self.trade_date, step_in_date, self._value_date, + self._yc, self._sc, False) + Delta = self._fee_leg.accrued(step_in_date) + df = self._yc.discount_factor(value_date) + if exercise_date > self.trade_date: + q = math.exp(-self.flat_hazard * (year_frac(self.trade_date, exercise_date)-0.5/365)) + else: + q = 1 clean_forward_annuity = a - Delta * df * q dl_pv = self._default_leg.pv( self.trade_date, step_in_date, self._value_date, self._yc, self._sc, self.recovery) forward_price = self.notional * (dl_pv - clean_forward_annuity * self.fixed_rate*1e-4) - fep = self.notional * (1 - self.recovery) * (1 - q) * df / \ - self._yc.discount_factor(self._value_date) - return forward_price + fep + fep = self.notional * (1 - self.recovery) * (1 - q) + return forward_price * self._yc.discount_factor(self._value_date) / df + fep @property def spread(self): @@ -100,7 +116,6 @@ class Index(): self._risky_annuity = self._fee_leg.pv(self.trade_date, self._step_in_date, self._value_date, self._yc, self._sc, False) - self._accrued = self._fee_leg.accrued(self._step_in_date) self._dl_pv = self._default_leg.pv( self.trade_date, self._step_in_date, self._value_date, self._yc, self._sc, self.recovery) @@ -113,12 +128,59 @@ class Index(): @property def pv(self): - return self.notional * (self._dl_pv - self._risky_annuity * self.fixed_rate*1e-4) + return self.notional * (self._dl_pv - self._risky_annuity * self.fixed_rate * 1e-4) + + @property + def accrued(self): + return - self.notional * self._accrued * self.fixed_rate * 1e-4 + + @property + def days_accrued(self): + return int(self._accrued * 360) @property def clean_pv(self): - accrued = self.notional * self._accrued * self.fixed_rate*1e-4 - return self.pv + accrued + return self.pv - self.accrued + + @property + def price(self): + return 100*(1-self.clean_pv/self.notional) + + @property + def DV01(self): + old_pv = self.pv + self.spread +=1 + dv01 = self.pv - old_pv + self.spread -= 1 + return dv01 + + @property + def IRDV01(self): + old_pv = self.pv + old_yc = self._yc + for rh in self._helpers: + rh.quote += 1e-4 + self._yc = ql_to_jp(self._ql_yc) + self.spread = self.spread ## to force recomputation + new_pv = self.pv + for r in self._helpers: + r.quote -= 1e-4 + self._yc = old_yc + self.spread = self.spread + return new_pv - old_pv + + @property + def rec_risk(self): + old_pv = self.pv + self.recovery -= 0.01 + self.spread = self.spread + pv_minus = self.pv + self.recovery += 0.02 + self.spread = self.spread + pv_plus = self.pv + self.recovery -= 0.01 + self.spread = self.spread + return (pv_plus - pv_minus)/2 @property def risky_annuity(self): @@ -134,15 +196,21 @@ class Index(): @trade_date.setter def trade_date(self, d): self.start_date = prev_immdate(pd.Timestamp(d)).date() - self._yc = build_yc(d, True) + settings = Settings() + settings.evaluation_date = Date.from_datetime(d) + self._helpers = rate_helpers(self.currency) + self._ql_yc = YC(self._helpers) + self._yc = ql_to_jp(self._ql_yc) self._trade_date = d self._step_in_date = self.trade_date + datetime.timedelta(days=1) + self._accrued = self._fee_leg.accrued(self._step_in_date) self._value_date = (pd.Timestamp(self._trade_date) + 3* BDay()).date() if self._spread is not None: self.spread = self.spread @classmethod - def from_name(cls, index, series, tenor, trade_date = datetime.date.today()): + def from_name(cls, index, series, tenor, trade_date = datetime.date.today(), + notional = 10e6): try: with serenitasdb.cursor() as c: c.execute("SELECT maturity, coupon FROM index_maturity " \ @@ -155,15 +223,32 @@ class Index(): recovery = 0.4 if index.lower() == "ig" else 0.3 start_date = prev_immdate(pd.Timestamp(trade_date)).date() instance = cls(start_date, maturity, recovery, coupon) + instance.name = "{}{} {}".format(index.upper(), series, tenor.upper()) + if index.upper() in ["IG", "HY"]: + instance.currency = "USD" + else: + instance.currency = "EUR" + instance.notional = notional instance.trade_date = trade_date return instance def __repr__(self): - return """Notional: {} -Maturity Date: {} -Coupon (bp): {} -Rec Rate: {}""".format(self.notional, self.end_date, self.fixed_rate, - self.recovery) + if self.days_accrued > 1: + accrued_str = "Accrued ({} Days)".format(self.days_accrued) + else: + accrued_str = "Accrued ({} Day)".format(self.days_accrued) + s = ["Trade Date\t{}".format(self.trade_date), + "Trd Spread (bp)\t{}\tCoupon (bp)\t{}".format(self.spread, self.fixed_rate), + "", + colored("Calculator", attrs = ['bold']), + "{:<20}\t{:>15}".format("Valuation Date", '{:%m/%d/%y}'.format(self.trade_date)), + "{:<20}\t{:>15}".format("Cash Settled On", '{:%m/%d/%y}'.format(self._value_date)), + "", + "{:<20}\t{:>15.8f}\t\t{:<20}\t{:>8,.2f}".format("Price", self.price, "Spread DV01", self.DV01), + "{:<20}\t{:>15,.0f}\t\t{:<20}\t{:>8,.2f}".format("Principal", self.clean_pv, "IR DV01", self.IRDV01), + "{:<20}\t{:>15,.0f}\t\t{:<20}\t{:>8,.2f}".format(accrued_str, self.accrued, "Rec Risk (1%)", self.rec_risk), + "{:<20}\t{:>15,.0f}\t\t{:<20}\t{:>8,.2f}".format("Cash Amount", self.pv, "Def Exposure", self.rec_risk)] + return "\n".join(s) def year_frac(d1, d2, day_count_conv = "Actual/365"): """ compute the year fraction between two dates """ |
