Skip to content

Commit 7bc9a1f

Browse files
authored
Merge pull request #139 from PyFE/heston-mc
Heston MC cumulatiative changes so far
2 parents 59719c4 + b3ddd55 commit 7bc9a1f

File tree

9 files changed

+480
-456
lines changed

9 files changed

+480
-456
lines changed

.idea/other.xml

+6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyfeng/bsm.py

+119-94
Large diffs are not rendered by default.

pyfeng/cev.py

+67-110
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@ class Cev(opt.OptAnalyticABC, smile.OptSmileABC, smile.MassZeroABC):
2222

2323
sigma = None
2424
beta = 0.5
25-
is_bsm_sigma = False
2625

2726
def __init__(self, sigma, beta=0.5, intr=0.0, divr=0.0, is_fwd=False):
2827
"""
@@ -45,20 +44,15 @@ def mass_zero(self, spot, texp, log=False):
4544
fwd = self.forward(spot, texp)
4645

4746
betac = 1.0 - self.beta
48-
a = 0.5 / betac
47+
a = 0.5/betac
4948
sigma_std = np.maximum(
50-
self.sigma / np.power(fwd, betac) * np.sqrt(texp), np.finfo(float).eps
49+
self.sigma/np.power(fwd, betac)*np.sqrt(texp), np.finfo(float).eps
5150
)
52-
x = 0.5 / np.square(betac * sigma_std)
51+
x = 0.5/np.square(betac*sigma_std)
5352

5453
if log:
55-
log_mass = (a - 1) * np.log(x) - x - np.log(spsp.gamma(a))
56-
log_mass += np.log(
57-
1
58-
+ (a - 1)
59-
/ x
60-
* (1 + (a - 2) / x * (1 + (a - 3) / x * (1 + (a - 4) / x)))
61-
)
54+
log_mass = (a - 1)*np.log(x) - x - np.log(spsp.gamma(a))
55+
log_mass += np.log(1 + (a - 1)/x*(1 + (a - 2)/x*(1 + (a - 3)/x*(1 + (a - 4)/x))))
6256
with np.errstate(divide="ignore"):
6357
log_mass = np.where(x > 100, log_mass, np.log(spst.gamma.sf(x=x, a=a)))
6458
return log_mass
@@ -77,41 +71,40 @@ def mass_zero_t0(self, spot, texp):
7771
"""
7872
fwd = self.forward(spot, texp)
7973
betac = 1.0 - self.beta
80-
alpha = self.sigma / np.power(fwd, betac)
81-
t0 = 0.5 / (betac * alpha) ** 2
74+
alpha = self.sigma/np.power(fwd, betac)
75+
t0 = 0.5/(betac*alpha)**2
8276
return t0
8377

8478
@staticmethod
85-
def price_formula(
86-
strike, spot, texp, sigma=None, cp=1, beta=0.5, intr=0.0, divr=0.0, is_fwd=False
87-
):
79+
def price_formula(strike, spot, texp, sigma=None, cp=1, beta=0.5, intr=0.0, divr=0.0, is_fwd=False):
8880
"""
81+
CEV model call/put option pricing formula (static method)
8982
9083
Args:
91-
strike:
92-
spot:
93-
texp:
94-
cp:
95-
sigma:
96-
beta:
97-
intr:
98-
divr:
99-
is_fwd:
84+
strike: strike price
85+
spot: spot (or forward)
86+
sigma: model volatility
87+
texp: time to expiry
88+
cp: 1/-1 for call/put option
89+
beta: elasticity parameter
90+
intr: interest rate (domestic interest rate)
91+
divr: dividend/convenience yield (foreign interest rate)
92+
is_fwd: if True, treat `spot` as forward price. False by default.
10093
10194
Returns:
102-
95+
Vanilla option price
10396
"""
10497

105-
disc_fac = np.exp(-texp * intr)
106-
fwd = spot * (1.0 if is_fwd else np.exp(-texp * divr) / disc_fac)
98+
disc_fac = np.exp(-texp*intr)
99+
fwd = spot*(1.0 if is_fwd else np.exp(-texp*divr)/disc_fac)
107100

108101
betac = 1.0 - beta
109-
betac_inv = 1.0 / betac
110-
alpha = sigma / np.power(fwd, betac)
111-
sigma_std = np.maximum(alpha * np.sqrt(texp), np.finfo(float).eps)
112-
kk = strike / fwd
113-
x = 1.0 / np.square(betac * sigma_std)
114-
y = np.power(kk, 2 * betac) * x
102+
betac_inv = 1.0/betac
103+
alpha = sigma/np.power(fwd, betac)
104+
sigma_std = np.maximum(alpha*np.sqrt(texp), np.finfo(float).eps)
105+
kk = strike/fwd
106+
x = 1.0/np.square(betac*sigma_std)
107+
y = np.power(kk, 2*betac)*x
115108

116109
# Need to clean up the case beta > 0
117110
if beta > 1.0:
@@ -123,43 +116,25 @@ def price_formula(
123116
# Computing call and put is a bit of computtion waste, but do this for vectorization.
124117
price = np.where(
125118
cp > 0,
126-
fwd * ncx2_sf(y, 2 + betac_inv, x) - strike * ncx2_cdf(x, betac_inv, y),
127-
strike * ncx2_sf(x, betac_inv, y) - fwd * ncx2_cdf(y, 2 + betac_inv, x),
119+
fwd*ncx2_sf(y, 2 + betac_inv, x) - strike*ncx2_cdf(x, betac_inv, y),
120+
strike*ncx2_sf(x, betac_inv, y) - fwd*ncx2_cdf(y, 2 + betac_inv, x),
128121
)
129-
return disc_fac * price
122+
return disc_fac*price
130123

131124
def delta(self, strike, spot, texp, cp=1):
132125
fwd, df, divf = self._fwd_factor(spot, texp)
133-
betac_inv = 1 / (1 - self.beta)
126+
betac_inv = 1/(1 - self.beta)
134127

135-
k_star = 1.0 / np.square(self.sigma / betac_inv) / texp
136-
x = k_star * np.power(fwd, 2 / betac_inv)
137-
y = k_star * np.power(strike, 2 / betac_inv)
128+
k_star = 1.0/np.square(self.sigma/betac_inv)/texp
129+
x = k_star*np.power(fwd, 2/betac_inv)
130+
y = k_star*np.power(strike, 2/betac_inv)
138131

139132
if self.beta < 1.0:
140-
delta = (
141-
0.5 * (cp - 1)
142-
+ spst.ncx2.sf(y, 2 + betac_inv, x)
143-
+ 2
144-
* x
145-
/ betac_inv
146-
* (
147-
spst.ncx2.pdf(y, 4 + betac_inv, x)
148-
- strike / fwd * spst.ncx2.pdf(x, betac_inv, y)
149-
)
150-
)
133+
delta = 0.5*(cp - 1) + spst.ncx2.sf(y, 2 + betac_inv, x)\
134+
+ 2*x/betac_inv*(spst.ncx2.pdf(y, 4 + betac_inv, x) - strike/fwd*spst.ncx2.pdf(x, betac_inv, y))
151135
else:
152-
delta = (
153-
0.5 * (cp - 1)
154-
+ spst.ncx2.sf(x, -betac_inv, y)
155-
- 2
156-
* x
157-
/ betac_inv
158-
* (
159-
spst.ncx2.pdf(x, -betac_inv, y)
160-
- strike / fwd * spst.ncx2.pdf(y, 4 - betac_inv, x)
161-
)
162-
)
136+
delta = 0.5*(cp - 1) + spst.ncx2.sf(x, -betac_inv, y)\
137+
- 2*x/betac_inv*(spst.ncx2.pdf(x, -betac_inv, y) - strike/fwd*spst.ncx2.pdf(y, 4 - betac_inv, x))
163138

164139
delta *= df if self.is_fwd else divf
165140
return delta
@@ -168,75 +143,57 @@ def cdf(self, strike, spot, texp, cp=1):
168143
fwd = self.forward(spot, texp)
169144

170145
betac = 1.0 - self.beta
171-
betac_inv = 1.0 / betac
172-
alpha = self.sigma / np.power(fwd, betac)
173-
sigma_std = np.maximum(alpha * np.sqrt(texp), np.finfo(float).eps)
174-
kk = strike / fwd
175-
x = 1.0 / np.square(betac * sigma_std)
176-
y = np.power(kk, 2 * betac) * x
177-
178-
cdf = np.where(
179-
cp > 0, spst.ncx2.cdf(x, betac_inv, y), spst.ncx2.sf(x, betac_inv, y)
180-
)
146+
betac_inv = 1.0/betac
147+
alpha = self.sigma/np.power(fwd, betac)
148+
sigma_std = np.maximum(alpha*np.sqrt(texp), np.finfo(float).eps)
149+
kk = strike/fwd
150+
x = 1.0/np.square(betac*sigma_std)
151+
y = np.power(kk, 2*betac)*x
152+
153+
cdf = np.where(cp > 0, spst.ncx2.cdf(x, betac_inv, y), spst.ncx2.sf(x, betac_inv, y))
181154
return cdf
182155

183156
def gamma(self, strike, spot, texp, cp=1):
184157
fwd, df, divf = self._fwd_factor(spot, texp)
185-
betac_inv = 1 / (1 - self.beta)
158+
betac_inv = 1/(1 - self.beta)
186159

187-
k_star = 1.0 / np.square(self.sigma / betac_inv) / texp
188-
x = k_star * np.power(fwd, 2 / betac_inv)
189-
y = k_star * np.power(strike, 2 / betac_inv)
160+
k_star = 1.0/np.square(self.sigma/betac_inv)/texp
161+
x = k_star*np.power(fwd, 2/betac_inv)
162+
y = k_star*np.power(strike, 2/betac_inv)
190163

191164
if self.beta < 1.0:
192-
gamma = (
193-
(2 + betac_inv - x) * spst.ncx2.pdf(y, 4 + betac_inv, x)
194-
+ x * spst.ncx2.pdf(y, 6 + betac_inv, x)
195-
+ strike
196-
/ fwd
197-
* (
198-
x * spst.ncx2.pdf(x, betac_inv, y)
199-
- y * spst.ncx2.pdf(x, 2 + betac_inv, y)
200-
)
201-
)
165+
gamma = (2 + betac_inv - x)*spst.ncx2.pdf(y, 4 + betac_inv, x) \
166+
+ x*spst.ncx2.pdf(y, 6 + betac_inv, x) \
167+
+ strike/fwd*(x*spst.ncx2.pdf(x, betac_inv, y) - y*spst.ncx2.pdf(x, 2 + betac_inv, y))
202168
else:
203-
gamma = (
204-
x * spst.ncx2.pdf(x, -betac_inv, y)
205-
- y * spst.ncx2.pdf(x, 2 - betac_inv, y)
206-
) + strike / fwd * (
207-
(2 - betac_inv - x) * spst.ncx2.pdf(y, 4 - betac_inv, x)
208-
+ x * spst.ncx2.pdf(y, 6 - betac_inv, x)
209-
)
169+
gamma = (x*spst.ncx2.pdf(x, -betac_inv, y) - y*spst.ncx2.pdf(x, 2 - betac_inv, y)) \
170+
+ strike/fwd*((2 - betac_inv - x)*spst.ncx2.pdf(y, 4 - betac_inv, x)
171+
+ x*spst.ncx2.pdf(y, 6 - betac_inv, x))
210172

211-
gamma *= 2 * (divf / betac_inv) ** 2 / df * x / fwd
173+
gamma *= 2*(divf/betac_inv)**2/df*x/fwd
212174

213175
if self.is_fwd:
214-
gamma *= (df / divf) ** 2
176+
gamma *= (df/divf)**2
215177

216178
return gamma
217179

218180
def vega(self, strike, spot, texp, cp=1):
219181
fwd, df, divf = self._fwd_factor(spot, texp)
220-
spot = fwd * df / divf
182+
spot = fwd*df/divf
221183

222-
betac_inv = 1 / (1 - self.beta)
184+
betac_inv = 1/(1 - self.beta)
223185

224-
k_star = 1.0 / np.square(self.sigma / betac_inv) / texp
225-
x = k_star * np.power(fwd, 2 / betac_inv)
226-
y = k_star * np.power(strike, 2 / betac_inv)
186+
k_star = 1.0/np.square(self.sigma/betac_inv)/texp
187+
x = k_star*np.power(fwd, 2/betac_inv)
188+
y = k_star*np.power(strike, 2/betac_inv)
227189

228190
if self.beta < 1.0:
229-
vega = -fwd * spst.ncx2.pdf(y, 4 + betac_inv, x) + strike * spst.ncx2.pdf(
230-
x, betac_inv, y
231-
)
191+
vega = -fwd*spst.ncx2.pdf(y, 4 + betac_inv, x) + strike*spst.ncx2.pdf(x, betac_inv, y)
232192
else:
233-
vega = fwd * spst.ncx2.pdf(x, -betac_inv, y) - strike * spst.ncx2.pdf(
234-
y, 4 - betac_inv, x
235-
)
236-
237-
sigma = self.sigma * spot ** (self.beta - 1)
193+
vega = fwd*spst.ncx2.pdf(x, -betac_inv, y) - strike*spst.ncx2.pdf(y, 4 - betac_inv, x)
238194

239-
vega *= df * 2 * x / sigma
195+
sigma = self.sigma*np.power(spot, self.beta - 1)
196+
vega *= df*2*x/sigma
240197
return vega
241198

242199
def theta(self, strike, spot, texp, cp=1):

pyfeng/data/heston_benchmark.xlsx

-88 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)