def _newton_implied_vol(prices, strikes, expiries, forwards, discount_factors,
                        is_call_options, initial_volatilities,
                        underlying_distribution, tolerance, max_iterations):
    """Uses Newton's method to find Black Scholes implied volatilities of options.

  Finds the volatility implied under the Black Scholes option pricing scheme for
  a set of European options given observed market prices. The implied volatility
  is found via application of Newton's algorithm for locating the root of a
  differentiable function.

  The implementation assumes that each cell in the supplied tensors corresponds
  to an independent volatility to find.

  Args:
    prices: A real `Tensor` of any shape. The prices of the options whose
      implied vol is to be calculated.
    strikes: A real `Tensor` of the same dtype as `prices` and a shape that
      broadcasts with `prices`. The strikes of the options.
    expiries: A real `Tensor` of the same dtype as `prices` and a shape that
      broadcasts with `prices`. The expiry for each option. The units should be
      such that `expiry * volatility**2` is dimensionless.
    forwards: A real `Tensor` of any shape that broadcasts to the shape of
      `prices`. The forwards to maturity.
    discount_factors: An optional real `Tensor` of same dtype as the `prices`.
      If not None, these are the discount factors to expiry (i.e. e^(-rT)). If
      None, no discounting is applied (i.e. it is assumed that the undiscounted
      option prices are provided ).
    is_call_options: A boolean `Tensor` of a shape compatible with `prices`.
      Indicates whether the option is a call (if True) or a put (if False). If
      not supplied, call options are assumed.
    initial_volatilities: A real `Tensor` of the same shape and dtype as
      `forwards`. The starting positions for Newton's method.
    underlying_distribution: Enum value of ImpliedVolUnderlyingDistribution to
      select the distribution of the underlying.
    tolerance: `float`. The root finder will stop where this tolerance is
      crossed.
    max_iterations: `int`. The maximum number of iterations of Newton's method.

  Returns:
    A three tuple of `Tensor`s, each the same shape as `forwards`. It
    contains the implied volatilities (same dtype as `forwards`), a boolean
    `Tensor` indicating whether the corresponding implied volatility converged,
    and a boolean `Tensor` which is true where the corresponding implied
    volatility is not a finite real number.
  """
    if underlying_distribution is utils.UnderlyingDistribution.LOG_NORMAL:
        pricer = _make_black_lognormal_objective_and_vega_func(
            prices, forwards, strikes, expiries, is_call_options,
            discount_factors)
    elif underlying_distribution is utils.UnderlyingDistribution.NORMAL:
        pricer = _make_bachelier_objective_and_vega_func(
            prices, forwards, strikes, expiries, is_call_options,
            discount_factors)

    results = newton.root_finder(pricer,
                                 initial_volatilities,
                                 max_iterations=max_iterations,
                                 tolerance=tolerance)
    return results
Example #2
0
  def _root_chi2(self, a, b, uniforms):
    c_init = a

    def equation(c_star):
      p, dpc = ncx2cdf_and_gradient(a, b, c_star, self._ncx2_cdf_truncation)
      return 1 - p - uniforms, -dpc

    result, _, _ = root_finder(equation, c_init)
    return tf.math.maximum(result, 0)
Example #3
0
def _adesi_whaley_critical_values(*,
                                  sigma,
                                  x,
                                  t,
                                  r,
                                  d,
                                  sign,
                                  is_call_options,
                                  max_iterations=20,
                                  tolerance=1e-8,
                                  dtype):
    """Computes critical value for the Baron-Adesi Whaley approximation."""

    # 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

    m = 2 * r / sigma**2
    n = 2 * (r - d) / sigma**2
    k = 1 - tf.exp(-r * t)
    q = _calc_q(n, m, sign, k)

    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))

    def value_and_gradient_func(price):
        return gradient.value_and_gradient(value_fn, price)

    # Calculate seed value for critical spot price for fewer iterations needed, as
    # defined in reference [1] part II, section B.
    # [1] https://deriscope.com/docs/Barone_Adesi_Whaley_1987.pdf
    q_inf = _calc_q(n, m, sign)
    s_inf = tf.math.divide_no_nan(
        x, 1 - tf.math.divide_no_nan(tf.constant(1, dtype=dtype), q_inf))
    h = (-(sign * (r - d) * t + 2 * sigma * tf.math.sqrt(t)) * sign *
         tf.math.divide_no_nan(x, s_inf - x))
    if is_call_options is None:
        s_seed = x + (s_inf - x) * (1 - tf.math.exp(h))
    else:
        s_seed = tf.where(is_call_options,
                          x + (s_inf - x) * (1 - tf.math.exp(h)),
                          s_inf + (x - s_inf) * tf.math.exp(h))

    s_crit, converged, failed = root_finder_newton.root_finder(
        value_and_grad_func=value_and_gradient_func,
        initial_values=s_seed,
        max_iterations=max_iterations,
        tolerance=tolerance,
        dtype=dtype)

    a = (sign * tf.math.divide_no_nan(s_crit, q) *
         (1 - tf.math.exp(-d * t) *
          _ncdf(sign * _calc_d1(s_crit, x, sigma, r - d, t))))

    return q, a, s_crit, converged, failed