aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--python/analytics/curve_trades.py (renamed from python/exploration/curve_trades.py)138
-rw-r--r--python/analytics/scenarios.py30
-rw-r--r--python/notebooks/Curve Trades.ipynb120
3 files changed, 231 insertions, 57 deletions
diff --git a/python/exploration/curve_trades.py b/python/analytics/curve_trades.py
index d17b35ed..b6c17436 100644
--- a/python/exploration/curve_trades.py
+++ b/python/analytics/curve_trades.py
@@ -3,16 +3,18 @@ from db import dbengine
from analytics import Index, Portfolio
from analytics.utils import roll_date
from dateutil.relativedelta import relativedelta
+from analytics.basket_index import MarkitBasketIndex
+from statsmodels.sandbox.regression.predstd import wls_prediction_std
+from scipy.interpolate import interp1d
+from itertools import chain
+from copy import deepcopy
+
import pandas as pd
import math
import statsmodels.formula.api as smf
-from analytics.basket_index import MarkitBasketIndex
import numpy as np
import matplotlib.pyplot as plt
-from statsmodels.sandbox.regression.predstd import wls_prediction_std
-from scipy.interpolate import interp1d
-
serenitasdb = dbengine('serenitasdb')
dawndb = dbengine('dawndb')
@@ -65,22 +67,6 @@ def ratio_within_series(index='IG', rolling=6, param='duration'):
df = df.reset_index(level=['index', 'version'], drop=True)
return df
-def curve_3_5_10(df):
- """
- Parameters
- ----------
- df: duration ratio within series"""
- #buy 3y, sell 5y, buy 10y
- s = - df.theta2['3yr'] / df.duration_ratio_to_5yr['3yr'] \
- + 2 * df.theta2['5yr'] \
- - df.theta2['10yr'] / df.duration_ratio_to_5yr['10yr']
- s.dropna().unstack(-1).plot()
-
-def curve_5_10(df):
- #buy sell 5y, buy 10y
- s = df.theta2['5yr'] - df.theta2['10yr'] / df.duration_ratio_to_5yr['10yr']
- s.dropna().unstack(-1).plot()
-
def on_the_run_theta(index='IG', rolling=6):
otr = on_the_run(index)
df = get_index_quotes(index, list(range(otr - rolling, otr + 1)),
@@ -206,22 +192,25 @@ def spread_fin_crisis(index='IG'):
plt.show()
-def forward_spread(index='IG', series=None, tenors=['3yr', '5yr', '7yr', '10yr']):
+def forward_spread(report_date, index='IG', series=None, tenors=['3yr', '5yr', '7yr', '10yr']):
if series is None:
series = on_the_run(index = index)
- b_index = MarkitBasketIndex(index, series, tenors)
+ b_index = MarkitBasketIndex(index, series, tenors, value_date=report_date)
b_index.tweak()
f_spread = []
date_range = pd.bdate_range(pd.datetime.today(), max(b_index.maturities), freq='M')
for d in date_range.date:
- b_index.trade_date = d
+ b_index.value_date = d
f_spread.append(b_index.spread())
return pd.concat(f_spread, keys=date_range).unstack(-1)
def spot_forward(index='IG', series=None, tenors=['3yr', '5yr', '7yr', '10yr']):
+ '''
+ Calculates the 1-year forward spot rate '''
+
if series is None:
series = on_the_run(index)
b_index = MarkitBasketIndex(index, series, tenors)
@@ -239,13 +228,13 @@ def spot_forward(index='IG', series=None, tenors=['3yr', '5yr', '7yr', '10yr']):
'1yr': [0., 0.]}, index=['0yr', '1yr'])
df_0.index.name = 'tenor'
df = df_0.append(df)
- df['maturity'] = [b_index.trade_date, maturity_1yr] + b_index.maturities
+ df['maturity'] = [b_index.value_date, maturity_1yr] + b_index.maturities
return df.reset_index().set_index('maturity')
-def curve_pos(trade_date, index='IG'):
+def curve_pos(value_date, index='IG'):
'''
- trade_date : :class:`datetime.date`
+ value_date : :class:`datetime.date`
index : string
one of 'IG', 'HY' or 'ITRX'
@@ -253,7 +242,7 @@ def curve_pos(trade_date, index='IG'):
sql_string = "SELECT * FROM cds where trade_date < %s"
df = pd.read_sql_query(sql_string, dawndb, parse_dates=['trade_date', 'maturity'],
- params=[trade_date])
+ params=[value_date])
df = df[df['folder'] == f'SER_{index}CURVE']
df.notional = df.notional.where(df.protection == 'Seller', -df.notional)
df = df.groupby(['security_id', 'maturity'])['notional'].sum()
@@ -268,18 +257,18 @@ def curve_pos(trade_date, index='IG'):
sql_string = "SELECT closespread FROM index_quotes where index = %s and series = %s and tenor = %s and date = %s"
for i, row in df[['index', 'tenor', 'series', 'notional']].iterrows():
temp = Index.from_name(row['index'], row.series, row.tenor)
- temp.value_date = trade_date
+ temp.value_date = value_date.date()
if row.notional > 0:
temp.direction = 'Seller'
temp.notional = abs(row.notional)
spread_df = pd.read_sql_query(sql_string, serenitasdb,
- params=[row['index'], row.series, row.tenor, trade_date])
+ params=[row['index'], row.series, row.tenor, value_date.date()])
temp.spread = spread_df.iloc[0][0]
indices.append(temp)
return Portfolio(indices)
-def curve_shape(trade_date, index='IG', percentile=.95):
+def curve_shape(value_date, index='IG', percentile=.95, spread=None):
'''
Returns a function to linearly interpolate between the curve based on maturity (in years)'''
@@ -288,15 +277,94 @@ def curve_shape(trade_date, index='IG', percentile=.95):
steepness = (curve_shape['10yr']/curve_shape['3yr'])
series = on_the_run(index)
- sql_string = "SELECT closespread FROM index_quotes where index = %s and series = %s and tenor = %s and date = %s"
- spread_df = pd.read_sql_query(sql_string, serenitasdb,
- params=[index, series, '5yr', trade_date.date()])
+ if spread is None:
+ sql_string = "SELECT closespread FROM index_quotes where index = %s and series = %s and tenor = %s and date = %s"
+ spread_df = pd.read_sql_query(sql_string, serenitasdb,
+ params=[index, series, '5yr', value_date.date()])
+ spread = spread_df.iloc[0][0]
sql_string = "SELECT tenor, maturity FROM index_maturity where index = %s and series = %s"
lookup_table = pd.read_sql_query(sql_string, serenitasdb, parse_dates=['maturity'], params=[index, series])
df = curve_shape[steepness == steepness.quantile(percentile, 'nearest')]
- df = df * spread_df.iloc[0][0]/df['5yr'][0]
+ df = df * spread/df['5yr'][0]
df = df.stack().rename('spread')
df = df.reset_index().merge(lookup_table, on=['tenor'])
- df['year_frac'] = (df.maturity - pd.to_datetime(trade_date)).dt.days/365
+ df['year_frac'] = (df.maturity - pd.to_datetime(value_date)).dt.days/365
return interp1d(np.hstack([0, df.year_frac]), np.hstack([0, df.spread]))
+
+def pos_pnl_abs(portf, value_date, index='IG', rolling=6, years=3):
+
+ '''
+ Runs PNL analysis on portf using historical on-the-run spread levels - off-the-runs spreads are duration linearly interpolated'''
+
+ series = on_the_run(index)
+ df = get_index_quotes(index, list(range(series - rolling, series + 1)),
+ tenor=['3yr', '5yr', '7yr', '10yr'], years=years)
+ df = df.groupby(level=['date', 'tenor']).nth(-1)['closespread'].unstack(-1)
+
+ sql_string = "SELECT tenor, maturity FROM index_maturity where index = %s and series = %s"
+ lookup_table = pd.read_sql_query(sql_string, serenitasdb, parse_dates=['maturity'], params=[index, series])
+ lookup_table['year_frac'] = (lookup_table.maturity - pd.to_datetime(value_date)).dt.days/365
+
+ indices = []
+ for i, ind in enumerate(portf.indices):
+ indices.append(deepcopy(ind))
+ portf_copy = Portfolio(indices)
+ portf_copy.reset_pv()
+
+ portf_copy.reset_pv()
+
+ r = []
+ for date, row in df.iterrows():
+ f = interp1d(np.hstack([0, lookup_table['year_frac']]), np.hstack([row[0]/2, row]))
+ for ind in portf_copy.indices:
+ ind.spread = f((pd.to_datetime(ind.end_date) - value_date).days/365)
+ r.append([[date, f(5)] + [portf_copy.pnl]])
+ df = pd.DataFrame.from_records(chain(*r), columns=['date', 'five_yr_spread', 'pnl'])
+ return df.set_index('date')
+
+def curve_scen_table(portf):
+
+ '''
+ Runs PNL scenario on portf by shocking different points on the curve - off-the-runs shocks are duration linearly interpolated'''
+
+ value_date = portf.value_date
+
+ indices = []
+ for i, ind in enumerate(portf.indices):
+ indices.append(deepcopy(ind))
+ portf_copy = Portfolio(indices)
+ portf_copy.reset_pv()
+
+ index = portf_copy.indices[0].index_type
+ series = on_the_run(index)
+ sql_string = "SELECT tenor, maturity FROM index_maturity where index = %s and series = %s"
+ lookup_table = pd.read_sql_query(sql_string, serenitasdb, parse_dates=['maturity'], params=[index, series])
+ lookup_table['year_frac'] = (lookup_table.maturity - pd.to_datetime(value_date)).dt.days/365
+
+ lookup_table = lookup_table.iloc[[0,1,3]]
+ year_frac = lookup_table.year_frac.tolist()
+ tenors = lookup_table.tenor.tolist()
+ shock = 10 #shocks in bps
+ r = []
+
+ tenor_shock = pd.DataFrame(0, index=lookup_table['year_frac'], columns=['shock'])
+ for t_frac, t_ten in zip(year_frac, tenors):
+ tenor_shock.loc[t_frac] = -shock
+ for w_frac, w_ten in zip(year_frac, tenors):
+ tenor_shock.loc[w_frac] = 0 if t_ten == w_ten else shock
+ #f is the shock amount interpolated based on tenor
+ f = interp1d(np.hstack([0, year_frac]), np.hstack([tenor_shock.shock.iloc[0], tenor_shock.shock]))
+ for i, ind in enumerate(portf_copy.indices):
+ ind.spread = max(0, portf.indices[i].spread + f((ind.end_date - value_date).days/365))
+ r.append([t_ten, w_ten] + [portf_copy.pv - portf.pv])
+ tenor_shock.loc[w_frac] = 0
+ tenor_shock.loc[t_frac] = 0
+ return pd.DataFrame.from_records(r, columns=['tighter', 'wider', 'pnl'])
+
+
+
+
+
+
+
diff --git a/python/analytics/scenarios.py b/python/analytics/scenarios.py
index 1438349c..dfd0be05 100644
--- a/python/analytics/scenarios.py
+++ b/python/analytics/scenarios.py
@@ -8,6 +8,7 @@ from itertools import chain
from functools import partial
from multiprocessing import Pool
from .index_data import _get_singlenames_curves
+from .curve_trades import curve_shape
def run_swaption_scenarios(swaption, date_range, spread_shock, vol_shock,
vol_surface, params=["pv"], vol_time_roll=True):
@@ -119,10 +120,12 @@ def run_tranche_scenarios(tranche, spread_range, date_range, corr_map=False):
_get_singlenames_curves.cache_clear()
orig_tranche_pvs = tranche.tranche_pvs().bond_price
results = []
+ print(tranche.tranche_pvs().bond_price)
for d in date_range:
temp_tranche.value_date = d.date()
for i, spread in enumerate(spread_range):
temp_tranche.tweak(spread)
+ print(tranche.tranche_pvs().bond_price)
if corr_map:
temp_tranche.rho = tranche.map_skew(temp_tranche, 'TLP')
index_pv[i] = temp_tranche._snacpv(spread * 1e-4,
@@ -149,3 +152,30 @@ def run_tranche_scenarios(tranche, spread_range, date_range, corr_map=False):
results = pd.concat(results, keys=date_range)
results.index.names = ['date', 'spread_range']
return results
+
+def run_curve_scenarios(portf, spread_range, date_range, curve_per):
+
+ """computes the pnl of a portfolio of indices for a range of spread/curve scenarios
+
+ Parameters
+ ----------
+ portf : Portfolio
+ spread_range : `np.array`
+ date_range : `pandas.Datetime.Index`
+ """
+
+ portf.reset_pv()
+ portf = deepcopy(portf)
+ index = portf.indices[0].index_type
+
+ r = []
+ for p in curve_per:
+ new_curve = curve_shape(date_range[0], index, p, 100)
+ for date in date_range:
+ portf.value_date = date.date()
+ for s in spread_range:
+ for ind in portf.indices:
+ ind.spread = new_curve((pd.to_datetime(ind.end_date) - date).days/365) * s/100
+ r.append([[date, s, p] + [portf.pnl]])
+ df = pd.DataFrame.from_records(chain(*r), columns=['date', 'spread', 'curve_per', 'pnl'])
+ return df.set_index('date')
diff --git a/python/notebooks/Curve Trades.ipynb b/python/notebooks/Curve Trades.ipynb
index 5637beaf..01669aae 100644
--- a/python/notebooks/Curve Trades.ipynb
+++ b/python/notebooks/Curve Trades.ipynb
@@ -6,11 +6,17 @@
"metadata": {},
"outputs": [],
"source": [
- "import curve_trades as ct\n",
+ "import analytics.curve_trades as ct\n",
"import matplotlib.pyplot as plt\n",
"import pandas as pd\n",
+ "import pandas as pd\n",
+ "import numpy as np\n",
+ "import graphics as g\n",
+ "import globeop_reports as go\n",
+ "\n",
"from ipywidgets import widgets\n",
- "import pandas as pd"
+ "from analytics.scenarios import run_curve_scenarios\n",
+ "from db import dbengine"
]
},
{
@@ -34,7 +40,8 @@
"metadata": {},
"outputs": [],
"source": [
- "index = w.value"
+ "index = w.value\n",
+ "report_date = (pd.datetime.today() - pd.offsets.BDay(2)).normalize()"
]
},
{
@@ -105,7 +112,10 @@
"source": [
"#Theta with 3-5-10 Strategy\n",
"df = ct.ratio_within_series(param='duration')\n",
- "ct.curve_3_5_10(df)"
+ "s = - df.theta2['3yr'] / df.duration_ratio_to_5yr['3yr'] \\\n",
+ " + 2 * df.theta2['5yr'] \\\n",
+ " - df.theta2['10yr'] / df.duration_ratio_to_5yr['10yr']\n",
+ "s.dropna().unstack(-1).plot()"
]
},
{
@@ -114,9 +124,9 @@
"metadata": {},
"outputs": [],
"source": [
- "#Theta with 5-10 Strategy\n",
- "df = ct.ratio_within_series(param='duration')\n",
- "ct.curve_5_10(df)"
+ "#Theta with 5-10 Strategy: buy sell 5y, buy 10y\n",
+ "s = df.theta2['5yr'] - df.theta2['10yr'] / df.duration_ratio_to_5yr['10yr']\n",
+ "s.dropna().unstack(-1).plot()"
]
},
{
@@ -146,10 +156,44 @@
"metadata": {},
"outputs": [],
"source": [
- "#Scenario Anslysis on current position\n",
- "report_date = (pd.datetime.today() - pd.offsets.BDay(1)).normalize()\n",
- "curve_pos = ct.curve_pos(report_date)\n",
- "origpv = curve_pos.pv"
+ "df = ct.forward_spread(report_date, index)\n",
+ "df.plot()\n",
+ "plt.ylabel('spread')\n",
+ "plt.xlabel('forward spread start date')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "df = ct.spot_forward(index)\n",
+ "df = df.rename(columns={'1yr': 'Spot Spread - 1 Year Forward', 'current': 'Spot Spread - Today'})\n",
+ "ax = df.plot(title = 'Credit Curve Roll Down')\n",
+ "plt.ylabel('spread (bps)')\n",
+ "ax.figure.savefig(\"/home/serenitas/edwin/PythonGraphs/curve_trades_roll_down.png\", bbox_inches='tight')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "portf = ct.curve_pos(report_date, index)\n",
+ "shock_min = -.5\n",
+ "shock_max = .8\n",
+ "spread_shock = np.arange(shock_min, shock_max, 0.05)\n",
+ "sql_string = \"SELECT closespread FROM index_quotes where index = %s and series = %s and tenor = %s and date = %s\"\n",
+ "spread_df = pd.read_sql_query(sql_string, dbengine('serenitasdb'),\n",
+ " params=[index, ct.on_the_run(index), '5yr', report_date])\n",
+ "spread_range = (1+ spread_shock) * spread_df.iloc[0][0]\n",
+ "#need to max it at the closest maturity date\n",
+ "date_range = pd.bdate_range(report_date, report_date + 180* pd.offsets.DateOffset(), freq='5B')\n",
+ "curve_per = np.arange(.01, .99, .1)\n",
+ "\n",
+ "df = run_curve_scenarios(portf, spread_range, date_range, curve_per)"
]
},
{
@@ -158,9 +202,8 @@
"metadata": {},
"outputs": [],
"source": [
- "flat_curve = ct.curve_shape(report_date, percentile = .05)\n",
- "for ind in curve_pos.indices:\n",
- " ind.spread = flat_curve((pd.to_datetime(ind.end_date) - trade_date).days/365)"
+ "df_plot = df[df.curve_per == curve_per[5]]\n",
+ "g.plot_time_color_map(df_plot, spread_range, attr='pnl')"
]
},
{
@@ -169,8 +212,14 @@
"metadata": {},
"outputs": [],
"source": [
+ "#Scenario Anslysis on current position\n",
+ "#curve_positions = ct.curve_pos(report_date, index)\n",
+ "#origpv = curve_positions.pv\n",
+ "#flat_curve = ct.curve_shape(report_date, index, percentile = .05)\n",
+ "#for ind in curve_positions.indices:\n",
+ "# ind.spread = flat_curve((pd.to_datetime(ind.end_date) - report_date).days/365)\n",
"#PNL in flattening to a 5% case\n",
- "curve_pos.pv - origpv"
+ "#curve_positions.pv - origpv"
]
},
{
@@ -179,10 +228,10 @@
"metadata": {},
"outputs": [],
"source": [
- "df = ct.forward_spread(index)\n",
- "df.plot()\n",
- "plt.ylabel('spread')\n",
- "plt.xlabel('forward spread start date')"
+ "curve_positions = ct.curve_pos(report_date, index)\n",
+ "df = ct.pos_pnl_abs(curve_positions, report_date)\n",
+ "navs = go.get_net_navs()\n",
+ "df_plot = df.pnl/navs.loc['2018-03-31'].endbooknav"
]
},
{
@@ -191,9 +240,36 @@
"metadata": {},
"outputs": [],
"source": [
- "df = ct.spot_forward(index)\n",
- "df.plot()\n",
- "plt.ylabel('spread')"
+ "fig, ax = plt.subplots()\n",
+ "ax.plot(df_plot.index, df_plot.values)\n",
+ "ax.set(xlabel='date', ylabel='% of NAV',\n",
+ " title='PNL impact from spread curve scenario')\n",
+ "plt.xticks(rotation=90)\n",
+ "y_ticks = ax.get_yticks()\n",
+ "ax.set_yticklabels(['{:.2f}%'.format(y*100) for y in y_ticks])\n",
+ "plt.tight_layout()\n",
+ "#ax.figure.savefig(\"/home/serenitas/edwin/PythonGraphs/curve_trades.png\", bbox_inches='tight')"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "#Historical PNL in a 5% case\n",
+ "df.pnl.quantile(.05)"
+ ]
+ },
+ {
+ "cell_type": "code",
+ "execution_count": null,
+ "metadata": {},
+ "outputs": [],
+ "source": [
+ "scen_table = ct.curve_scen_table(curve_positions)\n",
+ "scen_table.pnl = scen_table.pnl/navs.loc['2018-03-31'].endbooknav *100\n",
+ "scen_table.pivot(index='tighter', columns='wider')"
]
},
{