Python

American Put Option in BCC97 via LS_MCS_Multiple Replications

# LSM Algorithm for American Put in BCC97

# Delta Hedging an American Put Option in BCC97
# via Least Squares Monte Carlo (Multiple Replications)
#
import sys
sys.path.extend(['09_gmm', '11_cal', '12_val'])
import math
import numpy as np
import warnings
warnings.simplefilter('ignore')
import matplotlib as mpl
mpl.rcParams['font.family'] = 'serif'
import matplotlib.pyplot as plt
from H93_calibration import S0, kappa_r, theta_r, sigma_r, r0
from BCC97_simulation import *
from BSM_lsm_hedging_algorithm import plot_hedge_path
#
# Model Parameters
#
opt = np.load('11_cal/opt_full.npy')
kappa_v, theta_v, sigma_v, rho, v0, lamb, mu, delta = opt
#
# Simulation
#
K = S0
T = 1.0
M = 50
I = 50000
a = 1.0 # a from the interval [0.0, 2.0]
dis = 0.01 # change of S[t] in percent to estimate derivative
dt = T / M
moment_matching = True

def BCC97_lsm_put_value(S0, K, T, M, I):
    ''' Function to value American put options by LSM algorithm.
    Parameters
    ==========
    S0: float
    intial index level
    K: float
    strike of the put option
    T: float
    final date/time horizon
    M: int
    number of time steps
    I: int
    number of paths
    Returns
    =======
    V0: float
    LSM Monte Carlo estimator of American put option value
    S: NumPy array
    simulated index level paths
    r: NumPy array
    simulated short rate paths
    v: NumPy array
    simulated variance paths
    ex: NumPy array
    exercise matrix
    rg: NumPy array
    regression coefficients
    h: NumPy array
    inner value matrix
    dt: float
    length of time interval
    '''
    dt = T / M
    # Cholesky Matrix
    cho_matrix = generate_cholesky(rho)
    # Random Numbers
    rand = random_number_generator(M, I, anti_paths, moment_matching)
    # Short Rate Process Simulation
    r = SRD_generate_paths(r0, kappa_r, theta_r, sigma_r, T, M, I,
                           rand, 0, cho_matrix)
    # Variance Process Simulation
    v = SRD_generate_paths(v0, kappa_v, theta_v, sigma_v, T, M, I,
                           rand, 2, cho_matrix)
    # Index Level Process Simulation
    S = B96_generate_paths(S0, r, v, lamb, mu, delta, rand, 1, 3,
                           cho_matrix, T, M, I, moment_matching)
    h = np.maximum(K - S, 0) # inner value matrix
    V = np.maximum(K - S, 0) # value/cash flow matrix
    ex = np.zeros_like(V) # exercise matrix
    D = 10 # number of regression functions
    rg = np.zeros((M + 1, D + 1), dtype=np.float)
    # matrix for regression parameters
    for t in xrange(M - 1, 0, -1):
        df = np.exp(-(r[t] + r[t + 1]) / 2 * dt)
        # select only ITM paths
        itm = np.greater(h[t], 0)
        relevant = np.nonzero(itm)
        rel_S = np.compress(itm, S[t])
        no_itm = len(rel_S)
        if no_itm == 0:
            cv = np.zeros((I), dtype=np.float)
        else:
            rel_v = np.compress(itm, v[t])
            rel_r = np.compress(itm, r[t])
            rel_V = (np.compress(itm, V[t + 1])
                        * np.compress(itm, df))
            matrix = np.zeros((D + 1, no_itm), dtype=np.float)
            matrix[10] = rel_S * rel_v * rel_r
            matrix[9] = rel_S * rel_v
            matrix[8] = rel_S * rel_r
            matrix[7] = rel_v * rel_r
            matrix[6] = rel_S ** 2
            matrix[5] = rel_v ** 2
            matrix[4] = rel_r ** 2
            matrix[3] = rel_S
            matrix[2] = rel_v
            matrix[1] = rel_r
            matrix[0] = 1
            rg[t] = np.linalg.lstsq(matrix.transpose(), rel_V)[0]
            cv = np.dot(rg[t], matrix)
        erg = np.zeros((I), dtype=np.float)
        np.put(erg, relevant, cv)
        V[t] = np.where(h[t] > erg, h[t], V[t + 1] * df)
        # value array
        ex[t] = np.where(h[t] > erg, 1, 0)
        # exercise decision
    df = np.exp(-((r[0] + r[1]) / 2) * dt)
    V0 = max(np.sum(V[1, :] * df) / I, h[0, 0]) # LSM estimator
    return V0, S, r, v, ex, rg, h, dt

def BCC97_hedge_run(p):
    ''' Implements delta hedging for a single path. '''
    #
    # Initializations
    #
    np.random.seed(50000)
    po = np.zeros(M + 1, dtype=np.float) # vector for portfolio values
    vt = np.zeros(M + 1, dtype=np.float) # vector for option values
    delt = np.zeros(M + 1, dtype=np.float) # vector for deltas
    # random path selection ('real path')
    print
    print "DYNAMIC HEDGING OF AMERICAN PUT (BCC97)"
    print "---------------------------------------"
    ds = dis * S0
    V_1, S, r, v, ex, rg, h, dt = BCC97_lsm_put_value(S0 + (2 - a) * ds,
                                                      K, T, M, I)
    # 'data basis' for delta hedging
    V_2 = BCC97_lsm_put_value(S0 - a * ds, K, T, M, I)[0]
    delt[0] = (V_1 - V_2) / (2 * ds)
    V0LSM = BCC97_lsm_put_value(S0, K, T, M, I)[0]
    # initial option value for S0
    vt[0] = V0LSM # initial option values
    po[0] = V0LSM # initial portfolio values
    bo = V0LSM - delt[0] * S0 # initial bond position value
    print "Initial Hedge"
    print "Stocks %8.3f" % delt[0]
    print "Bonds %8.3f" % bo
    print "Cost %8.3f" % (delt[0] * S0 + bo)
    print
    print "Regular Rehedges "
    print 82 * "-"
    print "step|" + 7 * " %9s|" % ('S_t', 'Port', 'Put',
                                   'Diff', 'Stock', 'Bond', 'Cost')
    for t in range(1, M + 1, 1):
        if ex[t, p] == 0:
            df = math.exp((r[t, p] + r[t - 1, p]) / 2 * dt)
            if t != M:
                po[t] = delt[t - 1] * S[t, p] + bo * df
                vt[t] = BCC97_lsm_put_value(S[t, p], K, T - t * dt,
                                          M - t, I)[0]
                ds = dis * S[t, p]
                sd = S[t, p] + (2 - a) * ds # disturbed index level
                stateV_A = [sd * v[t, p] * r[t, p],
                            sd * v[t, p],
                            sd * r[t, p],
                            v[t, p] * r[t, p],
                            sd ** 2,
                            v[t, p] ** 2,
                            r[t, p] ** 2,
                            sd,
                            v[t, p],
                            r[t, p],
                                1]
                # state vector for S[t, p] + (2.0 - a) * dis
                stateV_A.reverse()
                V0A = max(0, np.dot(rg[t], stateV_A))
                # print V0A
                # revaluation via regression
                sd = S[t, p] - a * ds # disturbed index level
                stateV_B = [sd * v[t, p] * r[t, p],
                                sd * v[t, p],
                                sd * r[t, p],
                                v[t, p] * r[t, p],
                                sd ** 2,
                                v[t, p] ** 2,
                                r[t, p] ** 2,
                                sd,
                                v[t, p],
                                r[t, p],
                                1]
                # state vector for S[t, p] - a * dis
                stateV_B.reverse()
                V0B = max(0, np.dot(rg[t], stateV_B))
                # print V0B
                # revaluation via regression
                delt[t] = (V0A - V0B) / (2 * ds)
                bo = po[t] - delt[t] * S[t, p] # bond position value
            else:
                po[t] = delt[t - 1] * S[t, p] + bo * df
                vt[t] = h[t, p]
                # inner value at final date
                delt[t] = 0.0
            print "%4d|" % t + 7 * " %9.3f|" % (S[t, p], po[t], vt[t],
            (po[t] - vt[t]), delt[t], bo, delt[t] * S[t, p] + bo)
        else:
            po[t] = delt[t - 1] * S[t, p] + bo * df
            vt[t] = h[t, p]
        break
    errs = po - vt # hedge errors
    print "MSE %7.3f" % (np.sum(errs ** 2) / len(errs))
    print "Average Error %7.3f" % (np.sum(errs) / len(errs))
    print "Total P&L %7.3f" % np.sum(errs)
    return S[:, p], po, vt, errs, t