aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--python/swaption.py119
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 """