diff options
Diffstat (limited to 'python/analytics/cms_spread.py')
| -rw-r--r-- | python/analytics/cms_spread.py | 132 |
1 files changed, 116 insertions, 16 deletions
diff --git a/python/analytics/cms_spread.py b/python/analytics/cms_spread.py index c39bff6d..2d5b2109 100644 --- a/python/analytics/cms_spread.py +++ b/python/analytics/cms_spread.py @@ -1,6 +1,10 @@ -import numpy as np +import atexit +import datetime import matplotlib.pyplot as plt -from math import exp, sqrt, log +import numpy as np +import pandas as pd +import re +from math import exp, sqrt, log, pi from .black import bachelier, cnd_erf from numba import (cfunc, types, jit, float64, boolean, optional, vectorize) @@ -8,6 +12,8 @@ from quantlib.time.api import ( Date, Period, Days, Months, Years, UnitedStates, Actual365Fixed, Following, ModifiedFollowing) from quantlib.cashflows.cms_coupon import CmsCoupon +from quantlib.cashflows.conundrum_pricer import ( + AnalyticHaganPricer, YieldCurveModel) 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 @@ -22,8 +28,18 @@ from quantlib.quotes import SimpleQuote from quantlib.math.matrix import Matrix from scipy.interpolate import RectBivariateSpline +from scipy.special import roots_hermitenorm +from yieldcurve import YC from db import dbconn +_serenitasdb = dbconn('serenitasdb') +_dawndb = dbconn('dawndb') + +@atexit.register +def close_dbs(): + _serenitasdb.close() + _dawndb.close() + @vectorize([float64(float64, float64, float64, float64, float64, float64, float64, float64, float64)], cache=True, nopython=True) def h_call(z, K, S1, S2, mu_x, mu_y, sigma_x, sigma_y, rho): @@ -117,8 +133,7 @@ def get_swaption_vol_data(source="ICPL", vol_type=VolatilityType.ShiftedLognorma else: sql_str += "AND date=%s" params = (source, date) - conn = dbconn('serenitasdb') - with conn.cursor() as c: + with _serenitasdb.cursor() as c: c.execute(sql_str, params) surf_data = next(c) return surf_data[0], np.array(surf_data[1:-1], order='F', dtype='float64').T @@ -139,15 +154,13 @@ def get_swaption_vol_matrix(date, data, vol_type=VolatilityType.ShiftedLognormal [Period(i, Years) for i in range(1, 11)] + \ [Period(i, Years) for i in [15, 20, 25, 30]] swap_tenors = option_tenors[-14:] - return (SwaptionVolatilityMatrix. - from_reference_date(Date.from_datetime(date), - calendar, - Following, - option_tenors, - swap_tenors, - m, - Actual365Fixed(), - vol_type=vol_type)) + return (SwaptionVolatilityMatrix(calendar, + Following, + option_tenors, + swap_tenors, + m, + Actual365Fixed(), + vol_type=vol_type)) def quantlib_model(date, spread_index, yc, cap, rho, maturity, mean_rev=0., @@ -230,9 +243,7 @@ def get_cms_coupons(trade_date, notional, option_tenor, spread_index, is_in_arrears=True) return cms_beta, cms_gamma -def get_params(cms_beta, cms_gamma, cms_pricer, atm_vol): - cms_beta.set_pricer(cms_pricer) - cms_gamma.set_pricer(cms_pricer) +def get_params(cms_beta, cms_gamma, atm_vol): s_gamma = cms_gamma.index_fixing s_beta = cms_beta.index_fixing adjusted_gamma = cms_gamma.rate @@ -247,3 +258,92 @@ def get_params(cms_beta, cms_gamma, cms_pricer, atm_vol): sigma_x = vol_gamma * sqrt(T_alpha) sigma_y = vol_beta * sqrt(T_alpha) return (s_gamma, s_beta , mu_x, mu_y, sigma_x, sigma_y) + + +class CmsSpread: + def __init__(self, maturity, tenor1, tenor2, strike, option_tenor=None, + value_date=datetime.date.today(), notional=100_000_000, + fixing_days=2, corr=0.8, mean_reversion=0.1): + """ tenor1 < tenor2""" + self._value_date = value_date + if maturity is None: + maturity = Date.from_datetime(value_date) + option_tenor + else: + maturity = Date.from_datetime(maturity) + spread_index, self.yc = build_spread_index(tenor2, tenor1) + self.yc.link_to(YC(evaluation_date=value_date, extrapolation=True)) + cal = spread_index.fixing_calendar + fixing_date = cal.adjust(maturity, ModifiedFollowing) + payment_date = cal.advance(fixing_date, 2, Days) + accrued_end_date = payment_date + accrued_start_date = accrued_end_date - Period(1, Years) + self.strike = strike + self.notional = notional + self.fixing_days = 2 + self.cms1 = CmsCoupon(payment_date, + self.notional, + start_date=accrued_start_date, + end_date=accrued_end_date, + fixing_days=fixing_days, + index=spread_index.swap_index2, + is_in_arrears=True) + + self.cms2 = CmsCoupon(payment_date, + notional, + start_date=accrued_start_date, + end_date=accrued_end_date, + fixing_days=fixing_days, + index=spread_index.swap_index1, + is_in_arrears=True) + date, surf = get_swaption_vol_data(date=value_date, + vol_type=VolatilityType.ShiftedLognormal) + atm_vol = get_swaption_vol_matrix(value_date, surf) + self._corr = SimpleQuote(corr) + self._μ = SimpleQuote(mean_reversion) + self._cms_pricer = AnalyticHaganPricer(atm_vol, YieldCurveModel.Standard, self._μ) + self.cms1.set_pricer(self._cms_pricer) + self.cms2.set_pricer(self._cms_pricer) + self._params = get_params(self.cms1, self.cms2, atm_vol) + self._x, self._w = roots_hermitenorm(20) + + @staticmethod + def from_tradeid(trade_id): + with _dawndb.cursor() as c: + c.execute("SELECT " + "amount, expiration_date, floating_rate_index, strike, trade_date " + "FROM capfloors WHERE id = %s", (trade_id,)) + r = c.fetchone() + m = re.match("USD(\d{1,2})-(\d{1,2})CMS", r['floating_rate_index']) + if m: + tenor2, tenor1 = map(int, m.groups()) + instance = CmsSpread(r['expiration_date'], tenor1, tenor2, r['strike'] * 0.01, + value_date=r['trade_date'], notional=r['amount']) + return instance + + @property + def corr(self): + return self._corr.value + + @corr.setter + def corr(self, val): + self._corr.set_value(val) + + @property + def value_date(self): + return self._value_date + + @value_date.setter + def value_date(self, d: pd.Timestamp): + self._value_date = d + self.yc.link_to(YC(evaluation_date=d, extrapolation=True)) + date, surf = get_swaption_vol_data(date=d, + vol_type=VolatilityType.ShiftedLognormal) + atm_vol = get_swaption_vol_matrix(d, surf) + self._cms_pricer.swaption_volatility = atm_vol + self._params = get_params(self.cms1, self.cms2, atm_vol) + + @property + def pv(self): + return self.notional * 1 / sqrt(2 * pi) * \ + np.dot(self._w, h_call(self._x, self.strike, *self._params, self.corr)) * \ + self.yc.discount(self.cms1.fixing_date) |
