aboutsummaryrefslogtreecommitdiffstats
path: root/python/analytics
diff options
context:
space:
mode:
Diffstat (limited to 'python/analytics')
-rw-r--r--python/analytics/option.py214
1 files changed, 89 insertions, 125 deletions
diff --git a/python/analytics/option.py b/python/analytics/option.py
index ac14ca87..4721dfbc 100644
--- a/python/analytics/option.py
+++ b/python/analytics/option.py
@@ -436,16 +436,6 @@ class Swaption(BlackSwaption):
pv_black = property(None, __setpv_black)
-
-def compute_vol(option, strike, mid):
- option.strike = strike
- try:
- option.pv = mid
- except ValueError as e:
- return np.array([np.nan, np.nan, np.nan, option.moneyness, strike])
- else:
- return np.array([option.sigma, option.tail_prob, option.vega, option.moneyness, option.strike])
-
def _get_keys(df, models=["black", "precise"]):
for quotedate, source in (df[['quotedate', 'quote_source']].
drop_duplicates().
@@ -461,14 +451,14 @@ class QuoteSurface():
def __init__(self, index_type, series, tenor='5yr', trade_date=datetime.date.today()):
self._quotes = pd.read_sql_query(
"SELECT quotedate, index, series, ref, fwdspread, expiry, " \
- "swaption_quotes.*, source " \
+ "swaption_quotes.*, quote_source " \
"FROM swaption_quotes " \
"JOIN swaption_ref_quotes USING (ref_id)" \
"WHERE quotedate::date = %s AND index= %s AND series = %s " \
"AND quote_source != 'SG' " \
"ORDER BY quotedate DESC",
_engine,
- parse_dates = ['quotedate', 'expiry'],
+ parse_dates=['quotedate', 'expiry'],
params=(trade_date, index_type.upper(), series))
self._quotes.loc[(self._quotes.quote_source == "GS") & (self._quotes['index'] =="HY"),
["pay_bid", "pay_offer", "rec_bid", "rec_offer"]] *=100
@@ -481,7 +471,7 @@ class QuoteSurface():
self.trade_date = trade_date
def list(self, source=None):
- """returns list of vol surfaces"""
+ """returns list of quotes"""
r = []
for quotedate, quotesource in (self._quotes[['quotedate', 'quote_source']].
drop_duplicates().
@@ -528,7 +518,92 @@ class VolSurface(QuoteSurface):
ax.set_ylabel("Moneyness")
ax.set_zlabel("Volatility")
-@lru_cache(maxsize=8)
+def _compute_vol(option, strike, mid):
+ option.strike = strike
+ try:
+ option.pv = mid
+ except ValueError as e:
+ return np.array([np.nan, option.moneyness])
+ else:
+ return np.array([option.sigma, option.moneyness])
+
+def _calibrate(index, quotes, option_model, option_type):
+ T, r = [], []
+ column = 'pay_mid' if option_type == 'payer' else 'rec_mid'
+ with Pool(4) as p:
+ for expiry, df in quotes.groupby(['expiry']):
+ option = option_model(index, expiry.date(), 100, option_type)
+ T.append(option.T * np.ones(df.shape[0]))
+ r.append(np.stack(p.starmap(partial(_compute_vol, option),
+ df[['strike', column]].values)))
+ r = np.concatenate(r)
+ vol = r[:,0]
+ non_nan = ~np.isnan(vol)
+ vol = vol[non_nan]
+ time = np.hstack(T)
+ time = time[non_nan]
+ moneyness = r[non_nan,1]
+ return SmoothBivariateSpline(time, moneyness, vol)
+
+class ModelBasedVolSurface(VolSurface):
+ def __init__(self, index_type, series, tenor='5yr', trade_date=datetime.date.today()):
+ super().__init__(index_type, series, tenor, trade_date)
+ self._index = Index.from_name(index_type, series, tenor, trade_date, notional=1.)
+ self._surfaces = {}
+ self._quotes = self._quotes.assign(
+ pay_mid=self._quotes[['pay_bid', 'pay_offer']].mean(1) * 1e-4,
+ rec_mid=self._quotes[['rec_bid', 'rec_offer']].mean(1) * 1e-4)
+ if type(self) is BlackSwaptionVolSurface:
+ self._option_model = BlackSwaption
+ elif type(self) == SwaptionVolSurface:
+ self._option_model = Swaption
+
+ def list(self, source=None, option_type=None):
+ """returns list of vol surfaces"""
+ l = super().list(source)
+ if option_type is None:
+ return list(chain((e + ("payer",) for e in l),
+ (e + ("receiver",) for e in l)))
+ else:
+ return [e + (option_type,) for e in l]
+
+ def __getitem__(self, surface_id):
+ if surface_id not in self._surfaces:
+ quotedate, source, option_type = surface_id
+ quotes = self._quotes[(self._quotes.quotedate == quotedate) &
+ (self._quotes.quote_source == source)]
+ quotes.dropna(subset=
+ ['pay_mid' if option_type == "payer" else 'rec_mid'],
+ inplace=True)
+ self._index.ref = quotes.ref.iat[0]
+ self._surfaces[surface_id] = _calibrate(self._index, quotes,
+ self._option_model,
+ option_type)
+ return self._surfaces[surface_id]
+ else:
+ return self._surfaces[surface_id]
+
+ def plot(self, surface_id):
+ fig = plt.figure()
+ ax = fig.gca(projection='3d')
+ surf = self[surface_id]
+ time, moneyness = surf.get_knots()
+ xx, yy = np.meshgrid(np.arange(time[0], time[-1], 0.01),
+ np.arange(moneyness[0], moneyness[-1], 0.01))
+ surf = ax.plot_surface(xx, yy, self[surface_id].ev(xx, yy),
+ cmap=cm.viridis)
+ ax.set_xlabel("Year fraction")
+ ax.set_ylabel("Moneyness")
+ ax.set_zlabel("Volatility")
+
+
+class BlackSwaptionVolSurface(ModelBasedVolSurface):
+ pass
+
+class SwaptionVolSurface(ModelBasedVolSurface):
+ pass
+
+@lru_cache(maxsize=32)
def _forward_annuity(expiry, index):
step_in_date = expiry + datetime.timedelta(days=1)
expiry_settle = pd.Timestamp(expiry) + 3* BDay()
@@ -616,117 +691,6 @@ class MyInterp:
return -self.f[i](y) * grid_offset[i-1] / self._dgrid[i-1] + \
self.f[i-1](y) * grid_offset[i] / self._dgrid[i-1]
-class VolatilitySurface(ForwardIndex):
- def __init__(self, index_type, series, tenor='5yr', trade_date=datetime.date.today()):
- self._index = Index.from_name(index_type, series, tenor, trade_date, notional=1.)
- self._quotes = pd.read_sql_query(
- "SELECT swaption_quotes.*, ref FROM swaption_quotes " \
- "JOIN swaption_ref_quotes USING (quotedate, index, series, expiry)" \
- "WHERE quotedate::date = %s AND index= %s AND series = %s " \
- "AND quote_source != 'SG' " \
- "ORDER BY quotedate DESC",
- _engine,
- parse_dates = ['quotedate', 'expiry'],
- params=(trade_date, index_type.upper(), series))
- self._quotes.loc[(self._quotes.quote_source == "GS") & (self._quotes['index'] =="HY"),
- ["pay_bid", "pay_offer", "rec_bid", "rec_offer"]] *=100
- if self._quotes.empty:
- raise ValueError("No quotes for that day")
- self._quotes['quotedate'] = (self._quotes['quotedate'].
- dt.tz_convert('America/New_York').
- dt.tz_localize(None))
- self._quotes = self._quotes.sort_values('quotedate')
- self._surfaces = {}
- self._prob_surfaces = {}
-
- def vol(self, T, moneyness, surface_id):
- """computes the vol for a given moneyness and term."""
- return self._surfaces[surface_id](T, moneyness)
-
- def list(self, source=None, option_type=None, model=None):
- """returns list of vol surfaces"""
- r = []
- for k in _get_keys(self._quotes):
- if (source is None or k[1] == source) and \
- (option_type is None or k[2] == option_type) and \
- (model is None or k[3] == model):
- r.append(k)
- return r
-
- def __iter__(self):
- return self._surfaces.items()
-
- def plot(self, surface_id):
- fig = plt.figure()
- ax = fig.gca(projection='3d')
- surf = self[surface_id]
- time, moneyness = surf.get_knots()
- xx, yy = np.meshgrid(np.arange(time[0], time[-1], 0.01),
- np.arange(moneyness[0], moneyness[-1], 0.01))
- surf = ax.plot_surface(xx, yy, self[surface_id].ev(xx, yy),
- cmap = cm.viridis)
- ax.set_xlabel("Year fraction")
- ax.set_ylabel("Moneyness")
- ax.set_zlabel("Volatility")
-
- def prob_surf(self, surface_id):
- if surface_id not in self._prob_surfaces:
- self[surface_id]
- return self._prob_surfaces[surface_id]
-
- def prob_plot(self, surface_id):
- fig = plt.figure()
- ax = fig.gca(projection='3d')
- surf = self.prob_surf(surface_id)
- time, moneyness = surf.get_knots()
- print(time[0], time[-1], moneyness[0], moneyness[-1])
- xx, yy = np.meshgrid(np.arange(time[0], time[-1], 0.01),
- np.arange(moneyness[0], moneyness[-1], 0.01))
- surf = ax.plot_surface(xx, yy, expit(surf.ev(xx, yy)),
- cmap=cm.viridis, vmax=1)
- ax.set_xlabel("Year fraction")
- ax.set_ylabel("Moneyness")
- ax.set_zlabel("Tail Probability")
-
- def __getitem__(self, surface_id):
- if surface_id not in self._surfaces:
- quotedate, source, option_type, model = surface_id
- quotes = self._quotes[(self._quotes.quotedate == quotedate) &
- (self._quotes.quote_source == source)]
- self._index.ref = quotes.ref.iat[0]
- if model == "black":
- swaption_class = BlackSwaption
- else:
- swaption_class = Swaption
- moneyness, T, r = [], [], []
- if option_type == "payer":
- quotes = quotes.assign(mid=quotes[['pay_bid','pay_offer']].mean(1) * 1e-4)
- else:
- quotes = quotes.assign(mid=quotes[['rec_bid','rec_offer']].mean(1) * 1e-4)
- quotes = quotes.dropna(subset=['mid'])
- with Pool(4) as p:
- for expiry, df in quotes.groupby(['expiry']):
- option = swaption_class(self._index, expiry.date(), 100, option_type)
- T.append(option.T * np.ones(df.shape[0]))
- r.append(np.stack(p.starmap(partial(compute_vol, option), df[['strike', 'mid']].values)))
- r = np.concatenate(r)
- vol = r[:,0]
- non_nan = ~np.isnan(vol)
- vol = vol[non_nan]
- time = np.hstack(T)
- time = time[non_nan]
- moneyness = r[non_nan,3]
- strike = r[non_nan,4]
- f = SmoothBivariateSpline(time, moneyness, vol)
- skew = SmoothBivariateSpline(time, strike, vol).ev(time, strike, dy=1)
- tail_prob = r[non_nan,1] + r[non_nan,2] * skew * 1e4
- g = SmoothBivariateSpline(time, moneyness, logit(tail_prob))
- self._surfaces[surface_id] = f
- self._prob_surfaces[surface_id] = g
- return self._surfaces[surface_id]
- else:
- return self._surfaces[surface_id]
-
def calib_sabr(x, option, strikes, pv, beta):
alpha, rho, nu = x
F = option.forward_spread