Exemplo n.º 1
0
 def _adjust_convexity(self, valuation_date, market, model, pricing_context,
                       cms_rates, discount_factors):
   """Computes the convexity adjusted cms rate."""
   if model is None:
     return cms_rates
   elif model in (
       rc.InterestRateModelType.LOGNORMAL_SMILE_CONSISTENT_REPLICATION,
       rc.InterestRateModelType.NORMAL_SMILE_CONSISTENT_REPLICATION):
     return self._convexity_smile_replication(
         valuation_date, market, model, cms_rates, pricing_context)
   else:
     level = self._swap.annuity(valuation_date, market, None)
     expiry_time = dates.daycount_actual_365_fixed(
         start_date=valuation_date,
         end_date=self._coupon_start_dates,
         dtype=self._dtype)
     with tf.GradientTape() as g:
       g.watch(cms_rates)
       fx = self._fs(cms_rates)
     dfx = tf.squeeze(g.gradient(fx, cms_rates))
     swap_vol = tf.convert_to_tensor(pricing_context, dtype=self._dtype)
     if model == rc.InterestRateModelType.LOGNORMAL_RATE:
       cms_rates = cms_rates + dfx * level * (cms_rates**2) * (
           tf.math.exp(swap_vol**2 * expiry_time) - 1.0) / discount_factors
     else:
       cms_rates = cms_rates + dfx * level * (
           swap_vol**2 * expiry_time) / discount_factors
     return cms_rates
Exemplo n.º 2
0
    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
Exemplo n.º 3
0
def get_daycount_fraction(date_start, date_end, convention, dtype):
  """Return the day count fraction between two dates."""
  if convention == DayCountConvention.ACTUAL_365:
    return dates.daycount_actual_365_fixed(
        start_date=date_start, end_date=date_end, dtype=dtype)
  elif convention == DayCountConvention.ACTUAL_360:
    return dates.daycount_actual_360(
        start_date=date_start, end_date=date_end, dtype=dtype)
  elif convention == DayCountConvention.THIRTY_360_ISDA:
    return dates.daycount_thirty_360_isda(
        start_date=date_start, end_date=date_end, dtype=dtype)
  else:
    raise ValueError('Daycount convention not implemented.')
Exemplo n.º 4
0
    def price(self,
              valuation_date,
              market,
              model=None,
              pricing_context=None,
              name=None):
        """Returns the present value of the swaption on the valuation date.

    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 FRA instrument.
      model: An optional input of type `InterestRateModelType` to specify which
        model to use for pricing.
        Default value: `None` in which case LOGNORMAL_RATE model is used.
      pricing_context: An optional input to provide additional parameters (such
        as model parameters) relevant for pricing.
      name: Python str. The name to give to the ops created by this function.
        Default value: `None` which maps to 'price'.

    Returns:
      A Rank 1 `Tensor` of real type containing the modeled price of each IRS
      contract based on the input market data.

    Raises:
      ValueError: If an unsupported model is supplied to the function.
    """
        model = model or rc.InterestRateModelType.LOGNORMAL_RATE
        name = name or (self._name + '_price')
        with tf.name_scope(name):
            swap_annuity = self._swap.annuity(valuation_date, market, model)
            forward_swap_rate = self._swap.par_rate(valuation_date, market,
                                                    model)
            strike = self._swap.fixed_rate

            expiry_time = dates.daycount_actual_365_fixed(
                start_date=valuation_date,
                end_date=self._expiry_date,
                dtype=self._dtype)
            # Ideally we would like the model to tell us how to price the option.
            # The default for European swaptions should be SABR, but the current
            # implementation needs some work.
            if model == rc.InterestRateModelType.LOGNORMAL_RATE:
                option_value = self._price_lognormal_rate(
                    market, pricing_context, forward_swap_rate, strike,
                    expiry_time)
            else:
                raise ValueError('Unsupported model.')

            return self._swap.notional[-1] * swap_annuity * option_value
Exemplo n.º 5
0
  def price(self,
            market: pmd.ProcessedMarketData,
            name: Optional[str] = None):
    """Returns the present value of the swaption on the valuation date.

    Args:
      market: A instance of type `ProcessedMarketData` which contains the
        necessary information for pricing the swaption.
      name: Python str. The name to give to the ops created by this function.
        Default value: `None` which maps to 'price'.

    Returns:
      A Rank `Tensor` of shape `batch_shape` containing the modeled price of
      each  Swaption contract based on the input market data.

    Raises:
      ValueError: If an unsupported model is supplied to the function.
    """
    model = (self._config.model or
             models.InterestRateModelType.HULL_WHITE_ONE_FACTOR)
    name = name or (self._name + "_price")
    with tf.name_scope(name):
      valuation_date = dateslib.convert_to_date_tensor(market.date)
      strike = self._swap.fixed_rate()

      expiry_time = dateslib.daycount_actual_365_fixed(
          start_date=valuation_date,
          end_date=self._expiry_date,
          dtype=self._dtype)

      if model == models.InterestRateModelType.HULL_WHITE_ONE_FACTOR:
        option_value = self._price_hull_white_1_factor(
            valuation_date, market, strike, expiry_time)
      else:
        raise ValueError("Unsupported model.")

      return option_value
Exemplo n.º 6
0
    def get_forward_rate(self,
                         start_date,
                         maturity_date,
                         daycount_fraction=None):
        """Returns the simply accrued forward rate between [start_dt, maturity_dt].

    Args:
      start_date: A `DateTensor` specifying the start of the accrual period
        for the forward rate.
      maturity_date: A `DateTensor` specifying the end of the accrual period
        for the forward rate. The shape of `maturity_date` must be the same
        as the shape of the `DateTensor` `start_date`.
      daycount_fraction: An optional `Tensor` of real dtype specifying the
        time between `start_date` and `maturity_date` in years computed using
        the forward rate's day count basis. The shape of the input should be
        the same as that of `start_date` and `maturity_date`.
        Default value: `None`, in which case the daycount fraction is computed
        using `ACTUAL_365` convention.

    Returns:
      A real tensor of same shape as the inputs containing the simply compounded
      forward rate.
    """
        start_date = dates.convert_to_date_tensor(start_date)
        maturity_date = dates.convert_to_date_tensor(maturity_date)
        if daycount_fraction is None:
            daycount_fraction = dates.daycount_actual_365_fixed(
                start_date=start_date,
                end_date=maturity_date,
                dtype=self._dtype)
        else:
            daycount_fraction = tf.convert_to_tensor(daycount_fraction,
                                                     self._dtype)
        dfstart = self.get_discount_factor(start_date)
        dfmaturity = self.get_discount_factor(maturity_date)
        return (dfstart / dfmaturity - 1.) / daycount_fraction
Exemplo n.º 7
0
    def _get_time(self, desired_dates):
        """Computes the year fraction from the curve's valuation date."""

        return dates.daycount_actual_365_fixed(start_date=self._valuation_date,
                                               end_date=desired_dates,
                                               dtype=self._dtype)
Exemplo n.º 8
0
    def from_market_data(cls,
                         valuation_date,
                         expiry_dates,
                         strikes,
                         implied_volatilities,
                         variance_process,
                         initial_spot,
                         initial_variance,
                         rho=None,
                         risk_free_rate=None,
                         dividend_yield=None,
                         time_step=None,
                         num_grid_points=None,
                         grid_minimums=None,
                         grid_maximums=None,
                         dtype=None):
        """Creates a `LocalStochasticVolatilityModel` from market data.

    This function computes the leverage function for the LSV model by first
    computing the joint probability density function `p(t, X(t), v(t))` where
    `X(t)` is the log of the spot price and `v(t)` is the variance at time `t`.
    The joint probablity density is computed using the Fokker-Planck equation of
    the LSV model (see 6.8.2 in Ref [1]):

    ```None
    dp/dt = 1/2 d^2 [v L(t,X)^2 p]/dX^2 + 1/2 d^2 [b(v)^2 p]/dv^2 +
            rho d^2 [sqrt(v)L(t,X)b(v) p]/dXdv -
            d[(r - d - 1/2 v L(t,X)^2)p]/dX -
            d[a(v) p]/dv
    ```

    where `a(v)` and `b(v)` are the drift and diffusion functions for the
    variance process. Defining

    ```None
    I_n(k,t) = int v^n p(t, k, v) dv
    ```

    we can calculate the leverage function as follows:
    ```None
    L(k, t) = sigma(exp(k), t) sqrt(I_0(k, t)/I_1(k, t)).
    ```

    Note that the computation of `I_0` and `I_1` require the knowledge of
    leverage function and hence the computation of the leverage function is
    implicit in nature.

    Args:
      valuation_date: A scalar `DateTensor` specifying the valuation
        (or settlement) date for the market data.
      expiry_dates: A `DateTensor` of shape `(num_expiries,)` containing the
        expiry dates on which the implied volatilities are specified.
      strikes: A `Tensor` of real dtype and shape `(num_expiries,
        num_strikes)` specifying the strike prices at which implied volatilities
        are specified.
      implied_volatilities: A `Tensor` of real dtype and shape `(num_expiries,
        num_strikes)` specifying the implied volatilities.
      variance_process: An instance of `LSVVarianceModel` or
        `ItoProcess` specifying the dynamics of the variance process of
        the LSV model.
      initial_spot: A real scalar `Tensor` specifying the underlying spot price
        on the valuation date.
      initial_variance: A real scalar `Tensor` specifying the initial variance
        on the valuation date.
      rho: A real scalar `Tensor` specifying the correlation between spot price
        and the stochastic variance.
      risk_free_rate: A real scalar `Tensor` specifying the (continuosly
        compounded) risk free interest rate. If the underlying is an FX rate,
        then use this input to specify the domestic interest rate.
        Default value: `None` in which case the input is set to zero.
      dividend_yield: A real scalar `Tensor` specifying the (continuosly
        compounded) divident yield. If the underlying is an FX rate, then use
        this input to specify the foreign interest rate.
        Default value: `None` in which case the input is set to zero.
      time_step: A real scalar `Tensor` specifying the time step during the
        numerical solution of the Fokker-Planck PDE.
        Default value: None, in which case `time_step` corresponding to 100 time
          steps is used.
      num_grid_points: A scalar integer `Tensor` specifying the number of
        discretization points for each spatial dimension.
        Default value: None, in which case number of grid points is set to 100.
      grid_minimums: An optional `Tensor` of size 2 containing the minimum grid
        points for PDE spatial discretization. `grid_minimums[0]` correspond
        to the minimum spot price in the spatial grid and `grid_minimums[1]`
        correspond to the minimum variance value.
      grid_maximums: An optional `Tensor` of size 2 containing the maximum grid
        points for PDE spatial discretization. `grid_maximums[0]` correspond
        to the maximum spot price in the spatial grid and `grid_maximums[1]`
        correspond to the maximum variance value.
      dtype: The default dtype to use when converting values to `Tensor`s.
        Default value: `None` which means that default dtypes inferred by
          TensorFlow are used.

    Returns:
      An instance of `LocalStochasticVolatilityModel` constructed using the
      input data.
    """
        if risk_free_rate is None:
            discount_factor_fn = lambda t: tf.ones_like(t, dtype=dtype)
        else:
            r = tf.convert_to_tensor(risk_free_rate, dtype=dtype)
            discount_factor_fn = lambda t: tf.math.exp(-r * t)
        lv_model = lvm.LocalVolatilityModel.from_market_data(
            dim=1,
            valuation_date=valuation_date,
            expiry_dates=expiry_dates,
            strikes=strikes,
            implied_volatilities=implied_volatilities,
            spot=initial_spot,
            discount_factor_fn=discount_factor_fn,
            dividend_yield=dividend_yield,
            dtype=dtype)

        dtype = dtype or lv_model.dtype()
        max_time = tf.math.reduce_max(
            dates.daycount_actual_365_fixed(start_date=valuation_date,
                                            end_date=expiry_dates,
                                            dtype=dtype))
        if time_step is None:
            time_step = max_time / 100.0

        rho = rho or 0.0
        num_grid_points = num_grid_points or 100

        leverage_fn = _leverage_function_using_pde(
            risk_free_rate=risk_free_rate,
            dividend_yield=dividend_yield,
            lv_model=lv_model,
            variance_model=variance_process,
            rho=[rho],
            initial_spot=initial_spot,
            initial_variance=initial_variance,
            time_step=time_step,
            max_time=max_time,
            num_grid_points=num_grid_points,
            grid_minimums=grid_minimums,
            grid_maximums=grid_maximums,
            dtype=dtype)
        return LocalStochasticVolatilityModel(leverage_fn,
                                              variance_process,
                                              risk_free_rate=risk_free_rate,
                                              dividend_yield=dividend_yield,
                                              rho=rho,
                                              dtype=dtype)
Exemplo n.º 9
0
  def _convexity_smile_replication(self, valuation_date, market, model,
                                   cms_rates, pricing_context):
    """Calculate CMS convexity correction by static replication."""
    normal_model = (
        model == rc.InterestRateModelType.NORMAL_SMILE_CONSISTENT_REPLICATION)
    swap_vol = tf.convert_to_tensor(pricing_context, dtype=self._dtype)
    expiry_time = dates.daycount_actual_365_fixed(
        start_date=valuation_date,
        end_date=self._coupon_start_dates,
        dtype=self._dtype)
    lower = tf.zeros_like(cms_rates) + 1e-6
    # TODO(b/154407973): Improve the logic to compute the upper limit.
    rate_limit = 2000.0
    upper = rate_limit * cms_rates
    num_points = 10001

    def _call_replication():
      def _intfun_call(x):
        d2fx = self._f_atm_second_derivative(x, cms_rates)

        forwards = tf.broadcast_to(tf.expand_dims(cms_rates, -1), x.shape)
        expiries = tf.broadcast_to(tf.expand_dims(expiry_time, -1), x.shape)
        option_val = _option_prices(
            volatilities=swap_vol, strikes=x, expiries=expiries,
            forwards=forwards, is_normal_model=normal_model,
            dtype=self._dtype)
        return d2fx * option_val

      intval_c = integration.integrate(
          _intfun_call, cms_rates, upper, num_points=num_points)
      dfk = self._f_atm_first_derivative(cms_rates, cms_rates)
      c_k = _option_prices(volatilities=swap_vol, strikes=cms_rates,
                           expiries=expiry_time, forwards=cms_rates,
                           is_normal_model=normal_model,
                           dtype=self._dtype)
      return (1.0 + dfk) * c_k + intval_c

    def _put_replication():
      def _intfun_put(x):
        d2fx = self._f_atm_second_derivative(x, cms_rates)

        forwards = tf.broadcast_to(tf.expand_dims(cms_rates, -1), x.shape)
        expiries = tf.broadcast_to(tf.expand_dims(expiry_time, -1), x.shape)
        option_val = _option_prices(
            volatilities=swap_vol, strikes=x, expiries=expiries,
            forwards=forwards, is_call_options=False,
            is_normal_model=normal_model, dtype=self._dtype)
        return d2fx * option_val

      intval_p = integration.integrate(
          _intfun_put, lower, cms_rates, num_points=num_points)
      dfk = self._f_atm_first_derivative(cms_rates, cms_rates)
      p_k = _option_prices(volatilities=swap_vol, strikes=cms_rates,
                           expiries=expiry_time, forwards=cms_rates,
                           is_call_options=False, is_normal_model=normal_model,
                           dtype=self._dtype)
      return (1.0 + dfk) * p_k - intval_p

    call_rep = _call_replication()
    put_rep = _put_replication()

    return cms_rates + (call_rep - put_rep)
Exemplo n.º 10
0
  def _price_hull_white_1_factor(self, valuation_date, market,
                                 strike, expiry_time):
    """Price the swaption using Hull-White 1-factor model."""

    if isinstance(
        self._swap.pay_leg(), cashflow_streams.FloatingCashflowStream):
      floating_leg = self._swap.pay_leg()
      fixed_leg = self._swap.receive_leg()
    else:
      fixed_leg = self._swap.pay_leg()
      floating_leg = self._swap.receive_leg()

    # Get the reference curve from the floating leg of the underlying swap
    reference_curve = market.yield_curve(floating_leg.reference_curve_type[0])
    valuation_date_ordinal = tf.cast(valuation_date.ordinal(),
                                     dtype=self._dtype)

    def _refercence_rate_fn(t):
      # The input `t` is a real `Tensor` specifying the time from valuation.
      # We convert it into a `DateTensor` by first conversting it into the
      # corresponding ordinal (assuming ACT_365 convention).
      interpolation_ordinals = tf.cast(
          tf.round(t * 365.0 +  valuation_date_ordinal), dtype=tf.int32)
      interpolation_dates = dateslib.convert_to_date_tensor(
          interpolation_ordinals)
      return reference_curve.discount_rate(interpolation_dates)

    floating_leg_start_times = dateslib.daycount_actual_365_fixed(
        start_date=valuation_date,
        end_date=floating_leg.coupon_start_dates,
        dtype=self._dtype)
    floating_leg_end_times = dateslib.daycount_actual_365_fixed(
        start_date=valuation_date,
        end_date=floating_leg.coupon_end_dates,
        dtype=self._dtype)
    fixed_leg_payment_times = dateslib.daycount_actual_365_fixed(
        start_date=valuation_date,
        end_date=fixed_leg.cashflow_dates,
        dtype=self._dtype)
    # Add the extra dimension corresponding to multiple payments in the fixed
    # leg.
    fixed_leg_coupon = tf.broadcast_to(tf.expand_dims(strike, axis=-1),
                                       fixed_leg_payment_times.shape)
    is_payer_swaption = tf.convert_to_tensor(
        isinstance(self._swap.pay_leg(), cashflow_streams.FixedCashflowStream),
        dtype=tf.bool)
    notional = self._swap.pay_leg().notional

    hw_price = hull_white.swaption_price(
        expiries=expiry_time,
        floating_leg_start_times=floating_leg_start_times,
        floating_leg_end_times=floating_leg_end_times,
        fixed_leg_payment_times=fixed_leg_payment_times,
        floating_leg_daycount_fractions=floating_leg.daycount_fractions,
        fixed_leg_daycount_fractions=fixed_leg.daycount_fractions,
        fixed_leg_coupon=fixed_leg_coupon,
        reference_rate_fn=_refercence_rate_fn,
        is_payer_swaption=is_payer_swaption,
        use_analytic_pricing=True,
        notional=notional,
        dim=1,
        mean_reversion=self._config.model_params.mean_reversion,
        volatility=self._config.model_params.volatility,
        dtype=self._dtype)
    return hw_price