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