diff options
Diffstat (limited to 'python/analytics/cms_spread.py')
| -rw-r--r-- | python/analytics/cms_spread.py | 112 |
1 files changed, 69 insertions, 43 deletions
diff --git a/python/analytics/cms_spread.py b/python/analytics/cms_spread.py index 71b8f601..da1672f1 100644 --- a/python/analytics/cms_spread.py +++ b/python/analytics/cms_spread.py @@ -10,14 +10,18 @@ from quantlib.time.api import ( Date, Period, Days, Months, Years, UnitedStates, Actual365Fixed, today, Following) from quantlib.time.calendars.united_states import GovernmentBond +from quantlib.termstructures.yields.api import YieldTermStructure from quantlib.indexes.swap.usd_libor_swap import UsdLiborSwapIsdaFixAm from quantlib.experimental.coupons.swap_spread_index import SwapSpreadIndex from quantlib.experimental.coupons.lognormal_cmsspread_pricer import \ LognormalCmsSpreadPricer +from quantlib.experimental.coupons.cms_spread_coupon import \ + CappedFlooredCmsSpreadCoupon from quantlib.termstructures.volatility.api import ( VolatilityType, SwaptionVolatilityMatrix) from quantlib.cashflows.conundrum_pricer import ( YieldCurveModel, NumericHaganPricer, AnalyticHaganPricer) +from quantlib.cashflows.linear_tsr_pricer import LinearTsrPricer from quantlib.quotes import SimpleQuote from quantlib.math.matrix import Matrix @@ -47,60 +51,62 @@ def h(v, X, S_alpha_beta, mu_beta, sigma_alpha_beta, T_alpha): def get_fixings(conn, tenor1, tenor2, fixing_date=None): if fixing_date: - sql_str = f'SELECT fixing_date, "{tenor1}y" ,"{tenor2}y" FROM USD_swap_fixings ' \ + sql_str = f'SELECT "{tenor1}y" ,"{tenor2}y" FROM USD_swap_fixings ' \ 'WHERE fixing_date=%s' with conn.cursor() as c: - c.execute(sql_str) - date, fixing1, fixing2 = next(c) + c.execute(sql_str, (fixing_date,)) + try: + fixing1, fixing2 = next(c) + except StopIteration: + raise RuntimeError(f"no fixings available for date {fixing_date}") else: sql_str = f'SELECT fixing_date, "{tenor1}y" ,"{tenor2}y" FROM USD_swap_fixings ' \ 'ORDER BY fixing_date DESC LIMIT 1' with conn.cursor() as c: c.execute(sql_str, fixing_date) - date, fixing1, fixing2 = next(c) + fixing_date, fixing1, fixing2 = next(c) - date = Date.from_datetime(date) + date = Date.from_datetime(fixing_date) fixing1 = float(fixing1) fixing2 = float(fixing2) return date, fixing1, fixing2 -def get_forward_spread(tenor1, tenor2, maturity): - yc = YC() - yc.extrapolation = True - conn = dbconn('serenitasdb') - fixing_date, fixing1, fixing2 = get_fixings(conn, tenor1, tenor2) - USISDA1 = UsdLiborSwapIsdaFixAm(Period(tenor1, Years), yc, yc) - USISDA1.add_fixing(fixing_date, fixing1) - USISDA2 = UsdLiborSwapIsdaFixAm(Period(tenor2, Years), yc, yc) - USISDA2.add_fixing(fixing_date, fixing2) +def build_spread_index(tenor1, tenor2): + yc = YieldTermStructure() + USISDA1 = UsdLiborSwapIsdaFixAm(Period(tenor1, Years), yc) + USISDA2 = UsdLiborSwapIsdaFixAm(Period(tenor2, Years), yc) spread_index = SwapSpreadIndex(f"{tenor1}-{tenor2}", USISDA1, USISDA2) - expiration = UnitedStates(GovernmentBond).advance( - Date.from_datetime(maturity), - 0, Days) - return spread_index.fixing(expiration) + return spread_index, yc -def get_swaption_vol_data(source="ICPL", vol_type=VolatilityType.ShiftedLognormal): +def get_swaption_vol_data(source="ICPL", vol_type=VolatilityType.ShiftedLognormal, + date=None): if vol_type == VolatilityType.Normal: table_name = "swaption_normal_vol" else: table_name = "swaption_lognormal_vol" - sql_str = f"SELECT * FROM {table_name} WHERE source = %s ORDER BY date DESC LIMIT 1" + sql_str = f"SELECT * FROM {table_name} WHERE source=%s " + if date is None: + sql_str += "ORDER BY date DESC LIMIT 1" + params = (source,) + else: + sql_str += "AND date=%s" + params = (source, date) conn = dbconn('serenitasdb') with conn.cursor() as c: - c.execute(sql_str, (source,)) + c.execute(sql_str, params) surf_data = next(c) - return surf_data[0], np.array(surf_data[1:-1], order='F').T, vol_type + return surf_data[0], np.array(surf_data[1:-1], order='F').T -def get_swaption_vol_surface(date, data, vol_type): +def get_swaption_vol_surface(date, vol_type): + date, surf, _ = get_swaption_vol_data(date=date, vol_type=vol_type) tenors = [1/12, 0.25, 0.5, 0.75] + list(range(1, 11)) + [15., 20., 25., 30.] - return RectBivariateSpline(tenors, tenors[-14:], data.T) + return RectBivariateSpline(tenors, tenors[-14:], surf) -def get_swaption_vol_matrix(date, data, vol_type): +def get_swaption_vol_matrix(date, data, vol_type=VolatilityType.ShiftedLognormal): # figure out what to do with nan calendar = UnitedStates() - data= np.delete(data, 3, axis=0) + data = np.delete(data, 3, axis=0) / 100 m = Matrix.from_ndarray(data) - print(m.to_ndarray()) option_tenors = [Period(i, Months) for i in [1, 3, 6]] + \ [Period(i, Years) for i in range(1, 11)] + \ [Period(i, Years) for i in [15, 20, 25, 30]] @@ -115,14 +121,27 @@ def get_swaption_vol_matrix(date, data, vol_type): Actual365Fixed(), vol_type=vol_type)) -def quantlib_model(atm_vol, model, vol_type, corr): - pricer = NumericHaganPricer(atm_vol, model, SimpleQuote(0.)) - yc = YC() +def quantlib_model(date, spread_index, yc, cap, rho, maturity, mean_rev=0., + vol_type=VolatilityType.ShiftedLognormal): + date, surf = get_swaption_vol_data(date=date, vol_type=vol_type) + atm_vol = get_swaption_vol_matrix(date, surf, vol_type) + pricer = LinearTsrPricer(atm_vol, SimpleQuote(mean_rev), yc) + vol_type = VolatilityType(atm_vol.volatility_type) cmsspread_pricer = LognormalCmsSpreadPricer(pricer, - SimpleQuote(corr), - yc, - vol_type=vol_type) - return cmsspread_pricer + SimpleQuote(rho), + yc) + end_date = Date.from_datetime(maturity) + pay_date = spread_index.fixing_calendar.advance(end_date, 0, Days) + start_date = end_date - Period(1, Years) + end_date = Date(19, 1, 2020) + cms_spread_coupon = CappedFlooredCmsSpreadCoupon( + pay_date, 300_000_000, start_date, end_date, + spread_index.fixing_days, spread_index, 1., -cap, + floor=0., + day_counter=Actual365Fixed(), + is_in_arrears=True) + cms_spread_coupon.set_pricer(cmsspread_pricer) + return cms_spread_coupon def plot_surf(surf, tenors): xx, yy = np.meshgrid(tenors, tenors[-14:]) @@ -130,14 +149,21 @@ def plot_surf(surf, tenors): ax = fig.gca(projection='3d') ax.plot_surface(xx, yy, surf.ev(xx, yy)) -def globeop_model(tenor1, tenor2, rho, strike, maturity): - forward = get_forward_spread(tenor1, tenor2, maturity) - surf = get_swaption_vol_surface() +def globeop_model(date, spread_index, yc, strike, rho, maturity, + vol_type=VolatilityType.Normal): + """ price cap spread option without convexity adjustment - T = Actual365Fixed().year_fraction(today(), Date.from_datetime(maturity)) - vol1 = float(surf(T, tenor1 )) * 0.01 - vol2 = float(surf(T, tenor2)) * 0.01 + vol_type Normal is the only supported one at the moment""" + maturity = Date.from_datetime(maturity) + fixing_date = spread_index.fixing_calendar.advance(maturity, units=Days) + forward = spread_index.fixing(fixing_date) + date, surf = get_swaption_vol_data(date=date, vol_type=vol_type) + atm_vol = get_swaption_vol_matrix(date, surf, vol_type=vol_type) + d = Date.from_datetime(date) + T = Actual365Fixed().year_fraction(d, maturity) + vol1 = atm_vol.volatility(maturity, spread_index.swap_index1.tenor, 0.) + vol2 = atm_vol.volatility(maturity, spread_index.swap_index2.tenor, 0.) vol_spread = sqrt(vol1**2 + vol2**2 - 2 * rho * vol1 * vol2) - yc = YC() - # the normal vols are not scale invariant. We multiply by 100 to get in percent terms. - return yc.discount(T) * bachelier(forward*100, strike*100, T, vol_spread) + # normal vol is not scale independent and is computed in percent terms, so we scale + # everything by 100. + return 0.01 * yc.discount(T) * bachelier(forward * 100, strike * 100, T, vol_spread) |
