diff options
Diffstat (limited to 'python/analytics/credit_default_swap.py')
| -rw-r--r-- | python/analytics/credit_default_swap.py | 231 |
1 files changed, 155 insertions, 76 deletions
diff --git a/python/analytics/credit_default_swap.py b/python/analytics/credit_default_swap.py index 99b4e658..e4998657 100644 --- a/python/analytics/credit_default_swap.py +++ b/python/analytics/credit_default_swap.py @@ -17,18 +17,42 @@ from weakref import WeakSet from yieldcurve import get_curve, rate_helpers, YC, ql_to_jp -class CreditDefaultSwap(): +class CreditDefaultSwap: """ minimal class to represent a credit default swap """ - __slots__ = ('_observed', 'fixed_rate', 'notional', '_start_date', - '_end_date', 'recovery', '_version', '_fee_leg', - '_default_leg', '_value_date', '_yc', '_sc', '_risky_annuity', - '_spread', '_price', 'name', 'issue_date', - 'currency', '_step_in_date', '_accrued', - '_cash_settle_date', '_dl_pv', '_pv', '_clean_pv', - '_original_clean_pv', '_trade_date', '_factor') - def __init__(self, start_date, end_date, recovery, fixed_rate, - notional=10e6, issue_date=None): + __slots__ = ( + "_observed", + "fixed_rate", + "notional", + "_start_date", + "_end_date", + "recovery", + "_version", + "_fee_leg", + "_default_leg", + "_value_date", + "_yc", + "_sc", + "_risky_annuity", + "_spread", + "_price", + "name", + "issue_date", + "currency", + "_step_in_date", + "_accrued", + "_cash_settle_date", + "_dl_pv", + "_pv", + "_clean_pv", + "_original_clean_pv", + "_trade_date", + "_factor", + ) + + def __init__( + self, start_date, end_date, recovery, fixed_rate, notional=10e6, issue_date=None + ): """ start_date : :class:`datetime.date` index start_date (Could be issue date, or last imm date) @@ -45,7 +69,7 @@ class CreditDefaultSwap(): self._end_date = end_date self.recovery = recovery - self._fee_leg = FeeLeg(self._start_date, end_date, True, 1., 1.) + self._fee_leg = FeeLeg(self._start_date, end_date, True, 1.0, 1.0) self._default_leg = ContingentLeg(self._start_date, end_date, True) self._value_date = None self._yc, self._sc = None, None @@ -54,9 +78,17 @@ class CreditDefaultSwap(): self.name = None self.issue_date = issue_date self._factor = 1 - for attr in ['currency', '_step_in_date', '_cash_settle_date', - '_accrued', '_dl_pv', '_pv', '_clean_pv', - '_original_clean_pv', '_trade_date']: + for attr in [ + "currency", + "_step_in_date", + "_cash_settle_date", + "_accrued", + "_dl_pv", + "_pv", + "_clean_pv", + "_original_clean_pv", + "_trade_date", + ]: setattr(self, attr, None) self._observed = WeakSet() @@ -65,9 +97,9 @@ class CreditDefaultSwap(): def _getslots(self): classes = reversed(self.__class__.__mro__) - next(classes) # skip object + next(classes) # skip object slots = chain.from_iterable(cls.__slots__ for cls in classes) - next(slots) # skip _observed + next(slots) # skip _observed yield from slots def __getstate__(self): @@ -88,13 +120,13 @@ class CreditDefaultSwap(): @start_date.setter def start_date(self, d): - self._fee_leg = FeeLeg(d, self.end_date, True, 1., 1.) + self._fee_leg = FeeLeg(d, self.end_date, True, 1.0, 1.0) self._default_leg = ContingentLeg(d, self.end_date, True) self._start_date = d @end_date.setter def end_date(self, d): - self._fee_leg = FeeLeg(self.start_date, d, True, 1., 1.) + self._fee_leg = FeeLeg(self.start_date, d, True, 1.0, 1.0) self._default_leg = ContingentLeg(self.start_date, d, True) self._end_date = d @@ -107,7 +139,7 @@ class CreditDefaultSwap(): @property def direction(self): - if self.notional > 0.: + if self.notional > 0.0: return "Buyer" else: return "Seller" @@ -122,17 +154,34 @@ 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])) + 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, self._step_in_date, - self._cash_settle_date, self._yc, - self._sc, False) + self._risky_annuity = self._fee_leg.pv( + self.value_date, + self._step_in_date, + self._cash_settle_date, + self._yc, + self._sc, + False, + ) self._dl_pv = self._default_leg.pv( - self.value_date, self._step_in_date, self._cash_settle_date, - self._yc, self._sc, self.recovery) + self.value_date, + self._step_in_date, + self._cash_settle_date, + self._yc, + self._sc, + self.recovery, + ) self._pv = self._dl_pv - self._risky_annuity * self.fixed_rate * 1e-4 self._clean_pv = self._pv + self._accrued * self.fixed_rate * 1e-4 self._price = 100 * (1 - self._clean_pv) @@ -147,7 +196,7 @@ class CreditDefaultSwap(): @property def flat_hazard(self): - sc_data = self._sc.inspect()['data'] + sc_data = self._sc.inspect()["data"] # conversion to continuous compounding return sc_data[0][1] @@ -163,8 +212,7 @@ class CreditDefaultSwap(): @property def accrued(self): - return -self.notional * self._factor * self._accrued * \ - self.fixed_rate * 1e-4 + return -self.notional * self._factor * self._accrued * self.fixed_rate * 1e-4 @property def days_accrued(self): @@ -180,23 +228,40 @@ class CreditDefaultSwap(): @price.setter def price(self, val): - if self._price is None or math.fabs(val-self._price) > 1e-6: + if self._price is None or math.fabs(val - self._price) > 1e-6: self._clean_pv = (100 - val) / 100 self._sc = SpreadCurve( - self.value_date, self._yc, self.start_date, - self._step_in_date, self._cash_settle_date, - [self.end_date], array.array('d', [self.fixed_rate*1e-4]), - array.array('d', [self._clean_pv]), - array.array('d', [self.recovery])) + self.value_date, + self._yc, + self.start_date, + self._step_in_date, + self._cash_settle_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.value_date, self._step_in_date, self._cash_settle_date, - self._yc, self._sc, False) + self.value_date, + self._step_in_date, + self._cash_settle_date, + self._yc, + self._sc, + False, + ) self._dl_pv = self._default_leg.pv( - self.value_date, self._step_in_date, self._cash_settle_date, - self._yc, self._sc, self.recovery) + self.value_date, + self._step_in_date, + self._cash_settle_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._spread = ( + self._clean_pv / (self._risky_annuity - self._accrued) + self.fixed_rate * 1e-4 + ) self._price = val self.notify() @@ -214,7 +279,7 @@ class CreditDefaultSwap(): with warnings.catch_warnings(): warnings.simplefilter("ignore") self.value_date = self.value_date + relativedelta(days=1) - carry = self.notional * self.fixed_rate * 1e-4/360 + carry = self.notional * self.fixed_rate * 1e-4 / 360 roll_down = self.clean_pv - old_pv self.value_date = old_value_date return carry + roll_down @@ -262,7 +327,7 @@ class CreditDefaultSwap(): @property def value_date(self): if self._value_date is None: - raise AttributeError('Please set value_date first') + raise AttributeError("Please set value_date first") else: return self._value_date @@ -290,8 +355,11 @@ class CreditDefaultSwap(): raise ValueError("original pv not set") else: days_accrued = (self.value_date - self._trade_date).days / 360 - return self.notional * (self._clean_pv - self._original_clean_pv - - days_accrued * self.fixed_rate * 1e-4) + return self.notional * ( + self._clean_pv + - self._original_clean_pv + - days_accrued * self.fixed_rate * 1e-4 + ) def notify(self): for obj in self._observed: @@ -308,7 +376,7 @@ class CreditDefaultSwap(): self.spread = orig_spread * (1 + ss) r.append([getattr(self, p) for p in actual_params]) self.spread = orig_spread - ind = pd.Index(spread_shock, name='spread_shock', copy=False) + ind = pd.Index(spread_shock, name="spread_shock", copy=False) return pd.DataFrame(r, index=ind, columns=actual_params) def __repr__(self): @@ -319,37 +387,48 @@ class CreditDefaultSwap(): else: accrued_str = "Accrued ({} Day)".format(self.days_accrued) - s = ["{:<20}\tNotional {:>5}MM {}\tFactor {:>28}".format( - "Buy Protection" if self.notional > 0. else "Sell Protection", - abs(self.notional)/1_000_000, - self.currency, - self._factor), - "{:<20}\t{:>15}".format("CDS Index", colored(self.name, attrs=['bold'])), - ""] - rows = [["Trd Sprd (bp)", self.spread, "Coupon (bp)", self.fixed_rate], - ["1st Accr Start", self.issue_date, "Payment Freq", "Quarterly"], - ["Maturity Date", self.end_date, "Rec Rate", self.recovery], - ["Bus Day Adj", "Following", "DayCount", "ACT/360"]] - format_strings = [[None, '{:.2f}', None, '{:.0f}'], - [None, '{:%m/%d/%y}', None, None], - [None, '{:%m/%d/%y}', None, None], - [None, None, None, None]] + s = [ + "{:<20}\tNotional {:>5}MM {}\tFactor {:>28}".format( + "Buy Protection" if self.notional > 0.0 else "Sell Protection", + abs(self.notional) / 1_000_000, + self.currency, + self._factor, + ), + "{:<20}\t{:>15}".format("CDS Index", colored(self.name, attrs=["bold"])), + "", + ] + rows = [ + ["Trd Sprd (bp)", self.spread, "Coupon (bp)", self.fixed_rate], + ["1st Accr Start", self.issue_date, "Payment Freq", "Quarterly"], + ["Maturity Date", self.end_date, "Rec Rate", self.recovery], + ["Bus Day Adj", "Following", "DayCount", "ACT/360"], + ] + format_strings = [ + [None, "{:.2f}", None, "{:.0f}"], + [None, "{:%m/%d/%y}", None, None], + [None, "{:%m/%d/%y}", None, None], + [None, None, None, None], + ] s += build_table(rows, format_strings, "{:<20}{:>19}\t\t{:<20}{:>15}") - s += ["", - colored("Calculator", attrs=['bold'])] - rows = [["Valuation Date", self.value_date], - ["Cash Settled On", self._cash_settle_date]] - format_strings = [[None, '{:%m/%d/%y}'], - [None, '{:%m/%d/%y}']] + s += ["", colored("Calculator", attrs=["bold"])] + rows = [ + ["Valuation Date", self.value_date], + ["Cash Settled On", self._cash_settle_date], + ] + format_strings = [[None, "{:%m/%d/%y}"], [None, "{:%m/%d/%y}"]] s += build_table(rows, format_strings, "{:<20}\t{:>15}") s += [""] - rows = [["Price", self.price, "Spread DV01", self.DV01], - ["Principal", self.clean_pv, "IR DV01", self.IRDV01], - [accrued_str, self.accrued, "Rec Risk (1%)", self.rec_risk], - ["Cash Amount", self.pv, "Def Exposure", self.jump_to_default]] - format_strings = [[None, '{:.8f}', None, '{:,.2f}'], - [None, '{:,.0f}', None, '{:,.2f}'], - [None, '{:,.0f}', None, '{:,.2f}'], - [None, '{:,.0f}', None, '{:,.0f}']] + rows = [ + ["Price", self.price, "Spread DV01", self.DV01], + ["Principal", self.clean_pv, "IR DV01", self.IRDV01], + [accrued_str, self.accrued, "Rec Risk (1%)", self.rec_risk], + ["Cash Amount", self.pv, "Def Exposure", self.jump_to_default], + ] + format_strings = [ + [None, "{:.8f}", None, "{:,.2f}"], + [None, "{:,.0f}", None, "{:,.2f}"], + [None, "{:,.0f}", None, "{:,.2f}"], + [None, "{:,.0f}", None, "{:,.0f}"], + ] s += build_table(rows, format_strings, "{:<20}{:>19}\t\t{:<20}{:>15}") return "\n".join(s) |
