diff options
Diffstat (limited to 'python')
| -rw-r--r-- | python/analytics/__init__.py | 1 | ||||
| -rw-r--r-- | python/analytics/basket_index.py | 104 | ||||
| -rw-r--r-- | python/cds_curve.py | 143 | ||||
| -rw-r--r-- | python/index_data.py | 50 |
4 files changed, 169 insertions, 129 deletions
diff --git a/python/analytics/__init__.py b/python/analytics/__init__.py index 031df9cb..e23b7e64 100644 --- a/python/analytics/__init__.py +++ b/python/analytics/__init__.py @@ -1,3 +1,4 @@ from .index import Index, ForwardIndex from .option import BlackSwaption, Swaption, VolatilitySurface, ATMstrike from .portfolio import Portfolio +from .basket_index import BasketIndex diff --git a/python/analytics/basket_index.py b/python/analytics/basket_index.py new file mode 100644 index 00000000..407f93bd --- /dev/null +++ b/python/analytics/basket_index.py @@ -0,0 +1,104 @@ +import index_data as data +from dateutil.relativedelta import relativedelta +from pyisda.credit_index import CreditIndex +from typing import List +import pandas as pd +import datetime +from scipy.optimize import brentq + +class BasketIndex(CreditIndex): + index_type: str + series: int + recovery: float + step_in_date: pd.Timestamp + value_date: pd.Timestamp + tweaks: List[float] + + def __init__(self, index_type: str, series: int, tenors: List[str], *args, + trade_date: pd.Timestamp = pd.Timestamp.today().normalize()): + self.index_type = index_type + self.series = series + if index_type == 'IG': + 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, + 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() + self.index_desc = self.index_desc.reset_index().set_index('maturity') + curves, args = data.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 + self.value_date = value_date + self._trade_date = trade_date + self.tweaks = [] + super().__init__(self.issue_date, maturities, curves) + + @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) + _, jp_yc, _, step_in_date, value_date, _ = args + self.yc = jp_yc + self.step_in_date = step_in_date + self.value_date = value_date + self._trade_date = d + 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) + + def duration(self, maturity): + return super().duration(self.step_in_date, self.value_date, maturity, self.yc) + + def theta(self, maturity): + 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] + return super().theta(self.step_in_date, self.value_date, maturity, + self.yc, self.recovery, coupon, index_quote) + def tweak(self): + """ tweak the singlename curves to match index quotes""" + quotes = self.index_quotes.loc[self.trade_date] + self.tweaks = [] + for m, index_quote in quotes.iteritems(): + coupon = self.index_desc.loc[m, 'coupon'] + lo, hi = -0.3, 0.3 + while lo > -1: + try: + eps = brentq(lambda epsilon: self.pv(m, epsilon) - + index_quote, lo, hi) + except ValueError: + lo *= 1.1 + hi *= 1.1 + else: + break + else: + print("couldn't calibrate for date: {} and maturity: {}". + format(self.trade_date.date(), m.date())) + self.tweaks.append(np.NaN) + continue + self.tweaks.append(eps) + self.tweak_portfolio(eps, m) diff --git a/python/cds_curve.py b/python/cds_curve.py index 67851f33..6019dfed 100644 --- a/python/cds_curve.py +++ b/python/cds_curve.py @@ -1,72 +1,13 @@ -from pyisda.curve import YieldCurve, BadDay, SpreadCurve, fill_curve from pyisda.credit_index import CreditIndex +from analytics import BasketIndex from pyisda.legs import FeeLeg, ContingentLeg from pyisda.logging import enable_logging import datetime -import math import numpy as np import pandas as pd -from dateutil.relativedelta import relativedelta -from yieldcurve import YC, ql_to_jp, _USD_curves -from quantlib.settings import Settings -from quantlib.time.api import Date -from db import dbconn, dbengine -from multiprocessing import Pool -from index_data import get_index_quotes -from pandas.tseries.offsets import BDay -from scipy.optimize import brentq -from pyisda.logging import enable_logging -from analytics.utils import roll_date, previous_twentieth - -def get_singlenames_quotes(indexname, date): - conn = dbconn('serenitasdb') - with conn.cursor() as c: - c.execute("SELECT * FROM curve_quotes(%s, %s)", vars=(indexname, date)) - return [r for r in c] - -def build_curve(r, today_date, yc, start_date, step_in_date, value_date, end_dates): - spread_curve = 1e-4 * np.array(r['spread_curve'][1:], dtype='float') - upfront_curve = 1e-2 * np.array(r['upfront_curve'][1:], dtype='float') - recovery_curve = np.array(r['recovery_curve'][1:], dtype='float') - try: - sc = SpreadCurve(today_date, yc, start_date, step_in_date, value_date, - end_dates, spread_curve, upfront_curve, recovery_curve, - ticker=r['cds_ticker']) - if len(sc) != end_dates.shape[0]: - sc = fill_curve(sc, end_dates) - except ValueError as e: - print(r[0], e) - return None - return sc - -def build_curves_dist(quotes, args, workers=4): - ## about twice as fast as the non distributed version - ## non thread safe for some reason so need ProcessPool - with Pool(workers) as pool: - r = pool.starmap(build_curve, [(q, *args) for q in quotes], 30) - return r - -def build_curves(quotes, args): - return [build_curve(q, *args) for q in quotes if q is not None] - -def get_singlenames_curves(index_type, series, trade_date): - end_dates = roll_date(trade_date, [1, 2, 3, 4, 5, 7, 10], nd_array=True) - sn_quotes = get_singlenames_quotes("{}{}".format(index_type.lower(), series), - trade_date.date()) - if trade_date in _USD_curves: - jp_yc = yieldcurve.USD_curves[trade_date] - else: - Settings().evaluation_date = Date.from_datetime(trade_date) - yc = YC() - jp_yc = ql_to_jp(yc) - start_date = previous_twentieth(trade_date) - step_in_date = trade_date + datetime.timedelta(days=1) - value_date = pd.Timestamp(trade_date) + 3* BDay() - args = (trade_date, jp_yc, start_date, step_in_date, value_date, end_dates) - curves = build_curves_dist(sn_quotes, args) - return curves, args +from db import dbconn def all_curves_pv(curves, today_date, jp_yc, start_date, step_in_date, value_date, maturities): r = {} @@ -88,82 +29,26 @@ def all_curves_pv(curves, today_date, jp_yc, start_date, step_in_date, value_dat columns=['duration', 'protection_pv']) return pd.concat(r, axis=1).swaplevel(axis=1).sort_index(axis=1,level=0) -serenitas_engine = dbengine('serenitasdb') def calibrate_portfolio(index_type, series, tenors=['3yr', '5yr', '7yr', '10yr'], start_date=None): - if index_type == 'IG': - recovery = 0.4 - else: - recovery = 0.3 - index_quotes = (get_index_quotes(index_type, series, - tenors, years=None)['closeprice']. - unstack(). - reset_index(level='version'). - groupby(level='date').nth(0). - set_index('version', append=True)) - 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", serenitas_engine, - index_col='tenor', params=(index_type, series), - parse_dates=['maturity', 'issue_date']) - - index_quotes.columns = index_desc.loc[index_quotes.columns, "maturity"] - index_quotes = 1 - index_quotes / 100 - issue_date = index_desc.issue_date[0] - maturities = index_quotes.columns.sort_values().to_pydatetime() - index_desc = index_desc.reset_index().set_index('maturity') - start_date = start_date or index_quotes.index.get_level_values(0)[0] - index_quotes = index_quotes[start_date:] - curves, _ = get_singlenames_curves(index_type, series, start_date) - index = CreditIndex(issue_date, maturities, curves) + index = BasketIndex(index_type, series, tenors) r = {} - for k, s in index_quotes.iterrows(): - trade_date, version = k - curves, args = get_singlenames_curves(index_type, series, trade_date) - _, jp_yc, _, step_in_date, value_date, _ = args - index.curves = curves - tweak, duration, theta = [], [], [] - s.name = 'index_quote' - quotes = pd.concat([index_desc, s], axis=1).dropna() - for m, coupon, index_quote in quotes[['coupon', 'index_quote']].itertuples(): - lo, hi = -0.3, 0.3 - while lo > -1: - try: - eps = brentq(lambda epsilon: index.pv(step_in_date, value_date, - m, jp_yc, recovery, - coupon, epsilon) - - index_quote, lo, hi) - except ValueError: - lo *= 1.1 - hi *= 1.1 - else: - break - else: - print("couldn't calibrate for date: {} and maturity: {}". - format(trade_date.date(), m.date())) - tweak.append(np.NaN) - duration.append(np.NaN) - theta.append(np.NaN) - continue - #tweak the curves in place - index.tweak_portfolio(eps, m) - duration.append(index.duration(step_in_date, value_date, m, jp_yc)) - if step_in_date > m - relativedelta(years=1): - theta.append(np.NaN) - else: - theta.append(index.theta(step_in_date, value_date, - m, jp_yc, recovery, coupon, index_quote)) - tweak.append(eps) - r[trade_date] = pd.DataFrame({'duration': duration, - 'theta': theta, - 'tweak': tweak}, index=quotes.tenor) + if start_date: + index.index_quotes = index.index_quotes[startdate:] + for trade_date in index.index_quotes.index.get_level_values(0): + index.trade_date = trade_date + index.tweak() + durations = [index.duration(m) for m in index.maturities] + thetas = [index.theta(m) for m in index.maturities] + r[trade_date] = pd.DataFrame({'duration': durations, + 'theta': thetas, + 'tweak': index.tweaks}, index=tenors) return pd.concat(r) if __name__=="__main__": enable_logging() - index, series = "IG", 21 + index, series = "IG", 28 df = calibrate_portfolio(index, series, ['3yr', '5yr', '7yr', '10yr']) conn = dbconn('serenitasdb') with conn.cursor() as c: diff --git a/python/index_data.py b/python/index_data.py index f04d7d4a..62cffeb2 100644 --- a/python/index_data.py +++ b/python/index_data.py @@ -1,5 +1,12 @@ from db import dbengine, dbconn from dates import bond_cal +import numpy as np + +from analytics.utils import roll_date, previous_twentieth +from pandas.tseries.offsets import BDay +from pyisda.curve import SpreadCurve, fill_curve +from multiprocessing import Pool +from yieldcurve import get_curve import datetime import pandas as pd @@ -119,3 +126,46 @@ def index_returns(df=None, index=None, series=None, tenor=None, from_date=None, groupby(level=['index', 'series', 'tenor'])['price_return']. transform(add_accrued)) return df + +def get_singlenames_quotes(indexname, date): + conn = dbconn('serenitasdb') + with conn.cursor() as c: + c.execute("SELECT * FROM curve_quotes(%s, %s)", vars=(indexname, date)) + return [r for r in c] + +def build_curve(r, today_date, yc, start_date, step_in_date, value_date, end_dates): + spread_curve = 1e-4 * np.array(r['spread_curve'][1:], dtype='float') + upfront_curve = 1e-2 * np.array(r['upfront_curve'][1:], dtype='float') + recovery_curve = np.array(r['recovery_curve'][1:], dtype='float') + try: + sc = SpreadCurve(today_date, yc, start_date, step_in_date, value_date, + end_dates, spread_curve, upfront_curve, recovery_curve, + ticker=r['cds_ticker']) + if len(sc) != end_dates.shape[0]: + sc = fill_curve(sc, end_dates) + except ValueError as e: + print(r[0], e) + return None + return sc + +def build_curves(quotes, args): + return [build_curve(q, *args) for q in quotes if q is not None] + +def build_curves_dist(quotes, args, workers=4): + ## about twice as fast as the non distributed version + ## non thread safe for some reason so need ProcessPool + with Pool(workers) as pool: + r = pool.starmap(build_curve, [(q, *args) for q in quotes], 30) + return r + +def get_singlenames_curves(index_type, series, trade_date): + end_dates = roll_date(trade_date, [1, 2, 3, 4, 5, 7, 10], nd_array=True) + sn_quotes = get_singlenames_quotes("{}{}".format(index_type.lower(), series), + trade_date.date()) + jp_yc = get_curve(trade_date) + start_date = previous_twentieth(trade_date) + step_in_date = trade_date + datetime.timedelta(days=1) + value_date = pd.Timestamp(trade_date) + 3* BDay() + args = (trade_date, jp_yc, start_date, step_in_date, value_date, end_dates) + curves = build_curves_dist(sn_quotes, args) + return curves, args |
