Skip to content

Commit cbf0b9d

Browse files
committed
Add vectorized version
1 parent 5805cbd commit cbf0b9d

File tree

1 file changed

+64
-4
lines changed

1 file changed

+64
-4
lines changed

lectures/monte_carlo.md

Lines changed: 64 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ kernelspec:
1212
---
1313

1414

15+
1516
# Monte Carlo and Option Pricing
1617

1718
## Overview
@@ -48,6 +49,7 @@ from numpy.random import randn
4849
```
4950

5051

52+
5153
## An Introduction to Monte Carlo
5254

5355
In this section we describe how Monte Carlo can be used to compute
@@ -151,6 +153,7 @@ p = 0.5
151153
```
152154

153155

156+
154157
#### A Routine using Loops in Python
155158

156159

@@ -174,6 +177,7 @@ S / n
174177
```
175178

176179

180+
177181
We can also construct a function that contains these operations:
178182

179183
```{code-cell} ipython3
@@ -188,13 +192,15 @@ def compute_mean(n=1_000_000):
188192
```
189193

190194

195+
191196
Now let's call it.
192197

193198
```{code-cell} ipython3
194199
compute_mean()
195200
```
196201

197202

203+
198204
### A Vectorized Routine
199205

200206
If we want a more accurate estimate we should increase $n$.
@@ -219,6 +225,7 @@ compute_mean_vectorized()
219225
```
220226

221227

228+
222229
Notice that this routine is much faster.
223230

224231
We can increase $n$ to get more accuracy and still have reasonable speed:
@@ -230,6 +237,7 @@ compute_mean_vectorized(n=10_000_000)
230237
```
231238

232239

240+
233241
## Pricing a European Call Option under Risk Neutrality
234242

235243
Next we are going to price a European call option under risk neutrality.
@@ -276,6 +284,7 @@ $$
276284
$$
277285

278286

287+
279288
### A Comment on Risk
280289

281290
As suggested by the name, the risk-neutral price ignores risk.
@@ -297,6 +306,7 @@ Nonetheless, the risk-neutral price is an important benchmark, which economists
297306
and financial market participants try to calculate every day.
298307

299308

309+
300310
### Discounting
301311

302312
Another thing we ignored in the previous discussion was time.
@@ -326,6 +336,7 @@ $$
326336
$$
327337

328338

339+
329340
### European Call Options
330341

331342
Now let's price a European call option.
@@ -377,13 +388,15 @@ n = 10
377388
```
378389

379390

391+
380392
We set the simulation size to
381393

382394
```{code-cell} ipython3
383395
M = 10_000_000
384396
```
385397

386398

399+
387400
Here is our code
388401

389402
```{code-cell} ipython3
@@ -394,6 +407,7 @@ print(f"The Monte Carlo option price is approximately {P:3f}")
394407
```
395408

396409

410+
397411
## Pricing Via a Dynamic Model
398412

399413
In this exercise we investigate a more realistic model for the share price $S_n$.
@@ -465,6 +479,7 @@ $$
465479
Here $\{\eta_t\}$ is also IID and standard normal.
466480

467481

482+
468483
### Default Parameters
469484

470485
For the dynamic model, we adopt the following parameter values.
@@ -478,6 +493,7 @@ h0 = 0
478493
```
479494

480495

496+
481497
(Here `S0` is $S_0$ and `h0` is $h_0$.)
482498

483499
For the option we use the following defaults.
@@ -489,6 +505,7 @@ n = 10
489505
```
490506

491507

508+
492509
### Visualizations
493510

494511
With $s_t := \ln S_t$, the price dynamics become
@@ -511,6 +528,7 @@ def simulate_asset_price_path(μ=μ, S0=S0, h0=h0, n=n, ρ=ρ, ν=ν):
511528
```
512529

513530

531+
514532
Here we plot the paths and the log of the paths.
515533

516534
```{code-cell} ipython3
@@ -529,6 +547,7 @@ plt.show()
529547
```
530548

531549

550+
532551
### Computing the Price
533552

534553
Now that our model is more complicated, we cannot easily determine the
@@ -579,6 +598,7 @@ compute_call_price()
579598
```
580599

581600

601+
582602
## Exercises
583603

584604
```{exercise}
@@ -624,6 +644,7 @@ compute_call_price()
624644
```
625645

626646

647+
627648
Notice that this version is faster than the one using a Python loop.
628649

629650
Now let's try with larger $M$ to get a more accurate calculation.
@@ -634,6 +655,7 @@ compute_call_price(M=10_000_000)
634655
```
635656

636657

658+
637659
```{solution-end}
638660
```
639661

@@ -675,30 +697,68 @@ def compute_call_price_with_barrier(β=β,
675697
ρ=ρ,
676698
ν=ν,
677699
bp=bp,
678-
M=10_000):
700+
M=50_000):
679701
current_sum = 0.0
680702
# For each sample path
681703
for m in range(M):
682704
s = np.log(S0)
683705
h = h0
684706
payoff = 0
707+
option_is_null = False
685708
# Simulate forward in time
686709
for t in range(n):
687710
s = s + μ + np.exp(h) * randn()
688711
h = ρ * h + ν * randn()
689712
if np.exp(s) > bp:
690713
payoff = 0
714+
option_is_null = True
691715
break
692-
else:
693-
payoff = np.maximum(np.exp(s) - K, 0)
716+
717+
if not option_is_null:
718+
payoff = np.maximum(np.exp(s) - K, 0)
694719
# And add the payoff to current_sum
695720
current_sum += payoff
696721
697722
return β**n * current_sum / M
698723
```
699724

700725
```{code-cell} ipython3
701-
compute_call_price_with_barrier()
726+
%time compute_call_price_with_barrier()
727+
```
728+
729+
730+
731+
Let's look at the vectorized version which is faster than using Python loops.
732+
733+
```{code-cell} ipython3
734+
def compute_call_price_with_barrier_vector(β=β,
735+
μ=μ,
736+
S0=S0,
737+
h0=h0,
738+
K=K,
739+
n=n,
740+
ρ=ρ,
741+
ν=ν,
742+
bp=bp,
743+
M=50_000):
744+
s = np.full(M, np.log(S0))
745+
h = np.full(M, h0)
746+
option_is_null = np.full(M, False)
747+
for t in range(n):
748+
Z = np.random.randn(2, M)
749+
s = s + μ + np.exp(h) * Z[0, :]
750+
h = ρ * h + ν * Z[1, :]
751+
# Mark all the options null where S_n > barrier price
752+
option_is_null = np.where(np.exp(s) > bp, True, option_is_null)
753+
754+
# mark payoff as 0 in the indices where options are null
755+
payoff = np.where(option_is_null, 0, np.maximum(np.exp(s) - K, 0))
756+
expectation = np.mean(payoff)
757+
return β**n * expectation
758+
```
759+
760+
```{code-cell} ipython3
761+
%time compute_call_price_with_barrier_vector()
702762
```
703763

704764
```{solution-end}

0 commit comments

Comments
 (0)