VISIXION GmbH



Parallel Monte Carlo Valuation of American Options with IPython Notebook

This IPython Notebook illustrates how to implement a parallel valuation of American options by Monte Carlo simulation. The algorithm used is the Least-Squares Monte Carlo algorithm as proposed in Longstaff-Schwartz (2001): "Valuing American Options by Simulation: A Simple Least-Squares Approach." Review of Financial Studies, Vol. 14, 113-147. We observe speed-ups that are almost linear in the number of cores.

by Dr. Yves J. Hilpisch

Visixion GmbH

www.visixion.com

quant@visixion.com

April 2013

The LSM Algorithm

The following is an implementation of the Least-Squares Monte Carlo algorithm (LSM) of Longstaff-Schwartz (2001).

In [1]:
def optionValue(S0, vol, T, K=40, M=50, I=4096, r=0.06):
    import numpy as np
    np.random.seed(150000)  # fix the seed for every valuation
    dt = T / M  # time interval
    df = np.exp(-r * dt)  # discount factor per time time interval
    # Simulation of Index Levels
    S = np.zeros((M + 1, I), 'd')  # stock price matrix
    S[0, :] = S0  # intial values for stock price
    for t in range(1, M + 1):
        ran = np.random.standard_normal(I / 2)
        ran = np.concatenate((ran, -ran))  # antithetic variates
        ran = ran - np.mean(ran)  # correct first moment
        ran = ran / np.std(ran)  # correct second moment
        S[t, :] = S[t - 1, :] * np.exp((r - vol ** 2 / 2) * dt
                        + vol * ran * np.sqrt(dt))
    h = np.maximum(K - S, 0)  # inner values for put option
    V = np.zeros_like(h)  # value matrix
    V[-1] = h[-1]
    # Valuation by LSM
    for t in range(M - 1, 0, -1):
        rg = np.polyfit(S[t, :], V[t + 1, :] * df, 5)  # regression
        C = np.polyval(rg, S[t, :])  # evaluation of regression
        V[t, :] = np.where(h[t, :] > C, h[t, :],
                         V[t + 1, :] * df)  # exercise decision/optimization
    V0 = np.sum(V[1, :] * df) / I  # LSM estimator
    print "S0 %4.1f|vol %4.2f|T %2.1f| Option Value %8.3f" % (S0, vol, T, V0)
    return V0

The Sequential Calculation

We want to replicate the whole table 1 of the seminal paper.

In [2]:
def seqValue():
    optionValues = []
    for S0 in (36., 38., 40., 42., 44.):  # initial stock price values
        for vol in (0.2, 0.4):  # volatility values
            for T in (1.0, 2.0):  # times-to-maturity
                optionValues.append(optionValue(S0, vol, T))
    return optionValues

Now, we measure the time for the 20 different American put options of that table 1 with sequential execution.

In [3]:
from time import time
t0 = time()
optionValues = seqValue()  # calculate all values
t1 = time(); d1 = t1 - t0
print "Duration in Seconds %6.3f" % d1
S0 36.0|vol 0.20|T 1.0| Option Value    4.500
S0 36.0|vol 0.20|T 2.0| Option Value    4.854
S0 36.0|vol 0.40|T 1.0| Option Value    7.112
S0 36.0|vol 0.40|T 2.0| Option Value    8.472
S0 38.0|vol 0.20|T 1.0| Option Value    3.262
S0 38.0|vol 0.20|T 2.0| Option Value    3.723
S0 38.0|vol 0.40|T 1.0| Option Value    6.112
S0 38.0|vol 0.40|T 2.0| Option Value    7.682
S0 40.0|vol 0.20|T 1.0| Option Value    2.305
S0 40.0|vol 0.20|T 2.0| Option Value    2.861
S0 40.0|vol 0.40|T 1.0| Option Value    5.258
S0 40.0|vol 0.40|T 2.0| Option Value    6.899
S0 42.0|vol 0.20|T 1.0| Option Value    1.569
S0 42.0|vol 0.20|T 2.0| Option Value    2.164
S0 42.0|vol 0.40|T 1.0| Option Value    4.544
S0 42.0|vol 0.40|T 2.0| Option Value    6.171
S0 44.0|vol 0.20|T 1.0| Option Value    1.020
S0 44.0|vol 0.20|T 2.0| Option Value    1.602
S0 44.0|vol 0.40|T 1.0| Option Value    3.912
S0 44.0|vol 0.40|T 2.0| Option Value    5.591
Duration in Seconds  2.599

The Parallel Calculation

First, start a local cluster, if you have multiple cores in your machine.

In [4]:
# in the shell ...
####
# ipcluster start -n 4
####
# or maybe more cores

Enable parallel computing capabilities.

In [5]:
from IPython.parallel import Client
cluster_profile = "default"
# the profile is set to the default one
# see IPython docs for further details
c = Client(profile=cluster_profile)
view = c.load_balanced_view()

Again, a loop for the 20 options of table 1. This time asynchronously distributed to the 4 cores.

In [6]:
def parValue():
    optionValues = []
    for S in (36., 38., 40., 42., 44.):
        for vol in (0.2, 0.4):
            for T in (1.0, 2.0):
                value = view.apply_async(optionValue, S, vol, T)
                # asynchronously calculate the option values
                optionValues.append(value)
    return optionValues

Now, we measure the time needed with parallel execution.

In [7]:
def execution():
    optionValues = parValue()  # calculate all values
    print "Submitted tasks %d" % len(optionValues)
    c.wait(optionValues)
    # wait for all tasks to be finished
    return optionValues
t0 = time()
optionValues = execution()
t1 = time(); d2 = t1 - t0
print "Duration in Seconds %6.3f" % d2
Submitted tasks 20
Duration in Seconds  0.756
In [8]:
d1 / d2  # speed-up of parallel execution
Out[8]:
3.436817046801849

Single values for the American put options can be directly accessed.

In [9]:
optionValues[0].result
Out[9]:
4.4995294086976036

You can also inspect the metadata and access single data fields in JSON-typical manner.

In [10]:
optionValues[0].metadata
Out[10]:
{'after': [],
 'completed': datetime.datetime(2013, 4, 27, 8, 33, 35, 269030),
 'data': {},
 'engine_id': 0,
 'engine_uuid': '714c3d3a-704c-423d-a310-31a2d7ad2382',
 'follow': [],
 'msg_id': 'f4be8026-d0e6-416d-a1fc-0488f4fbe206',
 'outputs': [],
 'outputs_ready': True,
 'pyerr': None,
 'pyin': None,
 'pyout': None,
 'received': datetime.datetime(2013, 4, 27, 8, 33, 35, 271120),
 'started': datetime.datetime(2013, 4, 27, 8, 33, 35, 130691),
 'status': 'ok',
 'stderr': '',
 'stdout': 'S0 36.0|vol 0.20|T 1.0| Option Value    4.500\n',
 'submitted': datetime.datetime(2013, 4, 27, 8, 33, 35, 123543)}

The whole output of the 20 valuations.

In [11]:
for result in optionValues:
    print result.metadata['stdout'],
S0 36.0|vol 0.20|T 1.0| Option Value    4.500
S0 36.0|vol 0.20|T 2.0| Option Value    4.854
S0 36.0|vol 0.40|T 1.0| Option Value    7.112
S0 36.0|vol 0.40|T 2.0| Option Value    8.472
S0 38.0|vol 0.20|T 1.0| Option Value    3.262
S0 38.0|vol 0.20|T 2.0| Option Value    3.723
S0 38.0|vol 0.40|T 1.0| Option Value    6.112
S0 38.0|vol 0.40|T 2.0| Option Value    7.682
S0 40.0|vol 0.20|T 1.0| Option Value    2.305
S0 40.0|vol 0.20|T 2.0| Option Value    2.861
S0 40.0|vol 0.40|T 1.0| Option Value    5.258
S0 40.0|vol 0.40|T 2.0| Option Value    6.899
S0 42.0|vol 0.20|T 1.0| Option Value    1.569
S0 42.0|vol 0.20|T 2.0| Option Value    2.164
S0 42.0|vol 0.40|T 1.0| Option Value    4.544
S0 42.0|vol 0.40|T 2.0| Option Value    6.171
S0 44.0|vol 0.20|T 1.0| Option Value    1.020
S0 44.0|vol 0.20|T 2.0| Option Value    1.602
S0 44.0|vol 0.40|T 1.0| Option Value    3.912
S0 44.0|vol 0.40|T 2.0| Option Value    5.591

VISIXION GmbH



Visixion GmbH - The Python Quants

The Python Quants – the company Web site

www.visixion.com

Derivatives Analytics On Demand – our analytics suite

www.dexision.com

Derivatives Analytics with Python – our new book

Read an Excerpt and Order the Book

Contact Us

quant@visixion.com

@dyjh