Esempio n. 1
0
def _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_settlement_times, optimize, curve_interpolator,
                      initial_rates, instrument_weights, curve_tolerance,
                      maximum_iterations):
    """Build the zero swap curve."""
    # The procedure uses optimization to estimate the swap curve as follows:
    # 1. Start with an initial state of the swap curve.
    # 2. Define a loss function which measures the deviations between model prices
    #   of the IR swaps and their present values specified as input.
    # 3. Use numerical optimization (currently conjugate gradient optimization) to
    #   to build the swap curve such that the loss function is minimized.
    del fixed_leg_start_times, float_leg_daycount_fractions
    curve_tensors = _create_curve_building_tensors(float_leg_start_times,
                                                   float_leg_end_times,
                                                   fixed_leg_end_times,
                                                   pv_settlement_times)
    expiry_times = curve_tensors.expiry_times
    calc_groups_float = curve_tensors.calc_groups_float
    calc_groups_fixed = curve_tensors.calc_groups_fixed
    settle_times_float = curve_tensors.settle_times_float
    settle_times_fixed = curve_tensors.settle_times_fixed

    float_leg_calc_times_start = tf.concat(float_leg_start_times, axis=0)
    float_leg_calc_times_end = tf.concat(float_leg_end_times, axis=0)
    calc_fixed_leg_cashflows = tf.concat(fixed_leg_cashflows, axis=0)
    calc_fixed_leg_daycount = tf.concat(fixed_leg_daycount_fractions, axis=0)
    fixed_leg_calc_times = tf.concat(fixed_leg_end_times, axis=0)

    def _interpolate(x1, x_data, y_data):
        return curve_interpolator(x1, x_data, y_data)

    @make_val_and_grad_fn
    def loss_function(x):
        """Loss function for the optimization."""
        # Currently the loss function is a weighted root mean squared difference
        # between the model PV and market PV. The model PV is interest rate swaps is
        # computed as follows:

        # 1. Interpolate the swap curve at intermediate times required to compute
        #   forward rates for the computation of floating cashflows.
        # 2. Interpolate swap curve or the discount curve (if a separate discount
        #   curve is specified) at intermediate cashflow times.
        # 3. Compute the PV of the swap as the aggregate of floating and fixed legs.
        # 4. Compute the loss (which is being minized) as the weighted root mean
        #   squared difference between the model PV (computed above) and the market
        #   PV (specified as input).

        rates_start = _interpolate(float_leg_calc_times_start, expiry_times, x)
        rates_end = _interpolate(float_leg_calc_times_end, expiry_times, x)
        float_cashflows = (
            tf.math.exp(float_leg_calc_times_end * rates_end) /
            tf.math.exp(float_leg_calc_times_start * rates_start) - 1.)

        if self_discounting_float_leg:
            float_discount_rates = rates_end
            float_settle_rates = _interpolate(settle_times_float, expiry_times,
                                              x)
        else:
            float_discount_rates = _interpolate(float_leg_calc_times_end,
                                                float_leg_discount_times,
                                                float_leg_discount_rates)
            float_settle_rates = _interpolate(settle_times_float,
                                              float_leg_discount_times,
                                              float_leg_discount_rates)
        if self_discounting_fixed_leg:
            fixed_discount_rates = _interpolate(fixed_leg_calc_times,
                                                expiry_times, x)
            fixed_settle_rates = _interpolate(settle_times_fixed, expiry_times,
                                              x)
        else:
            fixed_discount_rates = _interpolate(fixed_leg_calc_times,
                                                fixed_leg_discount_times,
                                                fixed_leg_discount_rates)
            fixed_settle_rates = _interpolate(settle_times_fixed,
                                              fixed_leg_discount_times,
                                              fixed_leg_discount_rates)

        # exp(-r(t) * t) / exp(-r(t_s) * t_s)
        calc_discounts_float_leg = (
            tf.math.exp(-float_discount_rates * float_leg_calc_times_end +
                        float_settle_rates * settle_times_float))

        calc_discounts_fixed_leg = (
            tf.math.exp(-fixed_discount_rates * fixed_leg_calc_times +
                        fixed_settle_rates * settle_times_fixed))

        float_pv = tf.math.segment_sum(
            float_cashflows * calc_discounts_float_leg, calc_groups_float)
        fixed_pv = tf.math.segment_sum(
            calc_fixed_leg_daycount * calc_fixed_leg_cashflows *
            calc_discounts_fixed_leg, calc_groups_fixed)
        swap_pv = float_pv + fixed_pv

        value = tf.math.reduce_sum(input_tensor=instrument_weights *
                                   (swap_pv - present_values)**2)

        return value

    optimization_result = optimize(loss_function,
                                   initial_position=initial_rates,
                                   tolerance=curve_tolerance,
                                   max_iterations=maximum_iterations)

    discount_rates = optimization_result.position
    discount_factors = tf.math.exp(-discount_rates * expiry_times)
    results = scc.SwapCurveBuilderResult(
        times=expiry_times,
        rates=discount_rates,
        discount_factors=discount_factors,
        initial_rates=initial_rates,
        converged=optimization_result.converged,
        failed=optimization_result.failed,
        iterations=optimization_result.num_iterations,
        objective_value=optimization_result.objective_value)
    return results
Esempio n. 2
0
def _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_settlement_times, curve_interpolator, initial_rates,
                      curve_tolerance, maximum_iterations, dtype):
    """Build the zero swap curve using the bootstrap method."""

    # The procedure is recursive and as follows:
    # 1. Start with an initial state of the swap curve. Set this as the current
    #   swap curve.
    # 2. From the current swap curve, compute the relevant forward rates and
    #   discount factors (if cashflows are discounted using the swap curve). Use
    #   the specified interpolation method to compute rates at intermediate times
    #   as needed to calculate either the forward rate or the discount factors.
    # 3. Using the above and the input present values of bootstarpping
    #   instruments,compute the zero swap rate at expiry by inverting the swap
    #   pricing formula. The following illustrates the procedure:

    #   Assuming that a swap pays fixed payments c_i at times t_i (i=[1,...,n])
    #   and receives floating payments at times t_j (j=[1,...,m]), then the
    #   present value of the swap is given by

    #   ```None
    #   PV = sum_{j=1}^m (P_{j-1}/P_j - 1.) * P_j - sum_{i=1}^n a_i * c_i * P_i
    #                                                                        (A)
    #
    #   ```
    #   where P_i = exp(-r(t_i) * t_i) and a_i are the daycount fractions. We
    #   update the current estimate of the rate at curve node t_k = t_n = t_m
    #   by inverting the above equation:

    #   ```None
    #   P_k * (1 + a_i * c_i) = sum_{j=1}^{m - 1} (P_{j-1}/P_j - 1.) * P_j -
    #                           sum_{i=1}^{n - 1} a_i * c_i * P_i - PV       (B)

    #   ```
    #   From Eq. (B), we get r(t_k) = -log(P_k) / t_k.
    #   Using this as the next guess for the discount rates and we repeat the
    #   procedure from Step (2) until convergence.

    del fixed_leg_start_times, pv_settlement_times
    curve_tensors = _create_curve_building_tensors(
        float_leg_start_times, float_leg_end_times,
        float_leg_daycount_fractions, fixed_leg_end_times, fixed_leg_cashflows,
        fixed_leg_daycount_fractions)

    float_leg_calc_times_start = curve_tensors.float_leg_times_start
    float_leg_calc_times_end = curve_tensors.float_leg_times_end
    calc_fixed_leg_cashflows = curve_tensors.fixed_leg_cashflows
    calc_fixed_leg_daycount = curve_tensors.fixed_leg_daycount
    fixed_leg_calc_times = curve_tensors.fixed_leg_calc_times
    calc_groups_float = curve_tensors.calc_groups_float
    calc_groups_fixed = curve_tensors.calc_groups_fixed
    last_float_leg_start_time = curve_tensors.last_float_leg_start_time
    last_float_leg_end_time = curve_tensors.last_float_leg_end_time
    last_fixed_leg_end_time = curve_tensors.last_fixed_leg_calc_time
    last_fixed_leg_daycount = curve_tensors.last_fixed_leg_daycount
    last_fixed_leg_cashflows = curve_tensors.last_fixed_leg_cashflows
    expiry_times = curve_tensors.expiry_times

    def _one_step(converged, failed, iteration, discount_factor):
        """One step of the bootstrap iteration."""

        x = -tf.math.log(discount_factor) / expiry_times
        rates_start = curve_interpolator(float_leg_calc_times_start,
                                         expiry_times, x)
        rates_end = curve_interpolator(float_leg_calc_times_end, expiry_times,
                                       x)
        rates_start_last = curve_interpolator(last_float_leg_start_time,
                                              expiry_times, x)

        float_cashflows = (
            tf.math.exp(float_leg_calc_times_end * rates_end) /
            tf.math.exp(float_leg_calc_times_start * rates_start) - 1.)

        if self_discounting_float_leg:
            float_discount_rates = rates_end
        else:
            float_discount_rates = curve_interpolator(
                float_leg_calc_times_end, float_leg_discount_times,
                float_leg_discount_rates)
            last_float_discount_rate = curve_interpolator(
                last_float_leg_end_time, float_leg_discount_times,
                float_leg_discount_rates)
            last_float_discount_factor = tf.math.exp(
                -last_float_discount_rate * last_float_leg_end_time)
        if self_discounting_fixed_leg:
            fixed_discount_rates = curve_interpolator(fixed_leg_calc_times,
                                                      expiry_times, x)
        else:
            fixed_discount_rates = curve_interpolator(
                fixed_leg_calc_times, fixed_leg_discount_times,
                fixed_leg_discount_rates)
            last_fixed_discount_rate = curve_interpolator(
                last_fixed_leg_end_time, fixed_leg_discount_times,
                fixed_leg_discount_rates)
            last_fixed_discount_factor = tf.math.exp(-last_fixed_leg_end_time *
                                                     last_fixed_discount_rate)
            last_fixed_leg_cashflow_pv = (last_fixed_leg_daycount *
                                          last_fixed_leg_cashflows *
                                          last_fixed_discount_factor)

        calc_discounts_float_leg = tf.math.exp(-float_discount_rates *
                                               float_leg_calc_times_end)
        calc_discounts_fixed_leg = tf.math.exp(-fixed_discount_rates *
                                               fixed_leg_calc_times)

        float_pv = tf.math.segment_sum(
            float_cashflows * calc_discounts_float_leg, calc_groups_float)
        fixed_pv = tf.math.segment_sum(
            calc_fixed_leg_daycount * calc_fixed_leg_cashflows *
            calc_discounts_fixed_leg, calc_groups_fixed)

        if self_discounting_float_leg and self_discounting_fixed_leg:
            p_n_minus_1 = tf.math.exp(-rates_start_last *
                                      last_float_leg_start_time)
            scale = last_fixed_leg_cashflows * last_fixed_leg_daycount - 1.
            next_discount = (present_values - float_pv - fixed_pv -
                             p_n_minus_1) / scale
        elif self_discounting_float_leg:
            p_n_minus_1 = tf.math.exp(-rates_start_last *
                                      last_float_leg_start_time)
            next_discount = (float_pv +
                             (fixed_pv + last_fixed_leg_cashflow_pv) -
                             present_values + p_n_minus_1)
        else:
            p_n_minus_1 = tf.math.exp(-rates_start_last *
                                      last_float_leg_start_time)
            scale = present_values - float_pv - (
                fixed_pv +
                last_fixed_leg_cashflow_pv) + last_float_discount_factor
            next_discount = p_n_minus_1 * last_float_discount_factor / scale

        discount_diff = tf.math.abs(next_discount - discount_factor)
        converged = (~tf.math.reduce_any(tf.math.is_nan(discount_diff)) &
                     (tf.math.reduce_max(discount_diff) < curve_tolerance))

        return (converged, failed, iteration + 1, next_discount)

    def cond(converged, failed, iteration, x):
        # Note we do not need to check iteration count here because that
        # termination mode is imposed by the maximum_iterations parameter in the
        # while loop.
        del iteration, x
        return ~tf.math.logical_or(converged, failed)

    initial_vals = (False, False, 0,
                    tf.math.exp(-initial_rates * expiry_times))
    bootstrap_result = tf.compat.v2.while_loop(
        cond, _one_step, initial_vals, maximum_iterations=maximum_iterations)

    discount_factors = bootstrap_result[-1]
    discount_rates = -tf.math.log(discount_factors) / expiry_times
    results = scc.SwapCurveBuilderResult(times=expiry_times,
                                         rates=discount_rates,
                                         discount_factors=discount_factors,
                                         initial_rates=initial_rates,
                                         converged=bootstrap_result[0],
                                         failed=bootstrap_result[1],
                                         iterations=bootstrap_result[2],
                                         objective_value=tf.constant(
                                             0, dtype=dtype))
    return results