def _european_swaption_mc(model, expiries, fixed_leg_payment_times, fixed_leg_daycount_fractions, fixed_leg_coupon, notional, is_payer_swaption, times, time_step, num_time_steps, curve_times, num_samples, random_type, skip, seed, dtype, name): """Price European swaptions using Monte-Carlo.""" with tf.name_scope(name): if (times is None) and (time_step is None) and (num_time_steps is None): raise ValueError( 'One of `times`, `time_step` or `num_time_steps` must be ' 'provided for simulation based swaption valuation.') def _sample_discount_curve_path_fn(times, curve_times, num_samples): p_t_tau, r_t, df = model.sample_discount_curve_paths( times=times, curve_times=curve_times, num_samples=num_samples, random_type=random_type, time_step=time_step, num_time_steps=num_time_steps, seed=seed, skip=skip) p_t_tau = tf.expand_dims(p_t_tau, axis=-1) r_t = tf.expand_dims(r_t, axis=-1) df = tf.expand_dims(df, axis=-1) return p_t_tau, r_t, df payoff_discount_factors, payoff_bond_price = ( swaption_util.discount_factors_and_bond_prices_from_samples( expiries=expiries, payment_times=fixed_leg_payment_times, sample_discount_curve_paths_fn=_sample_discount_curve_path_fn, num_samples=num_samples, times=times, curve_times=curve_times, dtype=dtype)) # Add an axis corresponding to `dim` fixed_leg_pv = tf.expand_dims( fixed_leg_coupon * fixed_leg_daycount_fractions, axis=-1) * payoff_bond_price # Sum fixed coupon payments within each swap. # Here, axis=-2 is the payments axis - i.e. summing over all payments; and # the last axis is the `dim` axis, as explained in comment above # `fixed_leg_pv` (Note that for HJM the dim of this axis is 1 always). fixed_leg_pv = tf.math.reduce_sum(fixed_leg_pv, axis=-2) float_leg_pv = 1.0 - payoff_bond_price[..., -1, :] payoff_swap = payoff_discount_factors[..., -1, :] * (float_leg_pv - fixed_leg_pv) payoff_swap = tf.where(is_payer_swaption, payoff_swap, -1.0 * payoff_swap) payoff_swaption = tf.math.maximum(payoff_swap, 0.0) # Average over all simulation paths option_value = tf.math.reduce_mean(payoff_swaption, axis=0) return notional * option_value
def price(*, expiries, fixed_leg_payment_times, fixed_leg_daycount_fractions, fixed_leg_coupon, reference_rate_fn, num_hjm_factors, mean_reversion, volatility, time_step, notional=None, is_payer_swaption=None, num_samples=1, random_type=None, seed=None, skip=0, dtype=None, name=None): """Calculates the price of European swaptions using the HJM model. A European Swaption is a contract that gives the holder an option to enter a swap contract at a future date at a prespecified fixed rate. A swaption that grants the holder the right to pay fixed rate and receive floating rate is called a payer swaption while the swaption that grants the holder the right to receive fixed and pay floating payments is called the receiver swaption. Typically the start date (or the inception date) of the swap coincides with the expiry of the swaption. Mid-curve swaptions are currently not supported (b/160061740). This implementation uses the HJM model to numerically value the swaption via Monte-Carlo. For more information on the formulation of the HJM model, see quasi_gaussian_hjm.py. #### References: [1]: D. Brigo, F. Mercurio. Interest Rate Models-Theory and Practice. Second Edition. 2007. Section 6.7, page 237. Args: expiries: A real `Tensor` of any shape and dtype. The time to expiration of the swaptions. The shape of this input determines the number (and shape) of swaptions to be priced and the shape of the output. fixed_leg_payment_times: A real `Tensor` of the same dtype as `expiries`. The payment times for each payment in the fixed leg. The shape of this input should be `expiries.shape + [n]` where `n` denotes the number of fixed payments in each leg. The `fixed_leg_payment_times` should be greater-than or equal-to the corresponding expiries. fixed_leg_daycount_fractions: A real `Tensor` of the same dtype and compatible shape as `fixed_leg_payment_times`. The daycount fractions for each payment in the fixed leg. fixed_leg_coupon: A real `Tensor` of the same dtype and compatible shape as `fixed_leg_payment_times`. The fixed rate for each payment in the fixed leg. reference_rate_fn: A Python callable that accepts expiry time as a real `Tensor` and returns a `Tensor` of shape `input_shape + [num_hjm_factors]`. Returns the continuously compounded zero rate at the present time for the input expiry time. num_hjm_factors: A Python scalar which corresponds to the number of factors in the HJM model to be used for pricing. mean_reversion: A real positive `Tensor` of shape `[num_hjm_factors]`. Corresponds to the mean reversion rate of each factor. volatility: A real positive `Tensor` of the same `dtype` and shape as `mean_reversion` or a callable with the following properties: (a) The callable should accept a scalar `Tensor` `t` and a 1-D `Tensor` `r(t)` of shape `[num_samples]` and returns a 2-D `Tensor` of shape `[num_samples, num_hjm_factors]`. The variable `t` stands for time and `r(t)` is the short rate at time `t`. The function returns the instantaneous volatility `sigma(t) = sigma(t, r(r))`. When `volatility` is specified as a real `Tensor`, each factor is assumed to have a constant instantaneous volatility and the model is effectively a Gaussian HJM model. Corresponds to the instantaneous volatility of each factor. time_step: Scalar real `Tensor`. Maximal distance between time grid points in Euler scheme. Relevant when Euler scheme is used for simulation. This input is required. notional: An optional `Tensor` of same dtype and compatible shape as `strikes`specifying the notional amount for the underlying swaps. Default value: None in which case the notional is set to 1. is_payer_swaption: A boolean `Tensor` of a shape compatible with `expiries`. Indicates whether the swaption is a payer (if True) or a receiver (if False) swaption. If not supplied, payer swaptions are assumed. num_samples: Positive scalar `int32` `Tensor`. The number of simulation paths during Monte-Carlo valuation. This input is ignored during analytic valuation. Default value: The default value is 1. random_type: Enum value of `RandomType`. The type of (quasi)-random number generator to use to generate the simulation paths. This input is relevant only for Monte-Carlo valuation and ignored during analytic valuation. Default value: `None` which maps to the standard pseudo-random numbers. seed: Seed for the random number generator. The seed is only relevant if `random_type` is one of `[STATELESS, PSEUDO, HALTON_RANDOMIZED, PSEUDO_ANTITHETIC, STATELESS_ANTITHETIC]`. For `PSEUDO`, `PSEUDO_ANTITHETIC` and `HALTON_RANDOMIZED` the seed should be an Python integer. For `STATELESS` and `STATELESS_ANTITHETIC` must be supplied as an integer `Tensor` of shape `[2]`. This input is relevant only for Monte-Carlo valuation and ignored during analytic valuation. Default value: `None` which means no seed is set. skip: `int32` 0-d `Tensor`. The number of initial points of the Sobol or Halton sequence to skip. Used only when `random_type` is 'SOBOL', 'HALTON', or 'HALTON_RANDOMIZED', otherwise ignored. Default value: `0`. 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. name: Python string. The name to give to the ops created by this function. Default value: `None` which maps to the default name `hjm_swaption_price`. Returns: A `Tensor` of real dtype and shape expiries.shape + [num_hjm_factors] containing the computed swaption prices. For swaptions that have reset in the past (expiries<0), the function sets the corresponding option prices to 0.0. """ if time_step is None: raise ValueError('`time_step` must be provided for simulation based ' 'swaption valuation.') # TODO(b/160061740): Extend the functionality to support mid-curve swaptions. name = name or 'hjm_swaption_price' with tf.name_scope(name): expiries = tf.convert_to_tensor(expiries, dtype=dtype, name='expiries') dtype = dtype or expiries.dtype fixed_leg_payment_times = tf.convert_to_tensor( fixed_leg_payment_times, dtype=dtype, name='fixed_leg_payment_times') fixed_leg_daycount_fractions = tf.convert_to_tensor( fixed_leg_daycount_fractions, dtype=dtype, name='fixed_leg_daycount_fractions') fixed_leg_coupon = tf.convert_to_tensor(fixed_leg_coupon, dtype=dtype, name='fixed_leg_coupon') notional = tf.convert_to_tensor(notional, dtype=dtype, name='notional') notional = tf.expand_dims(tf.broadcast_to(notional, expiries.shape), axis=-1) if is_payer_swaption is None: is_payer_swaption = True is_payer_swaption = tf.convert_to_tensor(is_payer_swaption, dtype=tf.bool, name='is_payer_swaption') output_shape = expiries.shape.as_list() + [1] # Add a dimension corresponding to multiple cashflows in a swap if expiries.shape.rank == fixed_leg_payment_times.shape.rank - 1: expiries = tf.expand_dims(expiries, axis=-1) elif expiries.shape.rank < fixed_leg_payment_times.shape.rank - 1: raise ValueError( 'Swaption expiries not specified for all swaptions ' 'in the batch. Expected rank {} but received {}.'.format( fixed_leg_payment_times.shape.rank - 1, expiries.shape.rank)) # Expected shape: batch_shape + [m], where m is the number of fixed leg # payments per underlying swap. This is the same as # fixed_leg_payment_times.shape # # We need to explicitly use tf.repeat because we need to price # batch_shape + [m] bond options with different strikes along the last # dimension. expiries = tf.repeat(expiries, tf.shape(fixed_leg_payment_times)[-1], axis=-1) # Monte-Carlo pricing model = quasi_gaussian_hjm.QuasiGaussianHJM( num_hjm_factors, mean_reversion=mean_reversion, volatility=volatility, initial_discount_rate_fn=reference_rate_fn, dtype=dtype) def _sample_discount_curve_path_fn(times, curve_times, num_samples): p_t_tau, r_t, _ = model.sample_discount_curve_paths( times=times, curve_times=curve_times, num_samples=num_samples, random_type=random_type, time_step=time_step, seed=seed, skip=skip) p_t_tau = tf.expand_dims(p_t_tau, axis=-1) r_t = tf.expand_dims(r_t, axis=-1) return p_t_tau, r_t payoff_discount_factors, payoff_bond_price = ( swaption_util.discount_factors_and_bond_prices_from_samples( expiries=expiries, payment_times=fixed_leg_payment_times, sample_discount_curve_paths_fn=_sample_discount_curve_path_fn, num_samples=num_samples, time_step=time_step, dtype=dtype)) # Add an axis corresponding to `dim` fixed_leg_pv = tf.expand_dims( fixed_leg_coupon * fixed_leg_daycount_fractions, axis=-1) * payoff_bond_price # Sum fixed coupon payments within each swap. # Here, axis=-2 is the payments axis - i.e. summing over all payments; and # the last axis is the `dim` axis, as explained in comment above # `fixed_leg_pv` (Note that for HJM the dim of this axis is 1 always). fixed_leg_pv = tf.math.reduce_sum(fixed_leg_pv, axis=-2) float_leg_pv = 1.0 - payoff_bond_price[..., -1, :] payoff_swap = payoff_discount_factors[..., -1, :] * (float_leg_pv - fixed_leg_pv) payoff_swap = tf.where(is_payer_swaption, payoff_swap, -1.0 * payoff_swap) payoff_swaption = tf.math.maximum(payoff_swap, 0.0) option_value = tf.reshape(tf.math.reduce_mean(payoff_swaption, axis=0), output_shape) return notional * option_value