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