Setup

In [1]:
import numpy as np
import matplotlib.pyplot as plt
import math

Explanation

A common numerical method to generate sample paths of a standard (mean 0, variance t) Brownian Motion is to use the $Euler-Maruyama$ approximation:

$ B(t + ∆t) = B(t) +\sqrt[]{∆t}N(0, 1) $

where $N(0, 1)$ is sample draw from a standard Normal distribution, mean 0, variance 1. Similarly, to generate a Brownian Motion with drift parameter μ and diffusion coefficient σ, we can adapt the above:

$B(t+∆t) = B(t)+μ∆t+σ ∆tN(0,1)$

In this case, $B(t) ∼ N(μt, σ2t)$.

Examples

(1) Generate 1,000 paths of a Brownian Motion for 1y with 12 monthly periods, ∆t = 1/12, r = 5%,μ = 4.5%,σ = 10%

Generate the 1y monthly evolution of a random asset starting with $A(0)=100$, and whose return is modeled by above BM, i.e., $A(t) = A(0)e^{B(t)}$

Price the 105-strike call option: $ D(1y)E[max(0, A(1y) − 105)]$, where $D(1y) = \frac{1}{(1 + 5\%)}$. Repeat this 10 times to observe run-to-run variability, i.e, simulation noise.

(2) Antithetic Variance Reduction: Generate 500 sample paths for the Brownian Motion as above, and for each sample path, create its mirror-image: $B(t, ω∗) = −B(t, ω)$ to come up with 1000 total sample paths. Repeat Steps 2, 3 above. Does the simulation noise improve?

(3) Add a 110 Knock-out feature to above option: The call option is knocked out (expires worth- less right away) if asset exceeds 110 before expiration (1y). Does this reduce the cost of the option?

1

In [2]:
## assigning values
n = 12 
t = 1
mu = 0.045
sigma = 0.10
callprice = np.zeros(10)
df = 1/(1.05)
In [3]:
def generateIncrements(t,n,mu,sigma,numPaths): 
    dt=t/n
# Generate increments
    randomSamples=np.random.normal(0,1,(numPaths,n)) 
    X=mu*dt+sigma*math.sqrt(dt)*randomSamples 
    return(X)

def generateBM(X):
# Generate Brownian Motion
    numPaths=len(X)
    n=len(X[0])
    BM=np.zeros((numPaths,n+1)) 
    for t in range(1,n+1):
        BM[:,t]=BM[:,t-1]+X[:,t-1]
    return BM

def generateAssetPrices(A0,BM,plotFlag): # Generate Asset Prices
    A=A0*np.exp(BM)
    if plotFlag: 
        numPaths=len(A)
        plt.figure(figsize=(20,10))
        for i in range(numPaths): 
            plt.plot(A[i]) 
    return(A)

def calculateCallPrice(A,K,df): 
    finalValue=A[:,len(A[0])-1] 
    C0=df*np.average(np.maximum(0,finalValue-K)) 
    return(C0)
In [4]:
for i in range(10):
    Increment = generateIncrements(t,n,mu,sigma,numPaths=1000)
    Brownian = generateBM(Increment)
    AssetPrices = generateAssetPrices(100, Brownian, False)
    callprice[i] = calculateCallPrice(AssetPrices, 105, df)
    print("The call option price: ", callprice[i])
    
print("The average call option price is ", np.mean(callprice))
print('The standard deviation of call option prices is ', np.sqrt(np.var(callprice)))
The call option price:  4.137544936521846
The call option price:  4.152228614457264
The call option price:  4.103462266565741
The call option price:  4.09996098884982
The call option price:  3.9198815380196126
The call option price:  3.89436231025345
The call option price:  4.115936453940868
The call option price:  4.007656677497373
The call option price:  4.249157404318277
The call option price:  3.988635031056767
The average call option price is  4.066882622148102
The standard deviation of call option prices is  0.10551905004147283
In [5]:
generateAssetPrices(100, Brownian, True)
Out[5]:
array([[100.        ,  94.72536761,  88.0107111 , ...,  82.74883581,
         76.64453194,  73.15806415],
       [100.        , 101.94986031, 100.90163266, ...,  91.39132628,
         89.02496661,  87.97598567],
       [100.        ,  99.26464961,  93.14304591, ...,  90.64430953,
         93.96484465,  91.93190317],
       ...,
       [100.        ,  98.24007721,  97.42668662, ..., 103.53588088,
        101.04826292, 101.61788686],
       [100.        , 103.95051026, 102.77321947, ..., 120.89489437,
        125.14361616, 128.02403561],
       [100.        , 101.50699088, 104.86025824, ..., 108.6354823 ,
        111.2042595 , 116.36250388]])

Variance Reduction

In [6]:
def generateAntiTheticIncrements(t,n,mu,sigma,numPaths): 
    dt=t/n
    # Generate half of the randomSamples
    randomSamples1=np.random.normal(0,1,(int(numPaths/2),n))
    # Generate the other half as "mirror" image (in this case reflected around 0)
    randomSamples2=-1*randomSamples1 
    # Put them together
    randomSamples=np.vstack((randomSamples1,randomSamples2)) 
    # Generate Increments
    X=mu*dt+sigma*math.sqrt(dt)*randomSamples 
    return(X)

2

In [7]:
for i in range(10):
    Increment = generateAntiTheticIncrements(t,n,mu,sigma,numPaths=1000)
    Brownian = generateBM(Increment)
    AssetPrices = generateAssetPrices(100, Brownian, False)
    callprice[i] = calculateCallPrice(AssetPrices, 105, df)
    print("The call option price: ", callprice[i])
    
print("The average call option price is ", np.mean(callprice))
print('The standard deviation of call option prices is ', np.sqrt(np.var(callprice)))
The call option price:  4.112025795926078
The call option price:  4.013709567850783
The call option price:  4.4646699824833975
The call option price:  3.9768620008215207
The call option price:  4.028605083449746
The call option price:  3.959220382694704
The call option price:  4.250819077043692
The call option price:  4.0751471839633355
The call option price:  4.2379010282857275
The call option price:  4.102423427285536
The average call option price is  4.1221383529804525
The standard deviation of call option prices is  0.14769546794420607
In [8]:
AssetPrices = generateAssetPrices(100, Brownian, True)

The standard deviation is slightly lower; therefore, the simulation noise improves slightly

3

In [9]:
def calculateKnockCallPrice(A,K,df,KnockOut): 
    Alive = np.ones(len(A))
    for i in range(1000):
        for j in range(1,13):
            if A[i,j]>=KnockOut:
                Alive[i] = 0
    #Multiply the matrix element-wise
    finalValue=A[:,len(A[0])-1]*Alive
    C0=df*np.average(np.maximum(0,finalValue-K)) 
    return(C0)

Knockout effect on NORMAL increments

In [10]:
for i in range(10):
    Increment = generateIncrements(t,n,mu,sigma,numPaths=1000)
    Brownian = generateBM(Increment)
    AssetPrices = generateAssetPrices(100, Brownian, False)
    callprice[i] = calculateKnockCallPrice(AssetPrices, 105, df, 110)
    print("The call option price: ", callprice[i])
print("The average call option price is ", np.mean(callprice))
print('The standard deviation of call option prices is ', np.sqrt(np.var(callprice)))
The call option price:  0.21298008953745054
The call option price:  0.17804543921077098
The call option price:  0.20465116262384483
The call option price:  0.18784885419318323
The call option price:  0.26517919952417734
The call option price:  0.18062803583786538
The call option price:  0.24293766224705343
The call option price:  0.20677949857873829
The call option price:  0.1556073004829491
The call option price:  0.20085278800193457
The average call option price is  0.20355100302379675
The standard deviation of call option prices is  0.030295737986373816

Knockout effect on ANTITHETIC increments

In [11]:
for i in range(10):
    Increment = generateAntiTheticIncrements(t,n,mu,sigma,numPaths=1000)
    Brownian = generateBM(Increment)
    AssetPrices = generateAssetPrices(100, Brownian, False)
    callprice[i] = calculateKnockCallPrice(AssetPrices, 105, df, 110)
    print("The call option price: ", callprice[i])
print("The average call option price is ", np.mean(callprice))
print('The standard deviation of call option prices is ', np.sqrt(np.var(callprice)))
The call option price:  0.18561848428156874
The call option price:  0.2151871588717433
The call option price:  0.22951951753688765
The call option price:  0.19592473304269295
The call option price:  0.19652897766496316
The call option price:  0.24009598549185007
The call option price:  0.20585670657564484
The call option price:  0.2079205744918954
The call option price:  0.22321434741516114
The call option price:  0.21254094962516482
The average call option price is  0.2112407434997572
The standard deviation of call option prices is  0.015731809385084145

The knockout feature REDUCES the cost of the options in BOTH cases