コード例 #1
0
 def value_fn(s_crit):
     return (vanilla_prices.option_price(volatilities=sigma,
                                         strikes=x,
                                         expiries=t,
                                         spots=s_crit,
                                         discount_rates=r,
                                         dividend_rates=d,
                                         is_call_options=is_call_options,
                                         dtype=dtype) + sign *
             (1 - tf.math.exp(-d * t) *
              _ncdf(sign * _calc_d1(s_crit, x, sigma, r - d, t))) *
             tf.math.divide_no_nan(s_crit, q) - sign * (s_crit - x))
コード例 #2
0
 def value_fn(s_crit):
     return (
         vanilla_prices.option_price(volatilities=sigma,
                                     strikes=x,
                                     expiries=t,
                                     spots=s_crit,
                                     discount_rates=r,
                                     cost_of_carries=b,
                                     is_call_options=is_call_options,
                                     dtype=dtype) + sign *
         (1 - tf.math.exp(
             (b - r) * t) * _ncdf(sign * _calc_d1(s_crit, x, sigma, b, t)))
         * s_crit / q - sign * (s_crit - x))
コード例 #3
0
def _adesi_whaley(*, sigma, x, t, r, d, s, is_call_options, max_iterations,
                  tolerance, dtype):
  """Computes American option prices using the Baron-Adesi Whaley formula."""

  # The naming convention will align variables with the variables named in
  # reference [1], but made lower case, and differentiating between put and
  # call option values with the suffix _put and _call.
  # [1] https://deriscope.com/docs/Barone_Adesi_Whaley_1987.pdf
  sign = tf.where(is_call_options, tf.constant(1, dtype=dtype),
                  tf.constant(-1, dtype=dtype))

  q2, a2, s_crit, converged, failed = _adesi_whaley_critical_values(
      sigma=sigma,
      x=x,
      t=t,
      r=r,
      d=d,
      sign=sign,
      is_call_options=is_call_options,
      max_iterations=max_iterations,
      tolerance=tolerance,
      dtype=dtype)

  eu_prices = vanilla_prices.option_price(
      volatilities=sigma,
      strikes=x,
      expiries=t,
      spots=s,
      discount_rates=r,
      dividend_rates=d,
      is_call_options=is_call_options,
      dtype=dtype)

  # The divisive condition is different for put and call options
  condition = tf.where(is_call_options, s < s_crit, s > s_crit)

  american_prices = tf.where(condition, eu_prices + a2 * (s / s_crit)**q2,
                             (s - x) * sign)

  return american_prices, converged, failed
コード例 #4
0
def option_price(*,
                 strikes,
                 expiries,
                 forwards,
                 is_call_options,
                 alpha,
                 beta,
                 nu,
                 rho,
                 shift=0.0,
                 volatility_type=SabrImpliedVolatilityType.LOGNORMAL,
                 approximation_type=SabrApproximationType.HAGAN,
                 dtype=None,
                 name=None):
    """Computes the approximate European option price under the SABR model.

  For a review of the SABR model and the conventions used, please see the
  docstring for `implied_volatility`.

  #### Example
  ```python
  import tf_quant_finance as tff
  import tensorflow.compat.v2 as tf

  prices = tff.models.sabr.approximations.european_option_price(
    strikes=np.array([90.0, 100.0]),
    expiries=np.array([0.5, 1.0]),
    forwards=np.array([100.0, 110.0]),
    is_call_options=np.array([True, False]),
    alpha=3.2,
    beta=0.2,
    nu=1.4,
    rho=0.0005,
    dtype=tf.float64)

  # Expected: [10.41244961, 1.47123225]

  ```

  Args:
    strikes: Real `Tensor` of arbitrary shape, specifying the strike prices.
      Values must be strictly positive.
    expiries: Real `Tensor` of shape compatible with that of `strikes`,
      specifying the corresponding time-to-expiries of the options. Values must
      be strictly positive.
    forwards: Real `Tensor` of shape compatible with that of `strikes`,
      specifying the observed forward prices of the underlying. Values must be
      strictly positive.
    is_call_options: Boolean `Tensor` of shape compatible with that of
      `forward`, indicating whether the option is a call option (true) or put
      option (false).
    alpha: Real `Tensor` of shape compatible with that of `strikes`, specifying
      the initial values of the stochastic volatility. Values must be strictly
      positive.
    beta: Real `Tensor` of shape compatible with that of `strikes`, specifying
      the model exponent `beta`. Values must satisfy 0 <= `beta` <= 1.
    nu: Real `Tensor` of shape compatible with that of `strikes`, specifying the
      model vol-vol multipliers. Values must satisfy 0 <= `nu`.
    rho: Real `Tensor` of shape compatible with that of `strikes`, specifying
      the correlation factors between the Wiener processes modeling the forward
      and the volatility. Values must satisfy -1 < `rho` < 1.
    shift: Optional `Tensor` of shape compatible with that of `strkies`,
      specifying the shift parameter(s). In the shifted model, the process
      modeling the forward is modified as: dF = sigma * (F + shift) ^ beta * dW.
      With this modification, negative forward rates are valid as long as
      F > -shift.
      Default value: 0.0
    volatility_type: Either SabrImpliedVolatility.NORMAL or LOGNORMAL.
      Default value: `LOGNORMAL`.
    approximation_type: Instance of `SabrApproxmationScheme`.
      Default value: `HAGAN`.
    dtype: Optional: `tf.DType`. If supplied, the dtype to be used for
      converting values to `Tensor`s.
      Default value: `None`, which means that the default dtypes inferred from
        `strikes` is used.
    name: str. The name for the ops created by this function.
      Default value: 'sabr_approx_eu_option_price'.

  Returns:
    A real `Tensor` of the same shape as `strikes`, containing the
    corresponding options price.
  """
    name = name or 'sabr_approx_eu_option_price'

    with tf.name_scope(name):
        forwards = tf.convert_to_tensor(forwards, dtype=dtype, name='forwards')
        dtype = dtype or forwards.dtype

        strikes = tf.convert_to_tensor(strikes, dtype=dtype, name='strikes')
        expiries = tf.convert_to_tensor(expiries, dtype=dtype, name='expiries')

        is_call_options = tf.convert_to_tensor(is_call_options,
                                               dtype=tf.bool,
                                               name='is_call_options')

        if volatility_type == SabrImpliedVolatilityType.NORMAL:
            sigma_normal = implied_volatility(
                strikes=strikes,
                expiries=expiries,
                forwards=forwards,
                alpha=alpha,
                beta=beta,
                nu=nu,
                rho=rho,
                shift=shift,
                volatility_type=volatility_type,
                approximation_type=approximation_type,
                dtype=dtype)

            return vanilla_prices.option_price(volatilities=sigma_normal,
                                               strikes=strikes + shift,
                                               expiries=expiries,
                                               forwards=forwards + shift,
                                               is_call_options=is_call_options,
                                               is_normal_volatility=True)

        elif volatility_type == SabrImpliedVolatilityType.LOGNORMAL:
            sigma_black = implied_volatility(
                strikes=strikes,
                expiries=expiries,
                forwards=forwards,
                alpha=alpha,
                beta=beta,
                nu=nu,
                rho=rho,
                shift=shift,
                volatility_type=volatility_type,
                approximation_type=approximation_type,
                dtype=dtype)

            return vanilla_prices.option_price(volatilities=sigma_black,
                                               strikes=strikes + shift,
                                               expiries=expiries,
                                               forwards=forwards + shift,
                                               is_call_options=is_call_options,
                                               is_normal_volatility=False)
コード例 #5
0
def adesi_whaley(*,
                 volatilities,
                 strikes,
                 expiries,
                 spots=None,
                 forwards=None,
                 discount_rates=None,
                 continuous_dividends=None,
                 cost_of_carries=None,
                 discount_factors=None,
                 is_call_options=None,
                 max_iterations=100,
                 tolerance=1e-8,
                 dtype=None,
                 name=None):
    """Computes American option prices using the Baron-Adesi Whaley approximation.

  #### Example

  ```python
  spots = [80.0, 90.0, 100.0, 110.0, 120.0]
  strikes = [100.0, 100.0, 100.0, 100.0, 100.0]
  volatilities = [0.2, 0.2, 0.2, 0.2, 0.2]
  expiries = 0.25
  cost_of_carries = -0.04
  discount_rates = 0.08
  computed_prices = adesi_whaley(
      volatilities=volatilities,
      strikes=strikes,
      expiries=expiries,
      discount_rates=discount_rates,
      cost_of_carries=cost_of_carries,
      spots=spots,
      dtype=tf.float64)
  # Expected print output of computed prices:
  # [0.03, 0.59, 3.52, 10.31, 20.0]
  ```

  #### References:
  [1] Baron-Adesi, Whaley, Efficient Analytic Approximation of American Option
    Values, The Journal of Finance, Vol XLII, No. 2, June 1987
    https://deriscope.com/docs/Barone_Adesi_Whaley_1987.pdf

  Args:
    volatilities: Real `Tensor` of any shape and dtype. The volatilities to
      expiry of the options to price.
    strikes: A real `Tensor` of the same dtype and compatible shape as
      `volatilities`. The strikes of the options to be priced.
    expiries: A real `Tensor` of same dtype and compatible shape as
      `volatilities`. The expiry of each option. The units should be such that
      `expiry * volatility**2` is dimensionless.
    spots: A real `Tensor` of any shape that broadcasts to the shape of the
      `volatilities`. The current spot price of the underlying. Either this
      argument or the `forwards` (but not both) must be supplied.
    forwards: A real `Tensor` of any shape that broadcasts to the shape of
      `volatilities`. The forwards to maturity. Either this argument or the
      `spots` must be supplied but both must not be supplied.
    discount_rates: An optional real `Tensor` of same dtype as the
      `volatilities`. If not `None`, discount factors are calculated as e^(-rT),
      where r are the discount rates, or risk free rates.
      Default value: `None`, equivalent to r = 0 and discount factors = 1 when
      discount_factors also not given.
    continuous_dividends: An optional real `Tensor` of same dtype as the
      `volatilities`. If not `None`, cost_of_carries is calculated as r - q,
      where r are the discount rates and q is continuous_dividends. Either this
      or cost_of_carries can be given.
      Default value: `None`, equivalent to q = 0.
    cost_of_carries: An optional real `Tensor` of same dtype as the
      `volatilities`. Cost of storing a physical commodity, the cost of
      interest paid when long, or the opportunity cost, or the cost of paying
      dividends when short. If not `None`, and `spots` is supplied, used to
      calculate forwards from spots: F = e^(bT) * S. If `None`, value assumed
      to be equal to the discount rate - continuous_dividends
      Default value: `None`, equivalent to b = r.
    discount_factors: An optional real `Tensor` of same dtype as the
      `volatilities`. If not `None`, these are the discount factors to expiry
      (i.e. e^(-rT)). Mutually exclusive with discount_rate and cost_of_carry.
      If neither is given, no discounting is applied (i.e. the undiscounted
      option price is returned). If `spots` is supplied and `discount_factors`
      is not `None` then this is also used to compute the forwards to expiry.
      At most one of discount_rates and discount_factors can be supplied.
      Default value: `None`, which maps to -log(discount_factors) / expiries
    is_call_options: A boolean `Tensor` of a shape compatible with
      `volatilities`. Indicates whether the option is a call (if True) or a put
      (if False). If not supplied, call options are assumed.
    max_iterations: positive `int`. The maximum number of iterations of Newton's
      root finding method to find the critical spot price above and below which
      the pricing formula is different.
      Default value: 100
    tolerance: Positive scalar `Tensor`. As with max_iterations, used with the
      Newton root finder to find the critical spot price. The root finder will
      judge an element to have converged if `|f(x_n) - a|` is less than
      `tolerance` (where `f` is the target function as defined in [1] and
      `x_n` is the estimated critical value), or if `x_n` becomes `nan`. When an
      element is judged to have converged it will no longer be updated. If all
      elements converge before `max_iterations` is reached then the root finder
      will return early.
      Default value: 1e-8
    dtype: Optional `tf.DType`. If supplied, the dtype to be used for conversion
      of any supplied non-`Tensor` arguments to `Tensor`.
      Default value: None which maps to the default dtype inferred by
       TensorFlow.
    name: str. The name for the ops created by this function.
      Default value: None which is mapped to the default name `adesi_whaley`.

  Returns:
    A 3-tuple containing the following items in order:
       (a) option_prices: A `Tensor` of the same shape as `forwards`. The Black
         Scholes price of the options.
       (b) converged: A boolean `Tensor` of the same shape as `option_prices`
         above. Indicates whether the corresponding adesi-whaley approximation
         has converged to within tolerance.
       (c) failed: A boolean `Tensor` of the same shape as `option_prices`
         above. Indicates whether the corresponding options price is NaN or not
         a finite number. Note that converged being True implies that failed
         will be false. However, it may happen that converged is False but
         failed is not True. This indicates the search did not converge in the
         permitted number of iterations but may converge if the iterations are
         increased.

  Raises:
    ValueError:
      (a) If both `forwards` and `spots` are supplied or if neither is supplied.
      (b) If both `continuous_dividends` and `cost_of_carries` are supplied.
  """
    if (spots is None) == (forwards is None):
        raise ValueError(
            "Either spots or forwards must be supplied but not both.")
    if (discount_rates is not None) and (discount_factors is not None):
        raise ValueError(
            "At most one of discount_rates and discount_factors may "
            "be supplied")
    if (continuous_dividends is not None) and (cost_of_carries is not None):
        raise ValueError(
            "At most one of continuous_dividends and cost_of_carries "
            "may be supplied")
    with tf.name_scope(name or "adesi_whaley"):
        volatilities = tf.convert_to_tensor(volatilities,
                                            dtype=dtype,
                                            name="volatilities")
        dtype = volatilities.dtype  # This dtype should be common for all inputs
        strikes = tf.convert_to_tensor(strikes, dtype=dtype, name="strikes")
        expiries = tf.convert_to_tensor(expiries, dtype=dtype, name="expiries")
        if discount_rates is not None:
            discount_rates = tf.convert_to_tensor(discount_rates,
                                                  dtype=dtype,
                                                  name="discount_rates")
        elif discount_factors is not None:
            discount_factors = tf.convert_to_tensor(discount_factors,
                                                    dtype=dtype,
                                                    name="discount_factors")
            discount_rates = -tf.math.log(discount_factors) / expiries
        else:
            discount_rates = tf.constant(0.0,
                                         dtype=dtype,
                                         name="discount_rates")

        if cost_of_carries is not None:
            cost_of_carries = tf.convert_to_tensor(cost_of_carries,
                                                   dtype=dtype,
                                                   name="cost_of_carries")
        else:
            if continuous_dividends is not None:
                continuous_dividends = tf.convert_to_tensor(
                    continuous_dividends,
                    dtype=dtype,
                    name="continuous_dividends")
                cost_of_carries = tf.convert_to_tensor(discount_rates -
                                                       continuous_dividends,
                                                       name="cost_of_carries")
            else:
                cost_of_carries = tf.convert_to_tensor(discount_rates,
                                                       name="cost_of_carries")
        # Set forwards and spots
        if forwards is not None:
            spots = tf.convert_to_tensor(forwards *
                                         tf.exp(-cost_of_carries * expiries),
                                         dtype=dtype,
                                         name="spots")
        else:
            spots = tf.convert_to_tensor(spots, dtype=dtype, name="spots")
        if is_call_options is not None:
            is_call_options = tf.convert_to_tensor(is_call_options,
                                                   dtype=tf.bool,
                                                   name="is_call_options")
        else:
            is_call_options = tf.constant(True, name="is_call_options")
        # American option prices
        am_prices, converged, failed = _adesi_whaley(
            sigma=volatilities,
            x=strikes,
            t=expiries,
            s=spots,
            r=discount_rates,
            b=cost_of_carries,
            is_call_options=is_call_options,
            dtype=dtype,
            max_iterations=max_iterations,
            tolerance=tolerance)

        # For call options where b >= r as per reference [1], only the European
        # option price should be calclated, while for the rest the american price
        # formula should be used. For this reason, the vanilla EU price is
        # calculated for all the spot prices, (assuming they are all call options),
        # and a subset of these will be used further down, if any of the date points
        # fit the criteria that they are all call options with b >= r.
        eu_prices = vanilla_prices.option_price(
            volatilities=volatilities,
            strikes=strikes,
            expiries=expiries,
            spots=spots,
            discount_rates=discount_rates,
            cost_of_carries=cost_of_carries,
            dtype=dtype,
            name=name)
        calculate_eu = tf.logical_and(is_call_options,
                                      cost_of_carries >= discount_rates)
        converged = tf.where(calculate_eu, True, converged)
        failed = tf.where(calculate_eu, False, failed)
        return tf.where(calculate_eu, eu_prices, am_prices), converged, failed
コード例 #6
0
def bjerksund_stensland(*,
                        volatilities: types.RealTensor,
                        strikes: types.RealTensor,
                        expiries: types.RealTensor,
                        spots: types.RealTensor = None,
                        forwards: types.RealTensor = None,
                        discount_rates: types.RealTensor = None,
                        dividend_rates: types.RealTensor = None,
                        discount_factors: types.RealTensor = None,
                        is_call_options: types.BoolTensor = None,
                        modified_boundary: bool = True,
                        dtype: tf.DType = None,
                        name: str = None) -> types.RealTensor:
    """Computes prices of a batch of American options using Bjerksund-Stensland.

  #### Example

  ```python
    import tf_quant_finance as tff
    # Price a batch of 5 american call options.
    volatilities = [0.2, 0.2, 0.2, 0.2, 0.2]
    forwards = [80.0, 90.0, 100.0, 110.0, 120.0]
    # Strikes will automatically be broadcasted to shape [5].
    strikes = np.array([100.0])
    # Expiries will be broadcast to shape [5], i.e. each option has strike=100
    # and expiry = 0.25.
    expiries = 0.25
    discount_rates = 0.08
    dividend_rates = 0.12
    computed_prices = tff.black_scholes.approximations.bjerksund_stensland(
        volatilities=volatilities,
        strikes=strikes,
        expiries=expiries,
        discount_rates=discount_rates,
        dividend_rates=dividend_rates,
        forwards=forwards,
        is_call_options=True
        modified_boundary=True)
  # Expected print output of computed prices:
  # [ 0.03931201  0.70745419  4.01937524 11.31429842 21.20602005]
  ```

  #### References:
  [1] Bjerksund, P. and Stensland G., Closed Form Valuation of American Options,
      2002
      https://core.ac.uk/download/pdf/30824897.pdf

  Args:
    volatilities: Real `Tensor` of any shape and real dtype. The volatilities to
      expiry of the options to price.
    strikes: A real `Tensor` of the same dtype and compatible shape as
      `volatilities`. The strikes of the options to be priced.
    expiries: A real `Tensor` of same dtype and compatible shape as
      `volatilities`. The expiry of each option. The units should be such that
      `expiry * volatility**2` is dimensionless.
    spots: A real `Tensor` of any shape that broadcasts to the shape of the
      `volatilities`. The current spot price of the underlying. Either this
      argument or the `forwards` (but not both) must be supplied.
    forwards: A real `Tensor` of any shape that broadcasts to the shape of
      `volatilities`. The forwards to maturity. Either this argument or the
      `spots` must be supplied but both must not be supplied.
    discount_rates: An optional real `Tensor` of same dtype as the
      `volatilities` and of the shape that broadcasts with `volatilities`.
      If not `None`, discount factors are calculated as e^(-rT),
      where r are the discount rates, or risk free rates. At most one of
      discount_rates and discount_factors can be supplied.
      Default value: `None`, equivalent to r = 0 and discount factors = 1 when
      discount_factors also not given.
    dividend_rates: An optional real `Tensor` of same dtype as the
      `volatilities`. The continuous dividend rate on the underliers. May be
      negative (to indicate costs of holding the underlier).
      Default value: `None`, equivalent to zero dividends.
    discount_factors: An optional real `Tensor` of same dtype as the
      `volatilities`. If not `None`, these are the discount factors to expiry
      (i.e. e^(-rT)). Mutually exclusive with discount_rate and cost_of_carry.
      If neither is given, no discounting is applied (i.e. the undiscounted
      option price is returned). If `spots` is supplied and `discount_factors`
      is not `None` then this is also used to compute the forwards to expiry.
      At most one of discount_rates and discount_factors can be supplied.
      Default value: `None`, which maps to e^(-rT) calculated from
      discount_rates.
    is_call_options: A boolean `Tensor` of a shape compatible with
      `volatilities`. Indicates whether the option is a call (if True) or a put
      (if False). If not supplied, call options are assumed.
    modified_boundary: Python `bool`. Indicates whether the Bjerksund-Stensland
      1993 algorithm (single boundary) if False or Bjerksund-Stensland 2002
      algorithm (modified boundary) if True, is to be used.
    dtype: Optional `tf.DType`. If supplied, the dtype to be used for conversion
      of any supplied non-`Tensor` arguments to `Tensor`.
      Default value: `None` which maps to the default dtype inferred by
        TensorFlow.
    name: str. The name for the ops created by this function.
      Default value: `None` which is mapped to the default name `option_price`.

  Returns:
    A `Tensor` of the same shape as `forwards`.

  Raises:
    ValueError: If both `forwards` and `spots` are supplied or if neither is
      supplied.
    ValueError: If both `discount_rates` and `discount_factors` is supplied.
  """
    if (spots is None) == (forwards is None):
        raise ValueError(
            'Either spots or forwards must be supplied but not both.')
    if (discount_rates is not None) and (discount_factors is not None):
        raise ValueError(
            'At most one of discount_rates and discount_factors may '
            'be supplied')
    with tf.name_scope(name or 'option_price'):
        strikes = tf.convert_to_tensor(strikes, dtype=dtype, name='strikes')
        dtype = strikes.dtype
        volatilities = tf.convert_to_tensor(volatilities,
                                            dtype=dtype,
                                            name='volatilities')
        expiries = tf.convert_to_tensor(expiries, dtype=dtype, name='expiries')

        if discount_rates is not None:
            discount_rates = tf.convert_to_tensor(discount_rates,
                                                  dtype=dtype,
                                                  name='discount_rates')
            discount_factors = tf.exp(-discount_rates * expiries)
        elif discount_factors is not None:
            discount_factors = tf.convert_to_tensor(discount_factors,
                                                    dtype=dtype,
                                                    name='discount_factors')
            discount_rates = -tf.math.log(discount_factors) / expiries
        else:
            discount_rates = tf.convert_to_tensor(0.0,
                                                  dtype=dtype,
                                                  name='discount_rates')
            discount_factors = tf.convert_to_tensor(1.0,
                                                    dtype=dtype,
                                                    name='discount_factors')

        if dividend_rates is None:
            dividend_rates = tf.convert_to_tensor(0.0,
                                                  dtype=dtype,
                                                  name='dividend_rates')

        cost_of_carries = discount_rates - dividend_rates

        if forwards is not None:
            forwards = tf.convert_to_tensor(forwards,
                                            dtype=dtype,
                                            name='forwards')
            spots = tf.convert_to_tensor(forwards *
                                         tf.exp(-(cost_of_carries) * expiries),
                                         dtype=dtype,
                                         name='spots')
        else:
            spots = tf.convert_to_tensor(spots, dtype=dtype, name='spots')
            forwards = spots * tf.exp(cost_of_carries * expiries)

        if is_call_options is not None:
            is_call_options = tf.convert_to_tensor(is_call_options,
                                                   dtype=tf.bool,
                                                   name='is_call_options')
        else:
            is_call_options = tf.constant(True, name='is_call_options')

        if modified_boundary:
            bjerksund_stensland_model = _call_2002
        else:
            bjerksund_stensland_model = _call_1993

        # If cost of carry is greater than or equal to discount rate, then use
        # Black-Scholes option price
        american_prices = tf.where(
            tf.math.logical_and(cost_of_carries >= discount_rates,
                                is_call_options),
            vanilla_prices.option_price(volatilities=volatilities,
                                        strikes=strikes,
                                        expiries=expiries,
                                        spots=spots,
                                        discount_rates=discount_rates,
                                        dividend_rates=dividend_rates,
                                        is_call_options=is_call_options),
            # For put options, adjust inputs according to call-put transformation
            # function: P(S, X, T, r, b, sigma) = C(X, S, T, r - b, -b, sigma)
            tf.where(
                is_call_options,
                bjerksund_stensland_model(spots, strikes, expiries,
                                          discount_rates, cost_of_carries,
                                          volatilities),
                bjerksund_stensland_model(strikes, spots, expiries,
                                          discount_rates - cost_of_carries,
                                          -cost_of_carries, volatilities)))
        return american_prices
コード例 #7
0
def fair_strike(put_strikes,
                put_volatilities,
                call_strikes,
                call_volatilities,
                expiries,
                discount_rates,
                spots,
                reference_strikes,
                validate_args=False,
                dtype=None,
                name=None):
    """Calculates the fair value strike for a variance swap contract.

  This implements the approach in Appendix A of Demeterfi et al (1999), where a
  variance swap is defined as a forward contract on the square of annualized
  realized volatility (though the approach assumes continuous sampling). The
  variance swap payoff is, then:

  `notional * (realized_volatility^2 - variance_strike)`

  The method calculates the weight of each European option required to
  approximately replicate such a payoff using the discrete range of strike
  prices and implied volatilities of European options traded on the market. The
  fair value `variance_strike` is that which is expected to produce zero payoff.

  #### Example

  ```python
  dtype = tf.float64
  call_strikes = tf.constant([[100, 105, 110, 115], [1000, 1100, 1200, 1300]],
    dtype=dtype)
  call_vols = 0.2 * tf.ones((2, 4), dtype=dtype)
  put_strikes = tf.constant([[100, 95, 90, 85], [1000, 900, 800, 700]],
    dtype=dtype)
  put_vols = 0.2 * tf.ones((2, 4), dtype=dtype)
  reference_strikes = tf.constant([100.0, 1000.0], dtype=dtype)
  expiries = tf.constant([0.25, 0.25], dtype=dtype)
  discount_rates = tf.constant([0.05, 0.05], dtype=dtype)
  variance_swap_price(
    put_strikes,
    put_vols,
    call_strikes,
    put_vols,
    expiries,
    discount_rates,
    reference_strikes,
    reference_strikes,
    dtype=tf.float64)
  # [0.03825004, 0.04659269]
  ```

  #### References

  [1] Demeterfi, K., Derman, E., Kamal, M. and Zou, J., 1999. More Than You Ever
    Wanted To Know About Volatility Swaps. Goldman Sachs Quantitative Strategies
    Research Notes.

  Args:
    put_strikes: A real `Tensor` of shape  `batch_shape + [num_put_strikes]`
      containing the strike values of traded puts. This must be supplied in
      **descending** order, and its elements should be less than or equal to the
      `reference_strike`.
    put_volatilities: A real `Tensor` of shape  `batch_shape +
      [num_put_strikes]` containing the market volatility for each strike in
      `put_strikes. The final value is unused.
    call_strikes: A real `Tensor` of shape  `batch_shape + [num_call_strikes]`
      containing the strike values of traded calls. This must be supplied in
      **ascending** order, and its elements should be greater than or equal to
      the `reference_strike`.
    call_volatilities: A real `Tensor` of shape  `batch_shape +
      [num_call_strikes]` containing the market volatility for each strike in
      `call_strikes`. The final value is unused.
    expiries: A real `Tensor` of shape compatible with `batch_shape` containing
      the time to expiries of the contracts.
    discount_rates: A real `Tensor` of shape compatible with `batch_shape`
      containing the discount rate to be applied.
    spots: A real `Tensor` of shape compatible with `batch_shape` containing the
      current spot price of the asset.
    reference_strikes: A real `Tensor` of shape compatible with `batch_shape`
      containing an arbitrary value demarcating the atm boundary between liquid
      calls and puts. Typically either the spot price or the (common) first
      value of `put_strikes` or `call_strikes`.
    validate_args: Python `bool`. When `True`, input `Tensor`s are checked for
      validity. The checks verify the the matching length of strikes and
      volatilties. When `False` invalid inputs may silently render incorrect
      outputs, yet runtime performance will be improved.
      Default value: False.
    dtype: `tf.Dtype`. If supplied the dtype for the input and output `Tensor`s.
      Default value: None, leading to the default value inferred by Tensorflow.
    name: Python str. The name to give to the ops created by this function.
      Default value: `None` which maps to 'variance_swap_price'.

  Returns:
    A `Tensor` of shape `batch_shape` containing the fair value of variance for
    each item in the batch. Note this is on the decimal rather than square
    percentage scale.
  """
    with tf.name_scope(name or 'variance_swap_price'):
        put_strikes = tf.convert_to_tensor(put_strikes,
                                           dtype=dtype,
                                           name='put_strikes')
        dtype = dtype or put_strikes.dtype
        put_volatilities = tf.convert_to_tensor(put_volatilities,
                                                dtype=dtype,
                                                name='put_volatilities')
        call_strikes = tf.convert_to_tensor(call_strikes,
                                            dtype=dtype,
                                            name='call_strikes')
        call_volatilities = tf.convert_to_tensor(call_volatilities,
                                                 dtype=dtype,
                                                 name='call_volatilities')
        expiries = tf.convert_to_tensor(expiries, dtype=dtype, name='expiries')
        discount_rates = tf.expand_dims(
            tf.convert_to_tensor(discount_rates,
                                 dtype=dtype,
                                 name='discount_rates'), -1)
        spots = tf.expand_dims(
            tf.convert_to_tensor(spots, dtype=dtype, name='spots'), -1)
        reference_strikes = tf.convert_to_tensor(reference_strikes,
                                                 dtype=dtype,
                                                 name='reference_strikes')

        # Check the inputs are consistent in length.
        control_dependencies = []
        if validate_args:
            control_dependencies.append(
                tf.math.reduce_all(
                    tf.shape(put_strikes)[-1] == tf.shape(put_volatilities)
                    [-1]))
            control_dependencies.append(
                tf.math.reduce_all(
                    tf.shape(call_strikes)[-1] == tf.shape(call_volatilities)
                    [-1]))

        with tf.control_dependencies(control_dependencies):
            # Shape is `batch_shape + [num_put_strikes - 1]`
            put_weights = replicating_weights(put_strikes,
                                              reference_strikes,
                                              expiries,
                                              validate_args=validate_args)
            # Shape is `batch_shape + [num_call_strikes - 1]`
            call_weights = replicating_weights(call_strikes,
                                               reference_strikes,
                                               expiries,
                                               validate_args=validate_args)

            expiries = tf.expand_dims(expiries, -1)
            reference_strikes = tf.expand_dims(reference_strikes, -1)

            put_prices = vanilla_prices.option_price(
                volatilities=put_volatilities[..., :-1],
                strikes=put_strikes[..., :-1],
                expiries=expiries,
                spots=spots,
                discount_rates=discount_rates,
                is_call_options=False,
            )
            call_prices = vanilla_prices.option_price(
                volatilities=call_volatilities[..., :-1],
                strikes=call_strikes[..., :-1],
                expiries=expiries,
                spots=spots,
                discount_rates=discount_rates,
                is_call_options=True,
            )

            effective_rate = expiries * discount_rates
            discount_factor = tf.math.exp(effective_rate)

            s_ratio = spots / reference_strikes
            centrality_term = (2.0 / expiries) * (effective_rate -
                                                  discount_factor * s_ratio +
                                                  1 + tf.math.log(s_ratio))

            options_value = discount_factor * (tf.math.reduce_sum(
                put_weights * put_prices, axis=-1,
                keepdims=True) + tf.math.reduce_sum(
                    call_weights * call_prices, axis=-1, keepdims=True))

            # Return values, undoing the dimension expansion introduced earlier.
            return tf.squeeze(options_value + centrality_term, axis=-1)