aboutsummaryrefslogtreecommitdiffstats
path: root/python
diff options
context:
space:
mode:
Diffstat (limited to 'python')
-rw-r--r--python/analytics/portfolio.py34
-rw-r--r--python/analytics/tranche_basket.py24
-rw-r--r--python/notebooks/tranche and swaption portfolio strategy.ipynb140
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,