def test_call(self): """Test that the finite difference model correctly prices a call option.""" K = 100 def price(r, S): d1 = (np.log(S / K) + r + 0.0005) / 0.1 d2 = d1 - 0.1 price = stats.norm.cdf(d1) * S price -= stats.norm.cdf(d2) * K * np.exp(-r) return price dS = WienerJumpProcess(0.1, 0.1) dSdq = WienerJumpProcess(0.1, 0.1, 0.1) S = np.linspace(0, 200, 41) V = CallE(1, K) model = FDEModel(64, dS, V) model2 = FDEModel(64, dSdq, V) accuracy = np.array((15, 15, 15, 14, 12, 11, 9, 8, 6, 5, 4, 3, 2, 2, 1, 1, 1, 1, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2)) accuracy2 = np.array((15, 15, 11, 10, 9, 8, 7, 5, 4, 3, 3, 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1)) for scheme in SCHEMES: P = model.price(0, 200, 40, scheme=scheme) self.assertTrue((abs(P.V[0] - price(0.1, S)) < 10.0**-accuracy).all()) P = model2.price(0, 200, 40, scheme=scheme) self.assertTrue((abs(P.V[0] - price(0.2, S)) < 10.0**-accuracy2).all()) return
def test_call(self): """Test that the binomial model correctly prices a call option.""" def price(r): d1 = (np.log(100. / K) + r + 0.0005) / 0.1 d2 = d1 - 0.1 price = stats.norm.cdf(d1) * 100 price -= stats.norm.cdf(d2) * K * np.exp(-r) return price dS = WienerJumpProcess(0.1, 0.1) dSdq = WienerJumpProcess(0.1, 0.1, 0.1) accuracy = 10 step_down = set((59, 61, 64, 68, 72, 78, 83, 89, 100, 119)) step_up = set((136, 158, 172, 183, 193)) for K in range(1, 200): if K in step_down: accuracy -= 1 elif K in step_up: accuracy += 1 V = CallE(1, K) model = BinomialModel(128, dS, V) self.assertAlmostEqual(float(model.price(100)), price(0.1), accuracy) model = BinomialModel(128, dSdq, V) self.assertAlmostEqual(float(model.price(100)), price(0.2), accuracy)
def test_binomial_variable_hazard(self): """Test variable hazard rate.""" dS = WienerJumpProcess(0.1, 0.2, lambda S: (S / 100)**-1, cap_lambda=True) S = np.linspace(0, 200, 401) for pu, pd, po in zip(*dS.binomial(1, S)): self.assertGreaterEqual(min(pu, pd, po), 0) self.assertEqual(pu + pd + po, 1) dS.cap_lambda = False self.assertRaises(ValueError, dS.binomial, 1, S)
def test_forward(self): """Test that the binomial model correctly prices a forward contract.""" dS = WienerJumpProcess(0.1, 0.1) dSdq = WienerJumpProcess(0.1, 0.1, 0.02) for K in range(200): V = Forward(1, K) model = BinomialModel(2, dS, V) self.assertAlmostEqual(float(model.price(100)), 100 - K * np.exp(-0.1)) model = BinomialModel(2, dSdq, V) self.assertAlmostEqual(float(model.price(100)), 100 - K * np.exp(-0.12))
def test_fde(self): """Test parameters for the finite difference model.""" dS = WienerJumpProcess(0.1, 0.2, 0.1, 0.3) dt = 0.1 S = np.linspace(0, 200, 401) ds = S[1] - S[0] for i in ("explicit", "implicit"): a, b, c, d = dS.fde(dt, ds, S, i, "equal") L = sparse.dia_matrix(([a, b, c], [-1, 0, 1]), shape=S.shape*2) self.assertTrue((np.abs(L.sum(1) + (dS.r + dS.lambd_) * dt) < 1e-13).all()) self.assertGreaterEqual(d, 0)
def test_bad_values(self): """Test for bad input values.""" r = 0.1 s = 0.2 l = 0.1 e = 0.3 self.assertRaises(ValueError, WienerJumpProcess, r, -s, l, e) self.assertRaises(ValueError, WienerJumpProcess, r, 0, l, e) self.assertRaises(ValueError, WienerJumpProcess, r, s, -l, e) WienerJumpProcess(0, s, l, e) WienerJumpProcess(-r, s, l, e) WienerJumpProcess(r, s, l, 0) WienerJumpProcess(r, s, l, -e) WienerJumpProcess(r, s, l, 1.1)
def test_annuity(self): """Test the pricing of a series of payments.""" def price(r): erdt = np.exp(-r) i = 1 / erdt - 1 return (1 - erdt**(T * 2)) / i + 10 * erdt**(T * 2) dS = WienerJumpProcess(0.1, 0.1) dSdq = WienerJumpProcess(0.1, 0.1, 0.02) # Semi-annual payments of 1 T = 10 V = Annuity(T, np.arange(0.5, T + 0.5, 0.5), 1, 10) model = BinomialModel(T * 2, dS, V) self.assertAlmostEqual(float(model.price(100)), price(0.05)) model = BinomialModel(T * 2, dSdq, V) self.assertAlmostEqual(float(model.price(100)), price(0.06))
def test_forward(self): """Test that the finite difference model correctly prices a forward contract.""" dS = WienerJumpProcess(0.1, 0.1) dSdq = WienerJumpProcess(0.1, 0.1, 0.02) S = np.linspace(0, 200, 41) V = Forward(1, 100) model = FDEModel(64, dS, V) model2 = FDEModel(64, dSdq, V) for scheme in SCHEMES: if isinstance(scheme, CrankNicolsonScheme): accuracy = 5 else: accuracy = 2 P = model.price(0, 200, 40, scheme=scheme) self.assertTrue((abs(P.V[0] - (S - 100 * np.exp(-0.1))) < 10.0**-accuracy).all()) P = model2.price(0, 200, 40, scheme=scheme) self.assertTrue((abs(P.V[0] - (S - 100 * np.exp(-0.12))) < 10.0**-accuracy).all())
def test_annuity(self): """Test the pricing of a series of payments.""" def price(r): erdt = np.exp(-r) i = 1 / erdt - 1 return (1 - erdt**(T * 2)) / i + 10 * erdt**(T * 2) dS = WienerJumpProcess(0.1, 0.1) dSdq = WienerJumpProcess(0.1, 0.1, 0.02) # Semi-annual payments of 1 T = 10 V = Annuity(T, np.arange(0.5, T + 0.5, 0.5), 1, 10) model = FDEModel(T * 8, dS, V) model2 = FDEModel(T * 8, dSdq, V) for scheme in SCHEMES: P = model.price(0, 200, 25, scheme=scheme) self.assertTrue((np.abs(P.V[0] - price(0.05)) < 1e-1).all()) P = model2.price(0, 200, 25, scheme=scheme) self.assertTrue((np.abs(P.V[0] - price(0.06)) < 1e-1).all())
def test_binomial(self): """Test parameters for the binomial model.""" dS = WienerJumpProcess(0.1, 0.2, 0.1, 0.3) u, d, l, (pu, pd, po) = dS.binomial(1) self.assertGreater(u, np.exp(0.1)) self.assertLess(d, np.exp(0.1)) self.assertGreater(d, 0) self.assertGreaterEqual(l, 0) self.assertLessEqual(l, 1) self.assertGreaterEqual(min(pu, pd, po), 0) self.assertEqual(pu + pd + po, 1) dS.r = 1 self.assertRaises(ValueError, dS.binomial, 0.2) dS.lambd_ = np.double('inf') self.assertRaises(ValueError, dS.binomial, 0.01) self.assertRaises(ValueError, dS.binomial, 0) self.assertRaises(ValueError, dS.binomial, -0.01)
def test_value(self): """Test the value object for the finite difference model.""" dS = WienerJumpProcess(0.1, 0.1, 0.02, 0) N = 16 T = 1 ct = np.linspace(0, T, N // 2 + 1) c = 1 Sl = 0 Su = 200 K = 40 model = FDEModel(N, dS, Stack([CallA(T, 100), Annuity(T, ct, c)])) P = model.price(Sl, Su, K) S = P.S # Test N self.assertEqual(P.N, N) # Test time series self.assertEqual(P.t[0], 0) self.assertEqual(P.t[-1], T) self.assertEqual(len(P.t), N + 1) # Test Coupon sequence self.assertTrue((P.C[::2] == c).all()) self.assertTrue((P.C[1::2] == 0).all()) # Test K self.assertEqual(P.K, K) # Test Stock series self.assertEqual(P.S[0], Sl) self.assertEqual(P.S[-1], Su) self.assertEqual(len(P.S), K + 1) # Test default sequence self.assertEqual(len(P.V), N + 1) for i in range(1, N + 1): self.assertLessEqual(P.V[i][0] - P.C[i], P.V[i - 1][0]) self.assertLessEqual(P.V[i][-1] - P.C[i], P.V[i - 1][-1]) self.assertTrue((P.V[i][:-1] - 1e14 <= P.V[i][1:]).all()) self.assertEqual(len(P.X), N) for i in range(1, N): self.assertGreaterEqual(P.X[i][0], P.X[i - 1][0]) self.assertLessEqual(P.X[i][-1], P.X[i - 1][-1]) self.assertTrue((P.X[i][:-1] <= P.X[i][1:]).all())
def test_value(self): """Test the value object for the binomial model.""" dS = WienerJumpProcess(0.1, 0.1, 0.02, 0) N = 16 T = 1 ct = np.linspace(0, T, N // 2 + 1) c = 1 S = 100 model = BinomialModel(N, dS, Stack([CallA(T, S), Annuity(T, ct, c)])) P = model.price(S) # Test N self.assertEqual(P.N, N) # Test time series self.assertEqual(P.t[0], 0) self.assertEqual(P.t[-1], T) self.assertEqual(len(P.t), N + 1) # Test Coupon sequence self.assertTrue((P.C[::2] == c).all()) self.assertTrue((P.C[1::2] == 0).all()) # Test price sequence self.assertEqual(P.S[0][0], S) self.assertEqual(len(P.S), N + 1) for i in range(1, N + 1): self.assertGreaterEqual(P.S[i][0], P.S[i - 1][0]) self.assertLessEqual(P.S[i][-1], P.S[i - 1][-1]) self.assertTrue((P.S[i][:-1] > P.S[i][1:]).all()) # Test default sequence self.assertEqual(len(P.V), N + 1) for i in range(1, N + 1): self.assertGreaterEqual(P.V[i][0] - P.C[i], P.V[i - 1][0]) self.assertLessEqual(P.V[i][-1] - P.C[i], P.V[i - 1][-1]) self.assertTrue((P.V[i][:-1] >= P.V[i][1:]).all()) self.assertEqual(len(P.X), N) for i in range(1, N): self.assertGreaterEqual(P.X[i][0], P.X[i - 1][0]) self.assertLessEqual(P.X[i][-1], P.X[i - 1][-1]) self.assertTrue((P.X[i][:-1] >= P.X[i][1:]).all())
__all__ = [ "T", "dS", "dS_total", "dS_typical", "dS_partial", "A", "P", "C", "S", "B", "E", "payoff", ] # Time till maturity = 5 years T = 5 # Stock price is a Wiener process with default jump. # Drift rate = 5% # Volatility = 20% # Hazard rate = 2% # Total default (default = 100%) dS_total = WienerJumpProcess(r=0.05, sigma=0.2, lambd_=0.02, eta=1) # T default (default = 0%) dS_typical = WienerJumpProcess(r=0.05, sigma=0.2, lambd_=0.02, eta=0.3) # Partial default (default = 0%) dS_partial = WienerJumpProcess(r=0.05, sigma=0.2, lambd_=0.02, eta=0) # No default dS = WienerJumpProcess(r=0.05, sigma=0.2) # Bond # Nominal value = 100 # Semi-annual coupon = 4 # Recovery factor = 0 A = Annuity(T, np.arange(0.5, T + 0.5, 0.5), C=4, N=100, R=0) # American put option on portfolio # Strike = 105
"C", "S", "B", "E", "payoff", ] # Time till maturity = 5 years T = 5 # Stock price is a Wiener process with default jump. # Drift rate = 5% # Volatility = 25% # Hazard rate = 6.2% # Total default (default = 100%) dS = WienerJumpProcess(r=0.05, sigma=0.25, lambd_=0.062, eta=1) # Variable hazard (a=-1.2), total default dS_var = WienerJumpProcess(r=0.05, sigma=0.25, lambd_=lambda S: 0.062 * (S / 50)**-0.5, eta=1) # Bond # Nominal value = 100 # Semi-annual coupon = 4 # Recovery factor = 40% A = Annuity(T, np.arange(0.5, T + 0.5, 0.5), C=4, N=100, R=0.4) # American put option on portfolio # Strike = 105 # Time = 3
__all__ = [ "T", "dS", "dS_total", "dS_partial", "dS_var12", "dS_var20", "A", "P", "C", "S", "B", "E", "payoff", ] # Time till maturity = 5 years T = 5 # Stock price is a Wiener process with default jump. # Drift rate = 5% # Volatility = 20% # Hazard rate = 2% # Total default (default = 100%) dS_total = WienerJumpProcess(r=0.05, sigma=0.2, lambd_=0.02, eta=1) # Partial default (default = 0%) dS_partial = WienerJumpProcess(r=0.05, sigma=0.2, lambd_=0.02, eta=0) # No default dS = WienerJumpProcess(r=0.05, sigma=0.2) # Variable hazard (a=-1.2), total default dS_var12 = WienerJumpProcess(r=0.05, sigma=0.2, lambd_=lambda S: 0.02 * (S / 100)**-1.2, eta=1) # Variable hazard (a=-2.0), total default dS_var20 = WienerJumpProcess(r=0.05, sigma=0.2, lambd_=lambda S: 0.02 * (S / 100)**-2.0, eta=1) # Bond # Nominal value = 100 # Semi-annual coupon = 4 # Recovery factor = 0 A = Annuity(T, np.arange(0.5, T + 0.5, 0.5), C=4, N=100, R=0)