forked from ZJONSSON/black
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathblack.py
164 lines (137 loc) · 6.55 KB
/
black.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# -*- coding: iso-8859-1 -*-
# black.py (C) [email protected] (MIT Licence)
from __future__ import division
from scipy.stats import norm
from scipy.optimize import fsolve
from numpy import inf
from math import log, sqrt, exp
from math import *
def black(cp=None, f=None, k=None, t=None, r=None, v=None, price=None, full=False, comp=inf):
""" General Purpose BSM machine. Leave one parameter blank and that is the parameter that will
be solved for.
cp = "c" for a call, anything else assumes a put
f = Forward Price of the underlying asset
k = Strike Price
t = Time until maturity (in years)
r = Interest rate
v = Implied volatility
comp = How many times interest is compounded a year (default = inf, i.e. continous rates)
full = If True, function returns a dictionary with price and all sensitivities
Otherwise only the calculated/calibrated parameter will be returned
"""
D1 = lambda f, k, sigma, t: (log(f/k)+(sigma**2/2)*t)/(sigma*sqrt(t))
D2 = lambda f, k, sigma, t: D1(f, k, sigma, t)-sigma*sqrt(t)
if comp != inf:
r = log((1+r/comp)**comp) # convert rates
optionType = 1 if cp.upper()[0] == "C" else -1 # If the first letter is not "c" or "C" it must be a put option
parameters = locals().copy()
def _black(cp, f, k, t, r, v, price, full, calibration=False, **kwargs):
d1, d2 = D1(f, k, v, t), D2(f, k, v, t)
price = optionType * exp(-r*t)*(f*norm.cdf(optionType*d1)-k*norm.cdf(optionType*d2))
if not calibration and full:
return {
"price": price,
"delta": optionType * norm.cdf(d1),
"gamma": norm.pdf(d1)/(f*v*sqrt(t)),
"vega": f*norm.pdf(d1)*sqrt(t)
}
else:
return price
def _calibrate(value, field, parameters):
parameters.update({field: value[0]})
return abs(_black(calibration=True, **parameters) - price, )
missing = [a for a in ["f", "k", "t", "r", "v", "price"] if parameters[a] is None]
if len(missing) > 1:
raise Exception("Too many missing variables from: %s " % missing)
if len(missing) == 0:
raise Exception("All variables assigned - nothing to solve")
if missing[0] != "price":
# if we are missing any parameter different from price we need to calibrate
result = fsolve(_calibrate, 0.1, args=(missing[0], parameters))
if full is False:
return result # If full=False we simply return the calibrated parameter
return _black(**parameters)
def impliedBlack(cp, f, k, t, r, price, comp=inf):
""" Solves for implied volatility, given all other parameters
cp = "c" for a call, anything else assumes a put
f = Forward Price of the underlying asset
k = Strike Price
t = Time until maturity (in years)
r = Interest rate
price= Current Option Price
comp = How many times interest is compounded a year (default = inf, i.e. continous rates)
full = If True, function returns a dictionary with price and all sensitivities
Otherwise only the calculated/calibrated parameter will be returned
"""
if comp != inf:
r = log((1+r/comp)**comp) # convert rates
def _calibrate(v, price, cp, f, k, t, r):
return abs(black(cp, f, k, t, r, v, comp=inf)-price)
return fsolve(_calibrate, 0.1, args=(price, cp, f, k, t, r))
def blackScholes(cp, s, k, t, r, d, v, full=False, comp=inf):
""" blackScholes, risk-free-rate and dividend-yield make up the forward rate
cp = "c" for a call, anything else assumes a put
f = Forward Price of the underlying asset
k = Strike Price
t = Time until maturity (in years)
r = Interest rate
d = Dividend yield
v = Implied volatility
comp = How many times interest is compounded a year (default = inf, i.e. continous rates)
full = If True, function returns a dictionary with price and all sensitivities
Otherwise only the calculated/calibrated parameter will be returned
"""
if comp != inf:
r = toExp(r, comp)
d = toExp(d, comp)
f = s*exp(t*(d-r))
results = black(cp, f, k, t, r, v, full, comp=inf)
if not full:
return results
else:
for key, item in results.items():
if key in ["delta", "gamma"]:
item /= f/s
results["fwd"] = f
return results
def impliedBlackScholes(cp, s, k, t, r, d, price, comp=inf):
""" Solves for implied volatility, given all other parameters
cp = "c" for a call, anything else assumes a put
s = Spot Price of the underlying asset
k = Strike Price
t = Time until maturity (in years)
r = Interest rate
d = Dividend yield
v = Implied volatility
comp = How many times interest is compounded a year (default = inf, i.e. continous rates)
full = If True, function returns a dictionary with price and all sensitivities
Otherwise only the calculated/calibrated parameter will be returned
"""
def _calibrate(v, price, cp, f, k, t, r):
return abs(blackScholes(cp, s, k, t, r, d, v, comp=inf)-price)
if comp != inf:
r = toExp(r, comp)
d = toExp(d, comp)
return fsolve(_calibrate, 0.1, args=(price, cp, s, k, t, r))
def impliedPair(k, t, r, callPrice, putPrice, full=False, comp=inf):
""" Given put/call prices we solve for the forward and the implied volatility
k = Strike Price
t = Time until maturity (in years)
r = Interest rate
callPrice = Price of a call @ strike
putPrice = Price of a put @ strike
comp = How many times interest is compounded a year (default = inf, i.e. continous rates)
full = If True, function returns a dictionary with price and all sensitivities
Otherwise only the calculated/calibrated parameter will be returned
"""
if comp != inf:
r = log((1+r/comp)**comp) # convert rates
f = (callPrice-putPrice)*exp(r*t)+k
v = impliedBlack("c", f, k, t, r, callPrice, comp=comp)
if full:
results = black("c", f, k, t, r, v, full=True, comp=comp)
results["fwd"] = f
results["vol"] = v
return results
else:
return v