diff options
| -rw-r--r-- | python/analytics/option.py | 214 |
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 |
