diff options
Diffstat (limited to 'python/notebooks/tranche and swaption portfolio strategy.ipynb')
| -rw-r--r-- | python/notebooks/tranche and swaption portfolio strategy.ipynb | 287 |
1 files changed, 201 insertions, 86 deletions
diff --git a/python/notebooks/tranche and swaption portfolio strategy.ipynb b/python/notebooks/tranche and swaption portfolio strategy.ipynb index 1b0e0a08..c0355fb0 100644 --- a/python/notebooks/tranche and swaption portfolio strategy.ipynb +++ b/python/notebooks/tranche and swaption portfolio strategy.ipynb @@ -18,7 +18,7 @@ "from datetime import date\n", "from graphics import plot_color_map\n", "\n", - "value_date = (pd.datetime.today() - pd.offsets.BDay(2)).date()" + "value_date = (pd.datetime.today() - pd.offsets.BDay(1)).date()" ] }, { @@ -27,7 +27,50 @@ "metadata": {}, "outputs": [], "source": [ - "#Construct IG Swaption Portfolio\n", + "def color_plots(portf, scens, options_names):\n", + " sort_order = [True, False]\n", + " scens_pnl = scens.xs('pnl', axis=1, level=1)\n", + " just_spread = scens_pnl.xs((0,0), level=['corr_shock', 'vol_shock'])\n", + " combined = just_spread.sum(axis=1)\n", + " combined.name = 'combined_pnl'\n", + " plot_color_map(combined, sort_order)\n", + "\n", + " swaptions_only = just_spread[options_names].sum(axis=1)\n", + " swaptions_only.name = 'swaptions_pnl'\n", + " plot_color_map(swaptions_only, sort_order)\n", + "\n", + " tranches_only = just_spread[[x for x in portf.trade_ids if x not in options_names]].sum(axis=1)\n", + " tranches_only.name = 'tranches_pnl'\n", + " plot_color_map(tranches_only, sort_order)\n", + "\n", + " #Plot delta, swaption delta is in protection terms: switch to risk terms\n", + " sort_order = [True, False]\n", + " scens_delta = scens.xs('delta', axis=1, level=1)\n", + " scens_delta = scens_delta.mul(pd.Series(portf.notionals))\n", + " if 'delta' in portf.trade_ids:\n", + " scens_delta['delta'] = portf.notionals['delta']\n", + " scens_delta = scens_delta.xs((0,0), level=['corr_shock', 'vol_shock'])\n", + "\n", + " combined = scens_delta.sum(axis=1)\n", + " combined.name = 'Combined Delta'\n", + " plot_color_map(combined, sort_order)\n", + "\n", + " swaptions_only = scens_delta[options_names].sum(axis=1)\n", + " swaptions_only.name = 'Swaptions Only Delta'\n", + " plot_color_map(swaptions_only, sort_order)\n", + "\n", + " tranches_only = scens_delta[[x for x in portf.trade_ids if x not in options_names]].sum(axis=1)\n", + " tranches_only.name = 'Tranches Only Delta'\n", + " plot_color_map(tranches_only, sort_order)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "#Package 1\n", "index = 'IG'\n", "series = 30\n", "option_delta = CreditIndex(index, series, '5yr', value_date=value_date)\n", @@ -42,22 +85,17 @@ "option2.notional = 300_000_000\n", "option_delta.notional = option1.notional * option1.delta + option2.notional * option2.delta\n", "\n", - "#Get current Tranche positions\n", - "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", - " \"FROM cds WHERE swap_type='CD_INDEX_TRANCHE' AND termination_cp IS NULL\")\n", - "conn = dbconn('dawndb')\n", - "with conn.cursor() as c:\n", - " c.execute(sql_string)\n", - " trade_ids = [dealid for dealid, ntl in c if ntl != 0]\n", - "portf = Portfolio([DualCorrTranche.from_tradeid(dealid) for dealid in trade_ids],\n", - " trade_ids)\n", + "equity = DualCorrTranche('IG', 29, '5yr', attach=0, detach=3, corr_attach=np.nan, \n", + " corr_detach=.35, tranche_running=100, notional=-40000000, use_trunc=True)\n", + "mezz = DualCorrTranche('IG', 29, '5yr', attach=7, detach=15, corr_attach=.45, \n", + " corr_detach=.55, tranche_running=100, notional=240000000, use_trunc=True)\n", + "portf = Portfolio([equity, mezz], ['equity', 'mezz'])\n", "portf.trades.extend([option1, option2, option_delta])\n", "portf.trade_ids.extend(['opt1', 'opt2', 'delta'])\n", "\n", - "spread_shock = np.arange(-.3, 1.1, .1)\n", + "spread_shock = np.round(np.arange(-.3, 1, .1),2)\n", "corr_shock = np.arange(0, .1, 0.1)\n", - "vol_shock = np.arange(-.1, .3, 0.1)\n", + "vol_shock = np.arange(0, 0.1, 0.1)\n", "earliest_expiry = min(portf.swaptions, key=lambda x: x.exercise_date).exercise_date\n", "date_range = pd.date_range(value_date, earliest_expiry, periods=5)\n", "vs = BlackSwaptionVolSurface(index, series, value_date=value_date)\n", @@ -67,12 +105,14 @@ "portf.mark()\n", "portf.reset_pv()\n", "\n", - "scens = run_portfolio_scenarios(portf, date_range, params=[\"pnl\"],\n", + "scens = run_portfolio_scenarios(portf, date_range, params=[\"pnl\", 'delta'],\n", " spread_shock=spread_shock,\n", " corr_shock=corr_shock,\n", " vol_shock=vol_shock,\n", " vol_surface=vol_surface)\n", - "scens = round(scens,2)" + "scens = round(scens,2)\n", + "\n", + "color_plots(portf, scens, ['opt1', 'opt2', 'delta'])" ] }, { @@ -81,11 +121,41 @@ "metadata": {}, "outputs": [], "source": [ - "sort_order = [True, False]\n", - "output = scens.xs((0,0), level=['corr_shock', 'vol_shock']).sum(axis=1)\n", - "(1+output.index.get_level_values(1)) * portf.swaptions[0].ref\n", - "output.name = 'pnl'\n", - "plot_color_map(output, sort_order)" + "#simple IG package: sell OTM swaption vs. short 3-7 delta neutral at start\n", + "index = 'IG'\n", + "series = 30\n", + "option_delta = CreditIndex(index, series, '5yr', value_date=value_date)\n", + "option_delta.spread = 60\n", + "option2 = BlackSwaption(option_delta, date(2018, 11, 21), 85, option_type=\"payer\")\n", + "option2.sigma = .588\n", + "option2.direction = 'Short'\n", + "option2.notional = 500_000_000\n", + "option_delta.notional = 1\n", + "\n", + "mezz = DualCorrTranche('IG', 29, '5yr', attach=7, detach=15, corr_attach=.45, \n", + " corr_detach=.55, tranche_running=100, notional=50000000, use_trunc=True)\n", + "portf = Portfolio([option2, mezz], ['opt2', 'mezz'])\n", + "\n", + "spread_shock = np.round(np.arange(-.3, 1, .1),2)\n", + "corr_shock = np.arange(0, .1, 0.1)\n", + "vol_shock = np.arange(0, 0.1, 0.1)\n", + "earliest_expiry = min(portf.swaptions, key=lambda x: x.exercise_date).exercise_date\n", + "date_range = pd.date_range(value_date, earliest_expiry, periods=5)\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.value_date = value_date\n", + "portf.mark()\n", + "portf.reset_pv()\n", + "\n", + "scens = run_portfolio_scenarios(portf, date_range, params=[\"pnl\", 'delta'],\n", + " spread_shock=spread_shock,\n", + " corr_shock=corr_shock,\n", + " vol_shock=vol_shock,\n", + " vol_surface=vol_surface)\n", + "scens = round(scens,2)\n", + "\n", + "color_plots(portf, scens, ['opt2'])" ] }, { @@ -94,6 +164,48 @@ "metadata": {}, "outputs": [], "source": [ + "#simple HY package: sell OTM swaption vs. short 3-7 delta neutral at start\n", + "index = 'HY'\n", + "series = 30\n", + "option_delta = CreditIndex(index, series, '5yr', value_date=value_date)\n", + "option_delta.price = 106.75\n", + "option2 = BlackSwaption(option_delta, date(2018, 11, 21), 102, option_type=\"payer\")\n", + "option2.sigma = .469\n", + "option2.direction = 'Short'\n", + "option2.notional = 200_000_000\n", + "\n", + "mezz = DualCorrTranche('HY', 29, '5yr', attach=15, detach=25, corr_attach=.35, \n", + " corr_detach=.45, tranche_running=100, notional=16000000, use_trunc=True)\n", + "portf = Portfolio([option2, mezz], ['opt2', 'mezz'])\n", + "\n", + "spread_shock = np.round(np.arange(-.3, 1, .1),2)\n", + "corr_shock = np.arange(0, .1, 0.1)\n", + "vol_shock = np.arange(0, 0.1, 0.1)\n", + "earliest_expiry = min(portf.swaptions, key=lambda x: x.exercise_date).exercise_date\n", + "date_range = pd.date_range(value_date, earliest_expiry, periods=5)\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.value_date = value_date\n", + "portf.mark()\n", + "portf.reset_pv()\n", + "\n", + "scens = run_portfolio_scenarios(portf, date_range, params=[\"pnl\", 'delta'],\n", + " spread_shock=spread_shock,\n", + " corr_shock=corr_shock,\n", + " vol_shock=vol_shock,\n", + " vol_surface=vol_surface)\n", + "scens = round(scens,2)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "(1+output.index.get_level_values(1)) * portf.swaptions[0].ref\n", + "\n", "#negative notional == sell protection\n", "hy_tranche = DualCorrTranche('HY', 29, '5yr', attach=0, detach=15, corr_attach=np.nan, \n", " corr_detach=.35, tranche_running=500, notional=-10000000)\n", @@ -112,15 +224,60 @@ "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", + " \"FROM cds WHERE swap_type='CD_INDEX_TRANCHE' AND termination_cp IS NULL\")\n", + "swaption_sql_string = (\"select id, security_desc from swaptions where date(expiration_date) \"\n", + " \"> %s and swap_type = 'CD_INDEX_OPTION'\")\n", + "index_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", + " \"FROM cds WHERE swap_type='CD_INDEX' AND termination_cp IS null and folder = 'IGOPTDEL'\")\n", + "conn = dbconn('dawndb')\n", + "with conn.cursor() as c:\n", + " c.execute(t_sql_string)\n", + " t_trade_ids = [dealid for dealid, ntl in c if ntl != 0]\n", + " c.execute(swaption_sql_string, (value_date,))\n", + " swaption_trades = c.fetchall()\n", + " c.execute(index_sql_string)\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', value_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", + " portf.add_trade(CreditIndex.from_tradeid(index_id), 'index_' + str(index_id))\n", + " \n", + "spread_shock = np.round(np.arange(-.3, 1, .1),2)\n", + "corr_shock = np.arange(0, .1, 0.1)\n", + "vol_shock = np.arange(0, 0.1, 0.1)\n", + "#date_range = pd.date_range(value_date, date(2018,12,31), periods=5)\n", + "earliest_expiry = min(portf.swaptions, key=lambda x: x.exercise_date).exercise_date\n", + "date_range = pd.date_range(value_date, earliest_expiry, periods=5)\n", + "portf.swaptions[0]\n", + "vs = BlackSwaptionVolSurface(portf.swaptions[0].index.index_type, portf.swaptions[0].index.series, value_date=value_date)\n", + "vol_surface = vs[vs.list(option_type='payer')[-1]]\n", + "portf.value_date = value_date\n", + "portf.mark()\n", + "portf.reset_pv()\n", "\n", - "##\n", - "scens_more = run_portfolio_scenarios(portf, date_range, params=['pnl', 'delta'],\n", + "scens = run_portfolio_scenarios(portf, date_range, params=[\"pnl\", 'delta'],\n", " spread_shock=spread_shock,\n", " corr_shock=corr_shock,\n", " vol_shock=vol_shock,\n", " vol_surface=vol_surface)\n", - "#swaption delta is in protection terms: switch to risk terms\n", - "swaption_scens.delta = -swaption_scens.delta" + "\n", + "color_plots(portf, scens, ['opt_20', 'opt_21', 'index_954'])\n", + "\n", + "#sort_order = [True, False]\n", + "#scens_pnl = scens.xs('pnl', axis=1, level=1)\n", + "#just_spread = scens_pnl.xs(0, level=1)\n", + "#combined = just_spread.sum(axis=1)\n", + "#combined.name = 'tranche_pnl'\n", + "#plot_color_map(combined, sort_order)" ] }, { @@ -263,7 +420,7 @@ "swaption_scens.delta = -swaption_scens.delta\n", "\n", "notional = 30_000_000_000\n", - "t = bkt.TrancheBasket('IG', '29', '3yr')\n", + "t = bkt.TrancheBasket('IG', '29', '5yr')\n", "t.build_skew()\n", "#get back to 17bps, .36 delta\n", "port_spread = 67\n", @@ -280,70 +437,28 @@ "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", + "series = 29\n", + "ss = DualCorrTranche('IG', 29, '5yr', attach=15, detach=100, corr_attach=.59685, \n", + " corr_detach=.7, tranche_running=100, notional=-230000000)\n", + "mezz = DualCorrTranche('IG', 29, '5yr', attach=7, detach=15, corr_attach=.46984, \n", + " corr_detach=.59685, tranche_running=100, notional=50000000)\n", + "portf = Portfolio([ss, mezz], ['ss', 'mezz'])\n", "\n", + "spread_shock = np.round(np.arange(-.3, 1, .1), 2)\n", + "corr_shock = np.arange(0, .1, 0.1)\n", + "vol_shock = np.arange(0, 0.1, 0.1)\n", + "date_range = pd.date_range(value_date, date(2018,12,31), periods=5)\n", + "#portf.value_date = value_date\n", + "#portf.mark()\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", + "scens = run_portfolio_scenarios(portf, date_range, params=[\"pnl\", 'delta'],\n", + " spread_shock=spread_shock,\n", + " corr_shock=corr_shock)\n", + "scens = round(scens,2)\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()" + "color_plots(portf, scens, ['opt1', 'opt2', 'delta'])" ] }, { @@ -379,7 +494,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.7.0" + "version": "3.7.1" } }, "nbformat": 4, |
