diff options
Diffstat (limited to 'python')
| -rw-r--r-- | python/analytics/basket_index.py | 7 | ||||
| -rw-r--r-- | python/analytics/tranche_basket.py | 82 | ||||
| -rw-r--r-- | python/analytics/tranche_functions.py | 4 |
3 files changed, 62 insertions, 31 deletions
diff --git a/python/analytics/basket_index.py b/python/analytics/basket_index.py index bcf3d17c..83d03421 100644 --- a/python/analytics/basket_index.py +++ b/python/analytics/basket_index.py @@ -4,6 +4,7 @@ from .utils import tenor_t from functools import partial from pandas.util import hash_pandas_object from pyisda.credit_index import CreditIndex +from pyisda.date import previous_twentieth from typing import List from yieldcurve import get_curve import numpy as np @@ -65,6 +66,7 @@ class BasketIndex(CreditIndex): self.step_in_date = value_date + Day() self.cash_settle_date = value_date + 3 * BDay() self.tweaks = [] + self.start_date = previous_twentieth(value_date) super().__init__(self.issue_date, maturities, curves, value_date=value_date) def __reduce__(self): @@ -118,6 +120,7 @@ class BasketIndex(CreditIndex): self.yc = get_curve(d, self.currency) self.step_in_date = d + Day() self.cash_settle_date = d + 3 * BDay() + self.start_date = previous_twentieth(d) # or d + 1? CreditIndex.value_date.__set__(self, d) @property @@ -233,9 +236,9 @@ class BasketIndex(CreditIndex): self.tweaks.append(eps) self.tweak_portfolio(eps, m) - def _snacpv(self, spread, coupon, recov): + def _snacpv(self, spread, coupon, recov, maturity): return upfront_charge(self.value_date, self.cash_settle_date, self.start_date, - self.step_in_date, self.start_date, self.maturity, + self.step_in_date, self.start_date, maturity, coupon, self.yc, spread, recov) class MarkitBasketIndex(BasketIndex): 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 diff --git a/python/analytics/tranche_functions.py b/python/analytics/tranche_functions.py index 94bc7381..16661b41 100644 --- a/python/analytics/tranche_functions.py +++ b/python/analytics/tranche_functions.py @@ -315,6 +315,7 @@ def tranche_pl(L, cs, K1, K2, scaled=False): def tranche_pv(L, R, cs, K1, K2): return tranche_pl(L, cs, K1, K2) + tranche_cl(L, R, cs, K2, K2) + def credit_schedule(tradedate, tenor, coupon, yc, enddate=None): tradedate = pydate_to_qldate(tradedate) if enddate is None: @@ -330,7 +331,8 @@ def credit_schedule(tradedate, tenor, coupon, yc, enddate=None): df = [yc.discount_factor(d) for d in pydates if d > tradedate + 1] coupons = [DC.year_fraction(d1, d2) * coupon for d1, d2 in zip(sched[:-2], sched[1:-1])] coupons.append(Actual360(True).year_fraction(sched[-2], sched[-1]) * coupon) - return pydates[0], pd.DataFrame({"df": df, "coupons": coupons}, dates[1:]) + return pd.DataFrame({"df": df, "coupons": coupons}, dates[1:]) + def cds_accrued(tradedate, coupon): """ computes accrued for a standard CDS |
