def test_polya_implied_vol_validate(self): """Test the Radiocic-Polya approx doesn't raise where it shouldn't.""" np.random.seed(6589) n = 100 dtypes = [np.float32, np.float64] for dtype in dtypes: volatilities = np.exp(np.random.randn(n) / 2) forwards = np.exp(np.random.randn(n)) strikes = forwards * (1 + (np.random.rand(n) - 0.5) * 0.2) expiries = np.exp(np.random.randn(n)) prices = self.evaluate( black_scholes.option_price(forwards, strikes, volatilities, expiries, dtype=dtype)) implied_vols = self.evaluate( polya_approx.implied_vol(prices, forwards, strikes, expiries, validate_args=True, dtype=dtype)) self.assertArrayNear(volatilities, implied_vols, 0.6)
def test_price_vol_and_expiry_scaling(self): """Tests that the price is invariant under vol->k vol, T->T/k**2.""" np.random.seed(1234) n = 20 forwards = np.exp(np.random.randn(n)) volatilities = np.exp(np.random.randn(n) / 2) strikes = np.exp(np.random.randn(n)) expiries = np.exp(np.random.randn(n)) scaling = 5.0 base_prices = self.evaluate( black_scholes.option_price(forwards, strikes, volatilities, expiries)) scaled_prices = self.evaluate( black_scholes.option_price(forwards, strikes, volatilities * scaling, expiries / scaling / scaling)) self.assertArrayNear(base_prices, scaled_prices, 1e-10)
def test_price_long_expiry_calls(self): """Tests that very long expiry call option behaves like the asset.""" forwards = np.array([1.0, 1.0, 1.0, 1.0]) strikes = np.array([1.1, 0.9, 1.1, 0.9]) volatilities = np.array([0.1, 0.2, 0.5, 0.9]) expiries = 1e10 expected_prices = forwards computed_prices = self.evaluate( black_scholes.option_price(forwards, strikes, volatilities, expiries)) self.assertArrayNear(expected_prices, computed_prices, 1e-10)
def test_price_long_expiry_puts(self): """Tests that very long expiry put option is worth the strike.""" forwards = np.array([1.0, 1.0, 1.0, 1.0]) strikes = np.array([0.1, 10.0, 3.0, 0.0001]) volatilities = np.array([0.1, 0.2, 0.5, 0.9]) expiries = 1e10 expected_prices = strikes computed_prices = self.evaluate( black_scholes.option_price(forwards, strikes, volatilities, expiries, is_call_options=False)) self.assertArrayNear(expected_prices, computed_prices, 1e-10)
def test_option_prices(self): """Tests that the BS prices are correct.""" forwards = np.array([1.0, 2.0, 3.0, 4.0, 5.0]) strikes = np.array([3.0, 3.0, 3.0, 3.0, 3.0]) volatilities = np.array([0.0001, 102.0, 2.0, 0.1, 0.4]) expiries = 1.0 computed_prices = self.evaluate( black_scholes.option_price(forwards, strikes, volatilities, expiries)) expected_prices = np.array([ 0.0, 2.0, 2.0480684764112578, 1.0002029716043364, 2.0730313058959933 ]) self.assertArrayNear(expected_prices, computed_prices, 1e-10)
def test_price_zero_expiry(self): """Tests that zero expiry is correctly handled.""" # If the expiry is zero, the option's value should be correct. forwards = np.array([1.0, 1.0, 1.0, 1.0]) strikes = np.array([1.1, 0.9, 1.1, 0.9]) volatilities = np.array([0.1, 0.2, 0.5, 0.9]) expiries = 0.0 is_call_options = np.array([True, True, False, False]) expected_prices = np.array([0.0, 0.1, 0.1, 0.0]) computed_prices = self.evaluate( black_scholes.option_price(forwards, strikes, volatilities, expiries, is_call_options=is_call_options)) self.assertArrayNear(expected_prices, computed_prices, 1e-10)
def test_polya_implied_vol(self): """Basic test of the implied vol calculation.""" np.random.seed(6589) n = 100 dtypes = [np.float32, np.float64] for dtype in dtypes: volatilities = np.exp(np.random.randn(n).astype(dtype) / 2) forwards = np.exp(np.random.randn(n).astype(dtype)) strikes = forwards * (1 + (np.random.rand(n).astype(dtype) - 0.5) * 0.2) expiries = np.exp(np.random.randn(n).astype(dtype)) prices = self.evaluate( black_scholes.option_price(forwards, strikes, volatilities, expiries)) implied_vols = self.evaluate( polya_approx.implied_vol( prices, forwards, strikes, expiries, dtype=dtype)) self.assertArrayNear(volatilities, implied_vols, 0.6)
def test_implied_vol_extensive(self): np.random.seed(135) num_examples = 1000 expiries = np.linspace(0.8, 1.2, num_examples) rates = np.linspace(0.03, 0.08, num_examples) discount_factors = np.exp(-rates * expiries) spots = np.ones(num_examples) forwards = spots / discount_factors strikes = np.linspace(0.8, 1.2, num_examples) volatilities = np.ones_like(forwards) call_options = np.random.binomial(n=1, p=0.5, size=num_examples) option_signs = 2.0 * call_options - 1.0 is_call_options = np.array(call_options, dtype=np.bool) prices = self.evaluate( black_scholes.option_price(forwards, strikes, volatilities, expiries, is_call_options=is_call_options, discount_factors=discount_factors, dtype=tf.float64)) implied_vols = self.evaluate( implied_vol(forwards, strikes, expiries, discount_factors, prices, option_signs, dtype=tf.float64, max_iterations=1000, tolerance=1e-8))[0] self.assertArrayNear(volatilities, implied_vols, 1e-7)
def test_binary_vanilla_call_consistency(self): r"""Tests code consistency through relationship of binary and vanilla prices. With forward F, strike K, discount rate r, and expiry T, a vanilla call option should have price CV: $$ VC(K) = e^{-rT}( N(d_1)F - N(d_2)K ) $$ A unit of cash paying binary call option should have price BC: $$ BC(K) = e^{-rT} N(d_2) $$ Where d_1 and d_2 are standard Black-Scholes quanitities and depend on K through the ratio F/K. Hence for a small increment e: $$ (VC(K + e) - Vc(K))/e \approx -N(d_2)e^{-rT} = -BC(K + e) $$ Similarly, for a vanilla put: $$ (VP(K + e) - VP(K))/e \approx N(-d_2)e^{-rT} = BP(K + e) $$ This enables a test for consistency of pricing between vanilla and binary options prices. """ np.random.seed(135) num_examples = 1000 forwards = np.exp(np.random.normal(size=num_examples)) strikes_0 = np.exp(np.random.normal(size=num_examples)) epsilon = 1e-8 strikes_1 = strikes_0 + epsilon volatilities = np.exp(np.random.normal(size=num_examples)) expiries = np.random.gamma(shape=1.0, scale=1.0, size=num_examples) call_options = np.random.binomial(n=1, p=0.5, size=num_examples) is_call_options = np.array(call_options, dtype=np.bool) discount_factors = np.ones_like(forwards) option_prices_0 = self.evaluate( black_scholes.option_price(forwards, strikes_0, volatilities, expiries, is_call_options=is_call_options, dtype=tf.float64)) option_prices_1 = self.evaluate( black_scholes.option_price(forwards, strikes_1, volatilities, expiries, is_call_options=is_call_options, dtype=tf.float64)) binary_approximation = (-1.0)**call_options * ( option_prices_1 - option_prices_0) / epsilon binary_prices = self.evaluate( black_scholes.binary_price(forwards, strikes_1, volatilities, expiries, is_call_options=is_call_options, discount_factors=discount_factors)) self.assertArrayNear(binary_approximation, binary_prices, 1e-6)