diff options
| -rw-r--r-- | python/analytics/basket_index.py | 84 | ||||
| -rw-r--r-- | python/tranche_functions.py | 31 |
2 files changed, 78 insertions, 37 deletions
diff --git a/python/analytics/basket_index.py b/python/analytics/basket_index.py index 261197cc..f41494fc 100644 --- a/python/analytics/basket_index.py +++ b/python/analytics/basket_index.py @@ -1,4 +1,6 @@ -import index_data as data +from index_data import (get_index_quotes, get_singlenames_curves, + get_tranche_quotes, _serenitas_engine) +from tranche_functions import credit_schedule from dateutil.relativedelta import relativedelta from pyisda.credit_index import CreditIndex from typing import List @@ -7,6 +9,7 @@ import pandas as pd import datetime from scipy.optimize import brentq from pandas.tseries.offsets import BDay +from pyisda.cdsone import upfront_charge class BasketIndex(CreditIndex): index_type: str @@ -24,25 +27,18 @@ class BasketIndex(CreditIndex): self.recovery = 0.4 else: self.recovery = 0.3 - self.index_quotes = (data.get_index_quotes(index_type, series, - tenors, years=None)['closeprice']. - unstack(). - groupby(level='date', as_index=False).nth(0). - reset_index(['index', 'series'], drop=True)) self.index_desc = pd.read_sql_query("SELECT tenor, maturity, coupon * 1e-4 AS coupon, " \ "issue_date "\ "FROM index_maturity " \ "WHERE index=%s AND series=%s", - data._serenitas_engine, + _serenitas_engine, index_col='tenor', params=(index_type, series), parse_dates=['maturity', 'issue_date']) - self.index_quotes.columns = self.index_desc.loc[self.index_quotes.columns, "maturity"] - self.index_quotes = 1 - self.index_quotes / 100 self.issue_date = self.index_desc.issue_date[0] - maturities = self.index_quotes.columns.sort_values().to_pydatetime() + maturities = self.index_desc.maturity.sort_values().dt.to_pydatetime() self.index_desc = self.index_desc.reset_index().set_index('maturity') - curves, args = data.get_singlenames_curves(index_type, series, trade_date) + curves, args = get_singlenames_curves(index_type, series, trade_date) _, jp_yc, _, step_in_date, value_date, _ = args self.yc = jp_yc self.step_in_date = step_in_date @@ -51,13 +47,16 @@ class BasketIndex(CreditIndex): self.tweaks = [] super().__init__(self.issue_date, maturities, curves) + def _get_quotes(self): + pass + @property def trade_date(self): return self._trade_date @trade_date.setter def trade_date(self, d: pd.Timestamp): - curves, args = data.get_singlenames_curves(self.index_type, self.series, d) + curves, args = get_singlenames_curves(self.index_type, self.series, d) _, jp_yc, _, step_in_date, value_date, _ = args self.yc = jp_yc self.step_in_date = step_in_date @@ -66,9 +65,8 @@ class BasketIndex(CreditIndex): self.curves = curves def pv(self, maturity: pd.Timestamp, epsilon=0.): - coupon = self.index_desc.loc[maturity, 'coupon'] return super().pv(self.step_in_date, self.value_date, maturity, self.yc, - self.recovery, coupon, epsilon) + self.recovery, self.coupon(maturity), epsilon) def duration(self, maturity): return super().duration(self.step_in_date, self.value_date, maturity, self.yc) @@ -77,16 +75,17 @@ class BasketIndex(CreditIndex): if self.step_in_date.date() > maturity - relativedelta(years=1): return np.NaN else: - coupon = self.index_desc.loc[maturity, 'coupon'] - index_quote = self.index_quotes.loc[self.trade_date,maturity] + index_quote = self.index_quotes.loc[self.trade_date, maturity] return super().theta(self.step_in_date, self.value_date, maturity, - self.yc, self.recovery, coupon, index_quote) + self.yc, self.recovery, self.coupon(maturity), index_quote) + def coupon(self, maturity): + return self.index_desc.loc[maturity, 'coupon'] + def tweak(self): """ tweak the singlename curves to match index quotes""" - quotes = self.index_quotes.loc[self.trade_date] + quotes = self._get_quotes() self.tweaks = [] - for m, index_quote in quotes.iteritems(): - coupon = self.index_desc.loc[m, 'coupon'] + for m, index_quote in quotes.items(): lo, hi = -0.3, 0.3 while lo > -1: try: @@ -105,6 +104,49 @@ class BasketIndex(CreditIndex): self.tweaks.append(eps) self.tweak_portfolio(eps, m) +class MarkitBasketIndex(BasketIndex): + def __init__(self, index_type: str, series: int, tenors: List[str], *args, + trade_date: pd.Timestamp=pd.Timestamp.today().normalize() - BDay()): + super().__init__(index_type, series, tenors, *args, trade_date) + self.index_quotes = (get_index_quotes(index_type, series, + tenors, years=None)['closeprice']. + unstack(). + groupby(level='date', as_index=False).nth(0). + reset_index(['index', 'series', 'version'], drop=True)) + self.index_quotes.columns = self.index_desc.loc[self.index_quotes.columns, "maturity"] + self.index_quotes = 1 - self.index_quotes / 100 + + def _get_quotes(self): + return self.index_quotes.loc[self.trade_date] + +class TrancheBasket(BasketIndex): + def __init__(self, index_type: str, series: int, tenor: str, *args, + trade_date: pd.Timestamp=pd.Timestamp.today().normalize() - BDay()): + super().__init__(index_type, series, [tenor], *args, trade_date) + self.tranche_quotes = get_tranche_quotes(index_type, series, tenor, trade_date.date()) + index_desc = self.index_desc.reset_index('maturity').set_index('tenor') + self.maturity = index_desc.loc[tenor].maturity + self.start_date, self.cs = credit_schedule(trade_date, tenor[:-1], 1, self.yc) + self.K_orig = [0] + [q['detach'] for q in self.tranche_quotes] + self.K = adjust_attachments(self.K_orig, self + + def _get_quotes(self): + refprice = self.tranche_quotes[0]['indexrefprice'] + refspread = self.tranche_quotes[0]['indexrefspread'] + if refprice is not None: + return {self.maturity: 1 - refprice / 100} + if refspread is not None: + return {self.maturity: + upfront_charge(self.trade_date, self.value_date, self.start_date, + self.step_in_date, self.start_date, self.maturity, + self.coupon(self.maturity), self.yc, + refspread * 1e-4, self.recovery)} + raise ValueError("ref is missing") + + @property + def survival_matrix(self): + return super().survival_matrix(self.cs.index.values.astype('M8[D]').view('int') + 134774) + if __name__ == "__main__": ig28 = BasketIndex("IG", 28, ["3yr", "5yr", "7yr", "10yr"]) from quantlib.time.api import Schedule, Rule, Date, Period, WeekendsOnly @@ -112,5 +154,5 @@ if __name__ == "__main__": settings = Settings() cds_schedule = Schedule.from_rule(settings.evaluation_date, Date.from_datetime(ig28.maturities[-1]), - Period('3M'), WeekendsOnly(), date_generation_rule=Rule.CDS2015) + Period('3M'), WeekendsOnly(), date_generation_rule=Rule.CDS2015) sp = ig28.survival_matrix(cds_schedule.to_npdates().view('int') + 134774) diff --git a/python/tranche_functions.py b/python/tranche_functions.py index 28481130..a92044e3 100644 --- a/python/tranche_functions.py +++ b/python/tranche_functions.py @@ -1,8 +1,8 @@ import numpy as np from ctypes import * from numpy.ctypeslib import ndpointer -from quantlib.time.schedule import Schedule, CDS -from quantlib.time.api import Actual360, Period, UnitedStates, Following, today +from quantlib.time.schedule import Schedule, CDS2015 +from quantlib.time.api import Actual360, Period, WeekendsOnly, ModifiedFollowing, Unadjusted, today from quantlib.util.converter import qldate_to_pydate, pydate_to_qldate import pandas as pd from scipy.special import h_roots @@ -284,33 +284,32 @@ 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 creditSchedule(tradedate, tenor, coupon, yc, enddate = None): +def credit_schedule(tradedate, tenor, coupon, yc, enddate=None): tradedate = pydate_to_qldate(tradedate) - start = tradedate - Period('4Mo') - enddate = pydate_to_qldate(enddate) if enddate is None: enddate = tradedate + Period(tenor) - cal = UnitedStates() + else: + enddate = pydate_to_qldate(enddate) + cal = WeekendsOnly() DC = Actual360() - sched = Schedule(start, enddate, Period('3Mo'), cal, date_generation_rule=CDS) - prevpaydate = sched.previous_date(tradedate) - sched = [d for d in sched if d>=prevpaydate] - df = [yc.discount(d) for d in sched if d > tradedate] - - dates = pd.to_datetime([str(d) for d in sched if d > tradedate], "%m/%d/%Y") - coupons = np.diff([DC.year_fraction(prevpaydate, d) * coupon for d in sched]) + sched = Schedule.from_rule(tradedate, enddate, Period('3M'), cal, + ModifiedFollowing, Unadjusted, CDS2015) + dates = sched.to_npdates() + pydates = dates.astype('O') + df = [yc.discount_factor(d) for d in pydates if d > tradedate] + 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 pd.DataFrame({"df":df, "coupons":coupons}, dates) + return pydates[0], pd.DataFrame({"df": df, "coupons": coupons}, dates[1:]) def cdsAccrued(tradedate, coupon): tradedate = pydate_to_qldate(tradedate) - start = tradedate - Period('3Mo') end = tradedate + Period('3Mo') start_protection = tradedate + 1 DC = Actual360() cal = UnitedStates() - sched = Schedule(start, end, Period('3Mo'), cal, date_generation_rule=CDS) + sched = Schedule(start, end, Period('3Mo'), cal, date_generation_rule=CDS2015) prevpaydate = sched.previous_date(tradedate) return DC.year_fraction(prevpaydate, start_protection) * coupon |
