Skip to content

Commit 505a4a9

Browse files
committed
Added Numba parallelisation to pricing via monte carlo
1 parent 3906035 commit 505a4a9

File tree

126 files changed

+1192
-1148
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

126 files changed

+1192
-1148
lines changed
File renamed without changes.

__init__.py

Lines changed: 0 additions & 5 deletions
This file was deleted.

docs/QUICKSTART_BONDS.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
## Quickstart Guide to Bonds
1+
## Quickstart Guide to Bond Analysis using financepy
22
To analyse a bond first load up the Date and other utility classes such as Frequency and DayCounts. The easiest way to do this is to use a wildcard import. The downside is that it imports a lot of unnecessary classes and will take a second or so. But it is probably the simplest way to start until you become familiar with the library structure.
33

44
```

financepy/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = "1.0.0"
1+
__version__ = "1.0.1"

financepy/models/cir_montecarlo.py

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
from enum import Enum
44
from numba import njit, float64, int64
5+
import numba as nb
6+
57
import numpy as np
68
from ..utils.helpers import label_to_string
79

@@ -122,7 +124,12 @@ def draw(rt, a, b, sigma, dt):
122124
########################################################################################
123125

124126

125-
@njit(float64[:](float64, float64, float64, float64, float64, float64, int64, int64))
127+
@njit(
128+
float64[:](float64, float64, float64, float64, float64, float64, int64, int64),
129+
parallel=True,
130+
fastmath=True,
131+
cache=True,
132+
)
126133
def rate_path_mc(r0, a, b, sigma, t, dt, seed, scheme):
127134
"""Generate a path of CIR rates using a number of numerical schemes."""
128135

@@ -136,7 +143,7 @@ def rate_path_mc(r0, a, b, sigma, t, dt, seed, scheme):
136143

137144
sigmasqrt_dt = sigma * np.sqrt(dt)
138145

139-
for _ in range(0, num_paths):
146+
for _ in nb.prange(num_paths):
140147

141148
r = r0
142149
z = np.random.normal(0.0, 1.0, size=num_steps - 1)
@@ -154,7 +161,7 @@ def rate_path_mc(r0, a, b, sigma, t, dt, seed, scheme):
154161
x = np.exp(-a * dt)
155162
y = 1.0 - x
156163

157-
for _ in range(0, num_paths):
164+
for _ in nb.prange(num_paths):
158165

159166
r = r0
160167
z = np.random.normal(0.0, 1.0, size=num_steps - 1)
@@ -171,7 +178,7 @@ def rate_path_mc(r0, a, b, sigma, t, dt, seed, scheme):
171178
sigmasqrt_dt = sigma * np.sqrt(dt)
172179
sigma2dt = sigma * sigma * dt / 4.0
173180

174-
for _ in range(0, num_paths):
181+
for _ in nb.prange(num_paths):
175182

176183
r = r0
177184
z = np.random.normal(0.0, 1.0, size=num_steps - 1)
@@ -190,7 +197,7 @@ def rate_path_mc(r0, a, b, sigma, t, dt, seed, scheme):
190197
bhat = b - sigma * sigma / 4.0 / a
191198
sqrt_dt = np.sqrt(dt)
192199

193-
for _ in range(0, num_paths):
200+
for _ in nb.prange(num_paths):
194201

195202
r = r0
196203
z = np.random.normal(0.0, 1.0, size=num_steps - 1)
@@ -204,7 +211,7 @@ def rate_path_mc(r0, a, b, sigma, t, dt, seed, scheme):
204211

205212
elif scheme == CIRNumericalScheme.EXACT.value:
206213

207-
for _ in range(0, num_paths):
214+
for _ in nb.prange(num_paths):
208215

209216
r = r0
210217

@@ -229,7 +236,10 @@ def rate_path_mc(r0, a, b, sigma, t, dt, seed, scheme):
229236
int64,
230237
int64,
231238
int64,
232-
)
239+
),
240+
parallel=True,
241+
fastmath=True,
242+
cache=True,
233243
)
234244
def zero_price_mc(r0, a, b, sigma, t, dt, num_paths, seed, scheme):
235245
"""Determine the CIR zero price using Monte Carlo."""
@@ -246,7 +256,7 @@ def zero_price_mc(r0, a, b, sigma, t, dt, num_paths, seed, scheme):
246256

247257
sigmasqrt_dt = sigma * np.sqrt(dt)
248258

249-
for _ in range(0, num_paths):
259+
for _ in nb.prange(num_paths):
250260

251261
r = r0
252262
rsum = r
@@ -268,7 +278,7 @@ def zero_price_mc(r0, a, b, sigma, t, dt, num_paths, seed, scheme):
268278
x = np.exp(-a * dt)
269279
y = 1.0 - x
270280

271-
for _ in range(0, num_paths):
281+
for _ in nb.prange(num_paths):
272282

273283
r = r0
274284
rsum = r0
@@ -290,7 +300,7 @@ def zero_price_mc(r0, a, b, sigma, t, dt, num_paths, seed, scheme):
290300
sigmasqrt_dt = sigma * np.sqrt(dt)
291301
sigma2dt = sigma * sigma * dt / 4.0
292302

293-
for _ in range(0, num_paths):
303+
for _ in nb.prange(num_paths):
294304

295305
r = r0
296306
rsum = r
@@ -313,7 +323,7 @@ def zero_price_mc(r0, a, b, sigma, t, dt, num_paths, seed, scheme):
313323
bhat = b - sigma * sigma / 4.0 / a
314324
sqrt_dt = np.sqrt(dt)
315325

316-
for _ in range(0, num_paths):
326+
for _ in nb.prange(num_paths):
317327

318328
r = r0
319329
rsum = r
@@ -331,7 +341,7 @@ def zero_price_mc(r0, a, b, sigma, t, dt, num_paths, seed, scheme):
331341

332342
elif scheme == CIRNumericalScheme.EXACT.value:
333343

334-
for _ in range(0, num_paths):
344+
for _ in nb.prange(num_paths):
335345

336346
r = r0
337347
rsum = r

financepy/models/gbm_process_simulator.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import numpy as np
44
from numba import njit # , float64, int64
5+
import numba as nb
6+
57
from ..utils.math import cholesky
68
from ..utils.error import FinError
79

@@ -14,7 +16,7 @@
1416
########################################################################################
1517

1618

17-
@njit(fastmath=True, cache=True)
19+
@njit(fastmath=True, cache=True, parallel=True)
1820
def get_paths_times(num_paths, num_time_steps, t, mu, stock_price, volatility, seed):
1921
"""Get the simulated GBM process for a single asset with even num paths and
2022
time steps. Inputs include the number of time steps, paths, the drift mu,
@@ -46,7 +48,7 @@ def get_paths_times(num_paths, num_time_steps, t, mu, stock_price, volatility, s
4648

4749
for it in range(1, num_time_steps + 1):
4850
g_1d = np.random.standard_normal((num_paths_even))
49-
for ip in range(0, num_paths_even):
51+
for ip in nb.prange(num_paths_even):
5052
w = np.exp(g_1d[ip] * vsqrt_dt)
5153
ip_start = ip * 2
5254
s_all[ip_start, it] = s_all[ip_start, it - 1] * m * w
@@ -121,7 +123,7 @@ def get_assets_paths_times(
121123
# or use g_corr = np.einsum('ijk,kl->ijl', g, c)
122124

123125
# Calculate the Cholesky dot product
124-
for ip in range(0, num_paths_even):
126+
for ip in nb.prange(num_paths_even):
125127
for it in range(0, num_time_steps + 1):
126128
for ia in range(0, num_assets):
127129
g_corr[ip][it][ia] = 0.0
@@ -131,7 +133,7 @@ def get_assets_paths_times(
131133
for ia in range(0, num_assets):
132134
s_all[ia, :, 0] = stock_prices[ia]
133135

134-
for ip in range(0, num_paths_even):
136+
for ip in nb.prange(num_paths_even):
135137
ip_start = ip * 2
136138
for it in range(1, num_time_steps + 1):
137139
for ia in range(0, num_assets):
@@ -205,13 +207,13 @@ def get_assets_paths(
205207

206208
# Calculate the dot product
207209
for ia in range(0, num_assets):
208-
for ip in range(0, num_paths_even):
210+
for ip in nb.prange(num_paths_even):
209211
g_corr[ip][ia] = 0.0
210212
for ib in range(0, num_assets):
211213
g_corr[ip][ia] += g[ip][ib] * c[ia][ib]
212214

213215
for ia in range(0, num_assets):
214-
for ip in range(0, num_paths_even):
216+
for ip in nb.prange(num_paths_even):
215217
z = g_corr[ip, ia]
216218
w = np.exp(z * vsqrt_dts[ia])
217219
ip_start = ip * 2

financepy/models/heston.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class HestonNumericalScheme(Enum):
5656
),
5757
cache=True,
5858
fastmath=True,
59+
parallel=True,
5960
)
6061
def get_paths(s0, r, q, v0, kappa, theta, sigma, rho, t, dt, num_paths, seed, scheme):
6162

financepy/models/process_simulator.py

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
from enum import Enum
55

66
from numba import njit, float64, int64
7+
import numba as nb
8+
79
import numpy as np
810

911
from ..utils.error import FinError
@@ -137,6 +139,7 @@ class FinHestonNumericalScheme(Enum):
137139
),
138140
cache=True,
139141
fastmath=True,
142+
parallel=True,
140143
)
141144
def get_heston_paths(
142145
num_paths,
@@ -164,7 +167,7 @@ def get_heston_paths(
164167

165168
if scheme == FinHestonNumericalScheme.EULER_SCHEME.value:
166169
# Basic scheme to first order with truncation on variance
167-
for i_path in range(0, num_paths):
170+
for i_path in nb.prange(num_paths):
168171
s = s0
169172
v = v0
170173
for i_step in range(1, num_steps + 1):
@@ -188,7 +191,7 @@ def get_heston_paths(
188191

189192
elif scheme == FinHestonNumericalScheme.EULERLOG_SCHEME.value:
190193
# Basic scheme to first order with truncation on variance
191-
for i_path in range(0, num_paths):
194+
for i_path in nb.prange(num_paths):
192195
x = log(s0)
193196
v = v0
194197
for i_step in range(1, num_steps + 1):
@@ -220,7 +223,7 @@ def get_heston_paths(
220223
c1 = sigma2 * q * (1.0 - q) / kappa
221224
c2 = theta * sigma2 * ((1.0 - q) ** 2) / 2.0 / kappa
222225

223-
for i_path in range(0, num_paths):
226+
for i_path in nb.prange(num_paths):
224227
x = log(s0)
225228
vn = v0
226229
for i_step in range(1, num_steps + 1):
@@ -283,6 +286,7 @@ class FinGBMNumericalScheme(Enum):
283286
float64[:, :](int64, int64, float64, float64, float64, float64, int64, int64),
284287
cache=True,
285288
fastmath=True,
289+
parallel=True,
286290
)
287291
def get_gbm_paths(num_paths, num_annual_steps, t, mu, stock_price, sigma, scheme, seed):
288292

@@ -298,7 +302,7 @@ def get_gbm_paths(num_paths, num_annual_steps, t, mu, stock_price, sigma, scheme
298302
s_all[:, 0] = stock_price
299303
for it in range(1, num_time_steps + 1):
300304
g1_d = np.random.standard_normal((num_paths))
301-
for ip in range(0, num_paths):
305+
for ip in nb.prange(num_paths):
302306
w = np.exp(g1_d[ip] * vsqrt_dt)
303307
s_all[ip, it] = s_all[ip, it - 1] * m * w
304308

@@ -308,7 +312,7 @@ def get_gbm_paths(num_paths, num_annual_steps, t, mu, stock_price, sigma, scheme
308312
s_all[:, 0] = stock_price
309313
for it in range(1, num_time_steps + 1):
310314
g1_d = np.random.standard_normal((num_paths))
311-
for ip in range(0, num_paths):
315+
for ip in nb.prange(num_paths):
312316
w = np.exp(g1_d[ip] * vsqrt_dt)
313317
s_all[ip, it] = s_all[ip, it - 1] * m * w
314318
s_all[ip + num_paths, it] = s_all[ip + num_paths, it - 1] * m / w
@@ -355,7 +359,7 @@ def get_vasicek_paths(
355359
if scheme == FinVasicekNumericalScheme.NORMAL.value:
356360
rate_path = np.empty((num_paths, num_steps + 1))
357361
rate_path[:, 0] = r0
358-
for i_path in range(0, num_paths):
362+
for i_path in nb.prange(num_paths):
359363
r = r0
360364
z = np.random.normal(0.0, 1.0, size=num_steps)
361365
for i_step in range(1, num_steps + 1):
@@ -364,7 +368,7 @@ def get_vasicek_paths(
364368
elif scheme == FinVasicekNumericalScheme.ANTITHETIC.value:
365369
rate_path = np.empty((2 * num_paths, num_steps + 1))
366370
rate_path[:, 0] = r0
367-
for i_path in range(0, num_paths):
371+
for i_path in nb.prange(num_paths):
368372
r1 = r0
369373
r2 = r0
370374
z = np.random.normal(0.0, 1.0, size=num_steps)
@@ -410,7 +414,7 @@ def get_cir_paths(
410414

411415
if scheme == CIRNumericalScheme.EULER_SCHEME.value:
412416
sigma_sqrt_dt = sigma * sqrt(dt)
413-
for i_path in range(0, num_paths):
417+
for i_path in nb.prange(num_paths):
414418
r = r0
415419
z = np.random.normal(0.0, 1.0, size=num_steps)
416420
for i_step in range(1, num_steps + 1):
@@ -426,7 +430,7 @@ def get_cir_paths(
426430
elif scheme == CIRNumericalScheme.LOGNORMAL_SCHEME.value:
427431
x = exp(-kappa * dt)
428432
y = 1.0 - x
429-
for i_path in range(0, num_paths):
433+
for i_path in nb.prange(num_paths):
430434
r = r0
431435
z = np.random.normal(0.0, 1.0, size=num_steps)
432436
for i_step in range(1, num_steps + 1):
@@ -439,7 +443,7 @@ def get_cir_paths(
439443
elif scheme == CIRNumericalScheme.MILSTEIN_SCHEME.value:
440444
sigma_sqrt_dt = sigma * sqrt(dt)
441445
sigma2dt = sigma * sigma * dt / 4.0
442-
for i_path in range(0, num_paths):
446+
for i_path in nb.prange(num_paths):
443447
r = r0
444448
z = np.random.normal(0.0, 1.0, size=num_steps)
445449
for i_step in range(1, num_steps + 1):
@@ -455,7 +459,7 @@ def get_cir_paths(
455459
elif scheme == CIRNumericalScheme.KAHLJACKEL_SCHEME.value:
456460
bhat = theta - sigma * sigma / 4.0 / kappa
457461
sqrt_dt = sqrt(dt)
458-
for i_path in range(0, num_paths):
462+
for i_path in nb.prange(num_paths):
459463
r = r0
460464
z = np.random.normal(0.0, 1.0, size=num_steps)
461465
for i_step in range(1, num_steps + 1):

financepy/models/vasicek_mc.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
from math import sqrt, exp
44
from numba import njit, float64, int64
5+
import numba as nb
56
import numpy as np
67

78
from ..utils.helpers import label_to_string
@@ -71,7 +72,12 @@ def zero_price(r0, a, b, sigma, t):
7172
########################################################################################
7273

7374

74-
@njit(float64[:](float64, float64, float64, float64, float64, float64, int64))
75+
@njit(
76+
float64[:](float64, float64, float64, float64, float64, float64, int64),
77+
parallel=True,
78+
fastmath=True,
79+
cache=True,
80+
)
7581
def rate_path_mc(r0, a, b, sigma, t, dt, seed):
7682
"""Generate a path of short rates using Vasicek model"""
7783

@@ -83,7 +89,7 @@ def rate_path_mc(r0, a, b, sigma, t, dt, seed):
8389

8490
sigmasqrt_dt = sigma * sqrt(dt)
8591

86-
for _ in range(0, num_paths):
92+
for _ in nb.prange(num_paths):
8793

8894
r = r0
8995
z = np.random.normal(0.0, 1.0, size=num_steps - 1)
@@ -100,16 +106,17 @@ def rate_path_mc(r0, a, b, sigma, t, dt, seed):
100106

101107
@njit(
102108
float64(float64, float64, float64, float64, float64, float64, int64, int64),
103-
fastmath=True,
104109
cache=True,
110+
fastmath=True,
111+
parallel=True,
105112
)
106113
def zero_price_mc(r0, a, b, sigma, t, dt, num_paths, seed):
107114
"""Generate zero price by Monte Carlo using Vasicek model"""
108115
np.random.seed(seed)
109116
num_steps = int(t / dt)
110117
sigmasqrt_dt = sigma * sqrt(dt)
111118
zcb = 0.0
112-
for _ in range(0, num_paths):
119+
for _ in nb.prange(num_paths):
113120
z = np.random.normal(0.0, 1.0, size=num_steps)
114121
rsum = 0.0
115122
r = r0

0 commit comments

Comments
 (0)