aboutsummaryrefslogtreecommitdiffstats
path: root/python
diff options
context:
space:
mode:
Diffstat (limited to 'python')
-rw-r--r--python/analytics/__init__.py1
-rw-r--r--python/analytics/basket_index.py104
-rw-r--r--python/cds_curve.py143
-rw-r--r--python/index_data.py50
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