aboutsummaryrefslogtreecommitdiffstats
path: root/python/analytics
diff options
context:
space:
mode:
Diffstat (limited to 'python/analytics')
-rw-r--r--python/analytics/basket_index.py7
-rw-r--r--python/analytics/tranche_basket.py82
-rw-r--r--python/analytics/tranche_functions.py4
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