aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--python/analytics/cms_spread.py132
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)