Chapter 4: Optimal Stopping#

firm_exit.py#

"""
Firm valuation with exit option.

"""

from quantecon.markov import tauchen
from quantecon import compute_fixed_point

import numpy as np
from collections import namedtuple
from numba import njit


# NamedTuple Model
Model = namedtuple("Model", ("n", "z_vals", "Q", "β", "s"))


def create_exit_model(
        n=200,                  # productivity grid size
        ρ=0.95, μ=0.1, ν=0.1,   # persistence, mean and volatility
        β=0.98, s=100.0         # discount factor and scrap value
    ):
    """
    Creates an instance of the firm exit model.
    """
    mc = tauchen(n, ρ, ν, mu=μ)
    z_vals, Q = mc.state_values, mc.P
    return Model(n=n, z_vals=z_vals, Q=Q, β=β, s=s)


@njit
def no_exit_value(model):
    """Compute value of firm without exit option."""
    n, z_vals, Q, β, s = model
    I = np.identity(n)
    return np.linalg.solve((I - β * Q), z_vals)


@njit
def T(v, model):
    """The Bellman operator Tv = max{s, π + β Q v}."""
    n, z_vals, Q, β, s = model
    h = z_vals + β * np.dot(Q, v)
    return np.maximum(s, h)


@njit
def get_greedy(v, model):
    """Get a v-greedy policy."""
    n, z_vals, Q, β, s = model
    σ = s >= z_vals + β * np.dot(Q, v)
    return σ


def vfi(model):
    """Solve by VFI."""
    v_init = no_exit_value(model)
    v_star = compute_fixed_point(lambda v: T(v, model), v_init, error_tol=1e-6,
                                 max_iter=1000, print_skip=25)
    σ_star = get_greedy(v_star, model)
    return v_star, σ_star


# Plots


import matplotlib.pyplot as plt
import matplotlib.pyplot as plt

plt.rcParams.update({"text.usetex": True, "font.size": 14})


def plot_val(savefig=False,
             figname="./figures/firm_exit_1.pdf"):

    fig, ax = plt.subplots(figsize=(9, 5.2))

    model = create_exit_model()
    n, z_vals, Q, β, s = model

    v_star, σ_star = vfi(model)
    h = z_vals + β * np.dot(Q, v_star)

    ax.plot(z_vals, h, "-", linewidth=3, alpha=0.6, label=r"$h^*$")
    ax.plot(z_vals, s * np.ones(n), "-", linewidth=3, alpha=0.6, label=r"$s$")
    ax.plot(z_vals, v_star, "k--", linewidth=1.5, alpha=0.8, label=r"$v^*$")

    ax.legend(frameon=False)
    ax.set_xlabel(r"$z$")

    if savefig:
        fig.savefig(figname)


def plot_comparison(savefig=False,
                    figname="./figures/firm_exit_2.pdf"):

    fig, ax = plt.subplots(figsize=(9, 5.2))

    model = create_exit_model()
    n, z_vals, Q, β, s = model

    v_star, σ_star = vfi(model)
    w = no_exit_value(model)

    ax.plot(z_vals, v_star, "k-", linewidth=2, alpha=0.6, label="$v^*$")
    ax.plot(z_vals, w, linewidth=2, alpha=0.6, label="no-exit value")

    ax.legend(frameon=False)
    ax.set_xlabel(r"$z$")

    if savefig:
        fig.savefig(figname)
plot_val()
Iteration    Distance       Elapsed (seconds)
---------------------------------------------
25           3.581e-02      8.320e-01         
50           1.723e-02      8.329e-01         
75           6.768e-03      8.335e-01         
100          2.643e-03      8.341e-01         
125          1.043e-03      8.347e-01         
150          4.139e-04      8.353e-01         
175          1.643e-04      8.359e-01         
200          6.522e-05      8.366e-01         
225          2.589e-05      8.372e-01         
250          1.028e-05      8.379e-01         
275          4.080e-06      8.385e-01         
300          1.619e-06      8.391e-01         
314          9.653e-07      8.395e-01         
Converged in 314 steps
../_images/ebd5273d64f36828f62893c19f55646ff32062c2b85322fa89942a00633f72c8.png
plot_comparison()
Iteration    Distance       Elapsed (seconds)
---------------------------------------------
25           3.581e-02      7.725e-04         
50           1.723e-02      1.290e-03         
75           6.768e-03      1.779e-03         
100          2.643e-03      2.271e-03         
125          1.043e-03      2.758e-03         
150          4.139e-04      3.296e-03         
175          1.643e-04      3.823e-03         
200          6.522e-05      4.346e-03         
225          2.589e-05      4.831e-03         
250          1.028e-05      5.316e-03         
275          4.080e-06      5.803e-03         
300          1.619e-06      6.342e-03         
314          9.653e-07      6.663e-03         
Converged in 314 steps
../_images/6e5485f6e8da29199e4332341c2d6a6242ed67f0ac79ae19103eeff6232eabac.png

american_option.py#

"""
Valuation for finite-horizon American call options in discrete time.

"""

from quantecon.markov import tauchen, MarkovChain
from quantecon import compute_fixed_point

import numpy as np
from collections import namedtuple
from numba import njit, prange


# NamedTuple Model
Model = namedtuple("Model", ("t_vals", "z_vals","w_vals", "Q",
                             "φ", "T", "β", "K"))


@njit
def exit_reward(t, i_w, i_z, T, K):
    """Payoff to exercising the option at time t."""
    return (t < T) * (i_z + i_w - K)


def create_american_option_model(
        n=100, μ=10.0,   # Markov state grid size and mean value
        ρ=0.98, ν=0.2,   # persistence and volatility for Markov state
        s=0.3,           # volatility parameter for W_t
        r=0.01,          # interest rate
        K=10.0, T=200):  # strike price and expiration date
    """
    Creates an instance of the option model with log S_t = Z_t + W_t.
    """
    t_vals = np.arange(T+1)
    mc = tauchen(n, ρ, ν)
    z_vals, Q = mc.state_values + μ, mc.P
    w_vals, φ, β = np.array([-s, s]), np.array([0.5, 0.5]), 1 / (1 + r)
    return Model(t_vals=t_vals, z_vals=z_vals, w_vals=w_vals, Q=Q,
                φ=φ, T=T, β=β, K=K)


@njit(parallel=True)
def C(h, model):
    """The continuation value operator."""
    t_vals, z_vals, w_vals, Q, φ, T, β, K = model
    Ch = np.empty_like(h)
    z_idx, w_idx = np.arange(len(z_vals)), np.arange(len(w_vals))
    for i in prange(len(t_vals)):
        t = t_vals[i]
        for i_z in prange(len(z_vals)):
            out = 0.0
            for i_w_1 in prange(len(w_vals)):
                for i_z_1 in prange(len(z_vals)):
                    t_1 = min(t + 1, T)
                    out += max(exit_reward(t_1, w_vals[i_w_1], z_vals[i_z_1], T, K),
                                h[t_1, i_z_1]) * Q[i_z, i_z_1] * φ[i_w_1]
            Ch[t, i_z] = β * out

    return Ch


def compute_cvf(model):
    """
    Compute the continuation value function by successive approx.
    """
    h_init = np.zeros((len(model.t_vals), len(model.z_vals)))
    h_star = compute_fixed_point(lambda h: C(h, model), h_init,
                                 error_tol=1e-6, max_iter=1000,
                                 print_skip=25)
    return h_star


# Plots


import matplotlib.pyplot as plt
import matplotlib.pyplot as plt

plt.rcParams.update({"text.usetex": True, "font.size": 14})


def plot_contours(savefig=False,
                  figname="./figures/american_option_1.pdf"):

    model = create_american_option_model()
    t_vals, z_vals, w_vals, Q, φ, T, β, K = model
    h_star = compute_cvf(model)
    fig, axes = plt.subplots(3, 1, figsize=(7, 11))
    z_idx, w_idx = np.arange(len(z_vals)), np.arange(len(w_vals))
    H = np.zeros((len(w_vals), len(z_vals)))
    for (ax_index, t) in zip(range(3), (1, 195, 198)):

        ax = axes[ax_index]
        for i_w in prange(len(w_vals)):
            for i_z in prange(len(z_vals)):
                H[i_w, i_z] = exit_reward(t, w_vals[i_w], z_vals[i_z], T,
                                          K) - h_star[t, i_z]

        cs1 = ax.contourf(w_vals, z_vals, np.transpose(H), alpha=0.5)
        ctr1 = ax.contour(w_vals, z_vals, np.transpose(H), levels=[0.0])
        plt.clabel(ctr1, inline=1, fontsize=13)
        plt.colorbar(cs1, ax=ax)

        ax.set_title(f"$t={t}$")
        ax.set_xlabel(r"$w$")
        ax.set_ylabel(r"$z$")

    fig.tight_layout()
    if savefig:
        fig.savefig(figname)


def plot_strike(savefig=False,
                fontsize=9,
                figname="./figures/american_option_2.pdf"):
    model = create_american_option_model()
    t_vals, z_vals, w_vals, Q, φ, T, β, K = model
    h_star = compute_cvf(model)

    # Built Markov chains for simulation
    z_mc = MarkovChain(Q, z_vals)
    P_φ = np.zeros((len(w_vals), len(w_vals)))
    for i in range(len(w_vals)):  # Build IID chain
        P_φ[i, :] = φ
    w_mc = MarkovChain(P_φ, w_vals)
    y_min = np.min(z_vals) + np.min(w_vals)
    y_max = np.max(z_vals) + np.max(w_vals)
    fig, axes = plt.subplots(3, 1, figsize=(7, 12))

    for ax in axes:

        # Generate price series
        z_draws = z_mc.simulate_indices(T, init=int(len(z_vals) / 2 - 10))
        w_draws = w_mc.simulate_indices(T)
        s_vals = z_vals[z_draws] + w_vals[w_draws]

        # Find the exercise date, if any.
        exercise_date = T + 1
        for t in range(T):
            k = exit_reward(t, w_vals[w_draws[t]], z_vals[z_draws[t]], T, K) - \
                    h_star[w_draws[t], z_draws[t]]
            if k >= 0:
                exercise_date = t

        if exercise_date >= T:
            print("Warning: Option not exercised.")
        else:
            # Plot
            ax.set_ylim(y_min, y_max)
            ax.set_xlim(0, T+1)
            ax.fill_between(range(T), np.ones(T) * K, np.ones(T) * y_max, alpha=0.2)
            ax.plot(range(T), s_vals, label=r"$S_t$")
            ax.plot((exercise_date,), (s_vals[exercise_date]), "ko")
            ax.vlines((exercise_date,), 0, (s_vals[exercise_date]), ls="--", colors="black")
            ax.legend(loc="upper left", fontsize=fontsize)
            ax.text(-10, 11, "in the money", fontsize=fontsize, rotation=90)
            ax.text(-10, 7.2, "out of the money", fontsize=fontsize, rotation=90)
            ax.text(exercise_date-20, 6,
                    "exercise date", fontsize=fontsize)
            ax.set_xticks((1, T))
            ax.set_yticks((y_min, y_max))

    if savefig:
        fig.savefig(figname)
plot_contours()
Iteration    Distance       Elapsed (seconds)
---------------------------------------------
25           6.682e-03      1.712e+00         
50           3.997e-03      1.793e+00         
75           2.923e-03      1.875e+00         
100          1.960e-03      1.961e+00         
125          1.242e-03      2.043e+00         
150          7.739e-04      2.124e+00         
175          4.811e-04      2.207e+00         
200          0.000e+00      2.289e+00         
Converged in 200 steps
../_images/928436e15961bb307f4b7a27a1bc55ae38129e39356605afb15258204d1f0583.png
plot_strike()
Iteration    Distance       Elapsed (seconds)
---------------------------------------------
25           6.682e-03      8.388e-02         
50           3.997e-03      1.649e-01         
75           2.923e-03      2.513e-01         
100          1.960e-03      3.349e-01         
125          1.242e-03      4.179e-01         
150          7.739e-04      5.030e-01         
175          4.811e-04      5.860e-01         
200          0.000e+00      6.693e-01         
Converged in 200 steps
../_images/bdb96453539d553115e563571158fdf62c6686bfbc92b8e0168d136a0e3ef480.png