├── 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 | 
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
--------------------------------------------------------------------------------