sdevpy 0.0.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. sdevpy/__init__.py +0 -0
  2. sdevpy/analytics/bachelier.py +66 -0
  3. sdevpy/analytics/black.py +81 -0
  4. sdevpy/analytics/fbsabr.py +183 -0
  5. sdevpy/analytics/mcheston.py +203 -0
  6. sdevpy/analytics/mcsabr.py +221 -0
  7. sdevpy/analytics/mczabr.py +220 -0
  8. sdevpy/analytics/sabr.py +72 -0
  9. sdevpy/example.py +2 -0
  10. sdevpy/machinelearning/callbacks.py +112 -0
  11. sdevpy/machinelearning/datasets.py +32 -0
  12. sdevpy/machinelearning/learningmodel.py +151 -0
  13. sdevpy/machinelearning/learningschedules.py +23 -0
  14. sdevpy/machinelearning/topology.py +65 -0
  15. sdevpy/maths/interpolations.py +28 -0
  16. sdevpy/maths/metrics.py +14 -0
  17. sdevpy/maths/optimization.py +1 -0
  18. sdevpy/maths/rand.py +99 -0
  19. sdevpy/projects/datafiles.py +28 -0
  20. sdevpy/projects/pinns/ernst_pinns.py +324 -0
  21. sdevpy/projects/pinns/pinns.py +345 -0
  22. sdevpy/projects/pinns/pinns_worst_of.py +635 -0
  23. sdevpy/projects/stovol/stovolgen.py +65 -0
  24. sdevpy/projects/stovol/stovolplot.py +110 -0
  25. sdevpy/projects/stovol/stovoltrain.py +247 -0
  26. sdevpy/projects/stovol/xsabrfit.py +255 -0
  27. sdevpy/settings.py +14 -0
  28. sdevpy/test.py +199 -0
  29. sdevpy/tools/clipboard.py +40 -0
  30. sdevpy/tools/constants.py +3 -0
  31. sdevpy/tools/filemanager.py +59 -0
  32. sdevpy/tools/jsonmanager.py +48 -0
  33. sdevpy/tools/timegrids.py +89 -0
  34. sdevpy/tools/timer.py +32 -0
  35. sdevpy/volsurfacegen/fbsabrgenerator.py +64 -0
  36. sdevpy/volsurfacegen/mchestongenerator.py +216 -0
  37. sdevpy/volsurfacegen/mcsabrgenerator.py +228 -0
  38. sdevpy/volsurfacegen/mczabrgenerator.py +227 -0
  39. sdevpy/volsurfacegen/sabrgenerator.py +282 -0
  40. sdevpy/volsurfacegen/smilegenerator.py +124 -0
  41. sdevpy/volsurfacegen/stovolfactory.py +44 -0
  42. sdevpy-0.0.1.dist-info/LICENSE +21 -0
  43. sdevpy-0.0.1.dist-info/METADATA +21 -0
  44. sdevpy-0.0.1.dist-info/RECORD +46 -0
  45. sdevpy-0.0.1.dist-info/WHEEL +5 -0
  46. sdevpy-0.0.1.dist-info/top_level.txt +1 -0
sdevpy/__init__.py ADDED
File without changes
@@ -0,0 +1,66 @@
1
+ """ Utilities for Bachelier model """
2
+ import numpy as np
3
+ from scipy.stats import norm
4
+ from scipy.optimize import minimize_scalar
5
+
6
+
7
+ def price(expiry, strike, is_call, fwd, vol):
8
+ """ Option price under the Bachelier model """
9
+ stdev = vol * expiry**0.5
10
+ d = (fwd - strike) / stdev
11
+ wd = d if is_call else -d
12
+ return stdev * (wd * norm.cdf(wd) + norm.pdf(d))
13
+
14
+
15
+ def implied_vol(expiry, strike, is_call, fwd, fwd_price):
16
+ """ P. Jaeckel's method in "Implied Normal Volatility", 6th Jun. 2017 """
17
+ m = fwd - strike
18
+ abs_m = np.abs(m)
19
+ # Special case at ATM
20
+ if abs_m < 1e-8:
21
+ return fwd_price * np.sqrt(2.0 * np.pi) / np.sqrt(expiry)
22
+
23
+ # General case
24
+ tilde_phi_star_c = -0.001882039271
25
+ theta = 1.0 if is_call else -1.0
26
+
27
+ tilde_phi_star = -np.abs(fwd_price - np.maximum(theta * m, 0.0)) / abs_m
28
+ em5 = 1e-5
29
+
30
+ if tilde_phi_star < tilde_phi_star_c:
31
+ g = 1.0 / (tilde_phi_star - 0.5)
32
+ g2 = g**2
33
+ em3 = 1e-3
34
+ num = 0.032114372355 - g2 * (0.016969777977 - g2 * (2.6207332461 * em3
35
+ - 9.6066952861 * em5 * g2))
36
+ den = 1.0 - g2 * (0.6635646938 - g2 * (0.14528712196 - 0.010472855461 * g2))
37
+ eta_bar = num / den
38
+ xb = g * (eta_bar * g2 + 1.0 / np.sqrt(2.0 * np.pi))
39
+ else:
40
+ h = np.sqrt(-np.log(-tilde_phi_star))
41
+ num = 9.4883409779 - h * (9.6320903635 - h * (0.58556997323 + 2.1464093351 * h))
42
+ den = 1.0 - h * (0.65174820867 + h * (1.5120247828 + 6.6437847132 * em5 * h))
43
+ xb = num / den
44
+
45
+ q = (norm.cdf(xb) + norm.pdf(xb) / xb - tilde_phi_star) / norm.pdf(xb)
46
+ xb2 = xb**2
47
+ num = 3.0 * q * xb2 * (2.0 - q * xb * (2.0 + xb2))
48
+ den = 6.0 + q * xb * (-12.0 + xb * (6.0 * q + xb * (-6.0 + q * xb * (3.0 + xb2))))
49
+ xs = xb + num / den
50
+ sigma = abs_m / (np.abs(xs) * np.sqrt(expiry))
51
+ return sigma
52
+
53
+
54
+ def implied_vol_solve(expiry, strike, is_call, fwd, fwd_price):
55
+ """ Direct method by numerical inversion using Brent """
56
+ options = {'xtol': 1e-4, 'maxiter': 100, 'disp': False}
57
+ xmin = 1e-6
58
+ xmax = 1.0
59
+
60
+ def error(vol):
61
+ premium = price(expiry, strike, is_call, fwd, vol)
62
+ return (premium - fwd_price) ** 2
63
+
64
+ res = minimize_scalar(fun=error, bracket=(xmin, xmax), options=options, method='brent')
65
+
66
+ return res.x
@@ -0,0 +1,81 @@
1
+ """ Utilities for Black-Scholes model """
2
+ import numpy as np
3
+ import scipy.stats
4
+ from scipy.optimize import minimize_scalar
5
+ import py_vollib.black.implied_volatility as jaeckel
6
+
7
+ N = scipy.stats.norm.cdf
8
+ # Ninv = scipy.stats.norm.ppf
9
+
10
+
11
+ def price(expiry, strike, is_call, fwd, vol):
12
+ """ Option price under the Black-Scholes model """
13
+ w = 1.0 if is_call else -1.0
14
+ s = vol * np.sqrt(expiry)
15
+ d1 = np.log(fwd / strike) / s + 0.5 * s
16
+ d2 = d1 - s
17
+ return w * (fwd * N(w * d1) - strike * N(w * d2))
18
+
19
+ def implied_vol_jaeckel(expiry, strike, is_call, fwd, fwd_price):
20
+ """ Black-Scholes implied volatility using P. Jaeckel's 'Let's be rational' method,
21
+ from package py_vollib. Install with pip install py_vollib or at
22
+ https://pypi.org/project/py_vollib/. Unfortunately we found it has instabilities
23
+ near ATM. """
24
+ flag = 'c' if is_call else 'p'
25
+ p = fwd_price
26
+ iv = jaeckel.implied_volatility_of_undiscounted_option_price(p, fwd, strike, expiry, flag)
27
+ return iv
28
+
29
+ def implied_vol(expiry, strike, is_call, fwd, fwd_price):
30
+ """ Direct method by numerical inversion using Brent """
31
+ options = {'xtol': 1e-4, 'maxiter': 100, 'disp': False}
32
+ xmin = 1e-6
33
+ xmax = 1.0
34
+
35
+ def error(vol):
36
+ premium = price(expiry, strike, is_call, fwd, vol)
37
+ return (premium - fwd_price) ** 2
38
+
39
+ res = minimize_scalar(fun=error, bracket=(xmin, xmax), options=options, method='brent')
40
+
41
+ return res.x
42
+
43
+ # def performance(spot_vol, repo_rate, div_rate, expiry, strike, fixings):
44
+ # shape = spot_vol.shape
45
+ # num_underlyings = int(shape[0] / 2)
46
+ # # print(num_underlyings)
47
+ # forward_perf = 1.0
48
+ # vol2 = 0.0
49
+ # for i in range(num_underlyings):
50
+ # forward = spot_vol[2 * i] * np.exp((repo_rate - div_rate) * expiry)
51
+ # perf = forward # / fixings[i]
52
+ # # perf = forward / fixings[i]
53
+ # forward_perf = forward_perf * perf
54
+ # vol = spot_vol[2 * i + 1]
55
+ # vol2 = vol2 + np.power(vol, 2)
56
+ #
57
+ # vol = np.sqrt(vol2)
58
+ # # print(vol)
59
+ #
60
+ # # return forward_perf
61
+ # return black_formula(forward_perf, strike, vol, expiry, True)
62
+
63
+ if __name__ == "__main__":
64
+ EXPIRY = 1.0
65
+ VOL = 0.25
66
+ IS_CALL = True
67
+ NUM_POINTS = 100
68
+ # FWD = 100
69
+ # K = 100
70
+ # p = price(EXPIRY, K, IS_CALL, FWD, VOL)
71
+ # iv = implied_vol(EXPIRY, K, IS_CALL, FWD, p)
72
+ # print(iv)
73
+ f_space = np.linspace(100, 120, NUM_POINTS)
74
+ k_space = np.linspace(20, 2180, NUM_POINTS)
75
+ prices = price(EXPIRY, k_space, IS_CALL, f_space, VOL)
76
+ # print(prices)
77
+ implied_vols = []
78
+ for i, k in enumerate(k_space):
79
+ implied_vols.append(implied_vol(EXPIRY, k, IS_CALL, f_space[i], prices[i]))
80
+
81
+ # print(implied_vols)
@@ -0,0 +1,183 @@
1
+ """ Monte-Carlo simulation for Free-Boundary SABR model (vanillas) """
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ import scipy.stats as sp
5
+ # from analytics.sabr import calculate_alpha
6
+ from tools.timegrids import SimpleTimeGridBuilder
7
+ from tools import timer
8
+
9
+
10
+ def price(expiries, strikes, are_calls, fwd, parameters, num_mc=10000, points_per_year=10,
11
+ scheme='Andersen'):
12
+ """ Calculate vanilla prices under Free-Boundary SABR model by Monte-Carlo simulation"""
13
+ floor = 0.00001
14
+
15
+ # Temporarily turn off the warnings for division by 0. This is because on certain paths,
16
+ # the spot becomes so close to 0 that Python effectively handles it as 0. This results in
17
+ # a warning when taking a negative power of it. However, this is not an issue as Python
18
+ # correctly finds +infinity and since we use a floor, this case is correctly handled.
19
+ np.seterr(divide='ignore')
20
+
21
+ # Build time grid
22
+ time_grid_builder = SimpleTimeGridBuilder(points_per_year=points_per_year)
23
+ time_grid_builder.add_grid(expiries)
24
+ time_grid = time_grid_builder.complete_grid()
25
+ num_factors = 2
26
+
27
+ # Find payoff times
28
+ is_payoff = np.in1d(time_grid, expiries)
29
+
30
+ # Retrieve parameters
31
+ lnvol = parameters['LnVol']
32
+ beta = parameters['Beta']
33
+ nu = parameters['Nu']
34
+ rho = parameters['Rho']
35
+ alpha = calculate_fbsabr_alpha(lnvol, fwd, beta)
36
+ nu2 = nu**2
37
+ sqrtmrho2 = np.sqrt(1.0 - rho**2)
38
+
39
+ # Draw all gaussians
40
+ # gaussians = rand.gaussians(num_steps, num_mc, num_factors, rand_method)
41
+
42
+ # Define dimensions
43
+ mean = np.zeros(num_factors)
44
+ corr = np.zeros((num_factors, num_factors))
45
+ for c in range(num_factors):
46
+ corr[c, c] = 1.0
47
+
48
+ # Draw for each step
49
+ seed = 42
50
+ rng = np.random.RandomState(seed)
51
+
52
+ # Initialize paths
53
+ spot = np.ones((2 * num_mc, 1)) * fwd
54
+ vol = np.ones((2 * num_mc, 1)) * 1.0
55
+
56
+ # Loop over time grid
57
+ ts = te = 0
58
+ payoff_count = 0
59
+ mc_prices = []
60
+ for i, t in enumerate(time_grid):
61
+ # print("time iteration " + str(i))
62
+ ts = te
63
+ te = t
64
+ dt = te - ts
65
+ sqrt_dt = np.sqrt(dt)
66
+
67
+ # Evolve
68
+ dz = rng.multivariate_normal(mean, corr, size=num_mc) * sqrt_dt
69
+ dz = np.concatenate((dz, -dz), axis=0) # Antithetic paths
70
+ dz0 = dz[:, 0].reshape(-1, 1)
71
+ dz1 = dz[:, 1].reshape(-1, 1)
72
+
73
+ vols = vol
74
+ abs_f = np.maximum(np.abs(spot), floor)
75
+
76
+ # Evolve vol
77
+ vol *= np.exp(-0.5 * nu2 * dt + nu * dz1)
78
+
79
+ # Evolve spot
80
+ if scheme == 'Euler':
81
+ dw = rho * dz1 + sqrtmrho2 * dz0
82
+ spot = spot + alpha * abs_f**beta * dw * vols
83
+ elif scheme == 'Andersen':
84
+ vole = vol
85
+ spot = spot + alpha * abs_f**beta * (sqrtmrho2 * vols * dz0 + rho / nu * (vole - vols))
86
+ else:
87
+ raise ValueError("Unknown scheme in FBSABR: " + scheme)
88
+
89
+ # Calculate payoff
90
+ if is_payoff[i]:
91
+ w = [1.0 if is_call else -1.0 for is_call in are_calls[payoff_count]]
92
+ w = np.asarray(w).reshape(1, -1)
93
+ k = np.asarray(strikes[payoff_count]).reshape(1, -1)
94
+ payoff = np.maximum(w * (spot - k), 0.0)
95
+ rpayoff = np.mean(payoff, axis=0)
96
+ mc_prices.append(rpayoff)
97
+ payoff_count += 1
98
+
99
+ np.seterr(divide='warn')
100
+
101
+ return np.asarray(mc_prices)
102
+
103
+
104
+ def calculate_fbsabr_alpha(ln_vol, fwd, beta):
105
+ """ Calculate parameter alpha with our definition in terms of ln_vol, i.e.
106
+ alpha = ln_vol * fwd ^ (1.0 - beta) """
107
+ floor = 0.00001
108
+ abs_f = np.maximum(np.abs(fwd), floor)
109
+ return ln_vol * abs_f ** (1.0 - beta)
110
+
111
+ if __name__ == "__main__":
112
+ EXPIRIES = [0.05, 0.10, 0.25, 0.5]
113
+ NSTRIKES = 50
114
+ FWD = -0.005
115
+ SHIFT = 0.03
116
+ SFWD = FWD + SHIFT
117
+ IS_CALL = False
118
+ ARE_CALLS = [IS_CALL] * NSTRIKES
119
+ ARE_CALLS = [ARE_CALLS] * len(EXPIRIES)
120
+ LNVOL = 0.25
121
+ # Spread method
122
+ # SPREADS = np.linspace(-200, 200, NSTRIKES)
123
+ # SPREADS = np.asarray([SPREADS] * len(EXPIRIES))
124
+ # STRIKES = FWD + SPREADS / 10000.0
125
+ # SSTRIKES = STRIKES + SHIFT
126
+ # XAXIS = SPREADS
127
+ # Distribution method
128
+ np_expiries = np.asarray(EXPIRIES).reshape(-1, 1)
129
+ PERCENT = np.linspace(0.01, 0.99, NSTRIKES)
130
+ PERCENT = np.asarray([PERCENT] * len(EXPIRIES))
131
+ ITO = -0.5 * LNVOL**2 * np_expiries
132
+ DIFF = LNVOL * np.sqrt(np_expiries) * sp.norm.ppf(PERCENT)
133
+ SSTRIKES = SFWD * np.exp(ITO + DIFF)
134
+ STRIKES = SSTRIKES - SHIFT
135
+ XAXIS = STRIKES
136
+
137
+ PARAMETERS = {'LnVol': LNVOL, 'Beta': 0.1, 'Nu': 0.50, 'Rho': -0.25}
138
+ NUM_MC = 100 * 1000
139
+ POINTS_PER_YEAR = 25
140
+ # SCHEME = 'Andersen'
141
+ SCHEME = 'Euler'
142
+
143
+ # Calculate MC prices
144
+ mc_timer = timer.Stopwatch("MC")
145
+ mc_timer.trigger()
146
+ MC_PRICES = price(EXPIRIES, STRIKES, ARE_CALLS, FWD, PARAMETERS, NUM_MC, POINTS_PER_YEAR,
147
+ scheme=SCHEME)
148
+ mc_timer.stop()
149
+ mc_timer.print()
150
+
151
+ # print(MC_PRICES)
152
+
153
+ # Convert to IV and compare against approximate closed-form
154
+ import black
155
+ import bachelier
156
+ mc_ivs = []
157
+ for a, expiry in enumerate(EXPIRIES):
158
+ mc_iv = []
159
+ for j, sstrike in enumerate(SSTRIKES[a]):
160
+ # mc_iv.append(black.implied_vol(expiry, sstrike, IS_CALL, SFWD, MC_PRICES[a, j]))
161
+ mc_iv.append(bachelier.implied_vol(expiry, STRIKES[a, j], IS_CALL, FWD, MC_PRICES[a, j]))
162
+ mc_ivs.append(mc_iv)
163
+
164
+ plt.figure(figsize=(10, 8))
165
+ plt.subplots_adjust(hspace=0.40)
166
+ plt.subplot(2, 2, 1)
167
+ plt.plot(XAXIS[0], mc_ivs[0], label='MC')
168
+ plt.legend(loc='best')
169
+ plt.title(f"Expiry: {EXPIRIES[0]}")
170
+ plt.subplot(2, 2, 2)
171
+ plt.plot(XAXIS[1], mc_ivs[1], label='MC')
172
+ plt.legend(loc='best')
173
+ plt.title(f"Expiry: {EXPIRIES[1]}")
174
+ plt.subplot(2, 2, 3)
175
+ plt.plot(XAXIS[2], mc_ivs[2], label='MC')
176
+ plt.legend(loc='best')
177
+ plt.title(f"Expiry: {EXPIRIES[2]}")
178
+ plt.subplot(2, 2, 4)
179
+ plt.plot(XAXIS[3], mc_ivs[3], label='MC')
180
+ plt.legend(loc='best')
181
+ plt.title(f"Expiry: {EXPIRIES[3]}")
182
+
183
+ plt.show()
@@ -0,0 +1,203 @@
1
+ """ Monte-Carlo simulation for Heston model (vanillas). The model is defined as
2
+ dS = sqrt(v) * S * dW
3
+ dv = kappa * (theta - v) * dt + xi * sqrt(v) * dZ with <dW, dZ> = rho
4
+ """
5
+ import numpy as np
6
+ import matplotlib.pyplot as plt
7
+ import scipy.stats as sp
8
+ # from analytics.sabr import calculate_alpha
9
+ from tools.timegrids import SimpleTimeGridBuilder
10
+ from tools import timer
11
+
12
+
13
+ def price(expiries, strikes, are_calls, fwd, parameters, num_mc=10000, points_per_year=10):
14
+ """ Calculate vanilla prices under Heston model by Monte-Carlo simulation"""
15
+ scale = fwd
16
+ if scale < 0.0:
17
+ raise ValueError("Negative forward")
18
+
19
+ # Temporarily turn off the warnings for division by 0. This is because on certain paths,
20
+ # the spot becomes so close to 0 that Python effectively handles it as 0. This results in
21
+ # a warning when taking a negative power of it. However, this is not an issue as Python
22
+ # correctly finds +infinity and since we use a floor, this case is correctly handled.
23
+ np.seterr(divide='ignore')
24
+
25
+ # Build time grid
26
+ time_grid_builder = SimpleTimeGridBuilder(points_per_year=points_per_year)
27
+ time_grid_builder.add_grid(expiries)
28
+ time_grid = time_grid_builder.complete_grid()
29
+ num_factors = 2
30
+
31
+ # Find payoff times
32
+ is_payoff = np.in1d(time_grid, expiries)
33
+
34
+ # Retrieve parameters
35
+ lnvol = parameters['LnVol']
36
+ kappa = parameters['Kappa']
37
+ theta = parameters['Theta']
38
+ xi = parameters['Xi']
39
+ rho = parameters['Rho']
40
+ sqrtmrho2 = np.sqrt(1.0 - rho**2)
41
+ v0 = calculate_v0(lnvol)
42
+
43
+ # Draw all gaussians
44
+ # gaussians = rand.gaussians(num_steps, num_mc, num_factors, rand_method)
45
+
46
+ # Define dimensions
47
+ mean = np.zeros(num_factors)
48
+ corr = np.zeros((num_factors, num_factors))
49
+ for c in range(num_factors):
50
+ corr[c, c] = 1.0
51
+
52
+ # Draw for each step
53
+ seed = 42
54
+ rng = np.random.RandomState(seed)
55
+
56
+ # Initialize paths
57
+ spot = np.ones((2 * num_mc, 1)) * fwd
58
+ vol2 = np.ones((2 * num_mc, 1)) * v0
59
+
60
+ # Loop over time grid
61
+ ts = te = 0
62
+ payoff_count = 0
63
+ mc_prices = []
64
+ for i, t in enumerate(time_grid):
65
+ ts = te
66
+ te = t
67
+ dt = te - ts
68
+ sqrt_dt = np.sqrt(dt)
69
+
70
+ # Evolve
71
+ dz = rng.multivariate_normal(mean, corr, size=num_mc) * sqrt_dt
72
+ dz = np.concatenate((dz, -dz), axis=0) # Antithetic paths
73
+ dz0 = dz[:, 0].reshape(-1, 1)
74
+ dz1 = dz[:, 1].reshape(-1, 1)
75
+
76
+ # Evolve vol
77
+ vol2s = np.abs(vol2)
78
+ sqrt_vol2s = np.sqrt(vol2s)
79
+ vol2e = vol2s + kappa * (theta - vol2s) * dt + xi * sqrt_vol2s * dz1
80
+ vol2e = np.abs(vol2e)
81
+ vol2 = vol2e
82
+
83
+ # Evolve spot
84
+ intvol2 = 0.5 * (vol2s + vol2e) * dt
85
+ ito = 0.5 * intvol2
86
+ dw = rho * dz1 + sqrtmrho2 * dz0
87
+ spot *= np.exp(-ito + sqrt_vol2s * dw)
88
+
89
+ # Calculate payoff
90
+ if is_payoff[i]:
91
+ w = [1.0 if is_call else -1.0 for is_call in are_calls[payoff_count]]
92
+ w = np.asarray(w).reshape(1, -1)
93
+ k = np.asarray(strikes[payoff_count]).reshape(1, -1)
94
+ payoff = np.maximum(w * (spot - k), 0.0)
95
+ rpayoff = np.mean(payoff, axis=0)
96
+ mc_prices.append(rpayoff)
97
+ payoff_count += 1
98
+
99
+ np.seterr(divide='warn')
100
+
101
+ return np.asarray(mc_prices)
102
+
103
+ def calculate_v0(lnvol):
104
+ return lnvol**2
105
+
106
+
107
+ if __name__ == "__main__":
108
+ EXPIRIES = [0.5, 1.0, 5.0, 10.0]
109
+ NSTRIKES = 50
110
+ FWD = -0.005
111
+ SHIFT = 0.03
112
+ SFWD = FWD + SHIFT
113
+ IS_CALL = False
114
+ ARE_CALLS = [IS_CALL] * NSTRIKES
115
+ ARE_CALLS = [ARE_CALLS] * len(EXPIRIES)
116
+ LNVOL = 0.25
117
+ # Spread method
118
+ # SPREADS = np.linspace(-200, 200, NSTRIKES)
119
+ # SPREADS = np.asarray([SPREADS] * len(EXPIRIES))
120
+ # STRIKES = FWD + SPREADS / 10000.0
121
+ # SSTRIKES = STRIKES + SHIFT
122
+ # XAXIS = SPREADS
123
+ # Distribution method
124
+ np_expiries = np.asarray(EXPIRIES).reshape(-1, 1)
125
+ PERCENT = np.linspace(0.01, 0.99, NSTRIKES)
126
+ PERCENT = np.asarray([PERCENT] * len(EXPIRIES))
127
+ ITO = -0.5 * LNVOL**2 * np_expiries
128
+ DIFF = LNVOL * np.sqrt(np_expiries) * sp.norm.ppf(PERCENT)
129
+ SSTRIKES = SFWD * np.exp(ITO + DIFF)
130
+ STRIKES = SSTRIKES - SHIFT
131
+ XAXIS = STRIKES
132
+
133
+ THETA = LNVOL**2
134
+ PARAMETERS = {'LnVol': LNVOL, 'Kappa': 1.0, 'Theta': THETA, 'Xi': 0.50, 'Rho': -0.25}
135
+ NUM_MC = 100 * 1000
136
+ POINTS_PER_YEAR = 25
137
+ # SCHEME = 'LogAndersen'
138
+ # SCHEME = 'LogEuler'
139
+
140
+ # Calculate MC prices
141
+ mc_timer = timer.Stopwatch("MC")
142
+ mc_timer.trigger()
143
+ MC_PRICES = price(EXPIRIES, SSTRIKES, ARE_CALLS, SFWD, PARAMETERS, NUM_MC, POINTS_PER_YEAR)
144
+ mc_timer.stop()
145
+ mc_timer.print()
146
+
147
+ # Convert to IV and compare against approximate closed-form
148
+ import black
149
+ import bachelier
150
+ mc_ivs = []
151
+ n_ivs = []
152
+ for a, expiry in enumerate(EXPIRIES):
153
+ mc_iv = []
154
+ cf_iv = []
155
+ n_iv = []
156
+ for j, sstrike in enumerate(SSTRIKES[a]):
157
+ mc_iv.append(black.implied_vol(expiry, sstrike, IS_CALL, SFWD, MC_PRICES[a, j]))
158
+ n_iv.append(bachelier.implied_vol_solve(expiry, STRIKES[a, j], IS_CALL, FWD,
159
+ MC_PRICES[a, j]))
160
+ mc_ivs.append(mc_iv)
161
+ n_ivs.append(n_iv)
162
+
163
+ plt.figure(figsize=(10, 8))
164
+ plt.subplots_adjust(hspace=0.40)
165
+ plt.subplot(2, 2, 1)
166
+ plt.plot(XAXIS[0], mc_ivs[0], label='MC')
167
+ plt.legend(loc='best')
168
+ plt.title(f"Expiry: {EXPIRIES[0]}")
169
+ plt.subplot(2, 2, 2)
170
+ plt.plot(XAXIS[1], mc_ivs[1], label='MC')
171
+ plt.legend(loc='best')
172
+ plt.title(f"Expiry: {EXPIRIES[1]}")
173
+ plt.subplot(2, 2, 3)
174
+ plt.plot(XAXIS[2], mc_ivs[2], label='MC')
175
+ plt.legend(loc='best')
176
+ plt.title(f"Expiry: {EXPIRIES[2]}")
177
+ plt.subplot(2, 2, 4)
178
+ plt.plot(XAXIS[3], mc_ivs[3], label='MC')
179
+ plt.legend(loc='best')
180
+ plt.title(f"Expiry: {EXPIRIES[3]}")
181
+
182
+ plt.show()
183
+
184
+ # plt.figure(figsize=(10, 8))
185
+ # plt.subplots_adjust(hspace=0.40)
186
+ # plt.subplot(2, 2, 1)
187
+ # plt.plot(XAXIS[0], n_ivs[0], label='MC')
188
+ # plt.legend(loc='best')
189
+ # plt.title(f"NVOL Expiry: {EXPIRIES[0]}")
190
+ # plt.subplot(2, 2, 2)
191
+ # plt.plot(XAXIS[1], n_ivs[1], label='MC')
192
+ # plt.legend(loc='best')
193
+ # plt.title(f"NVOL Expiry: {EXPIRIES[1]}")
194
+ # plt.subplot(2, 2, 3)
195
+ # plt.plot(XAXIS[2], n_ivs[2], label='MC')
196
+ # plt.legend(loc='best')
197
+ # plt.title(f"NVOL Expiry: {EXPIRIES[2]}")
198
+ # plt.subplot(2, 2, 4)
199
+ # plt.plot(XAXIS[3], n_ivs[3], label='MC')
200
+ # plt.legend(loc='best')
201
+ # plt.title(f"NVOL Expiry: {EXPIRIES[3]}")
202
+
203
+ # plt.show()