diff options
Diffstat (limited to 'python/analytics/tranche_basket.py')
| -rw-r--r-- | python/analytics/tranche_basket.py | 82 |
1 files changed, 54 insertions, 28 deletions
diff --git a/python/analytics/tranche_basket.py b/python/analytics/tranche_basket.py index c97598d0..034ca416 100644 --- a/python/analytics/tranche_basket.py +++ b/python/analytics/tranche_basket.py @@ -7,6 +7,7 @@ from cityhash import CityHash64 from collections import namedtuple from db import dbconn from copy import deepcopy +from functools import partial from lru import LRU from pyisda.date import cds_accrued from scipy.optimize import brentq @@ -38,18 +39,25 @@ def BCloss_recov_dist_cached(default_prob, return _cache[h] - -class DualCorrTranche(BasketIndex): +class DualCorrTranche(): def __init__(self, index_type: str, series: int, tenor: str, *, attach: float, detach: float, corr_attach: float, corr_detach: float, tranche_running: float, notional: float=10_000_000, value_date: pd.Timestamp=pd.Timestamp.today().normalize()): - super().__init__(index_type, series, [tenor], value_date=value_date) + self._index = BasketIndex(index_type, series, [tenor], value_date=value_date) + + def get_quotes(index, spread): + maturity = index.maturities[0] + return {maturity: + index._snacpv(spread * 1e-4, index.coupon(maturity), index.recovery, + maturity)} + + self._index._get_quotes = partial(get_quotes, self._index) self.tenor = tenor self.K_orig = np.array([attach, detach]) / 100 self.attach, self.detach = attach, detach - self.K = adjust_attachments(self.K_orig, self.cumloss, self.factor) + self.K = adjust_attachments(self.K_orig, self._index.cumloss, self._index.factor) self._Ngh = 250 self._Ngrid = 201 self._Z, self._w = GHquad(self._Ngh) @@ -57,13 +65,14 @@ class DualCorrTranche(BasketIndex): self.notional = notional self.tranche_running = tranche_running self._direction = -1. if notional > 0 else 1. - self.start_date, self.cs = credit_schedule(value_date, self.tenor[:-1], - 1., self.yc) - self.default_prob = 1 - super().survival_matrix(self.cs.index.values.astype('M8[D]'). - view('int') + 134774)[0] - self._accrued = cds_accrued(value_date, self.tranche_running * 1e-4) + self.cs = credit_schedule(value_date, None, + 1., self._index.yc, self._index.maturities[0]) + self._accrued = cds_accrued(value_date, tranche_running * 1e-4) - value_date = property(BasketIndex.value_date.__get__) + def _default_prob(self): + return 1 - self._index.survival_matrix( + self.cs.index.values.astype('M8[D]'). + view('int') + 134774)[0] @classmethod def from_tradeid(cls, trade_id): @@ -77,21 +86,24 @@ class DualCorrTranche(BasketIndex): instance = cls(rec['index'], rec['series'], rec['tenor'], attach=rec['attach'], detach=rec['detach'], - corr_attach=np.nan, - corr_detach=np.nan, + corr_attach=rec['corr_attach'] or np.nan, + corr_detach=rec['corr_detach'] or np.nan, notional=rec['notional'], tranche_running=rec['coupon'], value_date=rec['trade_date']) instance.direction = rec['protection'] + instance._index.tweak(rec['index_ref']) + return instance + @property + def value_date(self): + return self._index.value_date + @value_date.setter def value_date(self, d: pd.Timestamp): - BasketIndex.value_date.__set__(self, d) - self.start_date, self.cs = credit_schedule(d, self.tenor[:-1], - 1., self.yc) - self.default_prob= 1 - super().survival_matrix(self.cs.index.values.astype('M8[D]'). - view('int') + 134774)[0] + self._index.value_date = d + self.cs = credit_schedule(d, None, 1., self._index.yc, self._index.maturities[0]) self._accrued = cds_accrued(d, self.tranche_running * 1e-4) def tranche_legs(self, K, rho): @@ -102,9 +114,9 @@ class DualCorrTranche(BasketIndex): elif np.isnan(rho): raise ValueError("rho needs to be a real number between 0. and 1.") else: - L, R = BCloss_recov_dist_cached(self.default_prob, - self.weights, - self.recovery_rates, + L, R = BCloss_recov_dist_cached(self._default_prob(), + self._index.weights, + self._index.recovery_rates, rho, self._Z, self._w, self._Ngrid) Legs = namedtuple('TrancheLegs', 'coupon_leg, protection_leg') @@ -142,12 +154,22 @@ class DualCorrTranche(BasketIndex): dK = np.diff(self.K) pl = np.diff(pl) / dK cl = np.diff(cl) / dK * self.tranche_running * 1e-4 - bp = 1 + pl + cl - self._accrued - Pvs = namedtuple('TranchePvs', 'coupon_leg, protection_leg, bond_price') - return Pvs(cl, pl, bp) + return float(-pl - cl + self._accrued) + + @property + def upfront(self): + return self._direction * self.notional * (self.pv - self._accrued) + + @upfront.setter + def upfront(self, upf): + def aux(rho): + self.rho[1] = rho + return self.upfront - upf + self.rho[1], r = brentq(aux, 0, 1, full_output=True) + print(r.converged) def reset_pv(self): - self._original_pv = self.pv.bond_price + self._original_pv = self.pv self._trade_date = self._value_date def pnl(self): @@ -178,10 +200,10 @@ class TrancheBasket(BasketIndex): self._Ngrid = 201 self._Z, self._w = GHquad(self._Ngh) self.rho = np.full(self.K.size, np.nan) + self.cs = credit_schedule(value_date, self.tenor[:-1], + 1, self.yc, self.maturity) def _get_tranche_quotes(self, value_date): - self.start_date, self.cs = credit_schedule(value_date, self.tenor[:-1], - 1, self.yc, self.maturity) if isinstance(value_date, datetime.datetime): value_date = value_date.date() df = get_tranche_quotes(self.index_type, self.series, @@ -228,6 +250,8 @@ class TrancheBasket(BasketIndex): @value_date.setter def value_date(self, d: pd.Timestamp): BasketIndex.value_date.__set__(self, d) + self.cs = credit_schedule(d, self.tenor[:-1], + 1, self.yc, self.maturity) try: self._get_tranche_quotes(d) except ValueError as e: @@ -242,14 +266,16 @@ class TrancheBasket(BasketIndex): def _get_quotes(self, spread=None): if spread is not None: return {self.maturity: - self._snacpv(spread * 1e-4, self.coupon(self.maturity), self.recovery)} + self._snacpv(spread * 1e-4, self.coupon(self.maturity), + self.recovery, self.maturity)} refprice = self.tranche_quotes.indexrefprice.iat[0] refspread = self.tranche_quotes.indexrefspread.iat[0] if refprice is not None: return {self.maturity: 1 - refprice / 100} if refspread is not None: return {self.maturity: - self._snacpv(refspread * 1e-4, self.coupon(self.maturity), self.recovery)} + self._snacpv(refspread * 1e-4, self.coupon(self.maturity), self.recovery, + self.maturity)} raise ValueError("ref is missing") @property |
