import analytics.option as opt import pandas as pd import numpy as np import matplotlib.pyplot as plt import numpy as np import matplotlib.colors as colors import math from matplotlib import cm from exploration.option_trades import * from pandas.tseries.offsets import * from analytics import Index, ForwardIndex from db import dbengine, dbconn from scipy.interpolate import * serenitasdb = dbengine('serenitasdb') def get_dfs(index="IG"): df0 = atm_vol(index, datetime.date(2014, 6, 11)) df = rolling_vol(df0, 'atm_vol', term=[1,2,3,4,5,6]) df1 = rolling_vol(df0, 'otm_vol', term=[1,2,3,4,5,6]) return (df,df1) def calendar_spread(): df = get_dfs()[0] df['cal 3m-1m'] = df['3m']-df['1m'] df['cal 5m-3m'] = df['5m']-df['3m'] df = df.sort_index() df = df.groupby(df.index.date).nth(-1) df[['cal 3m-1m','cal 5m-3m']].plot() #last 100,100-200,200-300 days avg = pd.DataFrame([df[-100:].mean(),df[-200:-100].mean(),df[-300:-200].mean()]) return (df[-1:], avg) def put_spread(index = "IG"): dfs = get_dfs() df = pd.concat([dfs[0], dfs[1]], axis = 1, keys=['atm','otm']) steepness = df['otm'] - df['atm'] steepness.plot() #last 100,100-200,200-300 days avg = pd.DataFrame([steepness[-100:].mean(),steepness[-200:-100].mean(),steepness[-300:-200].mean()]) return (steepness[-1:], avg) def swaption_analysis(): cal = calendar_spread() cal_otm = calendar_spread(moneyness = "otm_vol") vol_df = atm_vol('IG',27).groupby(level = 'quotedate').last().dropna() def beta_calc(): am = arch_model(10000*index_price_returns(index='IG')) res = am.fit(update_freq=0, disp='off') amIG = arch_model(100*index_returns()) resIG = amIG.fit(update_freq=0, disp='off') ltvar = lr_var(resIG) amHY = arch_model(1000*index_returns(index = 'HY')) resHY = amHY.fit(update_freq=0, disp='off') ltvar = lr_var(resHY) graphit = compute_allocation(all_tenors) def build_swaption(index, series, expiry, strike, ref, trade_date, t_range= None, spread_range = None): index_obj = Index.from_name(index, series, '5yr', trade_date) swap_obj = opt.Swaption(index_obj, expiry, strike, option_type="payer") swap_obj.notional = 100000000 if t_range is None: t_range = pd.bdate_range(trade_date, expiry- BDay(), freq = '5B') if spread_range is None: spread_range = pd.Series(np.arange(ref - 10, ref +19, 5)) vol_range = pd.Series(np.arange(25, 60, 5)) #not inclusive of end point df = pd.DataFrame(index = pd.MultiIndex.from_product([t_range, spread_range, vol_range], names = ['date', 'spread', 'vol']), columns = ['pv']) df = df.reset_index() def aux(row, index, swap): index.spread = row.spread manual_index_update(index, row.date.date()) swap.sigma = row.vol/100 swap._update() return swap.pv df['pv'] = df.apply(aux, axis=1, args=(index_obj, swap_obj)) #calculate mapped vol df['moneyness'] = (strike- df.spread)/df.spread df['days_to_expiry'] = (expiry - df.date).dt.days vol_surface = build_vol_surface_functions(trade_date, index, series) df['mapped_vol'] = df.apply(vol_from_surface, axis = 1, args=(vol_surface[0], vol_surface[1])) df['mapping_shift'] = pd.to_numeric(df.vol/100 - df.mapped_vol, errors = 'ignore') df = df.set_index(['date', 'spread', 'vol']) return df def calc_delta_pnl(index, series, ref, trade_date, notional, t_range, spread_range): index_obj = Index.from_name(index, series, '5yr', trade_date) index_obj.spread = ref index_obj.notional = notional startingpv = -index_obj.clean_pv index_pv = {} for date in t_range: for spread in spread_range: index_obj.spread = spread manual_index_update(index_obj, date) #import pdb; pdb.set_trace() index_pv[(date, spread)] = -index_obj.clean_pv + notional* (date.date()-trade_date).days/360* index_obj.fixed_rate/10000 df = pd.DataFrame.from_dict(index_pv, orient = 'index').reset_index() df['date'] = df['index'].apply(lambda x: x[0]) df['spread'] = df['index'].apply(lambda x: x[1]) del df['index'] df = df.set_index(['date','spread']).sort_index() df = (df - startingpv).unstack(-1) df.columns = df.columns.droplevel() return df def find_mapped_pv(bought, sold, date): sold = sold.xs(date).reset_index() bought = bought.xs(date).reset_index() #Bivariate B-Spline, instead of interp2d. Interp2d doesn't behave well and complains a lot. annoying x = bought.spread.unique() y = sorted(bought.mapping_shift.unique()) grid = np.meshgrid(x,y) f_buy = SmoothBivariateSpline(bought.spread, bought.mapping_shift, bought.pv, kx = 4, ky = 4) f_sold = SmoothBivariateSpline(sold.spread, sold.mapping_shift, sold.pv, kx = 4, ky = 4) intp_buy = f_buy.ev(grid[0],grid[1]) intp_sold = f_sold.ev(grid[0],grid[1]) df = pd.DataFrame(intp_buy, index = grid[1][0:,0], columns = grid[0][0]) df1 = pd.DataFrame(intp_sold, index = grid[1][0:,0], columns = grid[0][0]) #Use NDInterpolate - not copmplete #f_buy = LinearNDInterpolator((bought.spread, bought.mapping_shift), bought.pv) #f_sold = LinearNDInterpolator((sold.spread, sold.mapping_shift), sold.pv) #Use interp2d #x = bought.spread.unique() #y = sorted(bought.mapping_shift.unique()) #f_buy = interp2d(bought.spread, bought.mapping_shift, bought.pv) #f_sold = interp2d(sold.spread, sold.mapping_shift, sold.pv) #intp_buy = f_buy(x,y) #intp_sold = f_sold(x,y) #df = pd.DataFrame(data = intp_buy, index = y, columns = x) #df1 = pd.DataFrame(data = intp_sold, index = y, columns = x) PNL = df - df1 return PNL def result_fill(df, date): data = df.xs(date).reset_index() #make df.vol a variable to make this function more general f = interp2d(data.spread, data.vol, data.pv) x = np.arange(data.spread.min(), data.spread.max(), .5) y = np.arange(data.vol.min(), data.vol.max(), .5) intp_result = f(x,y) df1 = pd.DataFrame(data = intp_result, index = y, columns = x) return df1 def plot_color_map(df, val_date): #rows are spread, columns are volatility surface shift fig, ax = plt.subplots() #Different ways to do a colormap: imshow and pcolormesh. using imshow here midpoint = 1 - df.max().max()/(df.max().max() + abs(df.min().min())) shifted_cmap = shiftedColorMap(cm.RdYlGn, midpoint=midpoint, name='shifted') chart = ax.imshow(df, extent=(df.columns.min(), df.columns.max(), df.index.min(), df.index.max()) \ ,aspect= 'auto', interpolation='bilinear', cmap=shifted_cmap) ax.set_xlabel('Spread') ax.set_ylabel('Parallel Shift of Volatility Surface') ax.set_title('PV of Trade on ' + str(val_date.date())) fig.colorbar(chart, shrink = .8) fig.savefig("/home/serenitas/edwin/PythonGraphs/payer_swap_" + str(val_date.date()) + ".png") def build_vol_surface_functions(date = datetime.date(2017, 2, 23), index = 'IG', series = '27'): df1 = pd.read_sql_query('SELECT quotedate, expiry, series, strike, vol ' \ 'FROM swaption_quotes ' \ 'WHERE index = %s and series = %s and date(quotedate) = %s', serenitasdb, index_col=['quotedate', 'expiry', 'series'], params=(index.upper(), series, date), parse_dates=['quotedate', 'expiry']) index_data = pd.read_sql_query( 'SELECT quotedate, expiry, series, ref, fwdspread FROM swaption_ref_quotes ' \ 'WHERE index= %s and date(quotedate) = %s', serenitasdb, index_col=['quotedate', 'expiry', 'series'], params=(index.upper(), date), parse_dates=['quotedate', 'expiry']) df1 = df1.join(index_data) df1 = df1.groupby(df1.index).filter(lambda x: len(x) >= 2) df1 = df1.reset_index() #once the dates are in the columns you need the use .dt to access dates functions df1['days_to_expiry'] = (df1.expiry - df1.quotedate.dt.normalize().dt.tz_localize(None)).dt.days df1['moneyness'] = (df1.strike - df1.ref)/df1.ref df1 = df1.groupby(['days_to_expiry','moneyness']).nth(-1).vol df1 = df1.reset_index() f = LinearNDInterpolator((df1.days_to_expiry, df1.moneyness), df1.vol) g = NearestNDInterpolator((df1.days_to_expiry, df1.moneyness), df1.vol) return (f,g) def vol_from_surface(row, f, g): vol = f(row.days_to_expiry, row.moneyness) if math.isnan(vol) is True: vol = g(row.days_to_expiry, row.moneyness) return vol def calc_and_plot(bought, sold, traded_price, week, lowerbound, upperbound, deltaPNL=None): if week > len(bought.index.get_level_values(0).unique()): week = len(bought.index.get_level_values(0).unique())-1 date = bought.index.get_level_values(0).unique()[week] PNL = find_mapped_pv(bought, sold, date) - traded_price + deltaPNL.loc[date.date()] PNL = PNL[lowerbound:upperbound].sort_index(ascending = False) plot_color_map(PNL, date) return PNL def full_analysis(): index = 'IG' series = 27, buy_expiry = datetime.date(2017, 4, 19) buy_strike = 65 sell_expiry = datetime.date(2017, 5, 17) sell_strike = 72 ref = 62 trade_date = datetime.date(2017, 2, 23) delta_notional = 13000000 t_range = pd.bdate_range(trade_date, buy_expiry- BDay(), freq = '5B') spread_range = pd.Series(np.arange(ref - 10, ref +19, 5)) bought = build_swaption(index, series, buy_expiry, buy_strike, ref, trade_date, t_range, spread_range) sold = build_swaption(index, series, sell_expiry, sell_strike, ref, trade_date, t_range, spread_range) delta_PNL = calc_delta_pnl(index, series, ref, trade_date, delta_notional, t_range, spread_range) #Calc PNL and Plot: traded_price = 5000 lowerbound = -.05 #parallel shift down 5% vol upperbound = .1 #parallel shift up 10% vol week = 1 #negative to count backwards PNL = calc_and_plot(bought, sold, traded_price, week, lowerbound, upperbound, delta_PNL) return (bought, sold, PNL, delta_PNL) def manual_index_update(index, date): #index as Index Object index._yc = index._yc.expected_forward_curve(date) index._trade_date = date index._step_in_date = index.trade_date + datetime.timedelta(days=1) index._accrued = index._fee_leg.accrued(index._step_in_date) index._value_date = (pd.Timestamp(index._trade_date) + 3* BDay()).date() index._update() import numpy as np import matplotlib import matplotlib.pyplot as plt from mpl_toolkits.axes_grid1 import AxesGrid def shiftedColorMap(cmap, start=0, midpoint=0.5, stop=1.0, name='shiftedcmap'): ''' Function to offset the "center" of a colormap. Useful for data with a negative min and positive max and you want the middle of the colormap's dynamic range to be at zero Input ----- cmap : The matplotlib colormap to be altered start : Offset from lowest point in the colormap's range. Defaults to 0.0 (no lower ofset). Should be between 0.0 and `midpoint`. midpoint : The new center of the colormap. Defaults to 0.5 (no shift). Should be between 0.0 and 1.0. In general, this should be 1 - vmax/(vmax + abs(vmin)) For example if your data range from -15.0 to +5.0 and you want the center of the colormap at 0.0, `midpoint` should be set to 1 - 5/(5 + 15)) or 0.75 stop : Offset from highets point in the colormap's range. Defaults to 1.0 (no upper ofset). Should be between `midpoint` and 1.0. ''' cdict = { 'red': [], 'green': [], 'blue': [], 'alpha': [] } # regular index to compute the colors reg_index = np.linspace(start, stop, 257) # shifted index to match the data shift_index = np.hstack([ np.linspace(0.0, midpoint, 128, endpoint=False), np.linspace(midpoint, 1.0, 129, endpoint=True) ]) for ri, si in zip(reg_index, shift_index): r, g, b, a = cmap(ri) cdict['red'].append((si, r, r)) cdict['green'].append((si, g, g)) cdict['blue'].append((si, b, b)) cdict['alpha'].append((si, a, a)) newcmap = matplotlib.colors.LinearSegmentedColormap(name, cdict) plt.register_cmap(cmap=newcmap) return newcmap