Exemple #1
0
 def test_linear_interpolation_broadcast_y(self):
     """Tests compatible `x_data` and `y_data`."""
     x = [-10, -1, 1, 3, 6, 7, 8, 15, 18, 25, 30, 35]
     x_data = [-1, 2, 6, 8, 18]
     y_data = 10
     result = self.evaluate(
         linear.interpolate(x, x_data, y_data, dtype=tf.float64))
     self.assertAllClose(result, np.repeat(10, len(x)), 1e-8)
Exemple #2
0
 def test_linear_interpolation_unequal_lengths_xys(self):
     """Tests incompatible `x_data` and `y_data`."""
     x = [1, 2]
     x_data = [-1, 2, 6, 8, 18]
     y_data = [10, -1, -5, 7, 9, 20]
     with self.assertRaises((tf.errors.InvalidArgumentError, ValueError)):
         self.evaluate(
             linear.interpolate(x, x_data, y_data, dtype=tf.float64))
Exemple #3
0
 def test_linear_interpolation_const_extrapolation(self):
     """Tests linear interpolation with const extrapolation."""
     x = [-10, -1, 1, 3, 6, 7, 8, 15, 18, 25, 30, 35]
     x_data = [-1, 2, 6, 8, 18, 30.0]
     y_data = [10, -1, -5, 7, 9, 20]
     result = self.evaluate(
         linear.interpolate(x, x_data, y_data, dtype=tf.float32))
     self.assertAllClose(
         result, [np.interp(x_coord, x_data, y_data) for x_coord in x],
         1e-8)
     self.assertIsInstance(result[0], np.float32)
Exemple #4
0
    def interpolate(self, x_values, y_values, name=None):
        """Performs 2-D interpolation on a specified set of points.

    Args:
      x_values: Real-valued `Tensor` of shape `batch_shape + [num_points]`.
        Defines the x-coordinates at which the interpolation should be
        performed. Note that `batch_shape` should be the same as in the
        underlying data.
      y_values: A `Tensor` of the same shape and `dtype` as `x_values`.
        Defines the y-coordinates at which the interpolation should be
        performed.
      name: Python `str` name prefixed to ops created by this function.
        Default value: `None` which is mapped to the default name
        `interpolate`.

    Returns:
      A `Tensor` of the same shape and `dtype` as `x_values`. Represents the
      interpolated values of the function on for the coordinates
      `(x_values, y_values)`.
    """
        name = name or self._name + "_interpolate"
        with tf.name_scope(name):
            x_values = tf.convert_to_tensor(x_values,
                                            dtype=self._dtype,
                                            name="x_values")
            y_values = tf.convert_to_tensor(y_values,
                                            dtype=self._dtype,
                                            name="y_values")

            # Broadcast `y_values` to the number of `x_data` points
            y_values = tf.expand_dims(y_values, axis=-2)
            # For each `x_data` point interpolate values of the function at the
            # y_values. Shape of `xy_values` is
            # batch_shape + `[num_x_data_points, num_points]`.
            xy_values = cubic.interpolate(y_values,
                                          self._spline_yz,
                                          name="interpolation_in_y_direction")
            # Interpolate the value of the function along x-direction
            # Prepare xy_values for linear interpolation. Put the batch dims in front
            xy_rank = xy_values.shape.rank
            perm = [xy_rank - 1] + list(range(xy_rank - 1))
            # Shape [num_points] + batch_shape + [num_x_data_points]
            yx_values = tf.transpose(xy_values, perm=perm)
            # Get the permutation to the original shape
            perm_original = list(range(1, xy_rank)) + [0]
            # Reshape to [num_points] + batch_shape + [1]
            x_values = tf.expand_dims(tf.transpose(x_values, [xy_rank - 2] +
                                                   list(range(xy_rank - 2))),
                                      axis=-1)
            # Interpolation takes care of braodcasting
            z_values = linear.interpolate(x_values, self._xdata, yx_values)
            return tf.squeeze(tf.transpose(z_values, perm=perm_original),
                              axis=-2)
Exemple #5
0
 def test_linear_interpolation_const_extrapolation_default_dtype(self):
     """Tests linear interpolation with const extrapolation."""
     x = [
         -10.0, -1.0, 1.0, 3.0, 6.0, 7.0, 8.0, 15.0, 18.0, 25.0, 30.0, 35.0
     ]
     x_data = [-1.0, 2.0, 6.0, 8.0, 18.0, 30.0]
     y_data = [10.0, -1.0, -5.0, 7.0, 9.0, 20.0]
     result = self.evaluate(linear.interpolate(x, x_data, y_data))
     self.assertAllClose(
         result, [np.interp(x_coord, x_data, y_data) for x_coord in x],
         1e-8)
     # All above real would be converted to float32.
     self.assertIsInstance(result[0], np.float32)
Exemple #6
0
    def interpolate(self, x_values, y_values, name=None):
        """Performs 2-D interpolation on a specified set of points.

    Args:
      x_values: Real-valued `Tensor` of rank 1. Defines the x-coordinates at
        which the interpolation should be performed.
      y_values: Rank 1 `Tensor` of the same `dtype` as `x_values`. Defines the
        y-coordinates at which the interpolation should be performed.
      name: Python `str` name prefixed to ops created by this function.
        Default value: `None` which is mapped to the default name
        `interpolate`.

    Returns:
      A `Tensor` of the same `dtype` as `x_values` and of shape
      `x_values.shape + y_values.shape`. Represents the interpolated values of
      the function on the grid `[x_values, y_values]`
    """
        name = name or self._name + "_interpolate"
        with tf.name_scope(name):
            x_values = tf.convert_to_tensor(x_values,
                                            dtype=self._dtype,
                                            name="x_values")
            y_values = tf.convert_to_tensor(y_values,
                                            dtype=self._dtype,
                                            name="y_values")

            num_x_data = self._xdata.shape.as_list()[-1]
            num_y_values = y_values.shape.as_list()[-1]
            # Broadcast `y_values` to the number of `x_data` points
            y_values = (
                tf.expand_dims(y_values, 0) +
                tf.zeros([num_x_data, num_y_values], dtype=y_values.dtype))
            # For each `x_data` point interpolate values of the function at the
            # y_values. Shape of `xy_values` is `[num_x_data, num_y_values]`.
            xy_values = cubic.interpolate(y_values,
                                          self._spline_yz,
                                          name="interpolation_in_y_direction")
            # Interpolate the value of the function along x-direction
            # Prepare xy_values for linear interpolation. Put the batch dims in front
            xy_rank = xy_values.shape.rank
            perm = [xy_rank - 1] + list(range(xy_rank - 1))
            yx_values = tf.transpose(xy_values, perm=perm)
            # Get the permutation to the original shape
            perm_original = list(range(1, xy_rank)) + [0]
            # Interpolation takes care of braodcasting
            z_values = linear.interpolate(x_values, self._xdata, yx_values)
            return tf.transpose(z_values, perm=perm_original)
Exemple #7
0
 def test_linear_interpolation_nonconst_extrapolation(self):
     """Tests linear interpolation with nonconst extrapolation."""
     x = [-10, -2, -1, 1, 3, 6, 7, 8, 15, 18, 25, 30, 31, 35]
     x_data = np.array([-1, 2, 6, 8, 18, 30.0])
     y_data_as_list = [10, -1, -5, 7, 9, 20]
     y_data = tf.convert_to_tensor(y_data_as_list, dtype=tf.float64)
     left_slope = 2.0
     right_slope = -3.0
     result = self.evaluate(
         linear.interpolate(x,
                            x_data,
                            y_data,
                            left_slope=left_slope,
                            right_slope=right_slope,
                            dtype=tf.float64))
     expected_left = 10.0 + left_slope * (np.array([-10.0, -2.0]) - (-1.0))
     expected_right = 20.0 + right_slope * (np.array([31.0, 35.0]) - 30.0)
     expected_middle = [
         np.interp(x_coord, x_data, y_data_as_list) for x_coord in x[2:-2]
     ]
     self.assertAllClose(
         result,
         np.concatenate([expected_left, expected_middle, expected_right]),
         1e-8)
Exemple #8
0
 def default_interpolator(xi, x, y):
     return linear.interpolate(xi, x, y, dtype=dtype)
Exemple #9
0
def swap_curve_bootstrap(float_leg_start_times,
                         float_leg_end_times,
                         fixed_leg_start_times,
                         fixed_leg_end_times,
                         fixed_leg_cashflows,
                         present_values,
                         present_values_settlement_times=None,
                         float_leg_daycount_fractions=None,
                         fixed_leg_daycount_fractions=None,
                         float_leg_discount_rates=None,
                         float_leg_discount_times=None,
                         fixed_leg_discount_rates=None,
                         fixed_leg_discount_times=None,
                         curve_interpolator=None,
                         initial_curve_rates=None,
                         curve_tolerance=1e-8,
                         maximum_iterations=50,
                         dtype=None,
                         name=None):
  """Constructs the zero swap curve using bootstrap method.

  A zero swap curve is a function of time which gives the interest rate that
  can be used to project forward rates at arbitrary `t` for the valuation of
  interest rate securities (e.g. FRAs, Interest rate futures, Swaps etc.).

  Suppose we have a set of `N` Interest Rate Swaps (IRS) `S_i` with increasing
  expiries whose market prices are known.
  Suppose also that the `i`th IRS issues cashflows at times `T_{ij}` where
  `1 <= j <= n_i` and `n_i` is the number of cashflows (including expiry)
  for the `i`th swap.
  Denote by `T_i` the time of final payment for the `i`th swap
  (hence `T_i = T_{i,n_i}`). This function estimates a set of rates `r(T_i)`
  such that when these rates are interpolated (using the user specified
  interpolation method) to all other cashflow times, the computed value of the
  swaps matches the market value of the swaps (within some tolerance).

  The algorithm implemented here uses the bootstrap method to iteratively
  construct the swap curve [1].

  ### Example:

  The following example illustrates the usage by building an implied swap curve
  from four vanilla (fixed to float) LIBOR swaps.

  ```python

  dtype = np.float64

  # Next we will set up LIBOR reset and payment times for four spot starting
  # swaps with maturities 1Y, 2Y, 3Y, 4Y. The LIBOR rate spans 6M.

  float_leg_start_times = [
            np.array([0., 0.5], dtype=dtype),
            np.array([0., 0.5, 1., 1.5], dtype=dtype),
            np.array([0., 0.5, 1.0, 1.5, 2.0, 2.5], dtype=dtype),
            np.array([0., 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5], dtype=dtype)
        ]

  float_leg_end_times = [
            np.array([0.5, 1.0], dtype=dtype),
            np.array([0.5, 1., 1.5, 2.0], dtype=dtype),
            np.array([0.5, 1.0, 1.5, 2.0, 2.5, 3.0], dtype=dtype),
            np.array([0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0], dtype=dtype)
        ]

  # Next we will set up start and end times for semi-annual fixed coupons.

  fixed_leg_start_times = [
            np.array([0., 0.5], dtype=dtype),
            np.array([0., 0.5, 1., 1.5], dtype=dtype),
            np.array([0., 0.5, 1.0, 1.5, 2.0, 2.5], dtype=dtype),
            np.array([0., 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5], dtype=dtype)
        ]

  fixed_leg_end_times = [
            np.array([0.5, 1.0], dtype=dtype),
            np.array([0.5, 1., 1.5, 2.0], dtype=dtype),
            np.array([0.5, 1.0, 1.5, 2.0, 2.5, 3.0], dtype=dtype),
            np.array([0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0], dtype=dtype)
        ]

  # Next setup a trivial daycount for floating and fixed legs.

  float_leg_daycount = [
            np.array([0.5, 0.5], dtype=dtype),
            np.array([0.5, 0.5, 0.5, 0.5], dtype=dtype),
            np.array([0.5, 0.5, 0.5, 0.5, 0.5, 0.5], dtype=dtype),
            np.array([0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5], dtype=dtype)
        ]

  fixed_leg_daycount = [
            np.array([0.5, 0.5], dtype=dtype),
            np.array([0.5, 0.5, 0.5, 0.5], dtype=dtype),
            np.array([0.5, 0.5, 0.5, 0.5, 0.5, 0.5], dtype=dtype),
            np.array([0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5], dtype=dtype)
        ]

  fixed_leg_cashflows = [
        # 1 year swap with 2.855% semi-annual fixed payments.
        np.array([-0.02855, -0.02855], dtype=dtype),
        # 2 year swap with 3.097% semi-annual fixed payments.
        np.array([-0.03097, -0.03097, -0.03097, -0.03097], dtype=dtype),
        # 3 year swap with 3.1% semi-annual fixed payments.
        np.array([-0.031, -0.031, -0.031, -0.031, -0.031, -0.031], dtype=dtype),
        # 4 year swap with 3.2% semi-annual fixed payments.
        np.array([-0.032, -0.032, -0.032, -0.032, -0.032, -0.032, -0.032,
        -0.032], dtype=dtype)
    ]

  # The present values of the above IRS.
    pvs = np.array([0., 0., 0., 0.], dtype=dtype)

  # Initial state of the curve.
  initial_curve_rates = np.array([0.01, 0.01, 0.01, 0.01], dtype=dtype)

  results = swap_curve_bootstrap(float_leg_start_times, float_leg_end_times,
                                 float_leg_daycount, fixed_leg_start_times,
                                 fixed_leg_end_times, fixed_leg_cashflows,
                                 fixed_leg_daycount, pvs, dtype=dtype,
                                 initial_curve_rates=initial_curve_rates)

  ### References:

  [1]: Patrick Hagan & Graeme West. Interpolation Methods for Curve
    Construction. Applied Mathematical Finance. Vol 13, No. 2, pp 89-129.
    June 2006.
    https://www.researchgate.net/publication/24071726_Interpolation_Methods_for_Curve_Construction

  Args:
    float_leg_start_times: List of `Tensor`s. Each `Tensor` must be of rank 1
      and of the same real dtype. They may be of different sizes. Each `Tensor`
      represents the beginning of the accrual period for the forward rate which
      determines the floating payment. Each element in the list belong to a
      unique swap to be used to build the curve.
    float_leg_end_times: List of `Tensor`s. Each `Tensor` must be of rank 1 and
      and the same shape and of the same real dtype as the corresponding element
      in `float_leg_start_times`. Each `Tensor` represents the end of the
      accrual period for the forward rate which determines the floating payment.
    fixed_leg_start_times: List of `Tensor`s. Each `Tensor` must be of rank 1
      and of the same real dtype. They may be of different sizes. Each `Tensor`
      represents the begining of the accrual period fixed coupon.
    fixed_leg_end_times: List of `Tensor`s. Each `Tensor` must be of the same
      shape and type as `fixed_leg_start_times`. Each `Tensor` represents the
      end of the accrual period for the fixed coupon.
    fixed_leg_cashflows: List of `Tensor`s. The list must be of the same length
      as the `fixed_leg_start_times`. Each `Tensor` must be of rank 1 and of the
      same dtype as the `Tensor`s in `fixed_leg_start_times`. The input contains
      fixed cashflows at each coupon payment time including notional (if any).
      The sign should be negative (positive) to indicate net outgoing (incoming)
      cashflow.
    present_values: List containing scalar `Tensor`s of the same dtype as
      elements of `fixed_leg_cashflows`. The length of the list must be the same
      as the length of `fixed_leg_cashflows`. The input contains the market
      price of the underlying instruments.
    present_values_settlement_times: List containing scalar `Tensor`s of the
      same dtype as elements of `present_values`. The length of the list must be
      the same as the length of `present_values`. The settlement times for the
      present values is the time from now when the instrument is traded to the
      time that the purchase price is actually delivered. If not supplied, then
      it is assumed that the settlement times are zero for every bond.
      Default value: `None`, which is equivalent to zero settlement times.
    float_leg_daycount_fractions: Optional list of `Tensor`s. Each `Tensor` must
      be of the same shape and type as `float_leg_start_times`. They may be of
      different sizes. Each `Tensor` represents the daycount fraction of the
      forward rate which determines the floating payment.
      Default value: `None`, If omitted the daycount fractions are computed as
      the difference between float_leg_end_times and float_leg_start_times.
    fixed_leg_daycount_fractions: Optional list of `Tensor`s. Each `Tensor` must
      be of the same shape and type as `fixed_leg_start_times`. Each `Tensor`
      represents the daycount fraction applicable for the fixed payment.
      Default value: `None`, If omitted the daycount fractions are computed as
      the difference between fixed_leg_end_times and fixed_leg_start_times.
    float_leg_discount_rates: Optional `Tensor` of the same dtype as
      `initial_discount_rates`. This input contains the continuously compounded
      discount rates the will be used to discount the floating cashflows. This
      allows the swap curve to constructed using an independent discount curve
      (e.g. OIS curve).
      Default value: `None`, in which case the cashflows are discounted using
      the curve that is being constructed.
    float_leg_discount_times: Optional `Tensor` of the same dtype and shape as
      `float_leg_discount_rates`. This input contains the times corresponding to
      the rates specified via the `float_leg_discount_rates`.
    fixed_leg_discount_rates: Optional `Tensor` of the same dtype as
      `initial_discount_rates`. This input contains the continuously compounded
      discount rates the will be used to discount the fixed cashflows. This
      allows the swap curve to constructed using an independent discount curve
      (e.g. OIS curve).
      Default value: `None`, in which case the cashflows are discounted using
      the curve that is being constructed.
    fixed_leg_discount_times: Optional `Tensor` of the same dtype and shape as
      `fixed_leg_discount_rates`. This input contains the times corresponding to
      the rates specified via the `fixed_leg_discount_rates`.
    curve_interpolator: Optional Python callable used to interpolate the zero
      swap rates at cashflow times. It should have the following interface:
      yi = curve_interpolator(xi, x, y)
      `x`, `y`, 'xi', 'yi' are all `Tensors` of real dtype. `x` and `y` are the
      sample points and values (respectively) of the function to be
      interpolated. `xi` are the points at which the interpolation is
      desired and `yi` are the corresponding interpolated values returned by the
      function.
      Default value: `None`, which maps to linear interpolation.
    initial_curve_rates: Optional `Tensor` of the same dtype and shape as
      `present_values`. The starting guess for the discount rates used to
      initialize the iterative procedure.
      Default value: `None`. If not supplied, the yields to maturity for the
        bonds is used as the initial value.
    curve_tolerance: Optional positive scalar `Tensor` of same dtype as
      elements of `bond_cashflows`. The absolute tolerance for terminating the
      iterations used to fit the rate curve. The iterations are stopped when the
      estimated discounts at the expiry times of the bond_cashflows change by a
      amount smaller than `discount_tolerance` in an iteration.
      Default value: 1e-8.
    maximum_iterations: Optional positive integer `Tensor`. The maximum number
      of iterations permitted when fitting the curve.
      Default value: 50.
    dtype: `tf.Dtype`. If supplied the dtype for the (elements of)
      `float_leg_start_times`, and `fixed_leg_start_times`.
      Default value: None which maps to the default dtype inferred by
      TensorFlow.
    name: Python str. The name to give to the ops created by this function.
      Default value: `None` which maps to 'swap_curve'.

  Returns:
    curve_builder_result: An instance of `SwapCurveBuilderResult` containing the
      following attributes.
      times: Rank 1 real `Tensor`. Times for the computed rates. These
        are chosen to be the expiry times of the supplied instruments.
      rates: Rank 1 `Tensor` of the same dtype as `times`.
        The inferred zero rates.
      discount_factor: Rank 1 `Tensor` of the same dtype as `times`.
        The inferred discount factors.
      initial_rates: Rank 1 `Tensor` of the same dtype as `times`. The
        initial guess for the rates.
      converged: Scalar boolean `Tensor`. Whether the procedure converged.
        The procedure is said to have converged when the maximum absolute
        difference in the discount factors from one iteration to the next falls
        below the `discount_tolerance`.
      failed: Scalar boolean `Tensor`. Whether the procedure failed. Procedure
        may fail either because a NaN value was encountered for the discount
        rates or the discount factors.
      iterations: Scalar int32 `Tensor`. Number of iterations performed.

  Raises:
    ValueError: If the initial state of the curve is not
    supplied to the function.

  """

  name = name or 'swap_curve_bootstrap'
  with tf.name_scope(name):

    if curve_interpolator is None:
      curve_interpolator = lambda xi, x, y: linear.interpolate(
          xi, x, y, dtype=dtype)

    if present_values_settlement_times is None:
      pv_settle_times = [tf.zeros_like(pv) for pv in present_values]
    else:
      pv_settle_times = present_values_settlement_times

    if float_leg_daycount_fractions is None:
      float_leg_daycount_fractions = [
          y - x for x, y in zip(float_leg_start_times, float_leg_end_times)
      ]

    if fixed_leg_daycount_fractions is None:
      fixed_leg_daycount_fractions = [
          y - x for x, y in zip(fixed_leg_start_times, fixed_leg_end_times)
      ]

    float_leg_start_times = _convert_to_tensors(dtype, float_leg_start_times,
                                                'float_leg_start_times')
    float_leg_end_times = _convert_to_tensors(dtype, float_leg_end_times,
                                              'float_leg_end_times')
    float_leg_daycount_fractions = _convert_to_tensors(
        dtype, float_leg_daycount_fractions, 'float_leg_daycount_fractions')
    fixed_leg_start_times = _convert_to_tensors(dtype, fixed_leg_start_times,
                                                'fixed_leg_start_times')
    fixed_leg_end_times = _convert_to_tensors(dtype, fixed_leg_end_times,
                                              'fixed_leg_end_times')
    fixed_leg_daycount_fractions = _convert_to_tensors(
        dtype, fixed_leg_daycount_fractions, 'fixed_leg_daycount_fractions')
    fixed_leg_cashflows = _convert_to_tensors(dtype, fixed_leg_cashflows,
                                              'fixed_leg_cashflows')
    present_values = _convert_to_tensors(dtype, present_values,
                                         'present_values')
    pv_settle_times = _convert_to_tensors(dtype, pv_settle_times,
                                          'pv_settle_times')

    self_discounting_float_leg = False
    self_discounting_fixed_leg = False
    # Determine how the floating and fixed leg will be discounted. If separate
    # discount curves for each leg are not specified, the curve will be self
    # discounted using the swap curve.
    if float_leg_discount_rates is None and fixed_leg_discount_rates is None:
      self_discounting_float_leg = True
      self_discounting_fixed_leg = True
      float_leg_discount_rates = [0.0]
      float_leg_discount_times = [0.]
      fixed_leg_discount_rates = [0.]
      fixed_leg_discount_times = [0.]
    elif fixed_leg_discount_rates is None:
      fixed_leg_discount_rates = float_leg_discount_rates
      fixed_leg_discount_times = float_leg_discount_times
    elif float_leg_discount_rates is None:
      self_discounting_float_leg = True
      float_leg_discount_rates = [0.]
      float_leg_discount_times = [0.]

    # Create tensors for discounting curves
    float_leg_discount_rates = _convert_to_tensors(dtype,
                                                   float_leg_discount_rates,
                                                   'float_disc_rates')
    float_leg_discount_times = _convert_to_tensors(dtype,
                                                   float_leg_discount_times,
                                                   'float_disc_times')
    fixed_leg_discount_rates = _convert_to_tensors(dtype,
                                                   fixed_leg_discount_rates,
                                                   'fixed_disc_rates')
    fixed_leg_discount_times = _convert_to_tensors(dtype,
                                                   fixed_leg_discount_times,
                                                   'fixed_disc_times')

    if initial_curve_rates is not None:
      initial_rates = tf.convert_to_tensor(
          initial_curve_rates, dtype=dtype, name='initial_rates')
    else:
      # TODO(b/144600429): Create a logic for a meaningful initial state of the
      # curve
      raise ValueError('Initial state of the curve is not specified.')

    return _build_swap_curve(float_leg_start_times,
                             float_leg_end_times,
                             float_leg_daycount_fractions,
                             fixed_leg_start_times,
                             fixed_leg_end_times,
                             fixed_leg_cashflows,
                             fixed_leg_daycount_fractions,
                             float_leg_discount_rates,
                             float_leg_discount_times,
                             fixed_leg_discount_rates,
                             fixed_leg_discount_times,
                             self_discounting_float_leg,
                             self_discounting_fixed_leg,
                             present_values,
                             pv_settle_times,
                             curve_interpolator,
                             initial_rates,
                             curve_tolerance,
                             maximum_iterations,
                             dtype)
Exemple #10
0
 def _interpolate(x1, x_data, y_data):
     return linear.interpolate(x1, x_data, y_data, dtype=dtype)
def swap_curve_fit(float_leg_start_times,
                   float_leg_end_times,
                   float_leg_daycount_fractions,
                   fixed_leg_start_times,
                   fixed_leg_end_times,
                   fixed_leg_daycount_fractions,
                   fixed_leg_cashflows,
                   present_values,
                   present_values_settlement_times=None,
                   float_leg_discount_rates=None,
                   float_leg_discount_times=None,
                   fixed_leg_discount_rates=None,
                   fixed_leg_discount_times=None,
                   optimize=None,
                   curve_interpolator=None,
                   initial_curve_rates=None,
                   instrument_weights=None,
                   curve_tolerance=1e-8,
                   maximum_iterations=50,
                   dtype=None,
                   name=None):
    """Constructs the zero swap curve using optimization.

  A zero swap curve is a function of time which gives the interest rate that
  can be used to project forward rates at arbitrary `t` for the valuation of
  interest rate securities.

  Suppose we have a set of `N` Interest Rate Swaps (IRS) `S_i` with increasing
  expiries whose market prices are known.
  Suppose also that the `i`th IRS issues cashflows at times `T_{ij}` where
  `1 <= j <= n_i` and `n_i` is the number of cashflows (including expiry)
  for the `i`th swap.
  Denote by `T_i` the time of final payment for the `i`th swap
  (hence `T_i = T_{i,n_i}`). This function estimates a set of rates `r(T_i)`
  such that when these rates are interpolated to all other cashflow times,
  the computed value of the swaps matches the market value of the swaps
  (within some tolerance). Rates at intermediate times are interpolated using
  the user specified interpolation method (the default interpolation method
  is linear interpolation on rates).

  ### Example:

  The following example illustrates the usage by building an implied swap curve
  from four vanilla (fixed to float) LIBOR swaps.

  ```python

  dtype = np.float64

  # Next we will set up LIBOR reset and payment times for four spot starting
  # swaps with maturities 1Y, 2Y, 3Y, 4Y. The LIBOR rate spans 6M.

  float_leg_start_times = [
            np.array([0., 0.5], dtype=dtype),
            np.array([0., 0.5, 1., 1.5], dtype=dtype),
            np.array([0., 0.5, 1.0, 1.5, 2.0, 2.5], dtype=dtype),
            np.array([0., 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5], dtype=dtype)
        ]

  float_leg_end_times = [
            np.array([0.5, 1.0], dtype=dtype),
            np.array([0.5, 1., 1.5, 2.0], dtype=dtype),
            np.array([0.5, 1.0, 1.5, 2.0, 2.5, 3.0], dtype=dtype),
            np.array([0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0], dtype=dtype)
        ]

  # Next we will set up start and end times for semi-annual fixed coupons.

  fixed_leg_start_times = [
            np.array([0., 0.5], dtype=dtype),
            np.array([0., 0.5, 1., 1.5], dtype=dtype),
            np.array([0., 0.5, 1.0, 1.5, 2.0, 2.5], dtype=dtype),
            np.array([0., 0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5], dtype=dtype)
        ]

  fixed_leg_end_times = [
            np.array([0.5, 1.0], dtype=dtype),
            np.array([0.5, 1., 1.5, 2.0], dtype=dtype),
            np.array([0.5, 1.0, 1.5, 2.0, 2.5, 3.0], dtype=dtype),
            np.array([0.5, 1.0, 1.5, 2.0, 2.5, 3.0, 3.5, 4.0], dtype=dtype)
        ]

  # Next setup a trivial daycount for floating and fixed legs.

  float_leg_daycount = [
            np.array([0.5, 0.5], dtype=dtype),
            np.array([0.5, 0.5, 0.5, 0.5], dtype=dtype),
            np.array([0.5, 0.5, 0.5, 0.5, 0.5, 0.5], dtype=dtype),
            np.array([0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5], dtype=dtype)
        ]

  fixed_leg_daycount = [
            np.array([0.5, 0.5], dtype=dtype),
            np.array([0.5, 0.5, 0.5, 0.5], dtype=dtype),
            np.array([0.5, 0.5, 0.5, 0.5, 0.5, 0.5], dtype=dtype),
            np.array([0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5], dtype=dtype)
        ]

  fixed_leg_cashflows = [
        # 1 year swap with 2.855% semi-annual fixed payments.
        np.array([-0.02855, -0.02855], dtype=dtype),
        # 2 year swap with 3.097% semi-annual fixed payments.
        np.array([-0.03097, -0.03097, -0.03097, -0.03097], dtype=dtype),
        # 3 year swap with 3.1% semi-annual fixed payments.
        np.array([-0.031, -0.031, -0.031, -0.031, -0.031, -0.031], dtype=dtype),
        # 4 year swap with 3.2% semi-annual fixed payments.
        np.array([-0.032, -0.032, -0.032, -0.032, -0.032, -0.032, -0.032,
        -0.032], dtype=dtype)
    ]

  # The present values of the above IRS.
  pvs = np.array([0., 0., 0., 0.], dtype=dtype)

  # Initial state of the curve.
  initial_curve_rates = np.array([0.01, 0.01, 0.01, 0.01], dtype=dtype)

  results = swap_curve_fit(float_leg_start_times, float_leg_end_times,
                           float_leg_daycount, fixed_leg_start_times,
                           fixed_leg_end_times, fixed_leg_cashflows,
                           fixed_leg_daycount, pvs, dtype=dtype,
                           initial_curve_rates=initial_curve_rates)

  ### References:
  [1]: Leif B.G. Andersen and Vladimir V. Piterbarg. Interest Rate Modeling,
      Volume I: Foundations and Vanilla Models. Chapter 6. 2010.

  Args:
    float_leg_start_times: List of `Tensor`s. Each `Tensor` must be of rank 1
      and of the same real dtype. They may be of different sizes. Each `Tensor`
      represents the beginning of the accrual period for the forward rate which
      determines the floating payment. Each element in the list belong to a
      unique swap to be used to build the curve.
    float_leg_end_times: List of `Tensor`s. Each `Tensor` must be of rank 1 and
      and the same shape and of the same real dtype as the corresponding element
      in `float_leg_start_times`. Each `Tensor` represents the end of the
      accrual period for the forward rate which determines the floating payment.
    float_leg_daycount_fractions: List of `Tensor`s. Each `Tensor` must be of
      the same shape and type as `float_leg_start_times`. They may be of
      different sizes. Each `Tensor` represents the daycount fraction of the
      forward rate which determines the floating payment.
    fixed_leg_start_times: List of `Tensor`s. Each `Tensor` must be of rank 1
      and of the same real dtype. They may be of different sizes. Each `Tensor`
      represents the begining of the accrual period fixed coupon.
    fixed_leg_end_times: List of `Tensor`s. Each `Tensor` must be of the same
      shape and type as `fixed_leg_start_times`. Each `Tensor` represents the
      end of the accrual period for the fixed coupon.
    fixed_leg_daycount_fractions: List of `Tensor`s. Each `Tensor` must be of
      the same shape and type as `fixed_leg_start_times`. Each `Tensor`
      represents the daycount fraction applicable for the fixed payment.
    fixed_leg_cashflows: List of `Tensor`s. The list must be of the same length
      as the `fixed_leg_start_times`. Each `Tensor` must be of rank 1 and of the
      same dtype as the `Tensor`s in `fixed_leg_start_times`. The input contains
      fixed cashflows at each coupon payment time including notional (if any).
      The sign should be negative (positive) to indicate net outgoing (incoming)
      cashflow.
    present_values: List containing scalar `Tensor`s of the same dtype as
      elements of `fixed_leg_cashflows`. The length of the list must be the same
      as the length of `fixed_leg_cashflows`. The input contains the market
      price of the underlying instruments.
    present_values_settlement_times: List containing scalar `Tensor`s of the
      same dtype as elements of `present_values`. The length of the list must be
      the same as the length of `present_values`. The settlement times for the
      present values is the time from now when the instrument is traded to the
      time that the purchase price is actually delivered. If not supplied, then
      it is assumed that the settlement times are zero for every bond.
      Default value: `None` which is equivalent to zero settlement times.
    initial_discount_rates: Optional `Tensor` of the same dtype and shape as
      `present_values`. The starting guess for the discount rates used to
      initialize the iterative procedure.
      Default value: `None`. If not supplied, the yields to maturity for the
        bonds is used as the initial value.
    float_leg_discount_rates: Optional `Tensor` of the same dtype as
      `initial_discount_rates`. This input contains the continuously compounded
      discount rates the will be used to discount the floating cashflows. This
      allows the swap curve to constructed using an independent discount curve
      (e.g. OIS curve). By default the cashflows are discounted using the curve
      that is being constructed.
    float_leg_discount_times: Optional `Tensor` of the same dtype and shape as
      `float_leg_discount_rates`. This input contains the times corresponding to
      the rates specified via the `float_leg_discount_rates`.
    fixed_leg_discount_rates: Optional `Tensor` of the same dtype as
      `initial_discount_rates`. This input contains the continuously compounded
      discount rates the will be used to discount the fixed cashflows. This
      allows the swap curve to constructed using an independent discount curve
      (e.g. OIS curve). By default the cashflows are discounted using the curve
      that is being constructed.
    fixed_leg_discount_times: Optional `Tensor` of the same dtype and shape as
      `fixed_leg_discount_rates`. This input contains the times corresponding to
      the rates specified via the `fixed_leg_discount_rates`.
    optimize: Optional Python callable which implements the algorithm used to
      minimize the objective function during curve construction. It should have
      the following interface:
      result = optimize(value_and_gradients_function, initial_position,
      tolerance, max_iterations)
      `value_and_gradients_function` is a Python callable that accepts a point
      as a real `Tensor` and returns a tuple of `Tensor`s of real dtype
      containing the value of the function and its gradient at that point.
      'initial_position' is a real `Tensor` containing the starting point of the
      optimization, 'tolerance' is a real scalar `Tensor` for stopping tolerance
      for the procedure and `max_iterations` specifies the maximum number of
      iterations.
      `optimize` should return a namedtuple containing the items: `position` (a
      tensor containing the optimal value), `converged` (a boolean indicating
      whether the optimize converged according the specified criteria),
      `failed` (a boolean indicating if the optimization resulted in a failure),
      `num_iterations` (the number of iterations used), and `objective_value` (
      the value of the objective function at the optimal value).
      The default value for `optimize` is None and conjugate gradient algorithm
      is used.
    curve_interpolator: Optional Python callable used to interpolate the zero
      swap rates at cashflow times. It should have the following interface:
      yi = curve_interpolator(xi, x, y)
      `x`, `y`, 'xi', 'yi' are all `Tensors` of real dtype. `x` and `y` are the
      sample points and values (respectively) of the function to be
      interpolated. `xi` are the points at which the interpolation is
      desired and `yi` are the corresponding interpolated values returned by the
      function. The default value for `curve_interpolator` is None in which
      case linear interpolation is used.
    instrument_weights: Optional 'Tensor' of the same dtype and shape as
      `present_values`. This input contains the weight of each instrument in
      computing the objective function for the conjugate gradient optimization.
      By default the weights are set to be the inverse of maturities.
    curve_tolerance: Optional positive scalar `Tensor` of same dtype as
      elements of `bond_cashflows`. The absolute tolerance for terminating the
      iterations used to fit the rate curve. The iterations are stopped when the
      estimated discounts at the expiry times of the bond_cashflows change by a
      amount smaller than `discount_tolerance` in an iteration.
      Default value: 1e-8.
    maximum_iterations: Optional positive integer `Tensor`. The maximum number
      of iterations permitted when fitting the curve.
      Default value: 50.
    dtype: `tf.Dtype`. If supplied the dtype for the (elements of)
      `float_leg_start_times`, and `fixed_leg_start_times`.
      Default value: None which maps to the default dtype inferred by
      TensorFlow.
    name: Python str. The name to give to the ops created by this function.
      Default value: `None` which maps to 'swap_curve'.

  Returns:
    curve_builder_result: An instance of `SwapCurveBuilderResult` containing the
      following attributes.
      times: Rank 1 real `Tensor`. Times for the computed discount rates. These
        are chosen to be the expiry times of the supplied cashflows.
      discount_rates: Rank 1 `Tensor` of the same dtype as `times`.
        The inferred discount rates.
      discount_factor: Rank 1 `Tensor` of the same dtype as `times`.
        The inferred discount factors.
      initial_discount_rates: Rank 1 `Tensor` of the same dtype as `times`. The
        initial guess for the discount rates.
      converged: Scalar boolean `Tensor`. Whether the procedure converged.
        The procedure is said to have converged when the maximum absolute
        difference in the discount factors from one iteration to the next falls
        below the `discount_tolerance`.
      failed: Scalar boolean `Tensor`. Whether the procedure failed. Procedure
        may fail either because a NaN value was encountered for the discount
        rates or the discount factors.
      iterations: Scalar int32 `Tensor`. Number of iterations performed.
      objective_value: Scalar real `Tensor`. The value of the ibjective function
        evaluated using the fitted swap curve.

  Raises:
    ValueError: If the initial state of the curve is not
    supplied to the function.

  """

    with tf.compat.v1.name_scope(
            name,
            default_name='swap_curve',
            values=[
                float_leg_start_times, float_leg_end_times,
                float_leg_daycount_fractions, fixed_leg_start_times,
                fixed_leg_end_times, fixed_leg_cashflows,
                fixed_leg_daycount_fractions, present_values,
                present_values_settlement_times
            ]):

        if optimize is None:
            optimize = optimizer.conjugate_gradient_minimize

        if curve_interpolator is None:
            curve_interpolator = lambda xi, x, y: linear.interpolate(
                xi, x, y, dtype=dtype)

        if instrument_weights is None:
            instrument_weights = _initialize_instrument_weights(
                float_leg_end_times, fixed_leg_end_times, dtype)
        if present_values_settlement_times is None:
            pv_settle_times = [tf.zeros_like(pv) for pv in present_values]
        else:
            pv_settle_times = present_values_settlement_times

        float_leg_start_times = _convert_to_tensors(dtype,
                                                    float_leg_start_times,
                                                    'float_leg_start_times')
        float_leg_end_times = _convert_to_tensors(dtype, float_leg_end_times,
                                                  'float_leg_end_times')
        float_leg_daycount_fractions = _convert_to_tensors(
            dtype, float_leg_daycount_fractions,
            'float_leg_daycount_fractions')
        fixed_leg_start_times = _convert_to_tensors(dtype,
                                                    fixed_leg_start_times,
                                                    'fixed_leg_start_times')
        fixed_leg_end_times = _convert_to_tensors(dtype, fixed_leg_end_times,
                                                  'fixed_leg_end_times')
        fixed_leg_daycount_fractions = _convert_to_tensors(
            dtype, fixed_leg_daycount_fractions,
            'fixed_leg_daycount_fractions')
        fixed_leg_cashflows = _convert_to_tensors(dtype, fixed_leg_cashflows,
                                                  'fixed_leg_cashflows')
        present_values = _convert_to_tensors(dtype, present_values,
                                             'present_values')
        pv_settle_times = _convert_to_tensors(dtype, pv_settle_times,
                                              'pv_settle_times')
        instrument_weights = _convert_to_tensors(dtype, instrument_weights,
                                                 'instrument_weights')
        self_discounting_float_leg = False
        self_discounting_fixed_leg = False
        # Determine how the floating and fixed leg will be discounted. If separate
        # discount curves for each leg are not specified, the curve will be self
        # discounted using the swap curve.
        if float_leg_discount_rates is None and fixed_leg_discount_rates is None:
            self_discounting_float_leg = True
            self_discounting_fixed_leg = True
            float_leg_discount_rates = [0.0]
            float_leg_discount_times = [0.]
            fixed_leg_discount_rates = [0.]
            fixed_leg_discount_times = [0.]
        elif fixed_leg_discount_rates is None:
            fixed_leg_discount_rates = float_leg_discount_rates
            fixed_leg_discount_times = float_leg_discount_times
        elif float_leg_discount_rates is None:
            self_discounting_float_leg = True
            float_leg_discount_rates = [0.]
            float_leg_discount_times = [0.]

        # Create tensors for discounting curves
        float_leg_discount_rates = _convert_to_tensors(
            dtype, float_leg_discount_rates, 'float_disc_rates')
        float_leg_discount_times = _convert_to_tensors(
            dtype, float_leg_discount_times, 'float_disc_times')
        fixed_leg_discount_rates = _convert_to_tensors(
            dtype, fixed_leg_discount_rates, 'fixed_disc_rates')
        fixed_leg_discount_times = _convert_to_tensors(
            dtype, fixed_leg_discount_times, 'fixed_disc_times')

        if initial_curve_rates is not None:
            initial_rates = tf.convert_to_tensor(initial_curve_rates,
                                                 dtype=dtype,
                                                 name='initial_rates')
        else:
            # TODO(b/144600429): Create a logic for a meaningful initial state of the
            # curve
            raise ValueError('Initial state of the curve is not specified.')

        return _build_swap_curve(
            float_leg_start_times, float_leg_end_times,
            float_leg_daycount_fractions, fixed_leg_start_times,
            fixed_leg_end_times, fixed_leg_cashflows,
            fixed_leg_daycount_fractions, float_leg_discount_rates,
            float_leg_discount_times, fixed_leg_discount_rates,
            fixed_leg_discount_times, self_discounting_float_leg,
            self_discounting_fixed_leg, present_values, pv_settle_times,
            optimize, curve_interpolator, initial_rates, instrument_weights,
            curve_tolerance, maximum_iterations, dtype)