├── B03898_01_codes ├── B03898_01_01.py ├── B03898_01_02.py ├── B03898_01_03.txt ├── B03898_01_04.txt ├── B03898_01_05.txt ├── B03898_01_06.txt ├── B03898_01_07.txt ├── B03898_01_08.txt ├── B03898_01_09.txt ├── B03898_01_10.txt └── B03898_01_11.txt ├── B03898_02_Codes ├── B03898_02_01.py ├── B03898_02_02.py ├── B03898_02_03.py ├── B03898_02_04.py ├── B03898_02_05.py ├── B03898_02_06.py ├── B03898_02_07.py ├── B03898_02_08.py ├── B03898_02_09.py ├── B03898_02_10.py └── B03898_02_11.py ├── B03898_03_codes ├── bisection.py ├── general_nonlinear_solvers_no_solution.py ├── general_nonlinear_solvers_with_solution.py ├── incremental_search.py ├── newton.py ├── scipy_optimize.py └── secant.py ├── B03898_04_codes ├── BinomialCRRLattice.py ├── BinomialCRROption.py ├── BinomialEuropeanOption.py ├── BinomialLROption.py ├── BinomialLRWithGreeks.py ├── BinomialTreeOption.py ├── FDCnAm.py ├── FDCnDo.py ├── FDCnEu.py ├── FDExplicitEu.py ├── FDImplicitEu.py ├── FiniteDifferences.py ├── ImpliedVolatilityModel.py ├── StockOption.py ├── TrinomialLattice.py ├── TrinomialTreeOption.py └── bisection.py ├── B03898_05_Codes ├── BootstrapYieldCurve.py ├── VasicekCZCB.py ├── bond_convexity.py ├── bond_mod_duration.py ├── bond_price.py ├── bond_ytm.py ├── brennan_schwartz.py ├── cir.py ├── exact_zcb.py ├── forward_rates.py ├── rendleman_bartter.py ├── vasicek.py └── zero_coupon_bond.py ├── B03898_06_codes ├── Chapter 6 Notebook.ipynb └── README ├── B03898_07_Codes ├── B03898_07_01.txt ├── B03898_07_02.py ├── B03898_07_03.txt ├── B03898_07_04.py ├── B03898_07_05.txt ├── B03898_07_06.txt ├── B03898_07_07.txt ├── B03898_07_08.py ├── B03898_07_09.txt ├── B03898_07_10.txt ├── B03898_07_11.py ├── B03898_07_12.txt ├── B03898_07_13.txt ├── B03898_07_14.py └── B03898_07_15.py ├── B03898_08_codes ├── AlgoSystem.py ├── ForexSystem.py ├── calculating_var.py ├── exploring_oanda_api.py ├── oandapy.py └── simple_order_routing.py ├── B03898_09_codes └── B03898_09_01.py ├── B03898_10_codes ├── BinomialCRROption.py ├── BinomialTreeOption.py ├── StockOption.py ├── TrinomialLattice.py ├── TrinomialTreeOption.py ├── binomial_crr_com.py ├── black_scholes.py ├── excel_com_client.xlsm ├── trinomial_lattice_com.py └── vba_codes.txt ├── LICENSE └── README.md /B03898_01_codes/B03898_01_01.py: -------------------------------------------------------------------------------- 1 | """" 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | class Greeting(object): 9 | def __init__(self, my_greeting): 10 | self.my_greeting = my_greeting 11 | 12 | def say_hello(self, name): 13 | print "%s %s" % (self.my_greeting, name) 14 | 15 | greeting = Greeting("Hello") 16 | greeting.say_hello("World") 17 | greeting.say_hello("Dog") 18 | greeting.say_hello("Cat") -------------------------------------------------------------------------------- /B03898_01_codes/B03898_01_02.py: -------------------------------------------------------------------------------- 1 | """" README ====== This file contains Python codes. ====== """ from functools import partial def greeting(my_greeting, name): print "%s %s" % (my_greeting, name) say_hello_to = partial(greeting, "Hello") say_hello_to("World") say_hello_to("Dog") say_hello_to("Cat") -------------------------------------------------------------------------------- /B03898_01_codes/B03898_01_03.txt: -------------------------------------------------------------------------------- 1 | README ====== This file contains Markdown code. To be run in IPython Notebook. ====== Text Examples === This is an example of an *italic* text. This is an example of a **bold*** text. This is an example of a list item: - Item #1 - Item #2 - Item #3 --- #heading 1 ##heading 2 ###heading 3 ####heading 4 #####heading 5 ######heading 6 -------------------------------------------------------------------------------- /B03898_01_codes/B03898_01_04.txt: -------------------------------------------------------------------------------- 1 | """" 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | answer = 3 + 5 9 | print answer -------------------------------------------------------------------------------- /B03898_01_codes/B03898_01_05.txt: -------------------------------------------------------------------------------- 1 | README 2 | ====== 3 | This file contains Python codes. 4 | To be run in IPython Notebook. 5 | ====== 6 | 7 | import matplotlib.pyplot as plt 8 | import numpy as np 9 | import math 10 | 11 | x = np.linspace(0, 2*math.pi) 12 | plt.plot(x, np.sin(x), label=r'$\sin(x)$') 13 | plt.plot(x, np.cos(x), 'ro', label=r'$\cos(x)$') 14 | plt.title(r'Two plots in a graph') 15 | plt.legend() -------------------------------------------------------------------------------- /B03898_01_codes/B03898_01_06.txt: -------------------------------------------------------------------------------- 1 | README 2 | ====== 3 | This file contains Markdown codes. 4 | To be run in IPython Notebook. 5 | ====== 6 | 7 | $$N(x) = \frac{1}{\sqrt{2\pi}}\int_{-\infty}^{x} e^{-\frac{z^2}{2}}\, dz$$ 8 | -------------------------------------------------------------------------------- /B03898_01_codes/B03898_01_07.txt: -------------------------------------------------------------------------------- 1 | README 2 | ====== 3 | This file contains Python codes. 4 | To be run in IPython Notebook. 5 | ====== 6 | 7 | from IPython.display import Math 8 | Math(r'N(x) = \frac{1}{\sqrt{2\pi}}\int_{-\infty}^{x} e^{-\frac{z^2}{2}}\, dz') 9 | 10 | -------------------------------------------------------------------------------- /B03898_01_codes/B03898_01_08.txt: -------------------------------------------------------------------------------- 1 | README 2 | ====== 3 | This file contains Python codes. 4 | To be run in IPython Notebook. 5 | ====== 6 | 7 | from IPython.display import Image 8 | Image(url='http://python.org/images/python-logo.gif') 9 | 10 | -------------------------------------------------------------------------------- /B03898_01_codes/B03898_01_09.txt: -------------------------------------------------------------------------------- 1 | README 2 | ====== 3 | This file contains Python codes. 4 | To be run in IPython Notebook. 5 | ====== 6 | 7 | from IPython.lib.display import YouTubeVideo 8 | 9 | # An introduction to Python by Google. 10 | YouTubeVideo('tKTZoB2Vjuk') 11 | 12 | 13 | -------------------------------------------------------------------------------- /B03898_01_codes/B03898_01_10.txt: -------------------------------------------------------------------------------- 1 | README ====== This file contains Python codes. To be run in IPython Notebook. ====== from IPython.display import HTML table = """
Header 1 Header 2
row 1, cell 1 row 1, cell 2
row 2, cell 1 row 2, cell 2
""" HTML(table) -------------------------------------------------------------------------------- /B03898_01_codes/B03898_01_11.txt: -------------------------------------------------------------------------------- 1 | README ====== This file contains Python codes. To be run in IPython Notebook. ====== import pandas.io.data as web import datetime start = datetime.datetime(2014, 1, 1) end = datetime.datetime(2014, 12, 31) df = web.DataReader("AAPL", 'yahoo', start, end) df.head() -------------------------------------------------------------------------------- /B03898_02_Codes/B03898_02_01.py: -------------------------------------------------------------------------------- 1 | """" 2 | README 3 | ====== 4 | This is a Python code. 5 | ====== 6 | """ 7 | 8 | """ Linear regression with SciPy """ 9 | from scipy import stats 10 | 11 | stock_returns = [0.065, 0.0265, -0.0593, -0.001, 0.0346] 12 | mkt_returns = [0.055, -0.09, -0.041, 0.045, 0.022] 13 | 14 | beta, alpha, r_value, p_value, std_err = \ 15 | stats.linregress(stock_returns, mkt_returns) 16 | print beta, alpha 17 | 18 | """ Calculating the SML """ 19 | rf = 0.05 20 | mrisk_prem = 0.085 21 | risk_prem = mrisk_prem * beta 22 | print "Risk premium:", risk_prem 23 | 24 | expected_stock_return = rf + risk_prem 25 | print "Expected stock return:", expected_stock_return -------------------------------------------------------------------------------- /B03898_02_Codes/B03898_02_02.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This is a Python code. 5 | ====== 6 | """ 7 | 8 | """ Least squares regression with statsmodels """ 9 | import numpy as np 10 | import statsmodels.api as sm 11 | 12 | # Generate some sample data 13 | num_periods = 9 14 | all_values = np.array([np.random.random(8) 15 | for i in range(num_periods)]) 16 | 17 | # Filter the data 18 | y_values = all_values[:, 0] # First column values as Y 19 | x_values = all_values[:, 1:] # All other values as X 20 | 21 | x_values = sm.add_constant(x_values) # Include the intercept 22 | results = sm.OLS(y_values, x_values).fit() # Regress and fit the model 23 | 24 | print results.summary() 25 | print results.params 26 | -------------------------------------------------------------------------------- /B03898_02_Codes/B03898_02_03.py: -------------------------------------------------------------------------------- 1 | """ README ====== This is a Python code. ====== """ """ A simple linear optimization problem with 2 variables """ import pulp x = pulp.LpVariable("x", lowBound=0) y = pulp.LpVariable("y", lowBound=0) problem = pulp.LpProblem("A simple maximization objective", pulp.LpMaximize) problem += 3*x + 2*y, "The objective function" problem += 2*x + y <= 100, "1st constraint" problem += x + y <= 80, "2nd constraint" problem += x <= 40, "3rd constraint" problem.solve() print "Maximization Results:" for variable in problem.variables(): print variable.name, "=", variable.varValue -------------------------------------------------------------------------------- /B03898_02_Codes/B03898_02_04.py: -------------------------------------------------------------------------------- 1 | """" 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ An example of implementing an IP model with binary conditions """ 9 | import pulp 10 | 11 | dealers = ["X", "Y", "Z"] 12 | variable_costs = {"X": 500, 13 | "Y": 350, 14 | "Z": 450} 15 | fixed_costs = {"X": 4000, 16 | "Y": 2000, 17 | "Z": 6000} 18 | 19 | # Define PuLP variables to solve 20 | quantities = pulp.LpVariable.dicts("quantity", 21 | dealers, 22 | lowBound=0, 23 | cat=pulp.LpInteger) 24 | is_orders = pulp.LpVariable.dicts("orders", dealers, 25 | cat=pulp.LpBinary) 26 | 27 | """ 28 | This is an example of implementing an IP model with binary 29 | variables the wrong way. 30 | """ 31 | # Initialize the model with constraints 32 | model = pulp.LpProblem("A cost minimization problem", pulp.LpMinimize) 33 | model += sum([(variable_costs[i] * quantities[i] + 34 | fixed_costs[i]) * is_orders[i] for i in dealers]), \ 35 | "Minimize portfolio cost" 36 | model += sum([quantities[i] for i in dealers]) == 150, \ 37 | "Total contracts required" 38 | model += 30 <= quantities["X"] <= 100, \ 39 | "Boundary of total volume of X" 40 | model += 30 <= quantities["Y"] <= 90, \ 41 | "Boundary of total volume of Y" 42 | model += 30 <= quantities["Z"] <= 70, \ 43 | "Boundary of total volume of Z" 44 | model.solve() # Will encounter an error -------------------------------------------------------------------------------- /B03898_02_Codes/B03898_02_05.py: -------------------------------------------------------------------------------- 1 | """" 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | import pulp 9 | 10 | dealers = ["X", "Y", "Z"] 11 | variable_costs = {"X": 500, 12 | "Y": 350, 13 | "Z": 450} 14 | fixed_costs = {"X": 4000, 15 | "Y": 2000, 16 | "Z": 6000} 17 | 18 | # Define PuLP variables to solve 19 | quantities = pulp.LpVariable.dicts("quantity", 20 | dealers, 21 | lowBound=0, 22 | cat=pulp.LpInteger) 23 | is_orders = pulp.LpVariable.dicts("orders", dealers, 24 | cat=pulp.LpBinary) 25 | 26 | """ 27 | This is an example of implementing an IP model with binary 28 | variables the correct way. 29 | """ 30 | # Initialize the model with constraints 31 | model = pulp.LpProblem("A cost minimization problem", 32 | pulp.LpMinimize) 33 | model += sum([variable_costs[i]*quantities[i] + 34 | fixed_costs[i]*is_orders[i] for i in dealers]), \ 35 | "Minimize portfolio cost" 36 | model += sum([quantities[i] for i in dealers]) == 150, \ 37 | "Total contracts required" 38 | model += is_orders["X"]*30 <= quantities["X"] <= \ 39 | is_orders["X"]*100, "Boundary of total volume of X" 40 | model += is_orders["Y"]*30 <= quantities["Y"] <= \ 41 | is_orders["Y"]*90, "Boundary of total volume of Y" 42 | model += is_orders["Z"]*30 <= quantities["Z"] <= \ 43 | is_orders["Z"]*70, "Boundary of total volume of Z" 44 | model.solve() 45 | 46 | print "Minimization Results:" 47 | for variable in model.variables(): 48 | print variable, "=", variable.varValue 49 | 50 | print "Total cost: %s" % pulp.value(model.objective) -------------------------------------------------------------------------------- /B03898_02_Codes/B03898_02_06.py: -------------------------------------------------------------------------------- 1 | """" README ====== This file contains Python codes. ====== """ """ Linear algebra with NumPy matrices """ import numpy as np A = np.array([[2, 1, 1], [1, 3, 2], [1, 0, 0]]) B = np.array([4, 5, 6]) print np.linalg.solve(A, B) -------------------------------------------------------------------------------- /B03898_02_Codes/B03898_02_07.py: -------------------------------------------------------------------------------- 1 | """" README ====== This file contains Python codes. ====== """ """ LU decomposition with SciPy """ import scipy.linalg as linalg import numpy as np A = np.array([[2., 1., 1.], [1., 3., 2.], [1., 0., 0.]]) B = np.array([4., 5., 6.]) LU = linalg.lu_factor(A) x = linalg.lu_solve(LU, B) print x P, L, U = linalg.lu(A) print P print L print U -------------------------------------------------------------------------------- /B03898_02_Codes/B03898_02_08.py: -------------------------------------------------------------------------------- 1 | """" README ====== This file contains Python codes. ====== """ """ Cholesky decomposition with NumPy """ import numpy as np A = np.array([[10., -1., 2., 0.], [-1., 11., -1., 3.], [2., -1., 10., -1.], [0.0, 3., -1., 8.]]) B = np.array([6., 25., -11., 15.]) L = np.linalg.cholesky(A) print L print np.dot(L, L.T.conj()) # A=L.L* y = np.linalg.solve(L, B) # L.L*.x=B. When L*.x=y, then L.y=B. print y x = np.linalg.solve(L.T.conj(), y) # x=L*'.y print x print np.mat(A) * np.mat(x).T # B=Ax -------------------------------------------------------------------------------- /B03898_02_Codes/B03898_02_09.py: -------------------------------------------------------------------------------- 1 | """" README ====== This file contains Python codes. ====== """ """ QR decomposition with scipy """ import scipy import numpy as np A = np.array([ [2., 1., 1.], [1., 3., 2.], [1., 0., 0]]) B = np.array([4., 5., 6.]) Q, R = scipy.linalg.qr(A) # QR decomposition y = np.dot(Q.T, B) # Let y=Q`.B x = scipy.linalg.solve(R, y) # Solve Rx=y print x -------------------------------------------------------------------------------- /B03898_02_Codes/B03898_02_10.py: -------------------------------------------------------------------------------- 1 | """" README ====== This file contains Python codes. ====== """ """ Solve Ax=B with the Jacobi method """ import numpy as np def jacobi(A, B, n, tol=1e-10): # Initializes x with zeroes with same shape and type as B x = np.zeros_like(B) for it_count in range(n): x_new = np.zeros_like(x) for i in range(A.shape[0]): s1 = np.dot(A[i, :i], x[:i]) s2 = np.dot(A[i, i + 1:], x[i + 1:]) x_new[i] = (B[i] - s1 - s2) / A[i, i] if np.allclose(x, x_new, tol): break x = x_new return x A = np.array([[10., -1., 2., 0.], [-1., 11., -1., 3.], [2., -1., 10., -1.], [0.0, 3., -1., 8.]]) B = np.array([6., 25., -11., 15.]) n = 25 x = jacobi(A,B,n) print "x =", x -------------------------------------------------------------------------------- /B03898_02_Codes/B03898_02_11.py: -------------------------------------------------------------------------------- 1 | """" README ====== This file contains Python codes. ====== """ """ Solve Ax=B with the Gauss-Seidel method """ import numpy as np def gauss(A, B, n, tol=1e-10): L = np.tril(A) # Returns the lower triangular matrix of A U = A - L # Decompose A = L + U L_inv = np.linalg.inv(L) x = np.zeros_like(B) for i in range(n): Ux = np.dot(U, x) x_new = np.dot(L_inv, B - Ux) if np.allclose(x, x_new, tol): break x = x_new return x A = np.array([[10., -1., 2., 0.], [-1., 11., -1., 3.], [2., -1., 10., -1.], [0.0, 3., -1., 8.]]) B = np.array([6., 25., -11., 15.]) n = 100 x = gauss(A, B, n) print "x =", x -------------------------------------------------------------------------------- /B03898_03_codes/bisection.py: -------------------------------------------------------------------------------- 1 | """" README ====== This file contains Python codes. ====== """ """ The bisection method """ def bisection(f, a, b, tol=0.1, maxiter=10): """ :param f: The function to solve :param a: The x-axis value where f(a)<0 :param b: The x-axis value where f(b)>0 :param tol: The precision of the solution :param maxiter: Maximum number of iterations :return: The x-axis value of the root, number of iterations used """ c = (a+b)*0.5 # Declare c as the midpoint ab n = 1 # Start with 1 iteration while n <= maxiter: c = (a+b)*0.5 if f(c) == 0 or abs(a-b)*0.5 < tol: # Root is found or is very close return c, n n += 1 if f(c) < 0: a = c else: b = c return c, n if __name__ == "__main__": y = lambda x: x**3 + 2*x**2 - 5 root, iterations = bisection(y, -5, 5, 0.00001, 100) print "Root is:", root print "Iterations:", iterations -------------------------------------------------------------------------------- /B03898_03_codes/general_nonlinear_solvers_no_solution.py: -------------------------------------------------------------------------------- 1 | """" README ====== This file contains Python codes. ====== """ """ General nonlinear solvers - with no solution """ import scipy.optimize as optimize y = lambda x: x**3 + 2.*x**2 - 5. dy = lambda x: 3.*x**2 + 4.*x print optimize.fsolve(y, -5., fprime=dy) print optimize.root(y, -5.) -------------------------------------------------------------------------------- /B03898_03_codes/general_nonlinear_solvers_with_solution.py: -------------------------------------------------------------------------------- 1 | """" README ====== This file contains Python codes. ====== """ """ General nonlinear solvers - with a solution """ import scipy.optimize as optimize y = lambda x: x**3 + 2.*x**2 - 5. dy = lambda x: 3.*x**2 + 4.*x print optimize.fsolve(y, 5., fprime=dy) print optimize.root(y, 5.) -------------------------------------------------------------------------------- /B03898_03_codes/incremental_search.py: -------------------------------------------------------------------------------- 1 | """" README ====== This file contains Python codes. ====== """ """ An incremental search algorithm """ import numpy as np def incremental_search(f, a, b, dx): """ :param f: The function to solve :param a: The left boundary x-axis value :param b: The right boundary x-axis value :param dx: The incremental value in searching :return: The x-axis value of the root, number of iterations used """ fa = f(a) c = a + dx fc = f(c) n = 1 while np.sign(fa) == np.sign(fc): if a >= b: return a - dx, n a = c fa = fc c = a + dx fc = f(c) n += 1 if fa == 0: return a, n elif fc == 0: return c, n else: return (a + c)/2., n if __name__ == "__main__": """ The keyword 'lambda' creates an anonymous function with input argument x """ y = lambda x: x**3 + 2.0*x**2 - 5. root, iterations = incremental_search(y, -5., 5., 0.001) print "Root is:", root print "Iterations:", iterations -------------------------------------------------------------------------------- /B03898_03_codes/newton.py: -------------------------------------------------------------------------------- 1 | """" README ====== This file contains Python codes. ====== """ """ The Newton-Raphson method """ def newton(f, df, x, tol=0.001, maxiter=100): """ :param f: The function to solve :param df: The derivative function of f :param x: Initial guess value of x :param tol: The precision of the solution :param maxiter: Maximum number of iterations :return: The x-axis value of the root, number of iterations used """ n = 1 while n <= maxiter: x1 = x - f(x)/df(x) if abs(x1 - x) < tol: # Root is very close return x1, n else: x = x1 n += 1 return None, n if __name__ == "__main__": y = lambda x: x**3 + 2*x**2 - 5 dy = lambda x: 3*x**2 + 4*x root, iterations = newton(y, dy, 5.0, 0.00001, 100) print "Root is:", root print "Iterations:", iterations -------------------------------------------------------------------------------- /B03898_03_codes/scipy_optimize.py: -------------------------------------------------------------------------------- 1 | """" README ====== This file contains Python codes. ====== """ """ Documentation at http://docs.scipy.org/doc/scipy/reference/optimize.html """ import scipy.optimize as optimize y = lambda x: x**3 + 2.*x**2 - 5. dy = lambda x: 3.*x**2 + 4.*x # Call method: bisect(f, a, b[, args, xtol, rtol, maxiter, ...]) print "Bisection method: %s" \ % optimize.bisect(y, -5., 5., xtol=0.00001) # Call method: newton(func, x0[, fprime, args, tol, ...]) print "Newton's method: %s" \ % optimize.newton(y, 5., fprime=dy) # When fprime=None, then the secant method is used. print "Secant method: %s" \ % optimize.newton(y, 5.) # Call method: brentq(f, a, b[, args, xtol, rtol, maxiter, ...]) print "Brent's method: %s" \ % optimize.brentq(y, -5., 5.) -------------------------------------------------------------------------------- /B03898_03_codes/secant.py: -------------------------------------------------------------------------------- 1 | """" README ====== This file contains Python codes. ====== """ """ The secant root-finding method """ def secant(f, a, b, tol=0.001, maxiter=100): """ :param f: The function to solve :param a: Initial x-axis guess value :param b: Initial x-axis guess value, where b>a :param tol: The precision of the solution :param maxiter: Maximum number of iterations :return: The x-axis value of the root, number of iterations used """ n = 1 while n <= maxiter: c = b - f(b)*((b-a)/(f(b)-f(a))) if abs(c-b) < tol: return c, n a = b b = c n += 1 return None, n if __name__ == "__main__": y = lambda x: x**3 + 2*x**2 - 5 root, iterations = secant(y, -5.0, 5.0, 0.00001, 100) print "Root is:", root print "Iterations:", iterations -------------------------------------------------------------------------------- /B03898_04_codes/BinomialCRRLattice.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ Price an option by the binomial CRR lattice """ 9 | from BinomialCRROption import BinomialCRROption 10 | import numpy as np 11 | 12 | 13 | class BinomialCRRLattice(BinomialCRROption): 14 | 15 | def _setup_parameters_(self): 16 | super(BinomialCRRLattice, self)._setup_parameters_() 17 | self.M = 2*self.N + 1 18 | 19 | def _initialize_stock_price_tree_(self): 20 | self.STs = np.zeros(self.M) 21 | self.STs[0] = self.S0 * self.u**self.N 22 | 23 | for i in range(self.M)[1:]: 24 | self.STs[i] = self.STs[i-1]*self.d 25 | 26 | def _initialize_payoffs_tree_(self): 27 | odd_nodes = self.STs[::2] 28 | return np.maximum( 29 | 0, (odd_nodes - self.K) if self.is_call 30 | else(self.K - odd_nodes)) 31 | 32 | def __check_early_exercise__(self, payoffs, node): 33 | self.STs = self.STs[1:-1] # Shorten the ends of the list 34 | odd_STs = self.STs[::2] 35 | early_ex_payoffs = \ 36 | (odd_STs-self.K) if self.is_call \ 37 | else (self.K-odd_STs) 38 | payoffs = np.maximum(payoffs, early_ex_payoffs) 39 | 40 | return payoffs 41 | 42 | if __name__ == "__main__": 43 | from BinomialCRRLattice import BinomialCRRLattice 44 | eu_option = BinomialCRRLattice( 45 | 50, 50, 0.05, 0.5, 2, 46 | {"sigma": 0.3, "is_call": False}) 47 | print "European put: %s" % eu_option.price() 48 | 49 | am_option = BinomialCRRLattice( 50 | 50, 50, 0.05, 0.5, 2, 51 | {"sigma": 0.3, "is_call": False, "is_eu": False}) 52 | print "American put: %s" % am_option.price() -------------------------------------------------------------------------------- /B03898_04_codes/BinomialCRROption.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ Price an option by the binomial CRR model """ 9 | from BinomialTreeOption import BinomialTreeOption 10 | import math 11 | 12 | 13 | class BinomialCRROption(BinomialTreeOption): 14 | 15 | def _setup_parameters_(self): 16 | self.u = math.exp(self.sigma * math.sqrt(self.dt)) 17 | self.d = 1./self.u 18 | self.qu = (math.exp((self.r-self.div)*self.dt) - 19 | self.d)/(self.u-self.d) 20 | self.qd = 1-self.qu 21 | 22 | if __name__ == "__main__": 23 | from BinomialCRROption import BinomialCRROption 24 | eu_option = BinomialCRROption( 25 | 50, 50, 0.05, 0.5, 2, 26 | {"sigma": 0.3, "is_call": False}) 27 | print "European put: %s" % eu_option.price() 28 | 29 | am_option = BinomialCRROption( 30 | 50, 50, 0.05, 0.5, 2, 31 | {"sigma": 0.3, "is_call": False, "is_eu": False}) 32 | print "American put: %s" % am_option.price() 33 | -------------------------------------------------------------------------------- /B03898_04_codes/BinomialEuropeanOption.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ Price a European option by the binomial tree model """ 9 | from StockOption import StockOption 10 | import math 11 | import numpy as np 12 | 13 | 14 | class BinomialEuropeanOption(StockOption): 15 | 16 | def _setup_parameters_(self): 17 | """ Required calculations for the model """ 18 | self.M = self.N + 1 # Number of terminal nodes of tree 19 | self.u = 1 + self.pu # Expected value in the up state 20 | self.d = 1 - self.pd # Expected value in the down state 21 | self.qu = (math.exp((self.r-self.div)*self.dt) - 22 | self.d) / (self.u-self.d) 23 | self.qd = 1-self.qu 24 | 25 | def _initialize_stock_price_tree_(self): 26 | # Initialize terminal price nodes to zeros 27 | self.STs = np.zeros(self.M) 28 | 29 | # Calculate expected stock prices for each node 30 | for i in range(self.M): 31 | self.STs[i] = self.S0*(self.u**(self.N-i))*(self.d**i) 32 | 33 | def _initialize_payoffs_tree_(self): 34 | # Get payoffs when the option expires at terminal nodes 35 | payoffs = np.maximum( 36 | 0, (self.STs-self.K) if self.is_call 37 | else (self.K-self.STs)) 38 | 39 | return payoffs 40 | 41 | def _traverse_tree_(self, payoffs): 42 | # Starting from the time the option expires, traverse 43 | # backwards and calculate discounted payoffs at each node 44 | for i in range(self.N): 45 | payoffs = (payoffs[:-1] * self.qu + 46 | payoffs[1:] * self.qd) * self.df 47 | 48 | return payoffs 49 | 50 | def __begin_tree_traversal__(self): 51 | payoffs = self._initialize_payoffs_tree_() 52 | return self._traverse_tree_(payoffs) 53 | 54 | def price(self): 55 | """ The pricing implementation """ 56 | self._setup_parameters_() 57 | self._initialize_stock_price_tree_() 58 | payoffs = self.__begin_tree_traversal__() 59 | 60 | return payoffs[0] # Option value converges to first node 61 | 62 | if __name__ == "__main__": 63 | from BinomialEuropeanOption import BinomialEuropeanOption 64 | eu_option = BinomialEuropeanOption( 65 | 50, 50, 0.05, 0.5, 2, 66 | {"pu": 0.2, "pd": 0.2, "is_call": False}) 67 | print eu_option.price() -------------------------------------------------------------------------------- /B03898_04_codes/BinomialLROption.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ Price an option by the Leisen-Reimer tree """ 9 | from BinomialTreeOption import BinomialTreeOption 10 | import math 11 | 12 | 13 | class BinomialLROption(BinomialTreeOption): 14 | 15 | def _setup_parameters_(self): 16 | odd_N = self.N if (self.N%2 == 1) else (self.N+1) 17 | d1 = (math.log(self.S0/self.K) + 18 | ((self.r-self.div) + 19 | (self.sigma**2)/2.) * 20 | self.T) / (self.sigma * math.sqrt(self.T)) 21 | d2 = (math.log(self.S0/self.K) + 22 | ((self.r-self.div) - 23 | (self.sigma**2)/2.) * 24 | self.T) / (self.sigma * math.sqrt(self.T)) 25 | pp_2_inversion = \ 26 | lambda z, n: \ 27 | .5 + math.copysign(1, z) * \ 28 | math.sqrt(.25 - .25 * math.exp( 29 | -((z/(n+1./3.+.1/(n+1)))**2.)*(n+1./6.))) 30 | pbar = pp_2_inversion(d1, odd_N) 31 | 32 | self.p = pp_2_inversion(d2, odd_N) 33 | self.u = 1/self.df * pbar/self.p 34 | self.d = (1/self.df - self.p*self.u)/(1-self.p) 35 | self.qu = self.p 36 | self.qd = 1-self.p 37 | 38 | if __name__ == "__main__": 39 | from BinomialLROption import BinomialLROption 40 | eu_option = BinomialLROption( 41 | 50, 50, 0.05, 0.5, 3, 42 | {"sigma": 0.3, "is_call": False}) 43 | print "European put: %s" % eu_option.price() 44 | 45 | am_option = BinomialLROption( 46 | 50, 50, 0.05, 0.5, 3, 47 | {"sigma": 0.3, "is_call": False, "is_eu": False}) 48 | print "American put: %s" % am_option.price() -------------------------------------------------------------------------------- /B03898_04_codes/BinomialLRWithGreeks.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ Compute option price, delta and gamma by the LR tree """ 9 | from BinomialLROption import BinomialLROption 10 | import numpy as np 11 | 12 | 13 | class BinomialLRWithGreeks(BinomialLROption): 14 | 15 | def __new_stock_price_tree__(self): 16 | """ 17 | Create additional layer of nodes to our 18 | original stock price tree 19 | """ 20 | self.STs = [np.array([self.S0*self.u/self.d, 21 | self.S0, 22 | self.S0*self.d/self.u])] 23 | 24 | for i in range(self.N): 25 | prev_branches = self.STs[-1] 26 | st = np.concatenate((prev_branches * self.u, 27 | [prev_branches[-1] * self.d])) 28 | self.STs.append(st) 29 | 30 | def price(self): 31 | self._setup_parameters_() 32 | self.__new_stock_price_tree__() 33 | payoffs = self.__begin_tree_traversal__() 34 | 35 | """ Option value is now in the middle node at t=0""" 36 | option_value = payoffs[len(payoffs)/2] 37 | 38 | payoff_up = payoffs[0] 39 | payoff_down = payoffs[-1] 40 | S_up = self.STs[0][0] 41 | S_down = self.STs[0][-1] 42 | dS_up = S_up - self.S0 43 | dS_down = self.S0 - S_down 44 | 45 | """ Get delta value """ 46 | dS = S_up - S_down 47 | dV = payoff_up - payoff_down 48 | delta = dV/dS 49 | 50 | """ Get gamma value """ 51 | gamma = ((payoff_up-option_value)/dS_up - 52 | (option_value-payoff_down)/dS_down) / \ 53 | ((self.S0+S_up)/2. - (self.S0+S_down)/2.) 54 | 55 | return option_value, delta, gamma 56 | 57 | if __name__ == "__main__": 58 | from BinomialLRWithGreeks import BinomialLRWithGreeks 59 | eu_call = BinomialLRWithGreeks( 60 | 50, 50, 0.05, 0.5, 300, {"sigma": 0.3, "is_call": True}) 61 | results = eu_call.price() 62 | print "European call values" 63 | print "Price: %s\nDelta: %s\nGamma: %s" % results 64 | 65 | eu_put = BinomialLRWithGreeks( 66 | 50, 50, 0.05, 0.5, 300, {"sigma":0.3, "is_call": False}) 67 | results = eu_put.price() 68 | print "European put values" 69 | print "Price: %s\nDelta: %s\nGamma: %s" % results -------------------------------------------------------------------------------- /B03898_04_codes/BinomialTreeOption.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ Price a European or American option by the binomial tree """ 9 | from StockOption import StockOption 10 | import math 11 | import numpy as np 12 | 13 | 14 | class BinomialTreeOption(StockOption): 15 | 16 | def _setup_parameters_(self): 17 | self.u = 1 + self.pu # Expected value in the up state 18 | self.d = 1 - self.pd # Expected value in the down state 19 | self.qu = (math.exp((self.r-self.div)*self.dt) - 20 | self.d)/(self.u-self.d) 21 | self.qd = 1-self.qu 22 | 23 | def _initialize_stock_price_tree_(self): 24 | # Initialize a 2D tree at T=0 25 | self.STs = [np.array([self.S0])] 26 | 27 | # Simulate the possible stock prices path 28 | for i in range(self.N): 29 | prev_branches = self.STs[-1] 30 | st = np.concatenate((prev_branches*self.u, 31 | [prev_branches[-1]*self.d])) 32 | self.STs.append(st) # Add nodes at each time step 33 | 34 | def _initialize_payoffs_tree_(self): 35 | # The payoffs when option expires 36 | return np.maximum( 37 | 0, (self.STs[self.N]-self.K) if self.is_call 38 | else (self.K-self.STs[self.N])) 39 | 40 | def __check_early_exercise__(self, payoffs, node): 41 | early_ex_payoff = \ 42 | (self.STs[node] - self.K) if self.is_call \ 43 | else (self.K - self.STs[node]) 44 | 45 | return np.maximum(payoffs, early_ex_payoff) 46 | 47 | def _traverse_tree_(self, payoffs): 48 | for i in reversed(range(self.N)): 49 | # The payoffs from NOT exercising the option 50 | payoffs = (payoffs[:-1] * self.qu + 51 | payoffs[1:] * self.qd) * self.df 52 | 53 | # Payoffs from exercising, for American options 54 | if not self.is_european: 55 | payoffs = self.__check_early_exercise__(payoffs, 56 | i) 57 | 58 | return payoffs 59 | 60 | def __begin_tree_traversal__(self): 61 | payoffs = self._initialize_payoffs_tree_() 62 | return self._traverse_tree_(payoffs) 63 | 64 | def price(self): 65 | self._setup_parameters_() 66 | self._initialize_stock_price_tree_() 67 | payoffs = self.__begin_tree_traversal__() 68 | 69 | return payoffs[0] 70 | 71 | if __name__ == "__main__": 72 | from BinomialTreeOption import BinomialTreeOption 73 | am_option = BinomialTreeOption( 74 | 50, 50, 0.05, 0.5, 2, 75 | {"pu": 0.2, "pd": 0.2, "is_call": False, "is_eu": False}) 76 | print am_option.price() -------------------------------------------------------------------------------- /B03898_04_codes/FDCnAm.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ==== 6 | """ 7 | 8 | """ Price an American option by the Crank-Nicolson method """ 9 | import numpy as np 10 | import sys 11 | 12 | from FDCnEu import FDCnEu 13 | 14 | 15 | class FDCnAm(FDCnEu): 16 | 17 | def __init__(self, S0, K, r, T, sigma, Smax, M, N, omega, tol, 18 | is_call=True): 19 | super(FDCnAm, self).__init__( 20 | S0, K, r, T, sigma, Smax, M, N, is_call) 21 | self.omega = omega 22 | self.tol = tol 23 | self.i_values = np.arange(self.M+1) 24 | self.j_values = np.arange(self.N+1) 25 | 26 | def _setup_boundary_conditions_(self): 27 | if self.is_call: 28 | self.payoffs = np.maximum( 29 | self.boundary_conds[1:self.M]-self.K, 0) 30 | else: 31 | self.payoffs = np.maximum( 32 | self.K-self.boundary_conds[1:self.M], 0) 33 | 34 | self.past_values = self.payoffs 35 | self.boundary_values = self.K * \ 36 | np.exp(-self.r * 37 | self.dt * 38 | (self.N-self.j_values)) 39 | 40 | def _traverse_grid_(self): 41 | """ Solve using linear systems of equations """ 42 | aux = np.zeros(self.M-1) 43 | new_values = np.zeros(self.M-1) 44 | 45 | for j in reversed(range(self.N)): 46 | aux[0] = self.alpha[1]*(self.boundary_values[j] + 47 | self.boundary_values[j+1]) 48 | rhs = np.dot(self.M2, self.past_values) + aux 49 | old_values = np.copy(self.past_values) 50 | error = sys.float_info.max 51 | 52 | while self.tol < error: 53 | new_values[0] = \ 54 | max(self.payoffs[0], 55 | old_values[0] + 56 | self.omega/(1-self.beta[1]) * 57 | (rhs[0] - 58 | (1-self.beta[1])*old_values[0] + 59 | (self.gamma[1]*old_values[1]))) 60 | 61 | for k in range(self.M-2)[1:]: 62 | new_values[k] = \ 63 | max(self.payoffs[k], 64 | old_values[k] + 65 | self.omega/(1-self.beta[k+1]) * 66 | (rhs[k] + 67 | self.alpha[k+1]*new_values[k-1] - 68 | (1-self.beta[k+1])*old_values[k] + 69 | self.gamma[k+1]*old_values[k+1])) 70 | 71 | new_values[-1] = \ 72 | max(self.payoffs[-1], 73 | old_values[-1] + 74 | self.omega/(1-self.beta[-2]) * 75 | (rhs[-1] + 76 | self.alpha[-2]*new_values[-2] - 77 | (1-self.beta[-2])*old_values[-1])) 78 | 79 | error = np.linalg.norm(new_values - old_values) 80 | old_values = np.copy(new_values) 81 | 82 | self.past_values = np.copy(new_values) 83 | 84 | self.values = np.concatenate(([self.boundary_values[0]], 85 | new_values, 86 | [0])) 87 | 88 | def _interpolate_(self): 89 | # Use linear interpolation on final values as 1D array 90 | return np.interp(self.S0, 91 | self.boundary_conds, 92 | self.values) 93 | 94 | if __name__ == "__main__": 95 | from FDCnDo import FDCnDo 96 | option = FDCnAm(50, 50, 0.1, 5./12., 0.4, 100, 100, 97 | 42, 1.2, 0.001) 98 | print option.price() 99 | 100 | option = FDCnAm(50, 50, 0.1, 5./12., 0.4, 100, 100, 101 | 42, 1.2, 0.001, False) 102 | print option.price() -------------------------------------------------------------------------------- /B03898_04_codes/FDCnDo.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ==== 6 | """ 7 | 8 | """ 9 | Price a down-and-out option by the Crank-Nicolson 10 | method of finite differences. 11 | """ 12 | import numpy as np 13 | 14 | from FDCnEu import FDCnEu 15 | 16 | 17 | class FDCnDo(FDCnEu): 18 | 19 | def __init__(self, S0, K, r, T, sigma, Sbarrier, Smax, M, N, 20 | is_call=True): 21 | super(FDCnDo, self).__init__( 22 | S0, K, r, T, sigma, Smax, M, N, is_call) 23 | self.dS = (Smax-Sbarrier)/float(self.M) 24 | self.boundary_conds = np.linspace(Sbarrier, 25 | Smax, 26 | self.M+1) 27 | self.i_values = self.boundary_conds/self.dS 28 | 29 | if __name__ == "__main__": 30 | from FDCnDo import FDCnDo 31 | option = FDCnDo(50, 50, 0.1, 5./12., 0.4, 40, 100, 120, 32 | 500) 33 | print option.price() 34 | 35 | option = FDCnDo(50, 50, 0.1, 5./12., 0.4, 40, 100, 120, 36 | 500, False) 37 | print option.price() -------------------------------------------------------------------------------- /B03898_04_codes/FDCnEu.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ==== 6 | """ 7 | 8 | """ Crank-Nicolson method of Finite Differences """ 9 | import numpy as np 10 | import scipy.linalg as linalg 11 | 12 | from FDExplicitEu import FDExplicitEu 13 | 14 | 15 | class FDCnEu(FDExplicitEu): 16 | 17 | def _setup_coefficients_(self): 18 | self.alpha = 0.25*self.dt*( 19 | (self.sigma**2)*(self.i_values**2) - 20 | self.r*self.i_values) 21 | self.beta = -self.dt*0.5*( 22 | (self.sigma**2)*(self.i_values**2) + 23 | self.r) 24 | self.gamma = 0.25*self.dt*( 25 | (self.sigma**2)*(self.i_values**2) + 26 | self.r*self.i_values) 27 | self.M1 = -np.diag(self.alpha[2:self.M], -1) + \ 28 | np.diag(1-self.beta[1:self.M]) - \ 29 | np.diag(self.gamma[1:self.M-1], 1) 30 | self.M2 = np.diag(self.alpha[2:self.M], -1) + \ 31 | np.diag(1+self.beta[1:self.M]) + \ 32 | np.diag(self.gamma[1:self.M-1], 1) 33 | 34 | def _traverse_grid_(self): 35 | """ Solve using linear systems of equations """ 36 | P, L, U = linalg.lu(self.M1) 37 | 38 | for j in reversed(range(self.N)): 39 | x1 = linalg.solve(L, 40 | np.dot(self.M2, 41 | self.grid[1:self.M, j+1])) 42 | x2 = linalg.solve(U, x1) 43 | self.grid[1:self.M, j] = x2 44 | 45 | if __name__ == "__main__": 46 | from FDCnEu import FDCnEu 47 | option = FDCnEu(50, 50, 0.1, 5./12., 0.4, 100, 100, 48 | 100, False) 49 | print option.price() 50 | 51 | option = FDCnEu(50, 50, 0.1, 5./12., 0.4, 100, 100, 52 | 1000, False) 53 | print option.price() 54 | 55 | -------------------------------------------------------------------------------- /B03898_04_codes/FDExplicitEu.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ Explicit method of Finite Differences """ 9 | import numpy as np 10 | 11 | from FiniteDifferences import FiniteDifferences 12 | 13 | 14 | class FDExplicitEu(FiniteDifferences): 15 | 16 | def _setup_boundary_conditions_(self): 17 | if self.is_call: 18 | self.grid[:, -1] = np.maximum( 19 | self.boundary_conds - self.K, 0) 20 | self.grid[-1, :-1] = (self.Smax - self.K) * \ 21 | np.exp(-self.r * 22 | self.dt * 23 | (self.N-self.j_values)) 24 | else: 25 | self.grid[:, -1] = \ 26 | np.maximum(self.K-self.boundary_conds, 0) 27 | self.grid[0, :-1] = (self.K - self.Smax) * \ 28 | np.exp(-self.r * 29 | self.dt * 30 | (self.N-self.j_values)) 31 | 32 | def _setup_coefficients_(self): 33 | self.a = 0.5*self.dt*((self.sigma**2) * 34 | (self.i_values**2) - 35 | self.r*self.i_values) 36 | self.b = 1 - self.dt*((self.sigma**2) * 37 | (self.i_values**2) + 38 | self.r) 39 | self.c = 0.5*self.dt*((self.sigma**2) * 40 | (self.i_values**2) + 41 | self.r*self.i_values) 42 | 43 | def _traverse_grid_(self): 44 | for j in reversed(self.j_values): 45 | for i in range(self.M)[2:]: 46 | self.grid[i,j] = self.a[i]*self.grid[i-1,j+1] +\ 47 | self.b[i]*self.grid[i,j+1] + \ 48 | self.c[i]*self.grid[i+1,j+1] 49 | 50 | 51 | if __name__ == "__main__": 52 | from FDExplicitEu import FDExplicitEu 53 | option = FDExplicitEu(50, 50, 0.1, 5./12., 0.4, 100, 100, 54 | 1000, False) 55 | print option.price() 56 | 57 | option = FDExplicitEu(50, 50, 0.1, 5./12., 0.4, 100, 100, 58 | 100, False) 59 | print option.price() -------------------------------------------------------------------------------- /B03898_04_codes/FDImplicitEu.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ 9 | Price a European option by the implicit method 10 | of finite differences. 11 | """ 12 | import numpy as np 13 | import scipy.linalg as linalg 14 | 15 | from FDExplicitEu import FDExplicitEu 16 | 17 | 18 | class FDImplicitEu(FDExplicitEu): 19 | 20 | def _setup_coefficients_(self): 21 | self.a = 0.5*(self.r*self.dt*self.i_values - 22 | (self.sigma**2)*self.dt*(self.i_values**2)) 23 | self.b = 1 + \ 24 | (self.sigma**2)*self.dt*(self.i_values**2) + \ 25 | self.r*self.dt 26 | self.c = -0.5*(self.r * self.dt*self.i_values + 27 | (self.sigma**2)*self.dt*(self.i_values**2)) 28 | self.coeffs = np.diag(self.a[2:self.M], -1) + \ 29 | np.diag(self.b[1:self.M]) + \ 30 | np.diag(self.c[1:self.M-1], 1) 31 | 32 | def _traverse_grid_(self): 33 | """ Solve using linear systems of equations """ 34 | P, L, U = linalg.lu(self.coeffs) 35 | aux = np.zeros(self.M-1) 36 | 37 | for j in reversed(range(self.N)): 38 | aux[0] = np.dot(-self.a[1], self.grid[0, j]) 39 | x1 = linalg.solve(L, self.grid[1:self.M, j+1]+aux) 40 | x2 = linalg.solve(U, x1) 41 | self.grid[1:self.M, j] = x2 42 | 43 | if __name__ == "__main__": 44 | from FDImplicitEu import FDImplicitEu 45 | option = FDImplicitEu(50, 50, 0.1, 5./12., 0.4, 100, 100, 46 | 100, False) 47 | print option.price() 48 | 49 | option = FDImplicitEu(50, 50, 0.1, 5./12., 0.4, 100, 100, 50 | 10000, False) 51 | print option.price() -------------------------------------------------------------------------------- /B03898_04_codes/FiniteDifferences.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ Shared attributes and functions of FD """ 9 | import numpy as np 10 | 11 | 12 | class FiniteDifferences(object): 13 | 14 | def __init__(self, S0, K, r, T, sigma, Smax, M, N, 15 | is_call=True): 16 | self.S0 = S0 17 | self.K = K 18 | self.r = r 19 | self.T = T 20 | self.sigma = sigma 21 | self.Smax = Smax 22 | self.M, self.N = int(M), int(N) # Ensure M&N are integers 23 | self.is_call = is_call 24 | 25 | self.dS = Smax / float(self.M) 26 | self.dt = T / float(self.N) 27 | self.i_values = np.arange(self.M) 28 | self.j_values = np.arange(self.N) 29 | self.grid = np.zeros(shape=(self.M+1, self.N+1)) 30 | self.boundary_conds = np.linspace(0, Smax, self.M+1) 31 | 32 | def _setup_boundary_conditions_(self): 33 | pass 34 | 35 | def _setup_coefficients_(self): 36 | pass 37 | 38 | def _traverse_grid_(self): 39 | """ Iterate the grid backwards in time """ 40 | pass 41 | 42 | def _interpolate_(self): 43 | """ 44 | Use piecewise linear interpolation on the initial 45 | grid column to get the closest price at S0. 46 | """ 47 | return np.interp(self.S0, 48 | self.boundary_conds, 49 | self.grid[:, 0]) 50 | 51 | def price(self): 52 | self._setup_boundary_conditions_() 53 | self._setup_coefficients_() 54 | self._traverse_grid_() 55 | return self._interpolate_() 56 | -------------------------------------------------------------------------------- /B03898_04_codes/ImpliedVolatilityModel.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ==== 6 | """ 7 | 8 | """ 9 | Get implied volatilities from a Leisen-Reimer binomial 10 | tree using the bisection method as the numerical procedure. 11 | """ 12 | from bisection import bisection 13 | from BinomialLROption import BinomialLROption 14 | 15 | class ImpliedVolatilityModel(object): 16 | 17 | def __init__(self, S0, r, T, div, N, 18 | is_call=False): 19 | self.S0 = S0 20 | self.r = r 21 | self.T = T 22 | self.div = div 23 | self.N = N 24 | self.is_call = is_call 25 | 26 | def _option_valuation_(self, K, sigma): 27 | # Use the binomial Leisen-Reimer tree 28 | lr_option = BinomialLROption( 29 | self.S0, K, self.r, self.T, self.N, 30 | {"sigma": sigma, 31 | "is_call": self.is_call, 32 | "div": self.div}) 33 | return lr_option.price() 34 | 35 | def get_implied_volatilities(self, Ks, opt_prices): 36 | impvols = [] 37 | for i in range(len(Ks)): 38 | # Bind f(sigma) for use by the bisection method 39 | f = lambda sigma: \ 40 | self._option_valuation_( 41 | Ks[i], sigma) - opt_prices[i] 42 | impv = bisection(f, 0.01, 0.99, 0.0001, 100)[0] 43 | impvols.append(impv) 44 | return impvols 45 | 46 | if __name__ == "__main__": 47 | # The data 48 | strikes = [75, 80, 85, 90, 92.5, 95, 97.5, 49 | 100, 105, 110, 115, 120, 125] 50 | put_prices = [0.16, 0.32, 0.6, 1.22, 1.77, 2.54, 3.55, 51 | 4.8, 7.75, 11.8, 15.96, 20.75, 25.81] 52 | 53 | model = ImpliedVolatilityModel(99.62, 0.0248, 78/365., 54 | 0.0182, 77, is_call=False) 55 | impvols_put = model.get_implied_volatilities(strikes, 56 | put_prices) 57 | 58 | # Begin plotting the results 59 | import matplotlib.pyplot as plt 60 | plt.plot(strikes, impvols_put) 61 | plt.xlabel('Strike Prices') 62 | plt.ylabel('Implied Volatilities') 63 | plt.title('AAPL Put Implied Volatilities expiring in 78 days') 64 | plt.show() -------------------------------------------------------------------------------- /B03898_04_codes/StockOption.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ Store common attributes of a stock option """ 9 | import math 10 | 11 | 12 | class StockOption(object): 13 | 14 | def __init__(self, S0, K, r, T, N, params): 15 | self.S0 = S0 16 | self.K = K 17 | self.r = r 18 | self.T = T 19 | self.N = max(1, N) # Ensure N have at least 1 time step 20 | self.STs = None # Declare the stock prices tree 21 | 22 | """ Optional parameterss used by derived classes """ 23 | self.pu = params.get("pu", 0) # Probability of up state 24 | self.pd = params.get("pd", 0) # Probability of down state 25 | self.div = params.get("div", 0) # Divident yield 26 | self.sigma = params.get("sigma", 0) # Volatility 27 | self.is_call = params.get("is_call", True) # Call or put 28 | self.is_european = params.get("is_eu", True) # Eu or Am 29 | 30 | """ Computed values """ 31 | self.dt = T/float(N) # Single time step, in years 32 | self.df = math.exp( 33 | -(r-self.div) * self.dt) # Discount factor -------------------------------------------------------------------------------- /B03898_04_codes/TrinomialLattice.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ Price an option by the trinomial lattice """ 9 | from TrinomialTreeOption import TrinomialTreeOption 10 | import numpy as np 11 | 12 | 13 | class TrinomialLattice(TrinomialTreeOption): 14 | 15 | def _setup_parameters_(self): 16 | super(TrinomialLattice, self)._setup_parameters_() 17 | self.M = 2*self.N+1 18 | 19 | def _initialize_stock_price_tree_(self): 20 | self.STs = np.zeros(self.M) 21 | self.STs[0] = self.S0 * self.u**self.N 22 | 23 | for i in range(self.M)[1:]: 24 | self.STs[i] = self.STs[i-1]*self.d 25 | 26 | def _initialize_payoffs_tree_(self): 27 | return np.maximum( 28 | 0, (self.STs-self.K) if self.is_call 29 | else(self.K-self.STs)) 30 | 31 | def __check_early_exercise__(self, payoffs, node): 32 | self.STs = self.STs[1:-1] # Shorten the ends of the list 33 | early_ex_payoffs = \ 34 | (self.STs-self.K) if self.is_call \ 35 | else(self.K-self.STs) 36 | payoffs = np.maximum(payoffs, early_ex_payoffs) 37 | 38 | return payoffs 39 | 40 | if __name__ == "__main__": 41 | from TrinomialLattice import TrinomialLattice 42 | eu_option = TrinomialLattice( 43 | 50, 50, 0.05, 0.5, 2, 44 | {"sigma": 0.3, "is_call":False}) 45 | print "European put: %s" % eu_option.price() 46 | 47 | am_option = TrinomialLattice( 48 | 50, 50, 0.05, 0.5, 2, 49 | {"sigma": 0.3, "is_call": False, "is_eu": False}) 50 | print "American put: %s" % am_option.price() -------------------------------------------------------------------------------- /B03898_04_codes/TrinomialTreeOption.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ Price an option by the Boyle trinomial tree """ 9 | from BinomialTreeOption import BinomialTreeOption 10 | import math 11 | import numpy as np 12 | 13 | 14 | class TrinomialTreeOption(BinomialTreeOption): 15 | 16 | def _setup_parameters_(self): 17 | """ Required calculations for the model """ 18 | self.u = math.exp(self.sigma*math.sqrt(2.*self.dt)) 19 | self.d = 1/self.u 20 | self.m = 1 21 | self.qu = ((math.exp((self.r-self.div) * 22 | self.dt/2.) - 23 | math.exp(-self.sigma * 24 | math.sqrt(self.dt/2.))) / 25 | (math.exp(self.sigma * 26 | math.sqrt(self.dt/2.)) - 27 | math.exp(-self.sigma * 28 | math.sqrt(self.dt/2.))))**2 29 | self.qd = ((math.exp(self.sigma * 30 | math.sqrt(self.dt/2.)) - 31 | math.exp((self.r-self.div) * 32 | self.dt/2.)) / 33 | (math.exp(self.sigma * 34 | math.sqrt(self.dt/2.)) - 35 | math.exp(-self.sigma * 36 | math.sqrt(self.dt/2.))))**2. 37 | 38 | self.qm = 1 - self.qu - self.qd 39 | 40 | def _initialize_stock_price_tree_(self): 41 | """ Initialize a 2D tree at t=0 """ 42 | self.STs = [np.array([self.S0])] 43 | 44 | for i in range(self.N): 45 | prev_nodes = self.STs[-1] 46 | self.ST = np.concatenate( 47 | (prev_nodes*self.u, [prev_nodes[-1]*self.m, 48 | prev_nodes[-1]*self.d])) 49 | self.STs.append(self.ST) 50 | 51 | def _traverse_tree_(self, payoffs): 52 | """ Traverse the tree backwards """ 53 | for i in reversed(range(self.N)): 54 | payoffs = (payoffs[:-2] * self.qu + 55 | payoffs[1:-1] * self.qm + 56 | payoffs[2:] * self.qd) * self.df 57 | 58 | if not self.is_european: 59 | payoffs = self.__check_early_exercise__(payoffs, 60 | i) 61 | 62 | return payoffs 63 | 64 | if __name__ == "__main__": 65 | from TrinomialTreeOption import TrinomialTreeOption 66 | print "European put:", TrinomialTreeOption( 67 | 50, 50, 0.05, 0.5, 2, {"sigma": 0.3, "is_call": False}).price() 68 | print "American put:", TrinomialTreeOption( 69 | 50, 50, 0.05, 0.5, 2, {"sigma": 0.3, "is_call": False, "is_eu": False}).price() 70 | -------------------------------------------------------------------------------- /B03898_04_codes/bisection.py: -------------------------------------------------------------------------------- 1 | """ README ====== This file contains Python codes. ====== """ """ The bisection method """ def bisection(f, a, b, tol=0.1, maxiter=10): """ :param f: The function to solve :param a: The x-axis value where f(a)<0 :param b: The x-axis value where f(b)>0 :param tol: The precision of the solution :param maxiter: Maximum number of iterations :return: The x-axis value of the root, number of iterations used """ c = (a+b)*0.5 # Declare c as the midpoint ab n = 1 # Start with 1 iteration while n <= maxiter: c = (a+b)*0.5 if f(c) == 0 or abs(a-b)*0.5 < tol: # Root is found or is very close return c, n n += 1 if f(c) < 0: a = c else: b = c return c, n if __name__ == "__main__": y = lambda x: x**3 + 2*x**2 - 5 root, iterations = bisection(y, -5, 5, 0.00001, 100) print "Root is:", root print "Iterations:", iterations -------------------------------------------------------------------------------- /B03898_05_Codes/BootstrapYieldCurve.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ Bootstrapping the yield curve """ 9 | import math 10 | 11 | class BootstrapYieldCurve(object): 12 | 13 | def __init__(self): 14 | self.zero_rates = dict() # Map each T to a zero rate 15 | self.instruments = dict() # Map each T to an instrument 16 | 17 | def add_instrument(self, par, T, coup, price, 18 | compounding_freq=2): 19 | """ Save instrument info by maturity """ 20 | self.instruments[T] = (par, coup, price, compounding_freq) 21 | 22 | def get_zero_rates(self): 23 | """ Calculate a list of available zero rates """ 24 | self.__bootstrap_zero_coupons__() 25 | self.__get_bond_spot_rates__() 26 | return [self.zero_rates[T] for T in self.get_maturities()] 27 | 28 | def get_maturities(self): 29 | """ Return sorted maturities from added instruments. """ 30 | return sorted(self.instruments.keys()) 31 | 32 | def __bootstrap_zero_coupons__(self): 33 | """ Get zero rates from zero coupon bonds """ 34 | for T in self.instruments.iterkeys(): 35 | (par, coup, price, freq) = self.instruments[T] 36 | if coup == 0: 37 | self.zero_rates[T] = \ 38 | self.zero_coupon_spot_rate(par, price, T) 39 | 40 | def __get_bond_spot_rates__(self): 41 | """ Get spot rates for every marurity available """ 42 | for T in self.get_maturities(): 43 | instrument = self.instruments[T] 44 | (par, coup, price, freq) = instrument 45 | 46 | if coup != 0: 47 | self.zero_rates[T] = \ 48 | self.__calculate_bond_spot_rate__( 49 | T, instrument) 50 | 51 | def __calculate_bond_spot_rate__(self, T, instrument): 52 | """ Get spot rate of a bond by bootstrapping """ 53 | try: 54 | (par, coup, price, freq) = instrument 55 | periods = T * freq # Number of coupon payments 56 | value = price 57 | per_coupon = coup / freq # Coupon per period 58 | 59 | for i in range(int(periods)-1): 60 | t = (i+1)/float(freq) 61 | spot_rate = self.zero_rates[t] 62 | discounted_coupon = per_coupon * \ 63 | math.exp(-spot_rate*t) 64 | value -= discounted_coupon 65 | 66 | # Derive spot rate for a particular maturity 67 | last_period = int(periods)/float(freq) 68 | spot_rate = -math.log(value / 69 | (par+per_coupon))/last_period 70 | return spot_rate 71 | 72 | except: 73 | print "Error: spot rate not found for T=%s" % t 74 | 75 | def zero_coupon_spot_rate(self, par, price, T): 76 | """ Get zero rate of a zero coupon bond """ 77 | spot_rate = math.log(par/price)/T 78 | return spot_rate 79 | 80 | 81 | if __name__ == "__main__": 82 | yield_curve = BootstrapYieldCurve() 83 | yield_curve.add_instrument(100, 0.25, 0., 97.5) 84 | yield_curve.add_instrument(100, 0.5, 0., 94.9) 85 | yield_curve.add_instrument(100, 1.0, 0., 90.) 86 | yield_curve.add_instrument(100, 1.5, 8, 96., 2) 87 | yield_curve.add_instrument(100, 2., 12, 101.6, 2) 88 | y = yield_curve.get_zero_rates() 89 | x = yield_curve.get_maturities() 90 | 91 | import matplotlib.pyplot as plt 92 | plt.plot(x, y) 93 | plt.title("Zero Curve") 94 | plt.ylabel("Zero Rate (%)") 95 | plt.xlabel("Maturity in Years") 96 | plt.show() -------------------------------------------------------------------------------- /B03898_05_Codes/VasicekCZCB.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ Price a callable zero coupon bond by the Vasicek model """ 9 | import math 10 | import numpy as np 11 | import scipy.stats as st 12 | 13 | 14 | class VasicekCZCB: 15 | 16 | def __init__(self): 17 | self.norminv = st.distributions.norm.ppf 18 | self.norm = st.distributions.norm.cdf 19 | 20 | def vasicek_czcb_values(self, r0, R, ratio, T, sigma, kappa, 21 | theta, M, prob=1e-6, 22 | max_policy_iter=10, 23 | grid_struct_const=0.25, rs=None): 24 | r_min, dr, N, dtau = \ 25 | self.vasicek_params(r0, M, sigma, kappa, theta, 26 | T, prob, grid_struct_const, rs) 27 | r = np.r_[0:N]*dr + r_min 28 | v_mplus1 = np.ones(N) 29 | 30 | for i in range(1, M+1): 31 | K = self.exercise_call_price(R, ratio, i*dtau) 32 | eex = np.ones(N)*K 33 | subdiagonal, diagonal, superdiagonal = \ 34 | self.vasicek_diagonals(sigma, kappa, theta, 35 | r_min, dr, N, dtau) 36 | v_mplus1, iterations = \ 37 | self.iterate(subdiagonal, diagonal, superdiagonal, 38 | v_mplus1, eex, max_policy_iter) 39 | return r, v_mplus1 40 | 41 | def vasicek_params(self, r0, M, sigma, kappa, theta, T, 42 | prob, grid_struct_const=0.25, rs=None): 43 | (r_min, r_max) = (rs[0], rs[-1]) if not rs is None \ 44 | else self.vasicek_limits(r0, sigma, kappa, 45 | theta, T, prob) 46 | dt = T/float(M) 47 | N = self.calculate_N(grid_struct_const, dt, 48 | sigma, r_max, r_min) 49 | dr = (r_max-r_min)/(N-1) 50 | return r_min, dr, N, dt 51 | 52 | def calculate_N(self, max_structure_const, dt, 53 | sigma, r_max, r_min): 54 | N = 0 55 | while True: 56 | N += 1 57 | grid_structure_interval = dt*(sigma**2)/( 58 | ((r_max-r_min)/float(N))**2) 59 | if grid_structure_interval > max_structure_const: 60 | break 61 | 62 | return N 63 | 64 | def vasicek_limits(self, r0, sigma, kappa, 65 | theta, T, prob=1e-6): 66 | er = theta+(r0-theta)*math.exp(-kappa*T) 67 | variance = (sigma**2)*T if kappa==0 else \ 68 | (sigma**2)/(2*kappa)*(1-math.exp(-2*kappa*T)) 69 | stdev = math.sqrt(variance) 70 | r_min = self.norminv(prob, er, stdev) 71 | r_max = self.norminv(1-prob, er, stdev) 72 | return r_min, r_max 73 | 74 | def vasicek_diagonals(self, sigma, kappa, theta, 75 | r_min, dr, N, dtau): 76 | rn = np.r_[0:N]*dr + r_min 77 | subdiagonals = kappa*(theta-rn)*dtau/(2*dr) - \ 78 | 0.5*(sigma**2)*dtau/(dr**2) 79 | diagonals = 1 + rn*dtau + sigma**2*dtau/(dr**2) 80 | superdiagonals = -kappa*(theta-rn)*dtau/(2*dr) - \ 81 | 0.5*(sigma**2)*dtau/(dr**2) 82 | 83 | # Implement boundary conditions. 84 | if N > 0: 85 | v_subd0 = subdiagonals[0] 86 | superdiagonals[0] = superdiagonals[0] - \ 87 | subdiagonals[0] 88 | diagonals[0] += 2*v_subd0 89 | subdiagonals[0] = 0 90 | 91 | if N > 1: 92 | v_superd_last = superdiagonals[-1] 93 | superdiagonals[-1] = superdiagonals[-1] - \ 94 | subdiagonals[-1] 95 | diagonals[-1] += 2*v_superd_last 96 | superdiagonals[-1] = 0 97 | 98 | return subdiagonals, diagonals, superdiagonals 99 | 100 | def check_exercise(self, V, eex): 101 | return V > eex 102 | 103 | def exercise_call_price(self, R, ratio, tau): 104 | K = ratio*np.exp(-R*tau) 105 | return K 106 | 107 | def vasicek_policy_diagonals(self, subdiagonal, diagonal, 108 | superdiagonal, v_old, v_new, 109 | eex): 110 | has_early_exercise = self.check_exercise(v_new, eex) 111 | subdiagonal[has_early_exercise] = 0 112 | superdiagonal[has_early_exercise] = 0 113 | policy = v_old/eex 114 | policy_values = policy[has_early_exercise] 115 | diagonal[has_early_exercise] = policy_values 116 | return subdiagonal, diagonal, superdiagonal 117 | 118 | def iterate(self, subdiagonal, diagonal, superdiagonal, 119 | v_old, eex, max_policy_iter=10): 120 | v_mplus1 = v_old 121 | v_m = v_old 122 | change = np.zeros(len(v_old)) 123 | prev_changes = np.zeros(len(v_old)) 124 | 125 | iterations = 0 126 | while iterations <= max_policy_iter: 127 | iterations += 1 128 | 129 | v_mplus1 = self.tridiagonal_solve(subdiagonal, 130 | diagonal, 131 | superdiagonal, 132 | v_old) 133 | subdiagonal, diagonal, superdiagonal = \ 134 | self.vasicek_policy_diagonals(subdiagonal, 135 | diagonal, 136 | superdiagonal, 137 | v_old, 138 | v_mplus1, 139 | eex) 140 | 141 | is_eex = self.check_exercise(v_mplus1, eex) 142 | change[is_eex] = 1 143 | 144 | if iterations > 1: 145 | change[v_mplus1 != v_m] = 1 146 | 147 | is_no_more_eex = False if True in is_eex else True 148 | if is_no_more_eex: 149 | break 150 | 151 | v_mplus1[is_eex] = eex[is_eex] 152 | changes = (change == prev_changes) 153 | 154 | is_no_further_changes = all((x == 1) for x in changes) 155 | if is_no_further_changes: 156 | break 157 | 158 | prev_changes = change 159 | v_m = v_mplus1 160 | 161 | return v_mplus1, (iterations-1) 162 | 163 | def tridiagonal_solve(self, a, b, c, d): 164 | nf = len(a) # Number of equations 165 | ac, bc, cc, dc = \ 166 | map(np.array, (a, b, c, d)) # Copy the array 167 | for it in xrange(1, nf): 168 | mc = ac[it]/bc[it-1] 169 | bc[it] = bc[it] - mc*cc[it-1] 170 | dc[it] = dc[it] - mc*dc[it-1] 171 | 172 | xc = ac 173 | xc[-1] = dc[-1]/bc[-1] 174 | 175 | for il in xrange(nf-2, -1, -1): 176 | xc[il] = (dc[il]-cc[il]*xc[il+1])/bc[il] 177 | 178 | del bc, cc, dc # Delete variables from memory 179 | 180 | return xc 181 | 182 | if __name__ == "__main__": 183 | r0 = 0.05 184 | R = 0.05 185 | ratio = 0.95 186 | sigma = 0.03 187 | kappa = 0.15 188 | theta = 0.05 189 | prob = 1e-6 190 | M = 250 191 | max_policy_iter=10 192 | grid_struct_interval = 0.25 193 | rs = np.r_[0.0:2.0:0.1] 194 | 195 | Vasicek = VasicekCZCB() 196 | r, vals = Vasicek.vasicek_czcb_values(r0, R, ratio, 1., 197 | sigma, kappa, theta, 198 | M, prob, 199 | max_policy_iter, 200 | grid_struct_interval, 201 | rs) 202 | import matplotlib.pyplot as plt 203 | plt.title("Callable Zero Coupon Bond Values by r") 204 | plt.plot(r, vals, label='1 yr') 205 | 206 | for T in [5., 7., 10., 20.]: 207 | r, vals = \ 208 | Vasicek.vasicek_czcb_values(r0, R, ratio, T, 209 | sigma, kappa, 210 | theta, M, prob, 211 | max_policy_iter, 212 | grid_struct_interval, 213 | rs) 214 | plt.plot(r, vals, label=str(T)+' yr', 215 | linestyle="--", marker=".") 216 | 217 | plt.ylabel("Value ($)") 218 | plt.xlabel("r") 219 | plt.legend() 220 | plt.grid(True) 221 | plt.show() -------------------------------------------------------------------------------- /B03898_05_Codes/bond_convexity.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ Calculate convexity of a bond """ 9 | from bond_ytm import bond_ytm 10 | from bond_price import bond_price 11 | 12 | 13 | def bond_convexity(price, par, T, coup, freq, dy=0.01): 14 | ytm = bond_ytm(price, par, T, coup, freq) 15 | 16 | ytm_minus = ytm - dy 17 | price_minus = bond_price(par, T, ytm_minus, coup, freq) 18 | 19 | ytm_plus = ytm + dy 20 | price_plus = bond_price(par, T, ytm_plus, coup, freq) 21 | 22 | convexity = (price_minus+price_plus-2*price)/(price*dy**2) 23 | return convexity 24 | 25 | if __name__ == "__main__": 26 | print bond_convexity(95.0428, 100, 1.5, 5.75, 2) -------------------------------------------------------------------------------- /B03898_05_Codes/bond_mod_duration.py: -------------------------------------------------------------------------------- 1 | """" 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ Calculate modified duration of a bond """ 9 | from bond_ytm import bond_ytm 10 | from bond_price import bond_price 11 | 12 | 13 | def bond_mod_duration(price, par, T, coup, freq, dy=0.01): 14 | ytm = bond_ytm(price, par, T, coup, freq) 15 | 16 | ytm_minus = ytm - dy 17 | price_minus = bond_price(par, T, ytm_minus, coup, freq) 18 | 19 | ytm_plus = ytm + dy 20 | price_plus = bond_price(par, T, ytm_plus, coup, freq) 21 | 22 | mduration = (price_minus-price_plus)/(2.*price*dy) 23 | return mduration 24 | 25 | if __name__ == "__main__": 26 | from bond_mod_duration import bond_mod_duration 27 | print bond_mod_duration(95.04, 100, 1.5, 5.75, 2, 0.01) 28 | -------------------------------------------------------------------------------- /B03898_05_Codes/bond_price.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ Get bond price from YTM """ 9 | def bond_price(par, T, ytm, coup, freq=2): 10 | freq = float(freq) 11 | periods = T*freq 12 | coupon = coup/100.*par/freq 13 | dt = [(i+1)/freq for i in range(int(periods))] 14 | price = sum([coupon/(1+ytm/freq)**(freq*t) for t in dt]) + \ 15 | par/(1+ytm/freq)**(freq*T) 16 | return price 17 | 18 | 19 | if __name__ == "__main__": 20 | from bond_ytm import bond_ytm 21 | ytm = bond_ytm(95.0428, 100, 1.5, 5.75, 2) 22 | print bond_price(100, 1.5, ytm, 5.75, 2) 23 | -------------------------------------------------------------------------------- /B03898_05_Codes/bond_ytm.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ Get yield-to-maturity of a bond """ 9 | import scipy.optimize as optimize 10 | 11 | 12 | def bond_ytm(price, par, T, coup, freq=2, guess=0.05): 13 | freq = float(freq) 14 | periods = T*freq 15 | coupon = coup/100.*par/freq 16 | dt = [(i+1)/freq for i in range(int(periods))] 17 | ytm_func = lambda(y): \ 18 | sum([coupon/(1+y/freq)**(freq*t) for t in dt]) + \ 19 | par/(1+y/freq)**(freq*t) - price 20 | 21 | return optimize.newton(ytm_func, guess) 22 | 23 | if __name__ == "__main__": 24 | ytm = bond_ytm(95.0428, 100, 1.5, 5.75, 2) 25 | print ytm 26 | 27 | 28 | -------------------------------------------------------------------------------- /B03898_05_Codes/brennan_schwartz.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ Simulate interest rate path by the Brennan Schwartz model """ 9 | import numpy as np 10 | 11 | def brennan_schwartz(r0, K, theta, sigma, T=1., N=10, seed=777): 12 | np.random.seed(seed) 13 | dt = T/float(N) 14 | rates = [r0] 15 | for i in range(N): 16 | dr = K*(theta-rates[-1])*dt + \ 17 | sigma*rates[-1]*np.random.normal() 18 | rates.append(rates[-1] + dr) 19 | return range(N+1), rates 20 | 21 | if __name__ == "__main__": 22 | x, y = brennan_schwartz(0.01875, 0.20, 0.01, 0.012, 10., 23 | 10000) 24 | 25 | import matplotlib.pyplot as plt 26 | plt.plot(x,y) 27 | plt.show() -------------------------------------------------------------------------------- /B03898_05_Codes/cir.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ Simulate interest rate path by the CIR model """ 9 | import math 10 | import numpy as np 11 | 12 | def cir(r0, K, theta, sigma, T=1., N=10,seed=777): 13 | np.random.seed(seed) 14 | dt = T/float(N) 15 | rates = [r0] 16 | for i in range(N): 17 | dr = K*(theta-rates[-1])*dt + \ 18 | sigma*math.sqrt(rates[-1])*np.random.normal() 19 | rates.append(rates[-1] + dr) 20 | return range(N+1), rates 21 | 22 | if __name__ == "__main__": 23 | x, y = cir(0.01875, 0.20, 0.01, 0.012, 10., 200) 24 | 25 | import matplotlib.pyplot as plt 26 | plt.plot(x,y) 27 | plt.show() 28 | -------------------------------------------------------------------------------- /B03898_05_Codes/exact_zcb.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | import numpy as np 9 | 10 | """ Get zero coupon bond price by Vasicek model """ 11 | def exact_zcb(theta, kappa, sigma, tau, r0=0.): 12 | B = (1 - np.exp(-kappa*tau)) / kappa 13 | A = np.exp((theta-(sigma**2)/(2*(kappa**2))) * 14 | (B-tau) - (sigma**2)/(4*kappa)*(B**2)) 15 | return A * np.exp(-r0*B) 16 | 17 | import math 18 | def exercise_value(K, R, t): 19 | return K*math.exp(-R*t) 20 | 21 | 22 | if __name__ == "__main__": 23 | Ts = np.r_[0.0:25.5:0.5] 24 | zcbs = [exact_zcb(0.5, 0.02, 0.03, t, 0.015) for t in Ts] 25 | 26 | import matplotlib.pyplot as plt 27 | plt.title("Zero Coupon Bond (ZCB) Values by Time") 28 | plt.plot(Ts, zcbs, label='ZCB') 29 | plt.ylabel("Value ($)") 30 | plt.xlabel("Time in years") 31 | plt.legend() 32 | plt.grid(True) 33 | plt.show() 34 | 35 | Ks = [exercise_value(0.95, 0.015, t) for t in Ts] 36 | plt.title("Zero Coupon Bond (ZCB) " 37 | "and Strike (K) Values by Time") 38 | plt.plot(Ts, zcbs, label='ZCB') 39 | plt.plot(Ts, Ks, label='K', linestyle="--", marker=".") 40 | plt.ylabel("Value ($)") 41 | plt.xlabel("Time in years") 42 | plt.legend() 43 | plt.grid(True) 44 | plt.show() 45 | 46 | -------------------------------------------------------------------------------- /B03898_05_Codes/forward_rates.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | 9 | """ 10 | Get a list of forward rates 11 | starting from the second time period 12 | """ 13 | 14 | 15 | class ForwardRates(object): 16 | 17 | def __init__(self): 18 | self.forward_rates = [] 19 | self.spot_rates = dict() 20 | 21 | def add_spot_rate(self, T, spot_rate): 22 | self.spot_rates[T] = spot_rate 23 | 24 | def __calculate_forward_rate___(self, T1, T2): 25 | R1 = self.spot_rates[T1] 26 | R2 = self.spot_rates[T2] 27 | forward_rate = (R2*T2 - R1*T1)/(T2 - T1) 28 | return forward_rate 29 | 30 | def get_forward_rates(self): 31 | periods = sorted(self.spot_rates.keys()) 32 | for T2, T1 in zip(periods, periods[1:]): 33 | forward_rate = \ 34 | self.__calculate_forward_rate___(T1, T2) 35 | self.forward_rates.append(forward_rate) 36 | 37 | return self.forward_rates 38 | 39 | if __name__ == "__main__": 40 | fr = ForwardRates() 41 | fr.add_spot_rate(0.25, 10.127) 42 | fr.add_spot_rate(0.50, 10.469) 43 | fr.add_spot_rate(1.00, 10.536) 44 | fr.add_spot_rate(1.50, 10.681) 45 | fr.add_spot_rate(2.00, 10.808) 46 | print fr.get_forward_rates() -------------------------------------------------------------------------------- /B03898_05_Codes/rendleman_bartter.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ Simulate interest rate path by the Rendleman-Barter model """ 9 | import numpy as np 10 | 11 | def rendleman_bartter(r0, theta, sigma, T=1.,N=10,seed=777): 12 | np.random.seed(seed) 13 | dt = T/float(N) 14 | rates = [r0] 15 | for i in range(N): 16 | dr = theta*rates[-1]*dt + \ 17 | sigma*rates[-1]*np.random.normal() 18 | rates.append(rates[-1] + dr) 19 | return range(N+1), rates 20 | 21 | if __name__ == "__main__": 22 | x, y = rendleman_bartter(0.01875, 0.01, 0.012, 10., 200) 23 | 24 | import matplotlib.pyplot as plt 25 | plt.plot(x,y) 26 | plt.show() -------------------------------------------------------------------------------- /B03898_05_Codes/vasicek.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ Simulate interest rate path by the Vasicek model """ 9 | import numpy as np 10 | 11 | def vasicek(r0, K, theta, sigma, T=1., N=10, seed=777): 12 | np.random.seed(seed) 13 | dt = T/float(N) 14 | rates = [r0] 15 | for i in range(N): 16 | dr = K*(theta-rates[-1])*dt + sigma*np.random.normal() 17 | rates.append(rates[-1] + dr) 18 | return range(N+1), rates 19 | 20 | if __name__ == "__main__": 21 | x, y = vasicek(0.01875, 0.20, 0.01, 0.012, 10., 200) 22 | 23 | import matplotlib.pyplot as plt 24 | plt.plot(x,y) 25 | plt.show() -------------------------------------------------------------------------------- /B03898_05_Codes/zero_coupon_bond.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | def zero_coupon_bond(par, y, t): 9 | """ 10 | Price a zero coupon bond. 11 | 12 | Par - face value of the bond. 13 | y - annual yield or rate of the bond. 14 | t - time to maturity in years. 15 | """ 16 | return par/(1+y)**t 17 | 18 | print zero_coupon_bond(100, 0.05, 5) -------------------------------------------------------------------------------- /B03898_06_codes/README: -------------------------------------------------------------------------------- 1 | README 2 | ====== 3 | Run "Chapter 6 Notebook.ipynb" on IPython Notebook. 4 | ====== -------------------------------------------------------------------------------- /B03898_07_Codes/B03898_07_01.txt: -------------------------------------------------------------------------------- 1 | README 2 | ====== 3 | This file contains Terminal commands. 4 | ====== 5 | 6 | hadoop fs -copyFromLocal /home/cloudera/Downloads/pg4300.txt pg4300.txt 7 | 8 | hadoop fs -ls 9 | 10 | -------------------------------------------------------------------------------- /B03898_07_Codes/B03898_07_02.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | Save this file as mapper.py. 6 | ====== 7 | """ 8 | 9 | #!/usr/bin/python 10 | import sys 11 | 12 | for line in sys.stdin: 13 | for word in line.strip().split(): 14 | print "%s\t%d" % (word, 1) 15 | -------------------------------------------------------------------------------- /B03898_07_Codes/B03898_07_03.txt: -------------------------------------------------------------------------------- 1 | README 2 | ====== 3 | This file contains Terminal commands. 4 | ====== 5 | 6 | chmod +x /home/cloudera/word_count/mapper.py 7 | -------------------------------------------------------------------------------- /B03898_07_Codes/B03898_07_04.py: -------------------------------------------------------------------------------- 1 | """" README ====== This file contains Python codes. Save this file as reduce.py. ====== """ #!/usr/bin/python import sys current_word = None current_count = 1 for line in sys.stdin: word, count = line.strip().split('\t') if current_word: if word == current_word: current_count += int(count) else: print "%s\t%d" % (current_word, current_count) current_count = 1 current_word = word if current_count > 1: print "%s\t%d" % (current_word, current_count) -------------------------------------------------------------------------------- /B03898_07_Codes/B03898_07_05.txt: -------------------------------------------------------------------------------- 1 | README 2 | ====== 3 | This file contains Terminal commands. 4 | ====== 5 | 6 | chmod +x /home/cloudera/word_count/reduce.py -------------------------------------------------------------------------------- /B03898_07_Codes/B03898_07_06.txt: -------------------------------------------------------------------------------- 1 | README 2 | ====== 3 | This file contains Terminal commands. 4 | ====== 5 | 6 | echo "foo foo quux labs foo bar quux" | /home/cloudera/word_count/mapper.py 7 | 8 | echo "foo foo quux labs foo bar quux" | /home/cloudera/word_count/mapper.py | sort -k1,1 | /home/cloudera/word_count/reduce.py 9 | 10 | hadoop jar \ 11 | /usr/lib/hadoop-0.20-mapreduce/contrib/streaming/hadoop-streaming-2.5.0-mr1-cdh5.3.0.jar \ 12 | -file /home/cloudera/word_count/mapper.py \ 13 | -mapper /home/cloudera/word_count/mapper.py \ 14 | -file /home/cloudera/word_count/reduce.py \ 15 | -reducer /home/cloudera/word_count/reduce.py \ 16 | -input pg4300.txt \ 17 | -output pg4300-output 18 | 19 | hadoop fs -ls 20 | 21 | hadoop fs -ls pg4300-output 22 | 23 | hadoop fs -cat pg4300-output/part-00000 -------------------------------------------------------------------------------- /B03898_07_Codes/B03898_07_07.txt: -------------------------------------------------------------------------------- 1 | README 2 | ====== 3 | This file contains Terminal commands. 4 | ====== 5 | 6 | hadoop fs -copyFromLocal /home/cloudera/Downloads/ibm.csv ibm.csv 7 | 8 | hadoop fs -ls -------------------------------------------------------------------------------- /B03898_07_Codes/B03898_07_08.py: -------------------------------------------------------------------------------- 1 | """" README ====== This file contains Python codes. Save this file as mapper.py. in the directory: /home/cloudera/stock/ ====== """ #!/usr/bin/python import sys is_first_line = True for line in sys.stdin: if is_first_line: is_first_line = False continue row = line.split(',') open_price = float(row[1]) close_price = float(row[-3]) change = (open_price-close_price)/open_price * 100 change_text = str(round(change,1)) + "%" print "%s\t%d" % (change_text, 1) -------------------------------------------------------------------------------- /B03898_07_Codes/B03898_07_09.txt: -------------------------------------------------------------------------------- 1 | README ====== This file contains Terminal commands. ====== chmod +x /home/cloudera/stock/mapper.py chmod +x /home/cloudera/stock/reduce.py hadoop jar \ /usr/lib/hadoop-0.20-mapreduce/contrib/streaming/hadoop-streaming-2.5.0-mr1-cdh5.3.0.jar \ -file /home/cloudera/stock/mapper.py \ -mapper /home/cloudera/stock/mapper.py \ -file /home/cloudera/stock/reduce.py \ -reducer /home/cloudera/stock/reduce.py \ -input ibm.csv \ -output stock-output hadoop fs -copyToLocal stock-output/part-00000 /home/cloudera/stock/ -------------------------------------------------------------------------------- /B03898_07_Codes/B03898_07_10.txt: -------------------------------------------------------------------------------- 1 | README 2 | ====== 3 | This file contains Terminal commands. 4 | ====== 5 | 6 | sudo yum install python-matplotlib 7 | -------------------------------------------------------------------------------- /B03898_07_Codes/B03898_07_11.py: -------------------------------------------------------------------------------- 1 | """" README ====== This file contains Python codes. Save this file as analysis.py. in the directory: /home/cloudera/stock/ ====== """ import matplotlib.pyplot as plt with open('/home/cloudera/stock/part-00000', 'rb') as f: x, y = [], [] for line in f.readlines(): data = line.split() x.append(float(data[0].strip('%'))) y.append(float(data[1])) print "Max:", max(x) print "Min:", min(x) plt.bar(x, y, width=0.1) plt.show() -------------------------------------------------------------------------------- /B03898_07_Codes/B03898_07_12.txt: -------------------------------------------------------------------------------- 1 | README 2 | ====== 3 | This file contains Terminal commands. 4 | ====== 5 | 6 | python /home/cloudera/stock/analysis.py -------------------------------------------------------------------------------- /B03898_07_Codes/B03898_07_13.txt: -------------------------------------------------------------------------------- 1 | README 2 | ====== 3 | This file contains Terminal commands. 4 | ====== 5 | 6 | mkdir -p data/db 7 | 8 | mongod –dbpath data/db -------------------------------------------------------------------------------- /B03898_07_Codes/B03898_07_14.py: -------------------------------------------------------------------------------- 1 | """ README ====== This file contains Python codes. ====== """ import pymongo try: client = pymongo.MongoClient("localhost", 27017) print "Connected successfully!!!" except pymongo.errors.ConnectionFailure, e: print "Could not connect to MongoDB: %s" % e -------------------------------------------------------------------------------- /B03898_07_Codes/B03898_07_15.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | import datetime as dt 9 | import pymongo 10 | 11 | client = pymongo.MongoClient("localhost", 27017) 12 | ticks_db = client.ticks_db 13 | aapl_collection = ticks_db.aapl 14 | 15 | tick = {"ticker": "aapl", 16 | "time": dt.datetime(2014, 11, 17, 10, 0, 0), 17 | "open": 115.58, 18 | "high": 116.08, 19 | "low": 114.49, 20 | "last": 114.96, 21 | "vol": 1900000} 22 | 23 | tick_id = aapl_collection.insert(tick) 24 | print tick_id 25 | print ticks_db.collection_names() 26 | 27 | print aapl_collection.find_one() 28 | print aapl_collection.find_one({"time": dt.datetime(2014, 11, 17, 10, 0, 0)}) 29 | 30 | from bson.objectid import ObjectId 31 | print aapl_collection.find_one({"_id": \ 32 | ObjectId("548490486d3ba7178b6c36ba")}) 33 | 34 | aapl_collection.remove() 35 | 36 | aapl_collection.insert([tick, 37 | {"ticker": "aapl", 38 | "time": dt.datetime(2014, 11, 17, 10, 1, 0), 39 | "open": 115.58, 40 | "high": 116.08, 41 | "low": 114.49, 42 | "last": 115.00, 43 | "vol": 2000000}, 44 | {"ticker": "aapl", 45 | "time": dt.datetime(2014, 11, 17, 10, 2, 0), 46 | "open": 115.58, 47 | "high": 116.08, 48 | "low": 113.49, 49 | "last": 115.00, 50 | "vol": 2100000}]) 51 | 52 | print aapl_collection.count() 53 | print aapl_collection.find({"open": 115.58}).count() 54 | 55 | for aapl_tick in aapl_collection.find(): 56 | print aapl_tick 57 | 58 | cutoff_time = dt.datetime(2014, 11, 17, 10, 2, 0) 59 | for tick in aapl_collection.find( 60 | {"time": {"$lt": cutoff_time}}).sort("time"): 61 | print tick 62 | 63 | sorted_ticks = aapl_collection.find().sort( 64 | [("time", pymongo.DESCENDING)]) 65 | for tick in sorted_ticks: 66 | print tick 67 | 68 | -------------------------------------------------------------------------------- /B03898_08_codes/AlgoSystem.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ Implementing the Mean-Reverting Algorithm """ 9 | from ib.ext.Contract import Contract 10 | from ib.ext.Order import Order 11 | from ib.opt import Connection, message 12 | import time 13 | import pandas as pd 14 | import datetime as dt 15 | 16 | 17 | class AlgoSystem: 18 | def __init__(self, symbol, qty, resample_interval, 19 | averaging_period=5, port=7496): 20 | self.client_id = 1 21 | self.order_id = 1 22 | self.qty = qty 23 | self.symbol_id, self.symbol = 0, symbol 24 | self.resample_interval = resample_interval 25 | self.averaging_period = averaging_period 26 | self.port = port 27 | self.tws_conn = None 28 | self.bid_price, self.ask_price = 0, 0 29 | self.last_prices = pd.DataFrame(columns=[self.symbol_id]) 30 | self.average_price = 0 31 | self.is_position_opened = False 32 | self.account_code = None 33 | self.unrealized_pnl, self.realized_pnl = 0, 0 34 | self.position = 0 35 | 36 | def error_handler(self, msg): 37 | if msg.typeName == "error" and msg.id != -1: 38 | print "Server Error:", msg 39 | 40 | def server_handler(self, msg): 41 | if msg.typeName == "nextValidId": 42 | self.order_id = msg.orderId 43 | elif msg.typeName == "managedAccounts": 44 | self.account_code = msg.accountsList 45 | elif msg.typeName == "updatePortfolio" \ 46 | and msg.contract.m_symbol == self.symbol: 47 | self.unrealized_pnl = msg.unrealizedPNL 48 | self.realized_pnl = msg.realizedPNL 49 | self.position = msg.position 50 | elif msg.typeName == "error" and msg.id != -1: 51 | return 52 | 53 | def tick_event(self, msg): 54 | if msg.field == 1: 55 | self.bid_price = msg.price 56 | elif msg.field == 2: 57 | self.ask_price = msg.price 58 | elif msg.field == 4: 59 | self.last_prices.loc[dt.datetime.now()] = msg.price 60 | resampled_prices = \ 61 | self.last_prices.resample(self.resample_interval, 62 | how='last', 63 | fill_method="ffill") 64 | self.average_price = resampled_prices.tail( 65 | self.averaging_period).mean()[0] 66 | 67 | self.perform_trade_logic() 68 | 69 | def perform_trade_logic(self): 70 | # Is buying at the market lower than the average price? 71 | is_buy_signal = self.ask_price < self.average_price 72 | 73 | # Is selling at the market higher than the average price? 74 | is_sell_signal = self.bid_price > self.average_price 75 | 76 | # Print signal values on every tick 77 | print dt.datetime.now(), \ 78 | " BUY/SELL? ", is_buy_signal, "/", is_sell_signal, \ 79 | " Avg:", self.average_price 80 | 81 | # Use generated signals, if any, to open a position 82 | if self.average_price != 0 \ 83 | and self.bid_price != 0 \ 84 | and self.ask_price != 0 \ 85 | and self.position == 0 \ 86 | and not self.is_position_opened: 87 | 88 | if is_sell_signal: 89 | self.place_market_order(self.symbol, self.qty, False) 90 | self.is_position_opened = True 91 | 92 | elif is_buy_signal: 93 | self.place_market_order(self.symbol, self.qty, True) 94 | self.is_position_opened = True 95 | 96 | # If position is already opened, use generated signals 97 | # to close the position. 98 | elif self.is_position_opened: 99 | 100 | if self.position > 0 and is_sell_signal: 101 | self.place_market_order(self.symbol, self.qty, False) 102 | self.is_position_opened = False 103 | 104 | elif self.position <0 and is_buy_signal: 105 | self.place_market_order(self.symbol, self.qty, True) 106 | self.is_position_opened = False 107 | 108 | # When the position is open, keep track of our 109 | # unrealized and realized P&Ls 110 | self.monitor_position() 111 | 112 | def monitor_position(self): 113 | print 'Position:%s UPnL:%s RPnL:%s' % (self.position, 114 | self.unrealized_pnl, 115 | self.realized_pnl) 116 | 117 | def place_market_order(self, symbol, qty, is_buy): 118 | contract = self.create_contract(symbol, 119 | 'STK', 120 | 'SMART', 121 | 'SMART', 122 | 'USD') 123 | buysell = 'BUY' if is_buy else 'SELL' 124 | order = self.create_order('MKT', qty, buysell) 125 | self.tws_conn.placeOrder(self.order_id, contract, order) 126 | self.order_id += 1 127 | 128 | print "Placed order", qty, "of", symbol, "to", buysell 129 | 130 | def create_contract(self, symbol, sec_type, exch, prim_exch, curr): 131 | contract = Contract() 132 | contract.m_symbol = symbol 133 | contract.m_secType = sec_type 134 | contract.m_exchange = exch 135 | contract.m_primaryExch = prim_exch 136 | contract.m_currency = curr 137 | return contract 138 | 139 | def create_order(self, order_type, quantity, action): 140 | order = Order() 141 | order.m_orderType = order_type 142 | order.m_totalQuantity = quantity 143 | order.m_action = action 144 | return order 145 | 146 | def request_market_data(self, symbol_id, symbol): 147 | contract = self.create_contract(symbol, 148 | 'STK', 149 | 'SMART', 150 | 'SMART', 151 | 'USD') 152 | self.tws_conn.reqMktData(symbol_id, contract, '', False) 153 | time.sleep(1) 154 | 155 | def cancel_market_data(self, symbol): 156 | self.tws_conn.cancelMktData(symbol) 157 | time.sleep(1) 158 | 159 | def request_account_updates(self, account_code): 160 | self.tws_conn.reqAccountUpdates(True, account_code) 161 | 162 | def connect_to_tws(self): 163 | self.tws_conn = Connection.create(port=self.port, 164 | clientId=self.client_id) 165 | self.tws_conn.connect() 166 | 167 | def disconnect_from_tws(self): 168 | if self.tws_conn is not None: 169 | self.tws_conn.disconnect() 170 | 171 | def register_callback_functions(self): 172 | # Assign server messages handling function. 173 | self.tws_conn.registerAll(self.server_handler) 174 | 175 | # Assign error handling function. 176 | self.tws_conn.register(self.error_handler, 'Error') 177 | 178 | # Register market data events. 179 | self.tws_conn.register(self.tick_event, 180 | message.tickPrice, 181 | message.tickSize) 182 | 183 | def start(self): 184 | try: 185 | self.connect_to_tws() 186 | self.register_callback_functions() 187 | self.request_market_data(self.symbol_id, self.symbol) 188 | self.request_account_updates(self.account_code) 189 | 190 | while True: 191 | time.sleep(1) 192 | 193 | except Exception, e: 194 | print "Error:", e 195 | self.cancel_market_data(self.symbol) 196 | 197 | finally: 198 | print "disconnected" 199 | self.disconnect_from_tws() 200 | 201 | if __name__ == "__main__": 202 | system = AlgoSystem("FB", 100, "30s", 5) 203 | system.start() -------------------------------------------------------------------------------- /B03898_08_codes/ForexSystem.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ 9 | Implementing the trend-following algorithm 10 | for trading foreign currencies 11 | """ 12 | import oandapy 13 | from datetime import datetime 14 | import pandas as pd 15 | 16 | 17 | class ForexSystem(oandapy.Streamer): 18 | def __init__(self, *args, **kwargs): 19 | oandapy.Streamer.__init__(self, *args, **kwargs) 20 | self.oanda = oandapy.API(kwargs["environment"], 21 | kwargs["access_token"]) 22 | 23 | self.instrument = None 24 | self.account_id = None 25 | self.qty = 0 26 | self.resample_interval = '10s' 27 | self.mean_period_short = 5 28 | self.mean_period_long = 20 29 | self.buy_threshold = 1.0 30 | self.sell_threshold = 1.0 31 | 32 | self.prices = pd.DataFrame() 33 | self.beta = 0 34 | self.is_position_opened = False 35 | self.opening_price = 0 36 | self.executed_price = 0 37 | self.unrealized_pnl = 0 38 | self.realized_pnl = 0 39 | self.position = 0 40 | self.dt_format = "%Y-%m-%dT%H:%M:%S.%fZ" 41 | 42 | def begin(self, **params): 43 | self.instrument = params["instruments"] 44 | self.account_id = params["accountId"] 45 | self.qty = params["qty"] 46 | self.resample_interval = params["resample_interval"] 47 | self.mean_period_short = params["mean_period_short"] 48 | self.mean_period_long = params["mean_period_long"] 49 | self.buy_threshold = params["buy_threshold"] 50 | self.sell_threshold = params["sell_threshold"] 51 | 52 | self.start(**params) # Start streaming prices 53 | 54 | def on_success(self, data): 55 | time, symbol, bid, ask = self.parse_tick_data( 56 | data["tick"]) 57 | self.tick_event(time, symbol, bid, ask) 58 | 59 | def parse_tick_data(self, dict_data): 60 | time = datetime.strptime(dict_data["time"], 61 | self.dt_format) 62 | ask = float(dict_data["ask"]) 63 | bid = float(dict_data["bid"]) 64 | instrument = dict_data["instrument"] 65 | return time, instrument, bid, ask 66 | 67 | def tick_event(self, time, symbol, bid, ask): 68 | midprice = (ask+bid)/2. 69 | self.prices.loc[time, symbol] = midprice 70 | 71 | resampled_prices = self.prices.resample( 72 | self.resample_interval, 73 | how='last', 74 | fill_method="ffill") 75 | 76 | mean_short = resampled_prices.tail( 77 | self.mean_period_short).mean()[0] 78 | mean_long = resampled_prices.tail( 79 | self.mean_period_long).mean()[0] 80 | self.beta = mean_short / mean_long 81 | 82 | self.perform_trade_logic(self.beta) 83 | self.calculate_unrealized_pnl(bid, ask) 84 | self.print_status() 85 | 86 | def perform_trade_logic(self, beta): 87 | 88 | if beta > self.buy_threshold: 89 | if not self.is_position_opened \ 90 | or self.position < 0: 91 | self.check_and_send_order(True) 92 | 93 | elif beta < self.sell_threshold: 94 | if not self.is_position_opened \ 95 | or self.position > 0: 96 | self.check_and_send_order(False) 97 | 98 | def check_and_send_order(self, is_buy): 99 | if self.place_market_order(self.qty, is_buy): 100 | # Update position upon successful order 101 | if is_buy: 102 | self.position += self.qty 103 | else: 104 | self.position -= self.qty 105 | 106 | if self.position == 0: 107 | self.is_position_opened = False 108 | self.calculate_realized_pnl(is_buy) 109 | else: 110 | self.opening_price = self.executed_price 111 | self.is_position_opened = True 112 | 113 | def calculate_realized_pnl(self, is_buy): 114 | self.realized_pnl += self.qty * ( 115 | (self.opening_price - self.executed_price) 116 | if is_buy else 117 | (self.executed_price - self.opening_price)) 118 | 119 | def calculate_unrealized_pnl(self, bid, ask): 120 | if self.is_position_opened: 121 | # Retrieve position from server 122 | pos = self.oanda.get_position(self.account_id, 123 | self.instrument) 124 | units = pos["units"] 125 | side = pos["side"] 126 | avg_price = float(pos["avgPrice"]) 127 | 128 | self.unrealized_pnl = units * ( 129 | (bid - avg_price) 130 | if (side == "buy") 131 | else (avg_price - ask)) 132 | else: 133 | self.unrealized_pnl = 0 134 | 135 | def place_market_order(self, qty, is_buy): 136 | side = "buy" if is_buy else "sell" 137 | response = self.oanda.create_order( 138 | account_id, 139 | instrument=self.instrument, 140 | units=qty, 141 | side=side, 142 | type='market') 143 | 144 | if response is not None: 145 | self.executed_price = float(response["price"]) 146 | print "Placed order %s %s %s at market." % (side, qty, self.instrument) 147 | return True # Order is successful 148 | 149 | return False # Order is unsuccessful 150 | 151 | def print_status(self): 152 | print "[%s] %s pos=%s beta=%s RPnL=%s UPnL=%s" % ( 153 | datetime.now().time(), 154 | self.instrument, 155 | self.position, 156 | round(self.beta, 5), 157 | self.realized_pnl, 158 | self.unrealized_pnl) 159 | 160 | def on_error(self, data): 161 | print data 162 | self.disconnect() 163 | 164 | if __name__ == "__main__": 165 | key = "4c7718c7e03d472c2369abf1cb7ceddb-" \ 166 | "142b7d845d68844e853bb95c63f1c8b9" 167 | account_id = 6858884 168 | system = ForexSystem(environment="practice", access_token=key) 169 | system.begin(accountId=account_id, 170 | instruments="EUR_USD", 171 | qty=1000, 172 | resample_interval="10s", 173 | mean_period_short=5, 174 | mean_period_long=20, 175 | buy_threshold=1., 176 | sell_threshold=1.) -------------------------------------------------------------------------------- /B03898_08_codes/calculating_var.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | import datetime as dt 9 | import numpy as np 10 | import pandas.io.data as web 11 | from scipy.stats import norm 12 | 13 | 14 | def calculate_daily_VaR(P, prob, mean, sigma, 15 | days_per_year=252.): 16 | min_ret = norm.ppf(1-prob, 17 | mean/days_per_year, 18 | sigma/np.sqrt(days_per_year)) 19 | return P - P*(min_ret+1) 20 | 21 | if __name__ == "__main__": 22 | start = dt.datetime(2013, 12, 1) 23 | end = dt.datetime(2014, 12, 1) 24 | 25 | prices = web.DataReader("AAPL", "yahoo", start, end) 26 | returns = prices["Adj Close"].pct_change().dropna() 27 | 28 | portvolio_value = 100000000.00 29 | confidence = 0.95 30 | mu = np.mean(returns) 31 | sigma = np.std(returns) 32 | 33 | VaR = calculate_daily_VaR(portvolio_value, confidence, 34 | mu, sigma) 35 | print "Value-at-Risk:", round(VaR, 2) -------------------------------------------------------------------------------- /B03898_08_codes/exploring_oanda_api.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | # Enter your account ID and API key here. 9 | account_id = 6858884 10 | key = "4c7718c7e03d472c2369abf1cb7ceddb-" \ 11 | "142b7d845d68844e853bb95c63f1c8b9" 12 | 13 | """ Fetch rates """ 14 | import oandapy 15 | 16 | oanda = oandapy.API(environment="practice", access_token=key) 17 | response = oanda.get_prices(instruments="EUR_USD") 18 | print response 19 | 20 | prices = response["prices"] 21 | bidding_price = float(prices[0]["bid"]) 22 | asking_price = float(prices[0]["ask"]) 23 | instrument = prices[0]["instrument"] 24 | time = prices[0]["time"] 25 | print "[%s] %s bid=%s ask=%s" % ( 26 | time, instrument, bidding_price, asking_price) 27 | 28 | """ Send an order """ 29 | from datetime import datetime, timedelta 30 | 31 | # set the trade to expire after one day 32 | trade_expire = datetime.now() + timedelta(days=1) 33 | trade_expire = trade_expire.isoformat("T") + "Z" 34 | 35 | response = oanda.create_order( 36 | account_id, 37 | instrument="EUR_USD", 38 | units=1000, 39 | side="sell", # "buy" or "sell" 40 | type="limit", 41 | price=1.105, 42 | expiry=trade_expire) 43 | print response -------------------------------------------------------------------------------- /B03898_08_codes/oandapy.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | import json 9 | import requests 10 | 11 | """ OANDA API wrapper for OANDA's REST API """ 12 | 13 | """ EndpointsMixin provides a mixin for the API instance 14 | Parameters that need to be embedded in the API url just need to be passed as a keyword argument. 15 | E.g. oandapy_instance.get_instruments(instruments="EUR_USD") 16 | """ 17 | class EndpointsMixin(object): 18 | 19 | """Rates""" 20 | 21 | def get_instruments(self, account_id, **params): 22 | """ Get an instrument list 23 | Docs: http://developer.oanda.com/docs/v1/rates/#get-an-instrument-list 24 | """ 25 | params['accountId'] = account_id 26 | endpoint = 'v1/instruments' 27 | return self.request(endpoint, params=params) 28 | 29 | def get_prices(self, **params): 30 | """ Get current prices 31 | Docs: http://developer.oanda.com/docs/v1/rates/#get-current-prices 32 | """ 33 | endpoint = 'v1/prices' 34 | return self.request(endpoint, params=params) 35 | 36 | def get_history(self, **params): 37 | """ Retrieve instrument history 38 | Docs: http://developer.oanda.com/docs/v1/rates/#retrieve-instrument-history 39 | """ 40 | endpoint = 'v1/candles' 41 | return self.request(endpoint, params=params) 42 | 43 | """Accounts""" 44 | 45 | def create_account(self, **params): 46 | """ Create an account. Valid only in sandbox. 47 | Docs: http://developer.oanda.com/docs/v1/accounts/#get-accounts-for-a-user 48 | """ 49 | endpoint = 'v1/accounts' 50 | return self.request(endpoint, "POST", params=params) 51 | 52 | def get_accounts(self, **params): 53 | """ Get accounts for a user. 54 | Docs: http://developer.oanda.com/docs/v1/accounts/#get-accounts-for-a-user 55 | """ 56 | endpoint = 'v1/accounts' 57 | return self.request(endpoint, params=params) 58 | 59 | def get_account(self, account_id, **params): 60 | """ Get account information 61 | Docs: http://developer.oanda.com/docs/v1/accounts/#get-account-information 62 | """ 63 | endpoint = 'v1/accounts/%s' % (account_id) 64 | return self.request(endpoint, params=params) 65 | 66 | """Orders""" 67 | 68 | def get_orders(self, account_id, **params): 69 | """ Get orders for an account 70 | Docs: http://developer.oanda.com/docs/v1/orders/#get-orders-for-an-account 71 | """ 72 | endpoint = 'v1/accounts/%s/orders' % (account_id) 73 | return self.request(endpoint, params=params) 74 | 75 | def create_order(self, account_id, **params): 76 | """ Create a new order 77 | Docs: http://developer.oanda.com/docs/v1/orders/#create-a-new-order 78 | """ 79 | endpoint = 'v1/accounts/%s/orders' % (account_id) 80 | return self.request(endpoint, "POST", params=params) 81 | 82 | def get_order(self, account_id, order_id, **params): 83 | """ Get information for an order 84 | Docs: http://developer.oanda.com/docs/v1/orders/#get-information-for-an-order 85 | """ 86 | endpoint = 'v1/accounts/%s/orders/%s' % (account_id, order_id) 87 | return self.request(endpoint, params=params) 88 | 89 | def modify_order(self, account_id, order_id, **params): 90 | """ Modify an existing order 91 | Docs: http://developer.oanda.com/docs/v1/orders/#modify-an-existing-order 92 | """ 93 | endpoint = 'v1/accounts/%s/orders/%s' % (account_id, order_id) 94 | return self.request(endpoint, "PATCH", params=params) 95 | 96 | def close_order(self, account_id, order_id, **params): 97 | """ Close an order 98 | Docs: http://developer.oanda.com/docs/v1/orders/#close-an-order 99 | """ 100 | endpoint = 'v1/accounts/%s/orders/%s' % (account_id, order_id) 101 | return self.request(endpoint, "DELETE", params=params) 102 | 103 | """Trades""" 104 | 105 | def get_trades(self, account_id, **params): 106 | """ Get a list of open trades 107 | Docs: http://developer.oanda.com/docs/v1/trades/#get-a-list-of-open-trades 108 | """ 109 | endpoint = 'v1/accounts/%s/trades' % (account_id) 110 | return self.request(endpoint, params=params) 111 | 112 | def get_trade(self, account_id, trade_id, **params): 113 | """ Get information on a specific trade 114 | Docs: http://developer.oanda.com/docs/v1/trades/#get-information-on-a-specific-trade 115 | """ 116 | endpoint = 'v1/accounts/%s/trades/%s' % (account_id, trade_id) 117 | return self.request(endpoint, params=params) 118 | 119 | def modify_trade(self, account_id, trade_id, **params): 120 | """ Modify an existing trade 121 | Docs: http://developer.oanda.com/docs/v1/trades/#modify-an-existing-trade 122 | """ 123 | endpoint = 'v1/accounts/%s/trades/%s' % (account_id, trade_id) 124 | return self.request(endpoint, "PATCH", params=params) 125 | 126 | def close_trade(self, account_id, trade_id, **params): 127 | """ Close an open trade 128 | Docs: http://developer.oanda.com/docs/v1/trades/#close-an-open-trade 129 | """ 130 | endpoint = 'v1/accounts/%s/trades/%s' % (account_id, trade_id) 131 | return self.request(endpoint, "DELETE", params=params) 132 | 133 | """Positions""" 134 | 135 | def get_positions(self, account_id, **params): 136 | """ Get a list of all open positions 137 | Docs: http://developer.oanda.com/docs/v1/positions/#get-a-list-of-all-open-positions 138 | """ 139 | endpoint = 'v1/accounts/%s/positions' % (account_id) 140 | return self.request(endpoint, params=params) 141 | 142 | def get_position(self, account_id, instrument, **params): 143 | """ Get the position for an instrument 144 | Docs: http://developer.oanda.com/docs/v1/positions/#get-the-position-for-an-instrument 145 | """ 146 | endpoint = 'v1/accounts/%s/positions/%s' % (account_id, instrument) 147 | return self.request(endpoint, params=params) 148 | 149 | def close_position(self, account_id, instrument, **params): 150 | """ Close an existing position 151 | Docs: http://developer.oanda.com/docs/v1/positions/#close-an-existing-position 152 | """ 153 | endpoint = 'v1/accounts/%s/positions/%s' % (account_id, instrument) 154 | return self.request(endpoint, "DELETE", params=params) 155 | 156 | """Transaction History""" 157 | 158 | def get_transaction_history(self, account_id, **params): 159 | """ Get transaction history 160 | Docs: http://developer.oanda.com/docs/v1/transactions/#get-transaction-history 161 | """ 162 | endpoint = 'v1/accounts/%s/transactions' % (account_id) 163 | return self.request(endpoint, params=params) 164 | 165 | def get_transaction(self, account_id, transaction_id): 166 | """ Get information for a transaction 167 | Docs: http://developer.oanda.com/docs/v1/transactions/#get-information-for-a-transaction 168 | """ 169 | endpoint = 'v1/accounts/%s/transactions/%s' % (account_id, transaction_id) 170 | return self.request(endpoint) 171 | 172 | """Forex Labs""" 173 | 174 | def get_eco_calendar(self, **params): 175 | """Returns up to 1 year of economic calendar info 176 | Docs: http://developer.oanda.com/rest-live/forex-labs/ 177 | """ 178 | endpoint = 'labs/v1/calendar' 179 | return self.request(endpoint, params=params) 180 | 181 | def get_historical_position_ratios(self, **params): 182 | """Returns up to 1 year of historical position ratios 183 | Docs: http://developer.oanda.com/rest-live/forex-labs/ 184 | """ 185 | endpoint = 'labs/v1/historical_position_ratios' 186 | return self.request(endpoint, params=params) 187 | 188 | def get_historical_spreads(self, **params): 189 | """Returns up to 1 year of spread information 190 | Docs: http://developer.oanda.com/rest-live/forex-labs/ 191 | """ 192 | endpoint = 'labs/v1/spreads' 193 | return self.request(endpoint, params=params) 194 | 195 | def get_commitments_of_traders(self, **params): 196 | """Returns up to 4 years of Commitments of Traders data from the CFTC 197 | Docs: http://developer.oanda.com/rest-live/forex-labs/ 198 | """ 199 | endpoint = 'labs/v1/commitments_of_traders' 200 | return self.request(endpoint, params=params) 201 | 202 | def get_orderbook(self, **params): 203 | """Returns up to 1 year of OANDA Order book data 204 | Docs: http://developer.oanda.com/rest-live/forex-labs/ 205 | """ 206 | endpoint = 'labs/v1/orderbook_data' 207 | return self.request(endpoint, params=params) 208 | 209 | 210 | """ Provides functionality for access to core OANDA API calls """ 211 | 212 | class API(EndpointsMixin, object): 213 | def __init__(self, environment="practice", access_token=None, headers=None): 214 | """Instantiates an instance of OandaPy's API wrapper 215 | :param environment: (optional) Provide the environment for oanda's REST api, either 'sandbox', 'practice', or 'live'. Default: practice 216 | :param access_token: (optional) Provide a valid access token if you have one. This is required if the environment is not sandbox. 217 | """ 218 | 219 | if environment == 'sandbox': 220 | self.api_url = 'http://api-sandbox.oanda.com' 221 | elif environment == 'practice': 222 | self.api_url = 'https://api-fxpractice.oanda.com' 223 | elif environment == 'live': 224 | self.api_url = 'https://api-fxtrade.oanda.com' 225 | 226 | self.access_token = access_token 227 | self.client = requests.Session() 228 | 229 | #personal token authentication 230 | if self.access_token: 231 | self.client.headers['Authorization'] = 'Bearer ' + self.access_token 232 | 233 | if headers: 234 | self.client.headers.update(headers) 235 | 236 | def request(self, endpoint, method='GET', params=None): 237 | """Returns dict of response from OANDA's open API 238 | :param endpoint: (required) OANDA API endpoint (e.g. v1/instruments) 239 | :type endpoint: string 240 | :param method: (optional) Method of accessing data, either GET or POST. (default GET) 241 | :type method: string 242 | :param params: (optional) Dict of parameters (if any) accepted the by OANDA API endpoint you are trying to access (default None) 243 | :type params: dict or None 244 | """ 245 | 246 | url = '%s/%s' % ( self.api_url, endpoint) 247 | 248 | method = method.lower() 249 | params = params or {} 250 | 251 | func = getattr(self.client, method) 252 | 253 | request_args = {} 254 | if method == 'get': 255 | request_args['params'] = params 256 | else: 257 | request_args['data'] = params 258 | 259 | try: 260 | response = func(url, **request_args) 261 | except requests.RequestException as e: 262 | print (str(e)) 263 | content = response.content.decode('utf-8') 264 | 265 | content = json.loads(content) 266 | 267 | # error message 268 | if response.status_code >= 400: 269 | raise OandaError(content) 270 | 271 | return content 272 | 273 | """HTTPS Streaming""" 274 | 275 | class Streamer(): 276 | """ Provides functionality for HTTPS Streaming 277 | Docs: http://developer.oanda.com/docs/v1/stream/#rates-streaming 278 | """ 279 | 280 | def __init__(self, environment="practice", access_token=None): 281 | """Instantiates an instance of OandaPy's streaming API wrapper. 282 | :param environment: (optional) Provide the environment for oanda's REST api, either 'practice', or 'live'. Default: practice 283 | :param access_token: (optional) Provide a valid access token if you have one. This is required if the environment is not sandbox. 284 | """ 285 | 286 | if environment == 'practice': 287 | self.api_url = 'https://stream-fxpractice.oanda.com/v1/prices' 288 | elif environment == 'live': 289 | self.api_url = 'https://stream-fxtrade.oanda.com/v1/prices' 290 | 291 | self.access_token = access_token 292 | self.client = requests.Session() 293 | self.client.stream = True 294 | self.connected = False 295 | 296 | #personal token authentication 297 | if self.access_token: 298 | self.client.headers['Authorization'] = 'Bearer ' + self.access_token 299 | 300 | def start(self, ignore_heartbeat=True, **params): 301 | """ Starts the stream with the given parameters 302 | :param accountId: (Required) The account that prices are applicable for. 303 | :param instruments: (Required) A (URL encoded) comma separated list of instruments to fetch prices for. 304 | :param ignore_heartbeat: (optional) Whether or not to display the heartbeat. Default: True 305 | """ 306 | self.connected = True 307 | 308 | request_args = {} 309 | request_args['params'] = params 310 | 311 | while self.connected: 312 | response = self.client.get(self.api_url, **request_args) 313 | 314 | if response.status_code != 200: 315 | self.on_error(response.content) 316 | 317 | for line in response.iter_lines(90): 318 | if not self.connected: 319 | break 320 | 321 | if line: 322 | data = json.loads(line.decode("utf-8")) 323 | if not (ignore_heartbeat and "heartbeat" in data): 324 | self.on_success(data) 325 | 326 | 327 | def on_success(self, data): 328 | """ Called when data is successfully retrieved from the stream 329 | Override this to handle your streaming data. 330 | :param data: response object sent from stream 331 | """ 332 | 333 | return True 334 | 335 | def on_error(self, data): 336 | """ Called when stream returns non-200 status code 337 | Override this to handle your streaming data. 338 | :param data: error response object sent from stream 339 | """ 340 | 341 | return 342 | 343 | def disconnect(self): 344 | """ Manually disconnects the streaming client 345 | """ 346 | self.connected = False 347 | 348 | 349 | """ Contains OANDA exception 350 | """ 351 | class OandaError(Exception): 352 | """ Generic error class, catches oanda response errors 353 | """ 354 | 355 | def __init__(self, error_response): 356 | msg = "OANDA API returned error code %s (%s) " % (error_response['code'], error_response['message']) 357 | 358 | super(OandaError, self).__init__(msg) 359 | -------------------------------------------------------------------------------- /B03898_08_codes/simple_order_routing.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | Requires IB TWS to run. 6 | ====== 7 | """ 8 | 9 | """ A Simple Order Routing Mechanism """ 10 | from ib.ext.Contract import Contract 11 | from ib.ext.Order import Order 12 | from ib.opt import Connection 13 | 14 | def error_handler(msg): 15 | print "Server Error:", msg 16 | 17 | def server_handler(msg): 18 | print "Server Msg:", msg.typeName, "-", msg 19 | 20 | def create_contract(symbol, sec_type, exch, prim_exch, curr): 21 | contract = Contract() 22 | contract.m_symbol = symbol 23 | contract.m_secType = sec_type 24 | contract.m_exchange = exch 25 | contract.m_primaryExch = prim_exch 26 | contract.m_currency = curr 27 | return contract 28 | 29 | def create_order(order_type, quantity, action): 30 | order = Order() 31 | order.m_orderType = order_type 32 | order.m_totalQuantity = quantity 33 | order.m_action = action 34 | return order 35 | 36 | if __name__ == "__main__": 37 | client_id = 1 38 | order_id = 122 39 | port = 7496 40 | tws_conn = None 41 | try: 42 | # Establish connection to TWS. 43 | tws_conn = Connection.create(port=port, 44 | clientId=client_id) 45 | tws_conn.connect() 46 | 47 | # Assign error handling function. 48 | tws_conn.register(error_handler, 'Error') 49 | 50 | # Assign server messages handling function. 51 | tws_conn.registerAll(server_handler) 52 | 53 | # Create AAPL contract and send order 54 | aapl_contract = create_contract('AAPL', 55 | 'STK', 56 | 'SMART', 57 | 'SMART', 58 | 'USD') 59 | 60 | # Go long 100 shares of AAPL 61 | aapl_order = create_order('MKT', 100, 'SELL') 62 | 63 | # Place order on IB TWS. 64 | tws_conn.placeOrder(order_id, aapl_contract, aapl_order) 65 | 66 | finally: 67 | # Disconnect from TWS 68 | if tws_conn is not None: 69 | tws_conn.disconnect() -------------------------------------------------------------------------------- /B03898_09_codes/B03898_09_01.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ Implementing a Backtesting System """ 9 | 10 | """ Store a single unit of data """ 11 | class TickData: 12 | def __init__(self, symbol, timestamp, 13 | last_price=0, total_volume=0): 14 | self.symbol = symbol 15 | self.timestamp = timestamp 16 | self.open_price = 0 17 | self.last_price = last_price 18 | self.total_volume = total_volume 19 | 20 | 21 | """ A container to store prices for a symbol """ 22 | class MarketData: 23 | def __init__(self): 24 | self.__recent_ticks__ = dict() 25 | 26 | def add_last_price(self, time, symbol, price, volume): 27 | tick_data = TickData(symbol, time, price, volume) 28 | self.__recent_ticks__[symbol] = tick_data 29 | 30 | def add_open_price(self, time, symbol, price): 31 | tick_data = self.get_existing_tick_data(symbol, time) 32 | tick_data.open_price = price 33 | 34 | def get_existing_tick_data(self, symbol, time): 35 | if not symbol in self.__recent_ticks__: 36 | tick_data = TickData(symbol, time) 37 | self.__recent_ticks__[symbol] = tick_data 38 | 39 | return self.__recent_ticks__[symbol] 40 | 41 | def get_last_price(self, symbol): 42 | return self.__recent_ticks__[symbol].last_price 43 | 44 | def get_open_price(self, symbol): 45 | return self.__recent_ticks__[symbol].open_price 46 | 47 | def get_timestamp(self, symbol): 48 | return self.__recent_ticks__[symbol].timestamp 49 | 50 | 51 | import pandas.io.data as web 52 | 53 | """ Download prices from an external data source """ 54 | class MarketDataSource: 55 | def __init__(self): 56 | self.event_tick = None 57 | self.ticker, self.source = None, None 58 | self.start, self.end = None, None 59 | self.md = MarketData() 60 | 61 | def start_market_simulation(self): 62 | data = web.DataReader(self.ticker, self.source, 63 | self.start, self.end) 64 | 65 | for time, row in data.iterrows(): 66 | self.md.add_last_price(time, self.ticker, 67 | row["Close"], row["Volume"]) 68 | self.md.add_open_price(time, self.ticker, row["Open"]) 69 | 70 | if not self.event_tick is None: 71 | self.event_tick(self.md) 72 | 73 | 74 | class Order: 75 | def __init__(self, timestamp, symbol, qty, is_buy, 76 | is_market_order, price=0): 77 | self.timestamp = timestamp 78 | self.symbol = symbol 79 | self.qty = qty 80 | self.price = price 81 | self.is_buy = is_buy 82 | self.is_market_order = is_market_order 83 | self.is_filled = False 84 | self.filled_price = 0 85 | self.filled_time = None 86 | self.filled_qty = 0 87 | 88 | 89 | class Position: 90 | def __init__(self): 91 | self.symbol = None 92 | self.buys, self.sells, self.net = 0, 0, 0 93 | self.realized_pnl = 0 94 | self.unrealized_pnl = 0 95 | self.position_value = 0 96 | 97 | def event_fill(self, timestamp, is_buy, qty, price): 98 | if is_buy: 99 | self.buys += qty 100 | else: 101 | self.sells += qty 102 | 103 | self.net = self.buys - self.sells 104 | changed_value = qty * price * (-1 if is_buy else 1) 105 | self.position_value += changed_value 106 | 107 | if self.net == 0: 108 | self.realized_pnl = self.position_value 109 | 110 | def update_unrealized_pnl(self, price): 111 | if self.net == 0: 112 | self.unrealized_pnl = 0 113 | else: 114 | self.unrealized_pnl = price * self.net + \ 115 | self.position_value 116 | 117 | return self.unrealized_pnl 118 | 119 | 120 | """ Base strategy for implementation """ 121 | class Strategy: 122 | def __init__(self): 123 | self.event_sendorder = None 124 | 125 | def event_tick(self, market_data): 126 | pass 127 | 128 | def event_order(self, order): 129 | pass 130 | 131 | def event_position(self, positions): 132 | pass 133 | 134 | def send_market_order(self, symbol, qty, is_buy, timestamp): 135 | if not self.event_sendorder is None: 136 | order = Order(timestamp, symbol, qty, is_buy, True) 137 | self.event_sendorder(order) 138 | 139 | 140 | """ 141 | Implementation of a mean-reverting strategy 142 | based on the Strategy class 143 | """ 144 | class MeanRevertingStrategy(Strategy): 145 | def __init__(self, symbol, 146 | lookback_intervals=20, 147 | buy_threshold=-1.5, 148 | sell_threshold=1.5): 149 | Strategy.__init__(self) 150 | self.symbol = symbol 151 | self.lookback_intervals = lookback_intervals 152 | self.buy_threshold = buy_threshold 153 | self.sell_threshold = sell_threshold 154 | self.prices = pd.DataFrame() 155 | self.is_long, self.is_short = False, False 156 | 157 | def event_position(self, positions): 158 | if self.symbol in positions: 159 | position = positions[self.symbol] 160 | self.is_long = True if position.net > 0 else False 161 | self.is_short = True if position.net < 0 else False 162 | 163 | def event_tick(self, market_data): 164 | self.store_prices(market_data) 165 | 166 | if len(self.prices) < self.lookback_intervals: 167 | return 168 | 169 | signal_value = self.calculate_z_score() 170 | timestamp = market_data.get_timestamp(self.symbol) 171 | 172 | if signal_value < self.buy_threshold: 173 | self.on_buy_signal(timestamp) 174 | elif signal_value > self.sell_threshold: 175 | self.on_sell_signal(timestamp) 176 | 177 | def store_prices(self, market_data): 178 | timestamp = market_data.get_timestamp(self.symbol) 179 | self.prices.loc[timestamp, "close"] = \ 180 | market_data.get_last_price(self.symbol) 181 | self.prices.loc[timestamp, "open"] = \ 182 | market_data.get_open_price(self.symbol) 183 | 184 | def calculate_z_score(self): 185 | self.prices = self.prices[-self.lookback_intervals:] 186 | returns = self.prices["close"].pct_change().dropna() 187 | z_score = ((returns-returns.mean())/returns.std())[-1] 188 | return z_score 189 | 190 | def on_buy_signal(self, timestamp): 191 | if not self.is_long: 192 | self.send_market_order(self.symbol, 100, 193 | True, timestamp) 194 | 195 | def on_sell_signal(self, timestamp): 196 | if not self.is_short: 197 | self.send_market_order(self.symbol, 100, 198 | False, timestamp) 199 | 200 | 201 | import datetime as dt 202 | import pandas as pd 203 | 204 | class Backtester: 205 | def __init__(self, symbol, start_date, end_date, 206 | data_source="google"): 207 | self.target_symbol = symbol 208 | self.data_source = data_source 209 | self.start_dt = start_date 210 | self.end_dt = end_date 211 | self.strategy = None 212 | self.unfilled_orders = [] 213 | self.positions = dict() 214 | self.current_prices = None 215 | self.rpnl, self.upnl = pd.DataFrame(), pd.DataFrame() 216 | 217 | def get_timestamp(self): 218 | return self.current_prices.get_timestamp( 219 | self.target_symbol) 220 | 221 | def get_trade_date(self): 222 | timestamp = self.get_timestamp() 223 | return timestamp.strftime("%Y-%m-%d") 224 | 225 | def update_filled_position(self, symbol, qty, is_buy, 226 | price, timestamp): 227 | position = self.get_position(symbol) 228 | position.event_fill(timestamp, is_buy, qty, price) 229 | self.strategy.event_position(self.positions) 230 | self.rpnl.loc[timestamp, "rpnl"] = position.realized_pnl 231 | 232 | print self.get_trade_date(), \ 233 | "Filled:", "BUY" if is_buy else "SELL", \ 234 | qty, symbol, "at", price 235 | 236 | def get_position(self, symbol): 237 | if symbol not in self.positions: 238 | position = Position() 239 | position.symbol = symbol 240 | self.positions[symbol] = position 241 | 242 | return self.positions[symbol] 243 | 244 | def evthandler_order(self, order): 245 | self.unfilled_orders.append(order) 246 | 247 | print self.get_trade_date(), \ 248 | "Received order:", \ 249 | "BUY" if order.is_buy else "SELL", order.qty, \ 250 | order.symbol 251 | 252 | def match_order_book(self, prices): 253 | if len(self.unfilled_orders) > 0: 254 | self.unfilled_orders = \ 255 | [order for order in self.unfilled_orders 256 | if self.is_order_unmatched(order, prices)] 257 | 258 | def is_order_unmatched(self, order, prices): 259 | symbol = order.symbol 260 | timestamp = prices.get_timestamp(symbol) 261 | 262 | if order.is_market_order and timestamp > order.timestamp: 263 | # Order is matched and filled. 264 | order.is_filled = True 265 | open_price = prices.get_open_price(symbol) 266 | order.filled_timestamp = timestamp 267 | order.filled_price = open_price 268 | self.update_filled_position(symbol, 269 | order.qty, 270 | order.is_buy, 271 | open_price, 272 | timestamp) 273 | self.strategy.event_order(order) 274 | return False 275 | 276 | return True 277 | 278 | def print_position_status(self, symbol, prices): 279 | if symbol in self.positions: 280 | position = self.positions[symbol] 281 | close_price = prices.get_last_price(symbol) 282 | position.update_unrealized_pnl(close_price) 283 | self.upnl.loc[self.get_timestamp(), "upnl"] = \ 284 | position.unrealized_pnl 285 | 286 | print self.get_trade_date(), \ 287 | "Net:", position.net, \ 288 | "Value:", position.position_value, \ 289 | "UPnL:", position.unrealized_pnl, \ 290 | "RPnL:", position.realized_pnl 291 | 292 | def evthandler_tick(self, prices): 293 | self.current_prices = prices 294 | self.strategy.event_tick(prices) 295 | self.match_order_book(prices) 296 | self.print_position_status(self.target_symbol, prices) 297 | 298 | def start_backtest(self): 299 | self.strategy = MeanRevertingStrategy(self.target_symbol) 300 | self.strategy.event_sendorder = self.evthandler_order 301 | 302 | mds = MarketDataSource() 303 | mds.event_tick = self.evthandler_tick 304 | mds.ticker = self.target_symbol 305 | mds.source = self.data_source 306 | mds.start, mds.end = self.start_dt, self.end_dt 307 | 308 | print "Backtesting started..." 309 | mds.start_market_simulation() 310 | print "Completed." 311 | 312 | 313 | if __name__ == "__main__": 314 | backtester = Backtester("AAPL", 315 | dt.datetime(2014, 1, 1), 316 | dt.datetime(2014, 12, 31)) 317 | backtester.start_backtest() 318 | 319 | import matplotlib.pyplot as plt 320 | backtester.rpnl.plot() 321 | plt.show() 322 | 323 | backtester.upnl.plot() 324 | plt.show() -------------------------------------------------------------------------------- /B03898_10_codes/BinomialCRROption.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ Price an option by the binomial CRR model """ 9 | from BinomialTreeOption import BinomialTreeOption 10 | import math 11 | 12 | 13 | class BinomialCRROption(BinomialTreeOption): 14 | 15 | def _setup_parameters_(self): 16 | self.u = math.exp(self.sigma * math.sqrt(self.dt)) 17 | self.d = 1./self.u 18 | self.qu = (math.exp((self.r-self.div)*self.dt) - 19 | self.d)/(self.u-self.d) 20 | self.qd = 1-self.qu 21 | 22 | if __name__ == "__main__": 23 | from BinomialCRROption import BinomialCRROption 24 | eu_option = BinomialCRROption( 25 | 50, 50, 0.05, 0.5, 2, 26 | {"sigma": 0.3, "is_call": False}) 27 | print "European put: %s" % eu_option.price() 28 | 29 | am_option = BinomialCRROption( 30 | 50, 50, 0.05, 0.5, 2, 31 | {"sigma": 0.3, "is_call": False, "is_eu": False}) 32 | print "American put: %s" % am_option.price() 33 | -------------------------------------------------------------------------------- /B03898_10_codes/BinomialTreeOption.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ Price a European or American option by the binomial tree """ 9 | from StockOption import StockOption 10 | import math 11 | import numpy as np 12 | 13 | 14 | class BinomialTreeOption(StockOption): 15 | 16 | def _setup_parameters_(self): 17 | self.u = 1 + self.pu # Expected value in the up state 18 | self.d = 1 - self.pd # Expected value in the down state 19 | self.qu = (math.exp((self.r-self.div)*self.dt) - 20 | self.d)/(self.u-self.d) 21 | self.qd = 1-self.qu 22 | 23 | def _initialize_stock_price_tree_(self): 24 | # Initialize a 2D tree at T=0 25 | self.STs = [np.array([self.S0])] 26 | 27 | # Simulate the possible stock prices path 28 | for i in range(self.N): 29 | prev_branches = self.STs[-1] 30 | st = np.concatenate((prev_branches*self.u, 31 | [prev_branches[-1]*self.d])) 32 | self.STs.append(st) # Add nodes at each time step 33 | 34 | def _initialize_payoffs_tree_(self): 35 | # The payoffs when option expires 36 | return np.maximum( 37 | 0, (self.STs[self.N]-self.K) if self.is_call 38 | else (self.K-self.STs[self.N])) 39 | 40 | def __check_early_exercise__(self, payoffs, node): 41 | early_ex_payoff = \ 42 | (self.STs[node] - self.K) if self.is_call \ 43 | else (self.K - self.STs[node]) 44 | 45 | return np.maximum(payoffs, early_ex_payoff) 46 | 47 | def _traverse_tree_(self, payoffs): 48 | for i in reversed(range(self.N)): 49 | # The payoffs from NOT exercising the option 50 | payoffs = (payoffs[:-1] * self.qu + 51 | payoffs[1:] * self.qd) * self.df 52 | 53 | # Payoffs from exercising, for American options 54 | if not self.is_european: 55 | payoffs = self.__check_early_exercise__(payoffs, 56 | i) 57 | 58 | return payoffs 59 | 60 | def __begin_tree_traversal__(self): 61 | payoffs = self._initialize_payoffs_tree_() 62 | return self._traverse_tree_(payoffs) 63 | 64 | def price(self): 65 | self._setup_parameters_() 66 | self._initialize_stock_price_tree_() 67 | payoffs = self.__begin_tree_traversal__() 68 | 69 | return payoffs[0] 70 | 71 | if __name__ == "__main__": 72 | from BinomialTreeOption import BinomialTreeOption 73 | am_option = BinomialTreeOption( 74 | 50, 50, 0.05, 0.5, 2, 75 | {"pu": 0.2, "pd": 0.2, "is_call": False, "is_eu": False}) 76 | print am_option.price() -------------------------------------------------------------------------------- /B03898_10_codes/StockOption.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ Store common attributes of a stock option """ 9 | import math 10 | 11 | 12 | class StockOption(object): 13 | 14 | def __init__(self, S0, K, r, T, N, params): 15 | self.S0 = S0 16 | self.K = K 17 | self.r = r 18 | self.T = T 19 | self.N = max(1, N) # Ensure N have at least 1 time step 20 | self.STs = None # Declare the stock prices tree 21 | 22 | """ Optional parameterss used by derived classes """ 23 | self.pu = params.get("pu", 0) # Probability of up state 24 | self.pd = params.get("pd", 0) # Probability of down state 25 | self.div = params.get("div", 0) # Divident yield 26 | self.sigma = params.get("sigma", 0) # Volatility 27 | self.is_call = params.get("is_call", True) # Call or put 28 | self.is_european = params.get("is_eu", True) # Eu or Am 29 | 30 | """ Computed values """ 31 | self.dt = T/float(N) # Single time step, in years 32 | self.df = math.exp( 33 | -(r-self.div) * self.dt) # Discount factor -------------------------------------------------------------------------------- /B03898_10_codes/TrinomialLattice.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ Price an option by the trinomial lattice """ 9 | from TrinomialTreeOption import TrinomialTreeOption 10 | import numpy as np 11 | 12 | 13 | class TrinomialLattice(TrinomialTreeOption): 14 | 15 | def _setup_parameters_(self): 16 | super(TrinomialLattice, self)._setup_parameters_() 17 | self.M = 2*self.N+1 18 | 19 | def _initialize_stock_price_tree_(self): 20 | self.STs = np.zeros(self.M) 21 | self.STs[0] = self.S0 * self.u**self.N 22 | 23 | for i in range(self.M)[1:]: 24 | self.STs[i] = self.STs[i-1]*self.d 25 | 26 | def _initialize_payoffs_tree_(self): 27 | return np.maximum( 28 | 0, (self.STs-self.K) if self.is_call 29 | else(self.K-self.STs)) 30 | 31 | def __check_early_exercise__(self, payoffs, node): 32 | self.STs = self.STs[1:-1] # Shorten the ends of the list 33 | early_ex_payoffs = \ 34 | (self.STs-self.K) if self.is_call \ 35 | else(self.K-self.STs) 36 | payoffs = np.maximum(payoffs, early_ex_payoffs) 37 | 38 | return payoffs 39 | 40 | if __name__ == "__main__": 41 | from TrinomialLattice import TrinomialLattice 42 | eu_option = TrinomialLattice( 43 | 50, 50, 0.05, 0.5, 2, 44 | {"sigma": 0.3, "is_call":False}) 45 | print "European put: %s" % eu_option.price() 46 | 47 | am_option = TrinomialLattice( 48 | 50, 50, 0.05, 0.5, 2, 49 | {"sigma": 0.3, "is_call": False, "is_eu": False}) 50 | print "American put: %s" % am_option.price() -------------------------------------------------------------------------------- /B03898_10_codes/TrinomialTreeOption.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | """ Price an option by the Boyle trinomial tree """ 9 | from BinomialTreeOption import BinomialTreeOption 10 | import math 11 | import numpy as np 12 | 13 | 14 | class TrinomialTreeOption(BinomialTreeOption): 15 | 16 | def _setup_parameters_(self): 17 | """ Required calculations for the model """ 18 | self.u = math.exp(self.sigma*math.sqrt(2.*self.dt)) 19 | self.d = 1/self.u 20 | self.m = 1 21 | self.qu = ((math.exp((self.r-self.div) * 22 | self.dt/2.) - 23 | math.exp(-self.sigma * 24 | math.sqrt(self.dt/2.))) / 25 | (math.exp(self.sigma * 26 | math.sqrt(self.dt/2.)) - 27 | math.exp(-self.sigma * 28 | math.sqrt(self.dt/2.))))**2 29 | self.qd = ((math.exp(self.sigma * 30 | math.sqrt(self.dt/2.)) - 31 | math.exp((self.r-self.div) * 32 | self.dt/2.)) / 33 | (math.exp(self.sigma * 34 | math.sqrt(self.dt/2.)) - 35 | math.exp(-self.sigma * 36 | math.sqrt(self.dt/2.))))**2. 37 | 38 | self.qm = 1 - self.qu - self.qd 39 | 40 | def _initialize_stock_price_tree_(self): 41 | """ Initialize a 2D tree at t=0 """ 42 | self.STs = [np.array([self.S0])] 43 | 44 | for i in range(self.N): 45 | prev_nodes = self.STs[-1] 46 | self.ST = np.concatenate( 47 | (prev_nodes*self.u, [prev_nodes[-1]*self.m, 48 | prev_nodes[-1]*self.d])) 49 | self.STs.append(self.ST) 50 | 51 | def _traverse_tree_(self, payoffs): 52 | """ Traverse the tree backwards """ 53 | for i in reversed(range(self.N)): 54 | payoffs = (payoffs[:-2] * self.qu + 55 | payoffs[1:-1] * self.qm + 56 | payoffs[2:] * self.qd) * self.df 57 | 58 | if not self.is_european: 59 | payoffs = self.__check_early_exercise__(payoffs, 60 | i) 61 | 62 | return payoffs 63 | 64 | if __name__ == "__main__": 65 | from TrinomialTreeOption import TrinomialTreeOption 66 | print "European put:", TrinomialTreeOption( 67 | 50, 50, 0.05, 0.5, 2, {"sigma": 0.3, "is_call": False}).price() 68 | print "American put:", TrinomialTreeOption( 69 | 50, 50, 0.05, 0.5, 2, {"sigma": 0.3, "is_call": False, "is_eu": False}).price() 70 | -------------------------------------------------------------------------------- /B03898_10_codes/binomial_crr_com.py: -------------------------------------------------------------------------------- 1 | """ Binomial CRR tree COM server """ 2 | from BinomialCRROption import BinomialCRROption 3 | import pythoncom 4 | 5 | class BinomialCRRCOMServer: 6 | _public_methods_ = [ 'pricer'] 7 | _reg_progid_ = "BinomialCRRCOMServer.Pricer" 8 | _reg_clsid_ = pythoncom.CreateGuid() 9 | 10 | def pricer(self, S0, K, r, T, N, sigma, 11 | is_call=True, div=0., is_eu=False): 12 | model = BinomialCRROption(S0, K, r, T, N, 13 | {"sigma": sigma, 14 | "div": div, 15 | "is_call": is_call, 16 | "is_eu": is_eu}) 17 | return model.price() 18 | 19 | if __name__ == "__main__": 20 | print "Registering COM server..." 21 | import win32com.server.register 22 | win32com.server.register.UseCommandLine(BinomialCRRCOMServer) -------------------------------------------------------------------------------- /B03898_10_codes/black_scholes.py: -------------------------------------------------------------------------------- 1 | """ 2 | README 3 | ====== 4 | This file contains Python codes. 5 | ====== 6 | """ 7 | 8 | 9 | import numpy as np 10 | import scipy.stats as stats 11 | import pythoncom 12 | 13 | class BlackScholes: 14 | _public_methods_ = ["call_pricer", "put_pricer"] 15 | _reg_progid_ = "BlackScholes.Pricer" 16 | _reg_clsid_ = pythoncom.CreateGuid() 17 | 18 | def d1(self, S0, K, r, T, sigma, div): 19 | return (np.log(S0/K) + ((r-div) + sigma**2 / 2) * T)/ \ 20 | (sigma * np.sqrt(T)) 21 | 22 | def d2(self, S0, K, r, T, sigma, div): 23 | return (np.log(S0 / K) + ((r-div) - sigma**2 / 2) * T) / \ 24 | (sigma * np.sqrt(T)) 25 | 26 | def call_pricer(self, S0, K, r, T, sigma, div): 27 | d1 = self.d1(S0, K, r, T, sigma, div) 28 | d2 = self.d2(S0, K, r, T, sigma, div) 29 | return S0 * np.exp(-div * T) * stats.norm.cdf(d1) \ 30 | - K * np.exp(-r * T) * stats.norm.cdf(d2) 31 | 32 | def put_pricer(self, S0, K, r, T, sigma, div): 33 | d1 = self.d1(S0, K, r, T, sigma, div) 34 | d2 = self.d2(S0, K, r, T, sigma, div) 35 | return K * np.exp(-r * T) * stats.norm.cdf(-d2) \ 36 | - S0 * np.exp(-div * T) *stats.norm.cdf(-d1) 37 | 38 | if __name__ == "__main__": 39 | # Run "python binomial_tree_am.py" 40 | # to register the COM server. 41 | # Run "python binomial_tree_am.py --unregister" 42 | # to unregister it. 43 | print "Registering COM server..." 44 | import win32com.server.register 45 | win32com.server.register.UseCommandLine(BlackScholes) -------------------------------------------------------------------------------- /B03898_10_codes/excel_com_client.xlsm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jamesmawm/Mastering-Python-for-Finance-source-codes/7b3a74a0dc78aaa977f8fe752cc3fa4f54063f33/B03898_10_codes/excel_com_client.xlsm -------------------------------------------------------------------------------- /B03898_10_codes/trinomial_lattice_com.py: -------------------------------------------------------------------------------- 1 | """ Trinomial Lattice COM server """ 2 | from TrinomialLattice import TrinomialLattice 3 | import pythoncom 4 | 5 | class TrinomialLatticeCOMServer: 6 | _public_methods_ = ['pricer'] 7 | _reg_progid_ = "TrinomialLatticeCOMServer.Pricer" 8 | _reg_clsid_ = pythoncom.CreateGuid() 9 | 10 | def pricer(self, S0, K, r, T, N, sigma, 11 | is_call=True, div=0., is_eu=False): 12 | model = TrinomialLattice(S0, K, r, T, N, 13 | {"sigma": sigma, 14 | "div": div, 15 | "is_call": is_call, 16 | "is_eu": is_eu}) 17 | return model.price() 18 | 19 | if __name__ == "__main__": 20 | print "Registering COM server..." 21 | import win32com.server.register 22 | win32com.server.register.UseCommandLine(TrinomialLatticeCOMServer) -------------------------------------------------------------------------------- /B03898_10_codes/vba_codes.txt: -------------------------------------------------------------------------------- 1 | README 2 | ====== 3 | This file contains Excel VBA codes. 4 | ====== 5 | 6 | 7 | Function BlackScholesOptionPrice( _ 8 | ByVal S0 As Integer, _ 9 | ByVal K As Integer, _ 10 | ByVal r As Double, _ 11 | ByVal T As Double, _ 12 | ByVal sigma As Double, _ 13 | ByVal dividend As Double, _ 14 | ByVal isCall As Boolean) 15 | 16 | Set BlackScholes = CreateObject("BlackScholes.Pricer") 17 | If isCall = True Then 18 | answer = BlackScholes.call_pricer(S0, K, r, T, sigma, dividend) 19 | Else 20 | answer = BlackScholes.put_pricer(S0, K, r, T, sigma, dividend) 21 | End If 22 | BlackScholesOptionPrice = answer 23 | End Function 24 | 25 | Function BinomialTreeCRROptionPrice( _ 26 | ByVal S0 As Integer, _ 27 | ByVal K As Integer, _ 28 | ByVal r As Double, _ 29 | ByVal T As Double, _ 30 | ByVal N As Integer, _ 31 | ByVal sigma As Double, _ 32 | ByVal isCall As Boolean, _ 33 | ByVal dividend As Double) 34 | Set BinCRRTree = CreateObject("BinomialCRRCOMServer.Pricer") 35 | answer = BinCRRTree.pricer(S0, K, r, T, N, sigma, isCall, _ 36 | dividend, True) 37 | BinomialTreeCRROptionPrice = answer 38 | End Function 39 | 40 | Function TrinomialLatticeOptionPrice( _ 41 | ByVal S0 As Integer, _ 42 | ByVal K As Integer, _ 43 | ByVal r As Double, _ 44 | ByVal T As Double, _ 45 | ByVal N As Integer, _ 46 | ByVal sigma As Double, _ 47 | ByVal isCall As Boolean, _ 48 | ByVal dividend As Double) 49 | Set TrinomialLattice = _ 50 | CreateObject("TrinomialLatticeCOMServer.Pricer") 51 | answer = TrinomialLattice.pricer(S0, K, r, T, N, sigma, _ 52 | isCall, dividend, True) 53 | TrinomialLatticeOptionPrice = answer 54 | End Function 55 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 James Ma Weiming 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is the accompanying source codes for my book 'Mastering Python for Finance'. 2 | 3 | ![alt text](https://d255esdrn735hr.cloudfront.net/sites/default/files/imagecache/ppv4_main_book_cover/4516OS_Mastering%20Python%20for%20Finance.jpg "Mastering Python for Finance") 4 | 5 | ISBN-10: 1784394513, ISBN-13: 978-1784394516 6 | 7 | Available on major sales channels including Amazon, 8 | Safari Online and Barnes & Noble, in paperback, Kindle and ebook. 9 | 10 | Get it from: 11 | - http://www.amazon.com/Mastering-Python-Finance-James-Weiming/dp/1784394513 12 | - https://www.packtpub.com/big-data-and-business-intelligence/mastering-python-finance 13 | 14 | Check out these awesome books too: 15 | - Python for Finance by Dr. Yves Hilpisch: 16 | http://shop.oreilly.com/product/0636920032441.do 17 | - Python for Quants. Volume I. by Dr. Pawel Lachowicz: 18 | http://www.quantatrisk.com/python-for-quants-volume-i/ 19 | 20 | 21 | Table of Contents 22 | === 23 | 1. Python for Financial Applications 24 | - Is Python for me? 25 | - Free and open source 26 | - High-level, powerful, and flexible 27 | - A wealth of standard libraries 28 | - Objected-oriented versus functional programming 29 | - The object-oriented approach 30 | - The functional approach 31 | - Which approach should I use? 32 | - Which Python version should I use? 33 | - Introducing IPython 34 | - Getting IPython 35 | - Using pip 36 | - The IPython Notebook 37 | - Notebook documents 38 | - Running the IPython Notebook 39 | - Creating a new notebook 40 | - Notebook cells 41 | - Code cell 42 | - Markdown cell 43 | - Raw NBConvert cell 44 | - Heading cells 45 | - Simple exercises with IPython Notebook 46 | - Creating a notebook with heading and Markdown cells 47 | - Saving notebooks 48 | - Mathematical operations in cells 49 | - Displaying graphs 50 | - Inserting equations 51 | - Displaying images 52 | - Inserting YouTube videos 53 | - Working with HTML 54 | - The pandas DataFrame object as an HTML table 55 | - Notebook for finance 56 | - Summary 57 | 2. The Importance of Linearity in Finance 58 | - The capital asset pricing model and the security market line 59 | - The Arbitrage Pricing Theory model 60 | - Multivariate linear regression of factor models 61 | - Linear optimization 62 | - Getting PuLP 63 | - A simple linear optimization problem 64 | - Outcomes of linear programs 65 | - Integer programming 66 | - An example of an integer programming model with binary conditions 67 | - A different approach with binary conditions 68 | - Solving linear equations using matrices 69 | - The LU decomposition 70 | - The Cholesky decomposition 71 | - The QR decomposition 72 | - Solving with other matrix algebra methods 73 | - The Jacobi method 74 | - The Gauss-Seidel method 75 | - Summary 76 | 3. Nonlinearity in Finance 77 | - Nonlinearity modeling 78 | - Examples of nonlinear models 79 | - The implied volatility model 80 | - The Markov regime-switching model 81 | - The threshold autoregressive model 82 | - Smooth transition models 83 | - An introduction to root-finding 84 | - Incremental search 85 | - The bisection method 86 | - Newton's method 87 | - The secant method 88 | - Combining root-finding methods 89 | - SciPy implementations 90 | - Root-finding scalar functions 91 | - General nonlinear solvers 92 | - Summary 93 | 4. Numerical Procedures 94 | - Introduction to options 95 | - Binomial trees in options pricing 96 | - Pricing European options 97 | - Are these formulas relevant to stocks? What about futures? 98 | - Writing the StockOption class 99 | - Writing the BinomialEuropeanOption class 100 | - Pricing American options with the BinomialTreeOption class 101 | - The Cox-Ross-Rubinstein model 102 | - Writing the BinomialCRROption class 103 | - Using a Leisen-Reimer tree 104 | - Writing the BinomialLROption class 105 | - The Greeks for free 106 | - Writing the BinomialLRWithGreeks class 107 | - Trinomial trees in options pricing 108 | - Writing the TrinomialTreeOption class 109 | - Lattices in options pricing 110 | - Using a binomial lattice 111 | - Writing the BinomialCRROption class 112 | - Using the trinomial lattice 113 | - Writing the TrinomialLattice class 114 | - Finite differences in options pricing 115 | - The explicit method 116 | - Writing the FiniteDifferences class 117 | - Writing the FDExplicitEu class 118 | - The implicit method 119 | - Writing the FDImplicitEu class 120 | - The Crank-Nicolson method 121 | - Writing the FDCnEu class 122 | - Pricing exotic barrier options 123 | - A down-and-out option 124 | - Writing the FDCnDo class 125 | - American options pricing with finite differences 126 | - Writing the FDCnAm class 127 | - Putting it all together – implied volatility modeling 128 | - Implied volatilities of AAPL American put option 129 | - Summary 130 | 5. Interest Rates and Derivatives 131 | - Fixed-income securities 132 | - Yield curves 133 | - Valuing a zero-coupon bond 134 | - Spot and zero rates 135 | - Bootstrapping a yield curve 136 | - Forward rates 137 | - Calculating the yield to maturity 138 | - Calculating the price of a bond 139 | - Bond duration 140 | - Bond convexity 141 | - Short-rate modeling 142 | - The Vasicek model 143 | - The Cox-Ingersoll-Ross model 144 | - The Rendleman and Bartter model 145 | - The Brennan and Schwartz model 146 | - Bond options 147 | - Callable bonds 148 | - Puttable bonds 149 | - Convertible bonds 150 | - Preferred stocks 151 | - Pricing a callable bond option 152 | - Pricing a zero-coupon bond by the Vasicek model 153 | - Value of early-exercise 154 | - Policy iteration by finite differences 155 | - Other considerations in callable bond pricing 156 | - Summary 157 | 6. Interactive Financial Analytics with Python and VSTOXX 158 | - Volatility derivatives 159 | - STOXX and the Eurex 160 | - The EURO STOXX 50 Index 161 | - The VSTOXX 162 | - The VIX 163 | - Gathering the EUROX STOXX 50 Index and VSTOXX data 164 | - Merging the data 165 | - Financial analytics of SX5E and V2TX 166 | - Correlation between SX5E and V2TX 167 | - Calculating the VSTOXX sub-indices 168 | - Getting the OESX data 169 | - Formulas to calculate the VSTOXX sub-index 170 | - Implementation of the VSTOXX sub-index value 171 | - Analyzing the results 172 | - Calculating the VSTOXX main index 173 | - Summary 174 | 7. Big Data with Python 175 | - Introducing big data 176 | - Hadoop for big data 177 | - HDFS 178 | - YARN 179 | - MapReduce 180 | - Is big data for me? 181 | - Getting Apache Hadoop 182 | - Getting a QuickStart VM from Cloudera 183 | - Getting VirtualBox 184 | - Running Cloudera VM on VirtualBox 185 | - A word count program in Hadoop 186 | - Downloading sample data 187 | - The map program 188 | - The reduce program 189 | - Testing our scripts 190 | - Running MapReduce on Hadoop 191 | - Hue for browsing HDFS 192 | - Going deeper – Hadoop for finance 193 | - Obtaining IBM stock prices from Yahoo! Finance 194 | - Modifying the map program 195 | - Testing our map program with IBM stock prices 196 | - Running MapReduce to count intraday price changes 197 | - Performing analysis on our MapReduce results 198 | - Introducing NoSQL 199 | - Getting MongoDB 200 | - Creating the data directory and running MongoDB 201 | - Running MongoDB from Windows 202 | - Running MongoDB from Mac OS X 203 | - Getting PyMongo 204 | - Running a test connection 205 | - Getting a database 206 | - Getting a collection 207 | - Inserting a document 208 | - Fetching a single document 209 | - Deleting documents 210 | - Batch-inserting documents 211 | - Counting documents in the collection 212 | - Finding documents 213 | - Sorting documents 214 | - Conclusion 215 | - Summary 216 | 8. Algorithmic Trading 217 | - Introduction to algorithmic trading 218 | - List of trading platforms with public API 219 | - Which is the best programming language to use? 220 | - System functionalities 221 | - Algorithmic trading with Interactive Brokers and IbPy 222 | - Getting Interactive Brokers' Trader WorkStation 223 | - Getting IbPy – the IB API wrapper 224 | - A simple order routing mechanism 225 | - Building a mean-reverting algorithmic trading system 226 | - Setting up the main program 227 | - Handling events 228 | - Implementing the mean-reverting algorithm 229 | - Tracking our positions 230 | - Forex trading with OANDA API 231 | - What is REST? 232 | - Setting up an OANDA account 233 | - Exploring the API 234 | - Getting oandapy – the OANDA REST API wrapper 235 | - Getting and parsing rates data 236 | - Sending an order 237 | - Building a trend-following forex trading platform 238 | - Setting up the main program 239 | Handling events 240 | - Implementing the trend-following algorithm 241 | - Tracking our positions 242 | - VaR for risk management 243 | - Summary 244 | 9. Backtesting 245 | - An introduction to backtesting 246 | - Concerns in backtesting 247 | - Concept of an event-driven backtesting system 248 | - Designing and implementing a backtesting system 249 | - The TickData class 250 | - The MarketData class 251 | - The MarketDataSource class 252 | - The Order class 253 | - The Position class 254 | - The Strategy class 255 | - The MeanRevertingStrategy class 256 | - The Backtester class 257 | - Running our backtesting system 258 | - Improving your backtesting system 259 | - Ten considerations for a backtesting model 260 | - Resources restricting your model 261 | - Criteria of evaluation of the model 262 | - Estimating the quality of backtest parameters 263 | - Be prepared to face model risk 264 | - Performance of a backtest with in-sample data 265 | - Addressing common pitfalls in backtesting 266 | - Have a common sense idea of your model 267 | - Understanding the context for the model 268 | - Make sure you have the right data 269 | - Data mine your results 270 | - Discussion of algorithms in backtesting 271 | - K-means clustering 272 | - K-nearest neighbor machine learning algorithm 273 | - Classification and regression tree analysis 274 | - The 2k factorial design 275 | - The genetic algorithm 276 | - Summary 277 | 10. Excel with Python 278 | - Overview of COM 279 | - Excel for finance 280 | - Building a COM server 281 | - Prerequisites 282 | - Getting the pythoncom module 283 | - Building the Black-Scholes model COM server 284 | - Registering and unregistering the COM server 285 | - Building the Cox-Ross-Rubinstein binomial tree model COM server 286 | - Building the trinomial lattice model COM server 287 | - Building the COM client in Excel 288 | - Setting up the VBA code 289 | - Setting up the cells 290 | - What else can I do with COM? 291 | - Summary --------------------------------------------------------------------------------