diff options
| -rw-r--r-- | python/analytics/__init__.py | 4 | ||||
| -rw-r--r-- | python/analytics/index.py | 9 | ||||
| -rw-r--r-- | python/analytics/option.py | 11 | ||||
| -rw-r--r-- | python/analytics/portfolio.py | 12 | ||||
| -rw-r--r-- | python/analytics/tranche_basket.py | 24 | ||||
| -rw-r--r-- | python/notebooks/VaR.ipynb | 40 |
6 files changed, 75 insertions, 25 deletions
diff --git a/python/analytics/__init__.py b/python/analytics/__init__.py index 39c0557b..e499288a 100644 --- a/python/analytics/__init__.py +++ b/python/analytics/__init__.py @@ -5,3 +5,7 @@ from .portfolio import Portfolio from .basket_index import MarkitBasketIndex from .tranche_basket import DualCorrTranche, TrancheBasket from .ir_swaption import IRSwaption + +_ontr = CreditIndex('HY', 31, '5yr') +_ontr.mark() +_beta = {'HY': 1, 'IG': .3} diff --git a/python/analytics/index.py b/python/analytics/index.py index 3130398a..4c841a74 100644 --- a/python/analytics/index.py +++ b/python/analytics/index.py @@ -1,6 +1,7 @@ import array import datetime import pandas as pd +import analytics from .credit_default_swap import CreditDefaultSwap from .db import _engine, dbengine, DataError @@ -8,7 +9,6 @@ from bbg_helpers import BBG_IP, retrieve_data, init_bbg_session from pandas.tseries.offsets import BDay from pyisda.curve import SpreadCurve - def g(index, spread, exercise_date, pv=None): """computes the strike clean price using the expected forward yield curve. """ step_in_date = exercise_date + datetime.timedelta(days=1) @@ -113,6 +113,13 @@ class CreditIndex(CreditDefaultSwap): return instance @property + def hy_equiv(self): + risk = self.notional * self.risky_annuity / analytics._ontr.risky_annuity + if self.index_type != 'HY': + risk *= analytics._beta[self.index_type] + return -risk if self.direction == 'Buyer' else risk + + @property def ref(self): if self._quote_is_price: return self.price diff --git a/python/analytics/option.py b/python/analytics/option.py index eecb9997..2930796e 100644 --- a/python/analytics/option.py +++ b/python/analytics/option.py @@ -3,6 +3,8 @@ import datetime import math import numpy as np import pandas as pd +import analytics + from db import dbengine from .black import black, Nx @@ -262,13 +264,20 @@ class BlackSwaption(ForwardIndex): self._update() notional_ratio = self.index.notional / self.notional dv01 = self.pv - old_pv - delta = -self.index._direction * dv01 * notional_ratio / \ + delta = self.index._direction * dv01 * notional_ratio / \ (self.index.pv - old_index_pv) self.index.spread = old_spread self._update() return delta @property + def hy_equiv(self): + risk = self.delta * abs(self.index.hy_equiv/ \ + self.index.notional) * self.notional + risk *= -1 if self.option_type == 'payer' else 1 + return -risk if self.direction == 'Short' else risk + + @property def T(self): if self._T: return self._T diff --git a/python/analytics/portfolio.py b/python/analytics/portfolio.py index a4ed393d..ef1d5578 100644 --- a/python/analytics/portfolio.py +++ b/python/analytics/portfolio.py @@ -166,10 +166,14 @@ class Portfolio: def theta(self): return sum(t.theta for t in self.trades) + @property + def hy_equiv(self): + return sum(t.hy_equiv for t in self.trades) + def _todf(self): headers = ["Product", "Index", "Notional", "Ref", "Strike", "Direction", "Type", "Expiry", "Vol", "PV", "Delta", "Gamma", "Theta", - "Vega", "attach", "detach", "Attach Rho", "Detach Rho"] + "Vega", "attach", "detach", "Attach Rho", "Detach Rho", "HY Equiv"] rec = [] for t in self.trades: if isinstance(t, CreditIndex): @@ -177,19 +181,19 @@ class Portfolio: r = ("Index", name, t.notional, t.ref, "N/A", t.direction, "N/A", "N/A", None, t.pv, 1., 0., t.theta, 0., - None, None, None, None) + None, None, None, None, t.hy_equiv) elif isinstance(t, BlackSwaption): name = f"{t.index.index_type}{t.index.series} {t.index.tenor}" r = ("Swaption", name, t.notional, t.ref, t.strike, t.direction, t.option_type, t.forward_date, t.sigma, t.pv, t.delta, t.gamma, t.theta, t.vega, - None, None, None, None) + None, None, None, None, t.hy_equiv) elif isinstance(t, DualCorrTranche): name = f"{t.index_type}{t.series} {t.tenor}" r = ("Tranche", name, t.notional, None, None, t.direction, None, None, None, t.upfront, t.delta, t.gamma, None, None, - t.attach, t.detach, t.rho[0], t.rho[1]) + t.attach, t.detach, t.rho[0], t.rho[1], t.hy_equiv) else: raise TypeError rec.append(r) diff --git a/python/analytics/tranche_basket.py b/python/analytics/tranche_basket.py index dab528c6..1af4d4eb 100644 --- a/python/analytics/tranche_basket.py +++ b/python/analytics/tranche_basket.py @@ -13,9 +13,11 @@ from pyisda.date import cds_accrued from scipy.optimize import brentq from scipy.interpolate import CubicSpline, PchipInterpolator from scipy.special import logit, expit + import datetime import pandas as pd import numpy as np +import analytics class DualCorrTranche(): @@ -175,7 +177,8 @@ class DualCorrTranche(): @property def pv(self): - return self._pv() + pl, cl = self._pv() + return float(-pl - cl + self._accrued) def _pv(self, epsilon=0.): """ computes coupon leg, protection leg and bond price. @@ -192,7 +195,7 @@ class DualCorrTranche(): dK = np.diff(self.K) pl = np.diff(pl) / dK cl = np.diff(cl) / dK * self.tranche_running * 1e-4 - return float(-pl - cl + self._accrued) + return pl, cl @property def upfront(self): @@ -246,7 +249,7 @@ class DualCorrTranche(): self._index.tweak_portfolio(ss, self.maturity, False) for corrs in corr_shock: #also need to map skew - self.rho = np.fmin(1, orig_rho * (1 + corrs)) + self.rho = orig_rho * (1 + corrs) r.append([getattr(self, p) for p in actual_params]) self._index.curves = orig_curves self.rho = orig_rho @@ -283,6 +286,18 @@ class DualCorrTranche(): self._index.factor @property + def duration(self): + return self._pv()[1] - self._accrued + + @property + def hy_equiv(self): + risk = self.notional * self.delta * float(self._index.duration()) / \ + analytics._ontr.risky_annuity + if self.index_type != 'HY': + risk *= analytics._beta[self.index_type] + return risk + + @property def delta(self): calc = self._greek_calc() factor = self.tranche_factor / self._index.factor @@ -306,7 +321,8 @@ class DualCorrTranche(): bp = [self.pv] for tweak in [eps, -eps, 2*eps]: indexbp.append(self.tranche_legs(1., None, tweak).bond_price) - bp.append(self._pv(tweak)) + pl, cl = self._pv(tweak) + bp.append(float(-pl - cl + self._accrued)) return {'indexbp': indexbp, 'bp': bp} class TrancheBasket(BasketIndex): diff --git a/python/notebooks/VaR.ipynb b/python/notebooks/VaR.ipynb index 2fa3ee1d..de2c26a1 100644 --- a/python/notebooks/VaR.ipynb +++ b/python/notebooks/VaR.ipynb @@ -28,7 +28,7 @@ "metadata": {}, "outputs": [], "source": [ - "date = (datetime.date.today() - pd.tseries.offsets.BDay(1)).date()\n", + "date = (datetime.date.today() - pd.tseries.offsets.BDay(3)).date()\n", "report_date = (date + pd.tseries.offsets.BMonthEnd(-1)).date()\n", "index_type = \"IG\"\n", "quantile = .025" @@ -127,6 +127,17 @@ "metadata": {}, "outputs": [], "source": [ + "position_date = (datetime.date.today() - pd.tseries.offsets.BDay(1)).date()\n", + "shock_date = (datetime.date.today() - pd.tseries.offsets.BDay(1)).date()\n", + "(position_date, shock_date)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "#Current tranche and swaptions positions\n", "t_sql_string = (\"SELECT id, sum(notional * case when protection='Buyer' then -1 else 1 end) \"\n", " \"OVER (partition by security_id, attach) AS ntl_agg \"\n", @@ -142,19 +153,19 @@ " \"AND trade_date <= %s\")\n", "with conn.cursor() as c:\n", " #Get Tranche Trade Ids\n", - " c.execute(t_sql_string, (date,))\n", + " c.execute(t_sql_string, (position_date,))\n", " t_trade_ids = [dealid for dealid, ntl in c if ntl != 0]\n", " #Get Swaption Trade Ids\n", - " c.execute(swaption_sql_string, (date, date))\n", + " c.execute(swaption_sql_string, (position_date, position_date))\n", " swaption_trades = c.fetchall()\n", " #Get Index/deltas Trade Ids\n", - " c.execute(index_sql_string, (date,))\n", + " c.execute(index_sql_string, (position_date,))\n", " index_trade_ids = [dealid for dealid, ntl in c if ntl != 0]\n", " \n", "portf = Portfolio([DualCorrTranche.from_tradeid(dealid) for dealid in t_trade_ids],\n", " t_trade_ids)\n", "for row in swaption_trades:\n", - " option_delta = CreditIndex(row[1].split()[1], row[1].split()[3][1:], '5yr', date)\n", + " option_delta = CreditIndex(row[1].split()[1], row[1].split()[3][1:], '5yr', position_date)\n", " option_delta.mark()\n", " portf.add_trade(BlackSwaption.from_tradeid(row[0], option_delta), 'opt_' + str(row[0]))\n", "for index_id in index_trade_ids:\n", @@ -162,30 +173,29 @@ " \n", "#Update manually - positive notional = long risk\n", "non_trancheSwap_risk_notional = 49119912 \n", - "\n", - "portf.add_trade(CreditIndex('HY', on_the_run('HY'), '5yr', value_date = date, notional = -non_trancheSwap_risk_notional), 'bond')\n", + "portf.add_trade(CreditIndex('HY', on_the_run('HY'), '5yr', value_date = shock_date, notional = -non_trancheSwap_risk_notional), 'bond')\n", " \n", - "portf.value_date = date\n", + "portf.value_date = shock_date\n", "portf.mark(interp_method=\"bivariate_linear\")\n", "portf.reset_pv()\n", "\n", "vol_surface = {}\n", "for trade in portf.swaptions:\n", " vs = BlackSwaptionVolSurface(trade.index.index_type, trade.index.series, \n", - " value_date=date, interp_method = \"bivariate_linear\")\n", + " value_date=shock_date, interp_method = \"bivariate_linear\")\n", " vol_surface[trade.index.index_type + trade.index.series] = vs[vs.list(option_type='payer')[-1]]\n", "vol_shock = [0]\n", "corr_shock = [0]\n", - "spread_shock = widen + tighten\n", - "date_range = [pd.Timestamp(date)]\n", + "spread_shock = tighten + [0] + widen\n", + "date_range = [pd.Timestamp(shock_date)]\n", "\n", - "scens = run_portfolio_scenarios(portf, date_range, params=[\"pnl\"],\n", + "scens = run_portfolio_scenarios(portf, date_range, params=[\"pnl\", \"hy_equiv\"],\n", " spread_shock=spread_shock,\n", " vol_shock=vol_shock,\n", " corr_shock=corr_shock,\n", " vol_surface=vol_surface)\n", "\n", - "scens.sum(axis=1)" + "scens.xs('pnl', level=1).sum(axis=1)" ] }, { @@ -202,8 +212,8 @@ " vol_surface=vol_surface)\n", "scens.sum(axis=1)\n", "\n", - "risk_notional = [t.notional * t._index.duration for t in portf.indices]\n", - "portf.trades[0]._index.duration()" + "#risk_notional = [t.notional * t._index.duration for t in portf.indices]\n", + "#portf.trades[0]._index.duration()" ] }, { |
