Module mean_variance_hedge.heston

Implementing the exact Heston Scheme by Broadie and Kaya (2003) (WIP)

Expand source code
"""
Implementing the exact Heston Scheme by Broadie and Kaya (2003) (WIP)

"""

import numpy as np


def generate_CIR_paths(n_samples, alpha, b, sigma, dt, v_0, T, seed = 2021):
    """
    Implements exact Cox-Ingersoll-Ross / square-root diffusion process
    based on Glasserman (2004)
    
    Exact simulation of the process V_t with dynamics given by the SDE:

    $$dV_{t} = alpha(b - V_{t}) dt + sigma sqrt(V_{t}) dW_{t}$$

    Inputs:

    + n_samples : Number of Vts to simulate
    + alpha : speed of mean reversion
    + b: mean level of variance
    + sigma: volatility of the variance
    + dt: time-increment, e.g. dt = 1 / 250 years
    + v_0: initial variance
    + T: (integer) number of time steps
    + seed: numpy seed for reproducibility

    Outputs:

    + Vts: A n_samples by (T + 1) numpy array, where in each row corresponds
    to a path V_{i, t} from 0 .. T
    """
    rng = np.random.default_rng(seed)
    
    Vts = np.zeros((n_samples, T + 1))
    Vts[:,0] = v_0
    d = 4 * b * alpha / sigma ** 2
    c = sigma ** 2  * (1 - np.exp(-alpha * dt)) / (4 * alpha)
    
    if d > 1:
        Zs = rng.random_normal(size=(n_samples, T + 1))
        chi_sqs = rng.chisquare (df = d - 1, size=(n_samples, T + 1))
    else:
        poissons = np.zeros((n_samples, T + 1))
        chi_sqs = np.zeros((n_samples, T + 1))
    
    for i in range(n_samples):
        for t in range(T):    
            l = Vts[i, t] * np.exp(-alpha * dt) / c
            if d > 1:
                Vts[i, t + 1] = c * ((Zs[i, t] + np.sqrt(l)) ** 2 + chi_sqs[i, t])
            else:
                poissons[i, t] = rng.poisson(lam = l / 2)
                chi_sqs[i, t] = rng.chisquare(df = d + 2 * poissons[i, t])
                Vts[i, t + 1] = c * chi_sqs[i, t]
    return Vts

def generate_Heston_paths(n_samples, S0, rho, r, alpha, b, sigma, dt, v_0, T, seed = 2021):
    """
    Almost Exact Heston scheme for the process with SDE
    
    $$dS_{t} = rS_{t} dt + S_{t} sqrt(V_{t}) dW_{t}$$

    $$dV_{t} = alpha(b - V_{t}) dt + sigma sqrt(V_{t}) dZ_{t}$$
    
    $$corr(dW_{t}, dZ_{t}) = rho$$

    Inputs:

    + n_samples : Number of Vtss to simulate
    + S0: Initial Stock Price
    + rho: correlation
    + r: risk-free rate
    + alpha : speed of mean reversion
    + b: mean level of variance
    + sigma: volatility of the variance
    + dt: time-increment, e.g. dt = 1 / 250 years
    + v_0: initial variance
    + T: (integer) number of time steps
    + seed: numpy seed for reproducibility

    Outputs:

    + Vts: A n_samples by T numpy array, where in each row corresponds
    to a path
    """

    rng = np.random.default_rng(seed)
    Zs = rng.standard_normal(size = (n_samples, T)) # random normal increments
    Zs = (Zs - Zs.mean(axis= 0)) / Zs.std(axis = 0)

    # pre-generate Vts
    Vts = generate_CIR_paths(n_samples = n_samples, alpha = alpha, 
                             b = b, sigma = sigma, 
                             dt = dt, v_0 = v_0, T = T, seed = seed)

    # simulate the log-price process
    log_Sts = np.zeros((n_samples, T + 1))
    log_Sts[:, 0] = np.log(S0)

    k0 = (r - rho / sigma * alpha * b) * dt
    k1 = (rho * alpha / sigma - 0.5) * dt - rho / sigma
    k2 = rho / sigma

    for i in range(T):
        log_Sts[:, i + 1] = (log_Sts[:,i] + k0 + k1 * Vts[:,i] + k2 * Vts[:,i+1] + 
                              np.sqrt((1.0 - rho ** 2) * Vts[:,i]) * np.sqrt(dt) * Zs[:, i])

    return np.exp(log_Sts), Vts

Functions

def generate_CIR_paths(n_samples, alpha, b, sigma, dt, v_0, T, seed=2021)

Implements exact Cox-Ingersoll-Ross / square-root diffusion process based on Glasserman (2004)

Exact simulation of the process V_t with dynamics given by the SDE:

$$dV_{t} = alpha(b - V_{t}) dt + sigma sqrt(V_{t}) dW_{t}$$

Inputs:

  • n_samples : Number of Vts to simulate
  • alpha : speed of mean reversion
  • b: mean level of variance
  • sigma: volatility of the variance
  • dt: time-increment, e.g. dt = 1 / 250 years
  • v_0: initial variance
  • T: (integer) number of time steps
  • seed: numpy seed for reproducibility

Outputs:

  • Vts: A n_samples by (T + 1) numpy array, where in each row corresponds to a path V_{i, t} from 0 .. T
Expand source code
def generate_CIR_paths(n_samples, alpha, b, sigma, dt, v_0, T, seed = 2021):
    """
    Implements exact Cox-Ingersoll-Ross / square-root diffusion process
    based on Glasserman (2004)
    
    Exact simulation of the process V_t with dynamics given by the SDE:

    $$dV_{t} = alpha(b - V_{t}) dt + sigma sqrt(V_{t}) dW_{t}$$

    Inputs:

    + n_samples : Number of Vts to simulate
    + alpha : speed of mean reversion
    + b: mean level of variance
    + sigma: volatility of the variance
    + dt: time-increment, e.g. dt = 1 / 250 years
    + v_0: initial variance
    + T: (integer) number of time steps
    + seed: numpy seed for reproducibility

    Outputs:

    + Vts: A n_samples by (T + 1) numpy array, where in each row corresponds
    to a path V_{i, t} from 0 .. T
    """
    rng = np.random.default_rng(seed)
    
    Vts = np.zeros((n_samples, T + 1))
    Vts[:,0] = v_0
    d = 4 * b * alpha / sigma ** 2
    c = sigma ** 2  * (1 - np.exp(-alpha * dt)) / (4 * alpha)
    
    if d > 1:
        Zs = rng.random_normal(size=(n_samples, T + 1))
        chi_sqs = rng.chisquare (df = d - 1, size=(n_samples, T + 1))
    else:
        poissons = np.zeros((n_samples, T + 1))
        chi_sqs = np.zeros((n_samples, T + 1))
    
    for i in range(n_samples):
        for t in range(T):    
            l = Vts[i, t] * np.exp(-alpha * dt) / c
            if d > 1:
                Vts[i, t + 1] = c * ((Zs[i, t] + np.sqrt(l)) ** 2 + chi_sqs[i, t])
            else:
                poissons[i, t] = rng.poisson(lam = l / 2)
                chi_sqs[i, t] = rng.chisquare(df = d + 2 * poissons[i, t])
                Vts[i, t + 1] = c * chi_sqs[i, t]
    return Vts
def generate_Heston_paths(n_samples, S0, rho, r, alpha, b, sigma, dt, v_0, T, seed=2021)

Almost Exact Heston scheme for the process with SDE

$$dS_{t} = rS_{t} dt + S_{t} sqrt(V_{t}) dW_{t}$$

$$dV_{t} = alpha(b - V_{t}) dt + sigma sqrt(V_{t}) dZ_{t}$$

$$corr(dW_{t}, dZ_{t}) = rho$$

Inputs:

  • n_samples : Number of Vtss to simulate
  • S0: Initial Stock Price
  • rho: correlation
  • r: risk-free rate
  • alpha : speed of mean reversion
  • b: mean level of variance
  • sigma: volatility of the variance
  • dt: time-increment, e.g. dt = 1 / 250 years
  • v_0: initial variance
  • T: (integer) number of time steps
  • seed: numpy seed for reproducibility

Outputs:

  • Vts: A n_samples by T numpy array, where in each row corresponds to a path
Expand source code
def generate_Heston_paths(n_samples, S0, rho, r, alpha, b, sigma, dt, v_0, T, seed = 2021):
    """
    Almost Exact Heston scheme for the process with SDE
    
    $$dS_{t} = rS_{t} dt + S_{t} sqrt(V_{t}) dW_{t}$$

    $$dV_{t} = alpha(b - V_{t}) dt + sigma sqrt(V_{t}) dZ_{t}$$
    
    $$corr(dW_{t}, dZ_{t}) = rho$$

    Inputs:

    + n_samples : Number of Vtss to simulate
    + S0: Initial Stock Price
    + rho: correlation
    + r: risk-free rate
    + alpha : speed of mean reversion
    + b: mean level of variance
    + sigma: volatility of the variance
    + dt: time-increment, e.g. dt = 1 / 250 years
    + v_0: initial variance
    + T: (integer) number of time steps
    + seed: numpy seed for reproducibility

    Outputs:

    + Vts: A n_samples by T numpy array, where in each row corresponds
    to a path
    """

    rng = np.random.default_rng(seed)
    Zs = rng.standard_normal(size = (n_samples, T)) # random normal increments
    Zs = (Zs - Zs.mean(axis= 0)) / Zs.std(axis = 0)

    # pre-generate Vts
    Vts = generate_CIR_paths(n_samples = n_samples, alpha = alpha, 
                             b = b, sigma = sigma, 
                             dt = dt, v_0 = v_0, T = T, seed = seed)

    # simulate the log-price process
    log_Sts = np.zeros((n_samples, T + 1))
    log_Sts[:, 0] = np.log(S0)

    k0 = (r - rho / sigma * alpha * b) * dt
    k1 = (rho * alpha / sigma - 0.5) * dt - rho / sigma
    k2 = rho / sigma

    for i in range(T):
        log_Sts[:, i + 1] = (log_Sts[:,i] + k0 + k1 * Vts[:,i] + k2 * Vts[:,i+1] + 
                              np.sqrt((1.0 - rho ** 2) * Vts[:,i]) * np.sqrt(dt) * Zs[:, i])

    return np.exp(log_Sts), Vts