diff options
Diffstat (limited to 'python')
| -rw-r--r-- | python/analytics/portfolio.py | 34 | ||||
| -rw-r--r-- | python/analytics/tranche_basket.py | 24 | ||||
| -rw-r--r-- | python/notebooks/tranche and swaption portfolio strategy.ipynb | 140 |
3 files changed, 157 insertions, 41 deletions
diff --git a/python/analytics/portfolio.py b/python/analytics/portfolio.py index 3b9e1a9a..eb932034 100644 --- a/python/analytics/portfolio.py +++ b/python/analytics/portfolio.py @@ -1,5 +1,6 @@ from .index import CreditIndex from .option import BlackSwaption +from .tranche_basket import DualCorrTranche from warnings import warn import pandas as pd import numpy as np @@ -23,7 +24,9 @@ def portf_repr(method): 'Theta': thousands, 'Vega': thousands, 'Vol': percent, - 'Ref': thousands}, + 'Ref': thousands, + 'Attach Rho': percent, + 'Detach Rho': percent}, 'index': False} if method == 'string': kwargs['line_width'] = 100 @@ -38,6 +41,7 @@ class Portfolio: self.trade_ids = trade_ids self.indices = [t for t in trades if isinstance(t, CreditIndex)] self.swaptions = [t for t in trades if isinstance(t, BlackSwaption)] + self.tranches = [t for t in trades if isinstance(t, DualCorrTranche)] value_dates = set(t.value_date for t in self.trades) self._keys = set([(index.index_type, index.series, index.tenor) for index in self.indices]) for swaption in self.swaptions: @@ -46,6 +50,13 @@ class Portfolio: if len(value_dates) >= 1: warn(f"not all instruments have the same trade date, picking {self._value_date}") + def add_trades(self, trades, trade_ids): + self.trades.append(trades) + self.trade_ids.append(trade_ids) + self.indices = [t for t in self.trades if isinstance(t, CreditIndex)] + self.swaptions = [t for t in self.trades if isinstance(t, BlackSwaption)] + self.tranches = [t for t in self.trades if isinstance(t, DualCorrTranche)] + def __iter__(self): for t in self.trades: yield t @@ -155,18 +166,27 @@ class Portfolio: def _todf(self): headers = ["Product", "Index", "Notional", "Ref", "Strike", "Direction", "Type", "Expiry", "Vol", "PV", "Delta", "Gamma", "Theta", - "Vega"] + "Vega", "attach", "detach", "Attach Rho", "Detach Rho"] rec = [] for t in self.trades: if isinstance(t, CreditIndex): name = f"{t.index_type}{t.series} {t.tenor}" - r = ("Index", name, - t.notional, t.ref, "N/A", t.direction, "N/A", "N/A", None, t.pv, 1., 0., t.theta, 0.) + 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) 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) + 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) + 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, + None, None, None, None, + t.attach, t.detach, t.rho[0], t.rho[1]) else: raise TypeError rec.append(r) diff --git a/python/analytics/tranche_basket.py b/python/analytics/tranche_basket.py index ff46c74e..f09cfedb 100644 --- a/python/analytics/tranche_basket.py +++ b/python/analytics/tranche_basket.py @@ -27,15 +27,14 @@ class DualCorrTranche(): value_date: pd.Timestamp=pd.Timestamp.today().normalize()): self._index = BasketIndex(index_type, series, [tenor], value_date=value_date) - # def get_quotes(self, spread): - # index = self. - - # maturity = self._index.maturities[0] - # return {maturity: - # self._snacpv(spread * 1e-4, self.coupon(maturity), self.recovery, - # maturity)} - # # monkey patch _get_quotes (don't try it at home) - # BasketIndex._get_quotes = get_quotes + def get_quotes(self, spread): + maturity = self.maturities[0] + return {maturity: + self._snacpv(spread * 1e-4, self.coupon(maturity), self.recovery, + maturity)} + # monkey patch _get_quotes (don't try it at home) + # but it works...: replaces _get_quotes with this simpler one + BasketIndex._get_quotes = get_quotes self.maturity = self._index.maturities[0] self.index_type = index_type self.series = series @@ -195,7 +194,7 @@ class DualCorrTranche(): return "\n".join(s) def shock(self, params=['pnl'], *, spread_shock, corr_shock, **kwargs): - orig_spread, orig_rho = self._index.spread()[0], self.rho + orig_rho = self.rho r = [] actual_params = [p for p in params if hasattr(self, p)] for ss in spread_shock: @@ -213,6 +212,11 @@ class DualCorrTranche(): index=pd.MultiIndex.from_product([spread_shock, corr_shock], names=['spread_shock', 'corr_shock'])) + def mark(self, **args): + #mark BasketIndex + #mark correlation + pass + class TrancheBasket(BasketIndex): def __init__(self, index_type: str, series: int, tenor: str, *, diff --git a/python/notebooks/tranche and swaption portfolio strategy.ipynb b/python/notebooks/tranche and swaption portfolio strategy.ipynb index 3bef916a..0e521ed8 100644 --- a/python/notebooks/tranche and swaption portfolio strategy.ipynb +++ b/python/notebooks/tranche and swaption portfolio strategy.ipynb @@ -12,14 +12,15 @@ "import matplotlib.pyplot as plt\n", "\n", "from analytics.scenarios import run_tranche_scenarios, run_portfolio_scenarios, run_tranche_scenarios_rolldown\n", - "from analytics import Swaption, BlackSwaption, Index, BlackSwaptionVolSurface, Portfolio, ProbSurface\n", + "from analytics import Swaption, BlackSwaption, CreditIndex, BlackSwaptionVolSurface, Portfolio, ProbSurface\n", + "from analytics import DualCorrTranche\n", "from db import dbengine\n", "from datetime import date\n", - "from graphics import plot_time_color_map\n", + "from graphics import plot_color_map\n", "\n", "dawnengine = dbengine('dawndb')\n", "\n", - "value_date = (pd.datetime.today() - pd.offsets.BDay(2)).date()" + "value_date = (pd.datetime.today() - pd.offsets.BDay(1)).date()" ] }, { @@ -31,7 +32,7 @@ "#Construct IG Swaption Portfolio\n", "index = 'IG'\n", "series = 30\n", - "option_delta = Index.from_name(index, series, '5yr')\n", + "option_delta = CreditIndex(index, series, '5yr', value_date=value_date)\n", "option_delta.spread = 65\n", "option1 = BlackSwaption(option_delta, date(2018, 10, 17), 60, option_type=\"payer\")\n", "option1.sigma = .398\n", @@ -43,8 +44,7 @@ "option2.notional = 300_000_000\n", "option_delta.notional = option1.notional * option1.delta + option2.notional * option2.delta\n", "\n", - "portf = Portfolio([option1, option2, option_delta])\n", - "portf.value_date = value_date\n", + "portf = Portfolio([option1, option2, option_delta], trade_ids=['opt1', 'opt2', 'delta'])\n", "portf.reset_pv()" ] }, @@ -67,8 +67,10 @@ "vs = BlackSwaptionVolSurface(index,series, value_date=value_date)\n", "ps = ProbSurface(index,series, value_date=value_date)\n", "vol_surface = vs[vs.list(option_type='payer')[-1]]\n", - "swaption_scens = run_portfolio_scenarios(portf, date_range, spread_shock, np.array([0]),\n", - " vol_surface, params=[\"pnl\", \"delta\"])\n", + "swaption_scens = run_portfolio_scenarios(portf, date_range, params=[\"pnl\", \"delta\"],\n", + " spread_shock=spread_shock,\n", + " vol_shock = np.array([0]),\n", + " vol_surface=vol_surface)\n", "#swaption delta is in protection terms: switch to risk terms\n", "swaption_scens.delta = -swaption_scens.delta" ] @@ -80,21 +82,30 @@ "outputs": [], "source": [ "#Get current Tranche positions\n", - "sql_string = \"select * from list_tranche_marks(%s)\"\n", - "pos = pd.read_sql_query(sql_string, dawnengine, params=(value_date,), parse_dates=['maturity'])\n", - "tranche_port = []\n", - "for i, r in pos.iterrows():\n", - " tranche_port.append(bkt.TrancheBasket(r.p_index, r.p_series, '5yr'))\n", - " tranche_port[i].build_skew()\n", - "pos['basket'] = tranche_port\n", - "#Set Shock Range\n", - "spread_range = (1+ spread_shock) * option_delta.spread\n", - "#Run tranche scenarios\n", - "temp = []\n", - "for i, r in pos.iterrows():\n", - " df = run_tranche_scenarios_rolldown(r.basket, spread_range, date_range, corr_map=False)\n", - " temp.append(r.notional*df.xs(str(r.attach) + \"-\" + str(r.detach), axis=1, level=1))\n", - "tranches_scens = sum(temp)" + "sql_string = (\"select sum(notional * case when protection='Buyer' then -1 else 1 end) as ntl, security_id, attach \"\n", + " \"from cds where swap_type='CD_INDEX_TRANCHE' and termination_cp is null group by security_id, notional, attach\")\n", + "open_pos = pd.read_sql_query(sql_string, dawnengine)\n", + "for i, r in open_pos[open_pos.ntl != 0].iterrows():\n", + " sql_string = \"select id from cds where security_id = %s and attach = %s\" \n", + " trade_ids = pd.read_sql_query(sql_string, dawnengine, params=[r.security_id, r.attach])\n", + " for i1, r1 in trade_ids.iterrows():\n", + " portf.add_trades(bkt.DualCorrTranche.from_tradeid(r1[0]), i1)\n", + "\n", + "#Set up portfolio scenarios\n", + "spread_shock = np.arange(-.05, .05, 0.05)\n", + "corr_shock = np.arange(-.1, .1, 0.1)\n", + "vol_shock = np.arange(-.1, .3, 0.5)\n", + "earliest_expiry = min(portf.swaptions, key=lambda x: x.exercise_date).exercise_date\n", + "date_range = pd.bdate_range(value_date, earliest_expiry - pd.offsets.BDay(), freq='20B')\n", + "vs = BlackSwaptionVolSurface(index,series, value_date=value_date)\n", + "ps = ProbSurface(index,series, value_date=value_date)\n", + "vol_surface = vs[vs.list(option_type='payer')[-1]]\n", + "portf.reset_pv()\n", + "scens = run_portfolio_scenarios(portf, date_range, params=[\"pnl\"],\n", + " spread_shock=spread_shock,\n", + " corr_shock=corr_shock,\n", + " vol_shock = vol_shock,\n", + " vol_surface=vol_surface)" ] }, { @@ -103,6 +114,15 @@ "metadata": {}, "outputs": [], "source": [ + "pos['basket'] = tranche_port\n", + "#Set Shock Range\n", + "spread_range = (1+ spread_shock) * option_delta.spread\n", + "#Run tranche scenarios\n", + "temp = []\n", + "for i, r in pos.iterrows():\n", + " df = run_tranche_scenarios_rolldown(r.basket, spread_range, date_range, corr_map=False)\n", + " temp.append(r.notional*df.xs(str(r.attach) + \"-\" + str(r.detach), axis=1, level=1))\n", + "tranches_scens = sum(temp)\n", "#Create snapshot of the the first scenario date\n", "total_scens = swaption_scens.reset_index().merge(tranches_scens.reset_index(), \n", " left_on=['date', 'spread'], \n", @@ -245,6 +265,78 @@ "metadata": {}, "outputs": [], "source": [ + "#IG Bullish Risk Reversal vs. shorting IG 7-15 risk\n", + "index = 'IG'\n", + "series = 30\n", + "option_delta = Index.from_name(index, series, '5yr', value_date)\n", + "option_delta.spread = 62\n", + "option1 = BlackSwaption(option_delta, date(2018, 9, 19), 60, option_type=\"receiver\")\n", + "option2 = BlackSwaption(option_delta, date(2018, 9, 19), 90, option_type=\"payer\")\n", + "option1.sigma = .344\n", + "option2.sigma = .585\n", + "option1.notional = 200_000_000\n", + "option2.notional = 400_000_000\n", + "option1.direction = 'Long'\n", + "option2.direction = 'Short'\n", + "option_delta.notional = 1\n", + "option_delta.direction = 'Seller' if option_delta.notional > 0 else 'Buyer'\n", + "option_delta.notional = abs(option_delta.notional)\n", + "portf = Portfolio([option1, option2, option_delta])\n", + "#Plot Scenarios Inputs: Portfolio, spread shock tightening%, spread shock widening%, snapshot period)\n", + "portf\n", + "\n", + "portf.reset_pv()\n", + "#Run Swaption sensitivities\n", + "#Set Shock range\n", + "shock_min = -.5\n", + "shock_max = 1\n", + "spread_shock = np.arange(shock_min, shock_max, 0.1)\n", + "#Set Date range\n", + "earliest_expiry = min(portf.swaptions, key=lambda x: x.exercise_date).exercise_date\n", + "date_range = pd.bdate_range(value_date, earliest_expiry - pd.offsets.BDay(), freq='10B')\n", + "#Setup Vol Surface\n", + "vs = BlackSwaptionVolSurface(index,series, value_date=value_date)\n", + "ps = ProbSurface(index,series, value_date=value_date)\n", + "vol_surface = vs[vs.list(option_type='payer')[-1]]\n", + "swaption_scens = run_portfolio_scenarios(portf, date_range, spread_shock, np.array([0]),\n", + " vol_surface, params=[\"pnl\", \"delta\"])\n", + "#swaption delta is in protection terms: switch to risk terms\n", + "swaption_scens.delta = -swaption_scens.delta\n", + "\n", + "notional = -100_000_000\n", + "t = bkt.TrancheBasket('IG', '29', '5yr')\n", + "t.build_skew()\n", + "spread_range = (1+ spread_shock) * option_delta.spread\n", + "tranches_scens = run_tranche_scenarios_rolldown(t, spread_range, date_range, corr_map=False)\n", + "tranches_scens = notional*tranches_scens.xs('7-15', axis=1, level=1)\n", + "\n", + "#Create snapshot of the the first scenario date\n", + "total_scens = swaption_scens.reset_index().merge(tranches_scens.reset_index(), \n", + " left_on=['date', 'spread'], \n", + " right_on=['date', 'spread_range'], \n", + " suffixes=['_s', '_t'])\n", + "total_scens['pnl'] = total_scens['pnl_s'] + total_scens['pnl_t']\n", + "total_scens['delta'] = total_scens['delta_s'] + total_scens['delta_t']\n", + "total_scens_single_date = total_scens.set_index('date').xs(date_range[0])\n", + "total_scens_single_date = total_scens_single_date.set_index('spread', drop=True)\n", + "\n", + "#tranche positions delta at different spreads\n", + "ax = total_scens_single_date.delta_t.plot(title = 'delta vs. spread levels')\n", + "ax.ticklabel_format(style='plain')\n", + "plt.tight_layout()\n", + "\n", + "#Tranche + Swaptions positions delta at different spreads\n", + "ax1 = total_scens_single_date.delta.plot()\n", + "ax1.ticklabel_format(style='plain')\n", + "plt.tight_layout()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ "dawnengine.dispose()" ] }, @@ -272,7 +364,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.6.5" + "version": "3.6.6" } }, "nbformat": 4, |
