aboutsummaryrefslogtreecommitdiffstats
path: root/python/analytics/cms_spread.py
diff options
context:
space:
mode:
Diffstat (limited to 'python/analytics/cms_spread.py')
-rw-r--r--python/analytics/cms_spread.py112
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)