diff options
| -rw-r--r-- | python/analytics/__init__.py | 1 | ||||
| -rw-r--r-- | python/analytics/credit_default_swap.py | 46 | ||||
| -rw-r--r-- | python/analytics/index_data.py | 43 | ||||
| -rw-r--r-- | python/analytics/singlename_cds.py | 51 | ||||
| -rw-r--r-- | python/analytics/utils.py | 7 |
5 files changed, 132 insertions, 16 deletions
diff --git a/python/analytics/__init__.py b/python/analytics/__init__.py index 62933b5b..8caeec55 100644 --- a/python/analytics/__init__.py +++ b/python/analytics/__init__.py @@ -15,6 +15,7 @@ from .option import ( ) from .portfolio import Portfolio from .basket_index import MarkitBasketIndex +from .singlename_cds import SingleNameCds from .tranche_basket import DualCorrTranche, TrancheBasket from .ir_swaption import IRSwaption diff --git a/python/analytics/credit_default_swap.py b/python/analytics/credit_default_swap.py index 5bddbf73..a2464f94 100644 --- a/python/analytics/credit_default_swap.py +++ b/python/analytics/credit_default_swap.py @@ -13,6 +13,7 @@ from pyisda.date import previous_twentieth from pyisda.legs import ContingentLeg, FeeLeg from termcolor import colored from .utils import build_table, get_fx +from typing import Union from weakref import WeakSet from yieldcurve import get_curve, rate_helpers, YC, ql_to_jp @@ -51,7 +52,13 @@ class CreditDefaultSwap: ) def __init__( - self, start_date, end_date, recovery, fixed_rate, notional=10e6, issue_date=None + self, + start_date: datetime.date, + end_date: datetime.date, + recovery: float, + fixed_rate: float, + notional: float = 10e6, + issue_date: Union[datetime.date, None] = None, ): """ start_date : :class:`datetime.date` @@ -134,6 +141,15 @@ class CreditDefaultSwap: def spread(self): if self._spread is not None: return self._spread * 1e4 + elif self._sc is not None: + return self._sc.par_spread( + self.value_date, + self._step_in_date, + self.start_date, + [self.end_date], + np.array([self.recovery]), + self._yc, + ) else: return None @@ -154,17 +170,18 @@ class CreditDefaultSwap: raise ValueError("Direction needs to be either 'Buyer' or 'Seller'") def _update(self): - self._sc = SpreadCurve( - self._yc.base_date, - self._yc, - self.start_date, - self._step_in_date, - self._cash_settle_date, - [self.end_date], - np.array([self._spread]), - np.zeros(1), - np.array([self.recovery]), - ) + if self._spread is not None: + self._sc = SpreadCurve( + self._yc.base_date, + self._yc, + self.start_date, + self._step_in_date, + self._cash_settle_date, + [self.end_date], + np.array([self._spread]), + np.zeros(1), + np.array([self.recovery]), + ) self._risky_annuity = self._fee_leg.pv( self.value_date, @@ -189,7 +206,7 @@ class CreditDefaultSwap: @spread.setter def spread(self, s): """ s: spread in bps """ - if self.spread is None or s != self.spread: + if self._spread is None or s != self.spread: self._spread = s * 1e-4 self._update() self.notify() @@ -361,8 +378,7 @@ class CreditDefaultSwap: self._step_in_date = d + datetime.timedelta(days=1) self._accrued = self._fee_leg.accrued(self._step_in_date) self._cash_settle_date = pd.Timestamp(self._value_date) + 3 * BDay() - if self._spread is not None: - self._update() + self._update() self.notify() def reset_pv(self): diff --git a/python/analytics/index_data.py b/python/analytics/index_data.py index 29d50cbd..57ad45cc 100644 --- a/python/analytics/index_data.py +++ b/python/analytics/index_data.py @@ -4,7 +4,7 @@ import numpy as np from .utils import tenor_t from functools import lru_cache -from pyisda.curve import SpreadCurve, Seniority, DocClause +from pyisda.curve import SpreadCurve, Seniority, DocClause, YieldCurve from multiprocessing import Pool from yieldcurve import get_curve @@ -244,3 +244,44 @@ def get_tranche_quotes(index_type, series, tenor, date=datetime.date.today()): df = pd.DataFrame.from_records(dict(d) for d in c) serenitas_pool.putconn(conn) return df + + +def get_singlename_curve( + ticker: str, + seniority: str, + doc_clause: str, + value_date: datetime.date, + yieldcurve: YieldCurve, + source: str = "MKIT", +): + conn = serenitas_pool.getconn() + with conn.cursor() as c: + c.execute( + "SELECT * FROM cds_quotes " + "JOIN (SELECT UNNEST(cds_curve) AS curve_ticker, " + " UNNEST(ARRAY[0.5, 1., 2., 3., 4., 5., 7., 10.]::float[]) AS tenor" + " FROM bbg_issuers" + " JOIN bbg_markit_mapping USING (company_id, seniority)" + " WHERE markit_ticker=%s and seniority=%s) a " + "USING (curve_ticker) WHERE date=%s AND source=%s ORDER BY tenor", + (ticker, seniority, value_date, source), + ) + df = pd.DataFrame(c, columns=[col.name for col in c.description]) + serenitas_pool.putconn(conn) + spread_curve = 0.5 * (df.runningbid + df.runningask).values * 1e-4 + upfront_curve = 0.5 * (df.upfrontbid + df.upfrontask).values * 1e-2 + return SpreadCurve( + value_date, + yieldcurve, + None, + None, + None, + df.tenor.values, + spread_curve, + upfront_curve, + df.recovery.values, + ticker=ticker, + seniority=Seniority[seniority], + doc_clause=DocClause[doc_clause], + defaulted=None, + ) diff --git a/python/analytics/singlename_cds.py b/python/analytics/singlename_cds.py new file mode 100644 index 00000000..3c59f039 --- /dev/null +++ b/python/analytics/singlename_cds.py @@ -0,0 +1,51 @@ +from .credit_default_swap import CreditDefaultSwap +from .index_data import get_singlename_curve +from pyisda.date import previous_twentieth, roll_date +from .utils import tenor_to_float +from typing import Union +from yieldcurve import get_curve + +import datetime + + +class SingleNameCds(CreditDefaultSwap): + __slots__ = ("ticker", "seniority", "doc_clause", "tenor") + + def __init__( + self, + ticker: str, + seniority: str = "Senior", + doc_clause: str = "XR14", + tenor: str = "5yr", + *, + end_date: Union[datetime.date, None] = None, + recovery: float = 0.4, + fixed_rate: float = 100.0, + notional: float = 10e6, + currency: str = "USD", + value_date: datetime.date = datetime.date.today() + ): + + if end_date is None: + end_date = roll_date(value_date, tenor_to_float(tenor)) + + super().__init__( + previous_twentieth(value_date), end_date, recovery, fixed_rate, notional + ) + + self.ticker = ticker + self.seniority = seniority + self.doc_clause = doc_clause + self.tenor = tenor + self.currency = currency + self.value_date = value_date + + value_date = property(CreditDefaultSwap.value_date.__get__) + + @value_date.setter + def value_date(self, d: datetime.date): + self._yc = get_curve(d, self.currency) + self._sc = get_singlename_curve( + self.ticker, self.seniority, self.doc_clause, d, self._yc + ) + CreditDefaultSwap.value_date.__set__(self, d) diff --git a/python/analytics/utils.py b/python/analytics/utils.py index 3d5c0da4..c0da47f6 100644 --- a/python/analytics/utils.py +++ b/python/analytics/utils.py @@ -69,6 +69,13 @@ def next_third_wed(d): return y +def tenor_to_float(t: str): + if t == "6m": + return 0.5 + else: + return float(t.rstrip("yr")) + + def roll_date(d, tenor, nd_array=False): """ roll date d to the next CDS maturity""" cutoff = pd.Timestamp("2015-09-20") |
