def ir_delta_parallel_leg( self, leg: Union[cashflow_streams.FloatingCashflowStream, cashflow_streams.FixedCashflowStream], processed_market_data: pmd.ProcessedMarketData, curve_type: Optional[curve_types.CurveType] = None, shock_size: Optional[float] = None) -> tf.Tensor: """Computes delta wrt to the curve parallel perturbation for a leg.""" # TODO(b/160671927): Find a better way to update market data entries. market_bumped = copy.copy(processed_market_data) reference_curve = None reference_curve_type = None if curve_type is None: # Extract discount and reference curves curve_type = leg.discount_curve_type if isinstance(leg, cashflow_streams.FloatingCashflowStream): reference_curve_type = leg.reference_curve_type reference_curve = processed_market_data.yield_curve( reference_curve_type) discount_curve = processed_market_data.yield_curve(curve_type) # IR delta is the sensitivity wrt the yield perturbation yields, times = market_data_utils.get_yield_and_time( discount_curve, processed_market_data.date, self._dtype) # Extract yields for reference curve, if needed if reference_curve is not None: reference_yields, reference_times = market_data_utils.get_yield_and_time( reference_curve, processed_market_data.date, self._dtype) def price_fn(bump): """Prices the leg with a given bump.""" discount_factors = (1 + yields + bump)**(-times) discount_curve.set_discount_factor_nodes(discount_factors) def _discount_curve_fn(input_curve_type): """Updates discount curve.""" if input_curve_type == curve_type: return discount_curve elif input_curve_type == reference_curve_type: reference_discount_factors = (1 + reference_yields + bump)**(-reference_times) reference_curve.set_discount_factor_nodes( reference_discount_factors) return reference_curve else: return processed_market_data.yield_curve(curve_type) market_bumped.yield_curve = _discount_curve_fn return leg.price(market_bumped) if shock_size is None: bump = tf.constant(0, dtype=self._dtype, name="bump") return tff_math.fwd_gradient(price_fn, bump) shock_size = tf.convert_to_tensor(shock_size, dtype=self._dtype, name="shock_size") price_no_bump = leg.price(processed_market_data) price_with_bump = price_fn(shock_size) delta = (price_with_bump - price_no_bump) / shock_size return delta
def price(self, market: pmd.ProcessedMarketData, name: Optional[str] = None) -> types.FloatTensor: """Returns the present value of the stream on the valuation date. Args: market: An instance of `ProcessedMarketData`. name: Python str. The name to give to the ops created by this function. Default value: `None` which maps to 'price'. Returns: A `Tensor` of shape `batch_shape` containing the modeled price of each FRA contract based on the input market data. """ name = name or (self._name + "_price") with tf.name_scope(name): discount_curve = market.yield_curve(self._discount_curve_type) reference_curve = market.yield_curve(self._reference_curve_type) fwd_rate = reference_curve.forward_rate( self._accrual_start_date, self._accrual_end_date, day_count_fraction=self._daycount_fractions) discount_at_settlement = discount_curve.discount_factor( self._accrual_start_date) discount_at_settlement = tf.where( self._daycount_fractions > 0., discount_at_settlement, tf.zeros_like(discount_at_settlement)) return (self._short_position * discount_at_settlement * self._notional_amount * (fwd_rate - self._fixed_rate) * self._daycount_fractions / (1. + self._daycount_fractions * fwd_rate))
def get_discount_curve(discount_curve_types: List[ Union[curve_types_lib.RiskFreeCurve, curve_types_lib.RateIndexCurve]], market: pmd.ProcessedMarketData, mask: List[int]) -> rate_curve.RateCurve: """Builds a batched discount curve. Given a list of discount curve an integer mask, creates a discount curve object to compute discount factors against the list of discount curves. #### Example ```none curve_types = [RiskFreeCurve("USD"), RiskFreeCurve("AUD")] # A mask to price a batch of 7 instruments with the corresponding discount # curves ["USD", "AUD", "AUD", "AUD" "USD", "USD", "AUD"]. mask = [0, 1, 1, 1, 0, 0, 1] market = MarketDataDict(...) get_discount_curve(curve_types, market, mask) # Returns a RateCurve object that can compute a discount factors for a # batch of 7 dates. ``` Args: discount_curve_types: A list of curve types. market: an instance of the processed market data. mask: An integer mask. Returns: An instance of `RateCurve`. """ discount_curves = [ market.yield_curve(curve_type) for curve_type in discount_curve_types ] discounts = [] dates = [] interpolation_method = None interpolate_rates = None for curve in discount_curves: discount, date = curve.discount_factors_and_dates() discounts.append(discount) dates.append(date) interpolation_method = curve.interpolation_method interpolate_rates = curve.interpolate_rates all_discounts = tf.stack(pad.pad_tensors(discounts), axis=0) all_dates = pad.pad_date_tensors(dates) all_dates = dateslib.DateTensor.stack(dates, axis=0) prepare_discounts = tf.gather(all_discounts, mask) prepare_dates = dateslib.dates_from_ordinals( tf.gather(all_dates.ordinal(), mask)) # All curves are assumed to have the same interpolation method # TODO(b/168411153): Extend to the case with multiple curve configs. discount_curve = rate_curve.RateCurve(prepare_dates, prepare_discounts, market.date, interpolator=interpolation_method, interpolate_rates=interpolate_rates) return discount_curve
def price(self, market: pmd.ProcessedMarketData, name: Optional[str] = None) -> types.FloatTensor: """Returns the present value of the American options. Args: market: An instance of `ProcessedMarketData`. name: Python str. The name to give to the ops created by this function. Default value: `None` which maps to 'price'. Returns: A `Tensor` of shape `batch_shape` containing the modeled price of each American option contract based on the input market data. """ name = name or (self._name + "_price") with tf.name_scope(name): discount_curve = cashflow_streams.get_discount_curve( self._discount_curve_type, market, self._discount_curve_mask) currencies = [ cur.currency.value for cur in self._discount_curve_type ] vol_surface = equity_utils.get_vol_surface(currencies, self._equity, market, self._equity_mask) spots = tf.stack(market.spot(currencies, self._equity), axis=0) discount_factors = discount_curve.discount_factor( self._expiry_date.expand_dims(axis=-1)) daycount_convention = discount_curve.daycount_convention day_count_fn = market_data_utils.get_daycount_fn( daycount_convention) if spots.shape.rank > 0: spots = tf.gather(spots, self._equity_mask) if self._model == "BS-LSM": # TODO(b/168798725): volatility should be time-dependent vols = vol_surface.volatility( expiry_dates=self._expiry_date.expand_dims(axis=-1), strike=tf.expand_dims(self._strike, axis=-1)) prices = utils.bs_lsm_price( spots=spots, expiry_times=day_count_fn(start_date=market.date, end_date=self._expiry_date, dtype=self._dtype), strikes=self._strike, volatility=tf.squeeze(vols, axis=-1), discount_factors=tf.squeeze(discount_factors), is_call_option=self._is_call_option, num_samples=self._num_samples, num_exercise_times=self._num_exercise_times, num_calibration_samples=self._num_calibration_samples, seed=self._seed) return self._short_position * self._contract_amount * prices else: raise ValueError("Only BS-LSM model is suppoted. " "Supplied {}".format(self._model))
def forward_rates(self, market: pmd.ProcessedMarketData, name: Optional[str] = None ) -> Tuple[types.DateTensor, types.FloatTensor]: """Returns forward rates for the floating leg. Args: market: An instance of `ProcessedMarketData`. name: Python str. The name to give to the ops created by this function. Default value: `None` which maps to 'forward_rates'. Returns: A tuple of two `Tensor`s of shape `batch_shape + [num_cashflows]` containing the dates and the corresponding forward rates for each stream based on the input market data. """ name = name or (self._name + "_forward_rates") with tf.name_scope(name): reference_curve = market.yield_curve(self._reference_curve_type) valuation_date = dateslib.convert_to_date_tensor(market.date) # TODO(cyrilchimisov): vectorize fixing computation past_fixing = market.fixings(self._start_date, self._reference_curve_type, self._reset_frequency) forward_rates = reference_curve.forward_rate( self._accrual_start_date, self._accrual_end_date, day_count_fraction=self._daycount_fractions) forward_rates = tf.where(self._daycount_fractions > 0., forward_rates, tf.zeros_like(forward_rates)) # If coupon end date is before the valuation date, the payment is in the # past. If valuation date is between coupon start date and coupon end # date, then the rate has been fixed but not paid. Otherwise the rate is # not fixed and should be read from the curve. forward_rates = tf.where( self._coupon_end_dates < valuation_date, tf.constant(0, dtype=self._dtype), tf.where(self._coupon_start_dates >= valuation_date, forward_rates, tf.expand_dims(past_fixing, axis=-1))) return self._coupon_end_dates, forward_rates
def _annuity(self, market: pmd.ProcessedMarketData) -> tf.Tensor: """Returns the annuity of each swap on the vauation date.""" num_fixed_legs = 0 if isinstance(self._pay_leg, cashflow_streams.FixedCashflowStream): fixed_leg = self._pay_leg num_fixed_legs += 1 if isinstance(self._receive_leg, cashflow_streams.FixedCashflowStream): fixed_leg = self._receive_leg num_fixed_legs += 1 if num_fixed_legs == 0: raise ValueError("Swap does not have a fixed leg.") if num_fixed_legs == 2: raise ValueError("Swap should not have both fixed leg.") discount_curve = market.yield_curve(self._discount_curve_type) discount_factors = discount_curve.discount_factor( fixed_leg.cashflow_dates) return tf.math.reduce_sum( discount_factors * fixed_leg.daycount_fractions, axis=-1)
def get_vol_surface(equity_types: List[str], market: pmd.ProcessedMarketData, mask: List[int]) -> volatility_surface.VolatilitySurface: """Builds a batched volatility surface. Given a list of discount curve an integer mask, creates a discount curve object to compute discount factors against the list of discount curves. #### Example ```none curve_types = ["GOOG", "MSFT"] # A mask to price a batch of 7 instruments with the corresponding discount # curves ["GOOG", "MSFT", "MSFT", "MSFT" "GOOG", "GOOG"]. mask = [0, 1, 1, 1, 0, 0] market = MarketDataDict(...) get_vol_surface(curve_types, market, mask) # Returns a VolatilitySurface object that can compute a volatilities for a # batch of 6 expiry dates and strikes. ``` Args: equity_types: A list of equity types. market: An instance of the processed market data. mask: An integer mask. Returns: An instance of `VolatilitySurface`. """ vols = market.volatility_surface(equity_types) expiries = vols.node_expiries().ordinal() strikes = vols.node_strikes() volatilities = vols.node_volatilities() prepare_strikes = tf.gather(strikes, mask) prepare_vols = tf.gather(volatilities, mask) prepare_expiries = dateslib.dates_from_ordinals(tf.gather(expiries, mask)) # All curves are assumed to have the same interpolation method # TODO(b/168411153): Extend to the case with multiple curve configs. vol_surface = volatility_surface.VolatilitySurface( valuation_date=market.date, expiries=prepare_expiries, strikes=prepare_strikes, volatilities=prepare_vols, daycount_convention=vols.daycount_convention) return vol_surface
def price(self, market: pmd.ProcessedMarketData, name: Optional[str] = None): """Returns the present value of the stream on the valuation date. Args: market: An instance of `ProcessedMarketData`. name: Python str. The name to give to the ops created by this function. Default value: `None` which maps to 'price'. Returns: A `Tensor` of shape `batch_shape` containing the modeled price of each stream based on the input market data. """ name = name or (self._name + "_price") with tf.name_scope(name): discount_curve = market.yield_curve(self._discount_curve_type) discount_factors = discount_curve.discount_factor( self._payment_dates) _, cashflows = self.cashflows(market) # Cashflow present values cashflow_pvs = (cashflows * discount_factors) return tf.math.reduce_sum(cashflow_pvs, axis=1)