def test_black_scholes_formula(self): S, K, r, q, sig, T = 100, 100, 0.02, 0.03, 0.2, 0.5 F = S * np.exp((r - q) * T) df = np.exp(-r * T) ans = get_european_call_bs(S, K, r, q, sig, T) self.assertAlmostEqual(ans, 5.323767401196562, 11) ans = get_european_put_bs(S, K, r, q, sig, T) self.assertAlmostEqual(ans, 5.817556815807109, 11)
def test_douglas_scheme(self): s1, q1, sig1 = 90, 0, 0.3 s2, q2, sig2 = 110, 0, 0.4 r = 0.01 t = 0.4 eq1 = EquityFactor('AAPL', s1, q1, sig1) eq2 = EquityFactor('MSFT', s2, q2, sig2) sec = ExchangeOption(eq1, eq2, t) # positive correlation rho = 0.4 pde = BlackScholesPDE2D(eq1, eq2, r, rho) engine = FiniteDifferenceEngine(sec, pde, DouglasScheme()) vals, states = engine.price({ 't': 50, 'AAPL': 200, 'MSFT': 210 }, scale=5) f = interp2d(states['MSFT'], states['AAPL'], vals) ans = f(s2, s1)[0] expected = get_european_call_bs( s1, s2, 0, 0, np.sqrt(sig1**2 + sig2**2 - 2 * rho * sig1 * sig2), t) assert_allclose(ans, expected, atol=0, rtol=0.0005) # negative correlation rho = -0.4 pde = BlackScholesPDE2D(eq1, eq2, r, rho) engine = FiniteDifferenceEngine(sec, pde, DouglasScheme()) vals, states = engine.price({ 't': 50, 'AAPL': 200, 'MSFT': 210 }, scale=5) f = interp2d(states['MSFT'], states['AAPL'], vals) ans = f(s2, s1)[0] expected = get_european_call_bs( s1, s2, 0, 0, np.sqrt(sig1**2 + sig2**2 - 2 * rho * sig1 * sig2), t) assert_allclose(ans, expected, atol=0, rtol=0.002)
def test_black_scholes(self): m = 100 # time steps n = 600 asset = 'AAPL' s, k = 248, 300 r, q, sig, t = 254 / s - 1, 0, 0.72, 0.25 sec = EuropeanCall(asset=asset, strike=k, tenor=t) pde = BlackScholesPDE1D(asset=asset, spot=s, r=r, q=q, sig=sig) engine = FiniteDifferenceEngine(sec, pde, CrankNicolsonScheme()) ans, states = engine.price({'t': m, asset: n}, scale=5) xs = states[asset] mask = (xs > k * 0.8) & (xs < k * 1.2) ans = ans[mask] xs = xs[mask] expected = [get_european_call_bs(x, k, r, q, sig, t) for x in xs] assert_allclose(ans, expected, atol=0, rtol=0.0005)
def test_european(self): s = 100 k = 100 r = 0.02 q = 0.05 sig = 0.3 t = 1 m = 1 n = int(1E7) # test call and put secs = [EuropeanCall('stock', k, t), EuropeanPut('stock', k, t)] bs = BlackScholes(spot=s, interest=r, dividend=q, volatility=sig) engine = MonteCarloEngine(secs, model=bs) price = engine.price(m, n) actual = [ get_european_call_bs(s, k, r, q, sig, t), get_european_put_bs(s, k, r, q, sig, t) ] assert_allclose(actual, price, rtol=1e-4, atol=1e-2) # test both relative diff and absolute diff
def test_american_to_bs(self): """ American call and put without interest and dividend should equal Black Scholes prices """ s = 100 k = 100 r = 0 q = 0 sig = 0.3 t = 1 m = 50 n = int(1E5) # American options without interest and dividend should equal Black Scholes prices secs = [AmericanCall('stock', k, t), AmericanPut('stock', k, t)] bs = BlackScholes(spot=s, interest=r, dividend=q, volatility=sig) lasso = LASSOFitter() engine = MonteCarloEngine(secs, model=bs, fitter=lasso) price = engine.price(m, n) actual = [ get_european_call_bs(s, k, r, q, sig, t), get_european_put_bs(s, k, r, q, sig, t) ] assert_allclose(actual, price, rtol=0.01, atol=0.1)
a22 = build_a22(r, q1, sig1, q2, sig2, xs, ys) a2 = a21 + a22 a0 = build_a_mixed(rho, r, q1, sig1, q2, sig2, xs, ys) bounds = build_boundaries(r, q1, sig1, q2, sig2, xs, ys) start = time.time() for i in range(n_steps): curr = step(prev, dt, a0, a1, a2) prev = curr print(i) curr = curr.reshape(m, n) print(f'{time.time() - start:.2f}s') f = interp2d(ys, xs, curr) diff = [] for x in np.linspace(s1 / np.exp(2 * sig1 * np.sqrt(t)), s1 * np.exp(2 * sig1 * np.sqrt(t)), 10): for y in np.linspace(s2 / np.exp(2 * sig2 * np.sqrt(t)), s2 * np.exp(2 * sig2 * np.sqrt(t)), 10): ans = f(y, x)[0] expected = get_european_call_bs( x, y, 0, 0, np.sqrt(sig1**2 + sig2**2 - 2 * rho * sig1 * sig2), t) diff.append(ans - expected) diff = np.array(diff) # print(diff) print(np.sqrt(np.sum(diff**2)))
def _setup_boundary_conditions_(self): super(CNEu, self)._setup_boundary_conditions_() self.coeffs_[0, 0] -= 2 * self.alpha[0] self.coeffs_[0, 1] += self.alpha[0] self.coeffs_[-1, -1] -= 2 * self.gamma[-1] self.coeffs_[-1, -2] += self.gamma[-1] def _traverse_grid_(self): P, L, U = linalg.lu(self.coeffs_) for j in reversed(self.jValues): Ux = linalg.solve(L, np.dot(self.coeffs, self.grid[1:-1, j + 1])) self.grid[1:-1, j] = linalg.solve(U, Ux) self.grid[0, j] = 2 * self.grid[1, j] - self.grid[2, j] self.grid[-1, j] = 2 * self.grid[-2, j] - self.grid[-3, j] S0 = 248 K = 300 r = 254 / S0 - 1 T = 0.25 sigma = 0.72 Smax = 1000 M = 100 # S N = 1000 # t is_call = True option = CNEu(S0, K, r, T, sigma, Smax, M, N, is_call) print(option.price()) print(get_european_call_bs(S0, K, r, 0, sigma, T))