diff options
Diffstat (limited to 'python/analytics/index.py')
| -rw-r--r-- | python/analytics/index.py | 107 |
1 files changed, 78 insertions, 29 deletions
diff --git a/python/analytics/index.py b/python/analytics/index.py index bc4f9299..1aa2ccc8 100644 --- a/python/analytics/index.py +++ b/python/analytics/index.py @@ -14,7 +14,6 @@ from db import dbengine from sqlalchemy import exc from pyisda.curve import SpreadCurve from .utils import previous_twentieth -from scipy.optimize import brentq from yieldcurve import YC, ql_to_jp, roll_yc, rate_helpers engine = dbengine('serenitasdb') @@ -35,7 +34,7 @@ def g(index, spread, exercise_date, forward_yc=None, pv=0.): if pv != 0.: return 1e4 * pv / a + spread else: - return (spread - index.fixed_rate) * a *1e-4 + return (spread - index.fixed_rate) * a * 1e-4 class Index(object): """ minimal class to represent a credit index """ @@ -56,6 +55,7 @@ class Index(object): self._start_date = start_date self._end_date = end_date self.recovery = recovery + self.factor = 1 self._fee_leg = FeeLeg(self._start_date, end_date, True, 1., 1.) self._default_leg = ContingentLeg(self._start_date, end_date, True) @@ -94,6 +94,21 @@ class Index(object): return self._spread * 1e4 else: return None + @property + def direction(self): + if self._direction == -1.: + return "Buyer" + else: + return "Seller" + + @direction.setter + def direction(self, d): + if d == "Buyer": + self._direction = -1. + elif d == "Seller": + self._direction = 1. + else: + raise ValueError("Direction needs to be either 'Buyer' or 'Seller'") def _update(self): self._sc = SpreadCurve(self.trade_date, self._yc, self.start_date, @@ -122,15 +137,22 @@ class Index(object): def flat_hazard(self): sc_data = self._sc.inspect()['data'] ## conversion to continuous compounding - return math.log1p(sc_data[0][1]) + return sc_data[0][1] @property def pv(self): - return self.notional * self._pv + return - self._direction * self.notional * self.factor * self._pv + + @pv.setter + def pv(self, val): + self._pv = val / (self.notional * self.factor) + self._clean_pv = self._pv + self._accrued * self.fixed_rate * 1e-4 + self.price = 100 * (1- self._clean_pv) @property def accrued(self): - return - self.notional * self._accrued * self.fixed_rate * 1e-4 + return self._direction * self.notional * self.factor * self._accrued * \ + self.fixed_rate * 1e-4 @property def days_accrued(self): @@ -138,7 +160,7 @@ class Index(object): @property def clean_pv(self): - return self.notional * self._clean_pv + return - self._direction * self.notional * self.factor * self._clean_pv @property def price(self): @@ -147,19 +169,23 @@ class Index(object): @price.setter def price(self, val): if self._price is None or math.fabs(val-self._price) > 1e-6: - def handle(x, self, val): - self._spread = x - self._update() - return val - self.price - eta = 1.2 - a = self.fixed_rate*1e-4 * 0.5 - b = a * eta - while True: - if handle(b, self, val) > 0: - break - b *= eta - self._spread = brentq(handle, a, b, args = (self, val)) - self._update() + self._clean_pv = (100 - val) / 100 + self._sc = SpreadCurve( + self.trade_date, self._yc, self.start_date, + self._step_in_date, self._value_date, + [self.end_date], array.array('d',[self.fixed_rate*1e-4]), + array.array('d', [self._clean_pv]), + array.array('d', [self.recovery])) + self._risky_annuity = self._fee_leg.pv( + self.trade_date, self._step_in_date, self._value_date, + self._yc, self._sc, False) + self._dl_pv = self._default_leg.pv( + self.trade_date, self._step_in_date, self._value_date, + self._yc, self._sc, self.recovery) + self._pv = self._clean_pv - self._accrued * self.fixed_rate * 1e-4 + self._spread = self._clean_pv / (self._risky_annuity - self._accrued) \ + + self.fixed_rate * 1e-4 + self._price = val @property def ref(self): @@ -219,7 +245,8 @@ class Index(object): @property def jump_to_default(self): - return self.notional * (1 - self.recovery) - self.clean_pv + return self.notional * self.factor * self._direction * \ + (self.recovery + self._clean_pv - 1) @property def risky_annuity(self): @@ -254,20 +281,28 @@ class Index(object): @classmethod def from_name(cls, index, series, tenor, trade_date=datetime.date.today(), - notional=10e6): + notional=10_000_000): try: - r = engine.execute("SELECT maturity, coupon FROM index_maturity " \ - "WHERE index=%s AND series=%s AND tenor = %s", - (index.upper(), series, tenor)) - maturity, coupon = r.fetchone() + df = pd.read_sql_query( + "SELECT indexfactor, lastdate, maturity, coupon " \ + "FROM index_desc WHERE index=%s AND series=%s AND tenor = %s " + "ORDER BY lastdate ASC", + engine, parse_dates=['lastdate'], + params=(index.upper(), series, tenor)) + maturity = df.maturity[0] + coupon = df.coupon[0] + df.loc[df.lastdate.isnull(),'lastdate'] = maturity + factor = df.loc[df.lastdate >= pd.Timestamp(trade_date), + 'indexfactor'].iat[0]/100 except exc.DataError as e: - raise - except TypeError: - raise ValueError("Index not found") + print(e) + return None else: recovery = 0.4 if index.lower() == "ig" else 0.3 instance = cls(trade_date, maturity, recovery, coupon, notional, index.upper()=="HY") + instance.factor = factor + instance.direction = "Buyer" instance.name = "MARKIT CDX.NA.{}.{} {:%m/%y} ".format( index.upper(), series, @@ -276,10 +311,24 @@ class Index(object): instance.currency = "USD" else: instance.currency = "EUR" - instance.notional = notional instance.trade_date = trade_date return instance + @classmethod + def from_tradeid(cls, trade_id): + engine = dbengine('dawndb') + r = engine.execute("SELECT * from cds WHERE id=%s", (trade_id,)) + rec = r.fetchone() + recovery = 0.4 if "IG" in rec.security_desc else 0.3 + instance = cls(rec.trade_date, rec.maturity, recovery, rec.fixed_rate * 100, + rec.notional, recovery==0.3) + instance.name = rec.security_desc + instance.currency = rec.currency + instance.direction = rec.protection + instance.trade_date = rec.trade_date + instance.pv = rec.upfront + return instance + def __repr__(self): if self.days_accrued > 1: accrued_str = "Accrued ({} Days)".format(self.days_accrued) |
