def _price_lognormal_rate(self, valuation_date, market, pricing_context): """Computes caplet/floorlet prices using lognormal model for forward rates. The function computes individual caplet prices for the batch of caps/floors using the lognormal model for the forward rates. If the volatilities are are supplied (through the input `pricing_context`) then they are used as forward rate volatilies. Otherwise, volatilities are extracted using the volatility surface for `market`. Args: valuation_date: A scalar `DateTensor` specifying the date on which valuation is being desired. market: A namedtuple of type `InterestRateMarket` which contains the necessary information for pricing the Cap/Floor. pricing_context: An optional input containing the black volatility for for the forward rates. Returns: A Rank 1 `Tensor` of real type containing the price of each caplet (or floorlet) based using the lognormal model for forward rates. """ discount_curve = market.discount_curve discount_factors = tf.where( self._payment_dates > valuation_date, discount_curve.get_discount_factor(self._payment_dates), 0.) forward_rates = self._get_forward_rate(valuation_date, market) if pricing_context is None: volatility_surface = market.volatility_curve black_vols = volatility_surface.interpolate( self._reset_dates, self._strike, self._term) else: black_vols = tf.convert_to_tensor(pricing_context, dtype=self._dtype) expiry_times = dates.daycount_actual_365_fixed( start_date=valuation_date, end_date=self._reset_dates, dtype=self._dtype) caplet_prices = black_scholes.option_price( forwards=forward_rates, strikes=self._strike, volatilities=black_vols, expiries=expiry_times, is_call_options=self._is_cap) intrinsic_value = tf.where( self._is_cap, tf.math.maximum(forward_rates - self._strike, 0.0), tf.math.maximum(self._strike - forward_rates, 0)) caplet_prices = tf.where( self._payment_dates < valuation_date, tf.constant(0., dtype=self._dtype), tf.where(self._accrual_start_dates < valuation_date, intrinsic_value, caplet_prices)) caplet_prices = self._notional * self._daycount_fractions * caplet_prices return discount_factors * caplet_prices
def _option_price(expiry_time, strike): vols = implied_volatility_surface(strike=strike, expiry_times=expiry_time) c_k_t = black_scholes.option_price(volatilities=vols, strikes=strike, expiries=expiry_time, spots=initial_spot_price, discount_rates=risk_free_rate, continuous_dividends=dividend_yield, dtype=dtype) return c_k_t
def _option_price(expiry_time, strike): discount_factors = tf.convert_to_tensor( discount_factor_fn(expiry_time), dtype=dtype) vols = implied_volatility_surface(strike=strike, expiry_times=expiry_time) c_k_t = black_scholes.option_price(volatilities=vols, strikes=strike, expiries=expiry_time, spots=initial_spot_price, dividend_rates=dividend_yield, discount_factors=discount_factors, dtype=dtype) return c_k_t
def _price_lognormal_rate(self, market, pricing_context, forward_swap_rate, strike, expiry_time): """Price the swaption using lognormal model for rate.""" # Ideally we would like the model to tell what piece of market data is # needed. For example, a Black lognormal model will tell us to pick # lognormal vols and Black normal model should tell us to pick normal # vols. if pricing_context is None: swaption_vol_cube = rc.get_implied_volatility_data(market) term = self._swap.swap_term black_vols = swaption_vol_cube.interpolate(self._expiry_date, strike, term) else: black_vols = tf.convert_to_tensor(pricing_context, dtype=self._dtype) return black_scholes.option_price(volatilities=black_vols, strikes=strike, expiries=expiry_time, forwards=forward_swap_rate, is_call_options=self._swap.is_payer, dtype=self._dtype)
def _option_prices(*, volatilities=None, strikes=None, forwards=None, expiries=None, is_call_options=True, is_normal_model=True, dtype=None): """Computes prices of European options using normal model. Args: volatilities: Real `Tensor` of any shape and dtype. The volatilities to expiry of the options to price. strikes: A real `Tensor` of the same dtype and compatible shape as `volatilities`. The strikes of the options to be priced. forwards: A real `Tensor` of any shape that broadcasts to the shape of `volatilities`. The forwards to maturity. Either this argument or the expiries: A real `Tensor` of same dtype and compatible shape as `volatilities`. The expiry of each option. The units should be such that `expiry * volatility**2` is dimensionless. is_call_options: A boolean `Tensor` of a shape compatible with `volatilities`. Indicates whether the option is a call (if True) or a put (if False). If not supplied, call options are assumed. is_normal_model: A boolean `Tensor` of a shape compatible with `volatilities`. Indicates whether the options should be priced using normal model (if True) or lognormal model (if False). If not supplied, normal model is assumed. dtype: Optional `tf.DType`. If supplied, the dtype to be used for conversion of any supplied non-`Tensor` arguments to `Tensor`. Default value: `None` which maps to the default dtype inferred by TensorFlow. Returns: Options prices computed using normal model for the underlying. """ dtype = dtype or tf.constant(0.0).dtype def _ncdf(x): sqrt_2 = tf.math.sqrt(tf.constant(2.0, dtype=dtype)) return (tf.math.erf(x / sqrt_2) + 1) / 2 sqrt_var = tf.math.sqrt(expiries) * volatilities d = (forwards - strikes) / sqrt_var mu = tf.constant(0., dtype=dtype) loc = tf.constant(1., dtype=dtype) value = tf.where( is_normal_model, tf.where(is_call_options, (forwards - strikes) * _ncdf(d) + sqrt_var * tfp.distributions.Normal(mu, loc).prob(d), (strikes - forwards) * _ncdf(-d) + sqrt_var * tfp.distributions.Normal(mu, loc).prob(d)), black_scholes.option_price( volatilities=volatilities, strikes=strikes, expiries=expiries, forwards=forwards, is_call_options=is_call_options, dtype=dtype)) value = tf.where( expiries > 0, value, tf.where(is_call_options, tf.maximum(forwards - strikes, 0.0), tf.maximum(strikes - forwards, 0.0))) return value