speice.io/blog/2015-11-27-autocallable/_notebook.md

377 KiB
Raw Permalink Blame History

using Gadfly

Athena/Phoenix Simulation

Underlying simulation

In order to price the autocallable bonds, we need to simulate the underlying assets. Let's go ahead and set up the simulation first, as this lays the foundation for what we're trying to do. We're going to use JNJ as the basis for our simulation. This implies the following parameters:

  • S_0 = $102.2 (as of time of writing)
  • q = 2.84%
  • r = [.49, .9, 1.21, 1.45, 1.69] (term structure as of time of writing, linear interpolation)
  • \mu = r - q (note that this implies a negative drift because of current low rates)
  • \sigma = \sigma_{imp} = 15.62% (from VIX implied volatility)

We additionally define some parameters for simulation:

  • T: The number of years to simulate
  • m: The number of paths to simulate
  • n: The number of steps to simulate in a year
S0 = 102.2
nominal = 100
q = 2.84 / 100
σ = 15.37 / 100
term = [0, .49, .9, 1.21, 1.45, 1.69] / 100 + 1

###
# Potential: Based on PEP
# S0 = 100.6
# σ = 14.86
# q = 2.7
###

# Simulation parameters
T = 5 # Using years as the unit of time
n = 250 # simulations per year
m = 100000 # paths
num_simulations = 5; # simulation rounds per price
5

Defining the simulation

To make things simpler, we simulate a single year at a time. This allows us to easily add in a dividend policy without too much difficulty, and update the simulation every year to match the term structure. The underlying uses GBM for simulation between years.

simulate_gbm = function(S0, μ, σ, T, n)
    # Set the initial state
    m = length(S0)
    t = T / n
    motion = zeros(m, n)
    motion[:,1] = S0
    
    # Build out all states
    for i=1:(n-1)
        motion[:,i+1] = motion[:,i] .* exp((μ - σ^2/2)*t) .* exp(sqrt(t) * σ .* randn(m))
    end
    
    return motion
end

function display_motion(motion, T)
    # Given a matrix of paths, display the motion
    n = length(motion[1,:])
    m = length(motion[:,1])
    x = repmat(1:n, m)
    
    # Calculate the ticks we're going to use. We'd like to
    # have an xtick every month, so calculate where those
    # ticks will actually be at.
    if (T > 3)
        num_ticks = T
        xlabel = "Years"
    else
        num_ticks = T * 12
        xlabel = "Months"
    end
    tick_width = n / num_ticks
    x_ticks = []
    for i=1:round(num_ticks)
        x_ticks = vcat(x_ticks, i*tick_width)
    end
    
    # Use one color for each path. I'm not sure if there's
    # a better way to do this without going through DataFrames
    colors = []
    for i = 1:m
        colors = vcat(colors, ones(n)*i)
    end
    
    plot(x=x, y=motion', color=colors, Geom.line,
    Guide.xticks(ticks=x_ticks, label=false),
    Guide.xlabel(xlabel),
    Guide.ylabel("Value"))
end;

Example simulation

Let's go ahead and run a sample simulation to see what the functions got us!

initial = ones(5) * S0
# Using μ=0, T=.25 for now, we'll use the proper values later
motion = simulate_gbm(initial, 0, σ, .25, 200) 

display_motion(motion, .25)

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:gadfly="http://www.gadflyjl.org/ns" version="1.2" width="141.42mm" height="100mm" viewBox="0 0 141.42 100" stroke="none" fill="#000000" stroke-width="0.3" font-size="3.88"

 id="fig-3a6dd25ad25c4037a166889ee51bb151">
Months 1 5 2 3 4 Color 65 70 75 80 85 90 95 100 105 110 115 120 125 130 135 140 145 150 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 50 100 150 70 72 74 76 78 80 82 84 86 88 90 92 94 96 98 100 102 104 106 108 110 112 114 116 118 120 122 124 126 128 130 132 134 136 138 140 142 144 146 Value

Computing the term structure

Now that we've got the basic motion set up, let's start making things a bit more sophisticated for the model. We're going to assume that the drift of the stock is the difference between the implied forward rate and the quarterly dividend rate.

We're given the yearly term structure, and need to calculate the quarterly forward rate to match this structure. The term structure is assumed to follow:

d(0, t) = d(0,t-1)\cdot f_{i-1, i}

Where f_{i-1, i} is the quarterly forward rate.

forward_term = function(yearly_term)
    # It is assumed that we have a yearly term structure passed in, and starts at year 0
    # This implies a nominal rate above 0 for the first year!
    years = length(term)-1 # because we start at 0
    structure = [(term[i+1] / term[i]) for i=1:years]
end;

Illustrating the term structure

Now that we've got our term structure, let's validate that we're getting the correct results! If we've done this correctly, then:

term[2] == term[1] * structure[1]
# Example term structure taken from:
# http://www.treasury.gov/resource-center/data-chart-center/interest-rates/Pages/TextView.aspx?data=yield
# Linear interpolation used years in-between periods, assuming real-dollar
# interest rates
forward_yield = forward_term(term)
calculated_term2 = term[1] * forward_yield[1]

println("Actual term[2]: $(term[2]); Calculated term[2]: $(calculated_term2)")
Actual term[2]: 1.0049; Calculated term[2]: 1.0049

The full underlying simulation

Now that we have the term structure set up, we can actually start doing some real simulation! Let's construct some paths through the full 5-year time frame. In order to do this, we will simulate 1 year at a time, and use the forward rates at those times to compute the drift. Thus, there will be 5 total simulations batched together.

full_motion = ones(5) * S0
full_term = vcat(term[1], forward_yield)
for i=1:T
    μ = (full_term[i] - 1 - q)
    year_motion = simulate_gbm(full_motion[:,end], μ, σ, 1, n)
    full_motion = hcat(full_motion, year_motion)
end

display_motion(full_motion, T)

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:gadfly="http://www.gadflyjl.org/ns" version="1.2" width="141.42mm" height="100mm" viewBox="0 0 141.42 100" stroke="none" fill="#000000" stroke-width="0.3" font-size="3.88"

 id="fig-0378e04b897742b597befd2e8e1c169e">
Years 1 5 2 3 4 Color -250 -200 -150 -100 -50 0 50 100 150 200 250 300 350 400 450 -200 -190 -180 -170 -160 -150 -140 -130 -120 -110 -100 -90 -80 -70 -60 -50 -40 -30 -20 -10 0 10 20 30 40 50 60 70 80 90 100 110 120 130 140 150 160 170 180 190 200 210 220 230 240 250 260 270 280 290 300 310 320 330 340 350 360 370 380 390 400 -200 0 200 400 -200 -180 -160 -140 -120 -100 -80 -60 -40 -20 0 20 40 60 80 100 120 140 160 180 200 220 240 260 280 300 320 340 360 380 400 Value

Final simulation

We're now going to actually build out the full motion that we'll use for computing the pricing of our autocallable products. It will be largely the same, but we will use far more sample paths for the simulation.

full_simulation = function(S0, T, n, m, term)
    forward = vcat(term[1], forward_term(term))

    # And an S0 to kick things off.
    final_motion = ones(m) * S0
    for i=1:T
        μ = (forward[i] - 1 - q)
        year_motion = simulate_gbm(final_motion[:,end], μ, σ, 1, n)
        final_motion = hcat(final_motion, year_motion)
    end
    return final_motion
end

tic()
full_simulation(S0, T, n, m, term)
time = toq()
@printf("Time to run simulation: %.2fs", time)
Time to run simulation: 5.34s

Athena Simulation

Now that we've defined our underlying simulation, let's actually try and price an Athena note. Athena has the following characteristics:

  • Automatically called if the underlying is above the call barrier at observation
  • Accelerated coupon paid if the underlying is above the call barrier at observation
    • The coupon paid is c \cdot i with i as the current year, and c the coupon rate
  • Principle protection up until a protection barrier at observation; All principle at risk if this barrier not met
  • Observed yearly
call_barrier = S0
strike = S0
protection_barrier = S0 * .6
coupon = nominal * .07

price_athena = function(initial_price, year_prices, call_barrier,
        protection_barrier, coupon, forward_structure)

    total_coupons = 0
    
    t = length(year_prices)

    for i=1:t
        price = year_prices[i]
        if price  call_barrier
            return (nominal + coupon*i) * exp((prod(forward_structure[i:end])-1)*(t-i))
        end
    end

    # We've reached maturity, time to check capital protection
    if year_prices[end] > protection_barrier
        return nominal
    else
        put = (strike - year_prices[end]) / strike
        return nominal*(1-put)
    end
end

forward_structure = forward_term(term)
price_function = (year_prices) -> price_athena(S0, year_prices,
    call_barrier, protection_barrier, coupon, forward_structure)

athena = function()
    year_indexes = [n*i for i=1:T]
    motion = full_simulation(S0, T, n, m, term)
    payoffs = [price_function(motion[i, year_indexes]) for i=1:m]
    return mean(payoffs)
end

mean_payoffs = zeros(num_simulations)
for i=1:num_simulations
    tic()
    mean_payoffs[i] = athena()
    time = toq()
    @printf("Mean of simulation %i: \$%.4f; Simulation time: %.2fs\n", i, mean_payoffs[i], time)
end

final_mean = mean(mean_payoffs)
println("Mean over $num_simulations simulations: $(mean(mean_payoffs))")
pv = final_mean * (exp(-(prod(forward_structure)-1)*T))
@printf("Present value of Athena note: \$%.2f, notional: \$%.2f", pv, nominal)
Mean of simulation 1: $103.2805; Simulation time: 5.59s
Mean of simulation 2: $103.3796; Simulation time: 5.05s
Mean of simulation 3: $103.4752; Simulation time: 5.18s
Mean of simulation 4: $103.4099; Simulation time: 5.37s
Mean of simulation 5: $103.3260; Simulation time: 5.32s
Mean over 5 simulations: 103.37421610015554
Present value of Athena note: $95.00, notional: $100.00

Phoenix without Memory Simulation

Let's move into pricing a Phoenix without memory. It's very similar to the Athena production, with the exception that we introduce a coupon barrier so coupons are paid even when the underlying is below the initial price.

The Phoenix product has the following characteristics (example here):

  • Automatically called if the underlying is above the call barrier at observation
  • Coupon paid if the underlying is above a coupon barrier at observation
  • Principle protection up until a protection barrier at observation; All principle at risk if this barrier not met
  • Observed yearly

Some example paths (all assume that a call barrier of the current price, and coupon barrier some level below that):

  • At the end of year 1, the stock is above the call barrier; the note is called and you receive the value of the stock plus the coupon being paid.
  • At the end of year 1, the stock is above the coupon barrier, but not the call barrier; you receive the coupon. At the end of year 2, the stock is below the coupon barrier; you receive nothing. At the end of year 3, the stock is above the call barrier; the note is called and you receive the value of the stock plus a coupon for year 3.

We're going to re-use the same simulation, with the following parameters:

  • Call barrier: 100%
  • Coupon barrier: 70%
  • Coupon: 6%
  • Capital protection until 70% (at maturity)
call_barrier = S0
coupon_barrier = S0 * .8
protection_barrier = S0 * .6
coupon = nominal * .06

price_phoenix_no_memory = function(initial_price, year_prices, call_barrier, coupon_barrier,
        protection_barrier, coupon, forward_structure)

    total_coupons = 0
    t = length(year_prices)

    for i=1:t
        price = year_prices[i]
        if price  call_barrier
            return (nominal + coupon + total_coupons)*exp((prod(forward_structure[i:end])-1)*(t-i))
        elseif price  coupon_barrier
            total_coupons = total_coupons * exp(forward_structure[i]-1) + coupon
        else
            total_coupons *= exp(forward_structure[i]-1)
        end
    end

    # We've reached maturity, time to check capital protection
    if year_prices[end] > protection_barrier
        return nominal + total_coupons
    else
        put = (strike - year_prices[end]) / strike
        return nominal*(1-put)
    end
end

forward_structure = forward_term(term)
price_function = (year_prices) -> price_phoenix_no_memory(S0, year_prices,
    call_barrier, coupon_barrier, protection_barrier, coupon, forward_structure)

phoenix_no_memory = function()
    year_indexes = [n*i for i=1:T]
    motion = full_simulation(S0, T, n, m, term)
    payoffs = [price_function(motion[i, year_indexes]) for i=1:m]
    return mean(payoffs)
end

mean_payoffs = zeros(num_simulations)
for i=1:num_simulations
    tic()
    mean_payoffs[i] = phoenix_no_memory()
    time = toq()
    @printf("Mean of simulation %i: \$%.4f; Simulation time: %.2fs\n", i, mean_payoffs[i], time)
end

final_mean = mean(mean_payoffs)
println("Mean over $num_simulations simulations: $(mean(mean_payoffs))")
pv = final_mean * exp(-(prod(forward_structure)-1)*(T))
@printf("Present value of Phoenix without memory note: \$%.2f", pv)
Mean of simulation 1: $106.0562; Simulation time: 5.72s
Mean of simulation 2: $106.0071; Simulation time: 5.85s
Mean of simulation 3: $105.9959; Simulation time: 5.87s
Mean of simulation 4: $106.0665; Simulation time: 5.93s
Mean of simulation 5: $106.0168; Simulation time: 5.81s
Mean over 5 simulations: 106.02850857209883
Present value of Phoenix without memory note: $97.44

Phoenix with Memory Simulation

The Phoenix with Memory structure is very similar to the Phoenix, but as the name implies, has a special "memory" property: It remembers any coupons that haven't been paid at prior observation times, and pays them all if the underlying crosses the coupon barrier. For example:

  • Note issued with 100% call barrier, 70% coupon barrier. At year 1, the underlying is at 50%, so no coupons are paid. At year 2, the underlying is at 80%, so coupons for both year 1 and 2 are paid, resulting in a double coupon.

You can also find an example here.

Let's go ahead and set up the simulation! The parameters will be the same, but we can expect that the value will go up because of the memory attribute

call_barrier = S0
coupon_barrier = S0 * .8
protection_barrier = S0 * .6
coupon = nominal * .07

price_phoenix_with_memory = function(initial_price, year_prices, call_barrier,
    coupon_barrier, protection_barrier, coupon, forward_structure)

    last_coupon = 0
    total_coupons = 0
    
    t = length(year_prices)

    for i=1:t
        price = year_prices[i]
        if price > call_barrier
            return (nominal + coupon + total_coupons)*exp((prod(forward_structure[i:end])-1)*(t-i))
        elseif price > coupon_barrier
            ####################################################################
            # The only difference between with/without memory is the below lines
            memory_coupons = (i - last_coupon) * coupon
            last_coupon = i
            total_coupons = total_coupons * exp(forward_structure[i]-1) + memory_coupons
            ####################################################################
        else
            total_coupons *= exp(forward_structure[i]-1)
        end
    end

    # We've reached maturity, time to check capital protection
    if year_prices[end] > protection_barrier
        return nominal + total_coupons
    else
        put = (strike - year_prices[end]) / strike
        return nominal*(1-put)
    end
end

forward_structure = forward_term(term)
price_function = (year_prices) -> price_phoenix_with_memory(S0, year_prices,
    call_barrier, coupon_barrier, protection_barrier, coupon, forward_structure)

phoenix_with_memory = function()
    year_indexes = [n*i for i=1:T]
    motion = full_simulation(S0, T, n, m, term)
    payoffs = [price_function(motion[i, year_indexes]) for i=1:m]
    return mean(payoffs)
end

mean_payoffs = zeros(num_simulations)
for i=1:num_simulations
    tic()
    mean_payoffs[i] = phoenix_with_memory()
    time = toq()
    @printf("Mean of simulation %i: \$%.4f; Simulation time: %.2fs\n",
        i, mean_payoffs[i], time)
end

final_mean = mean(mean_payoffs)
println("Mean over $num_simulations simulations: $(mean(mean_payoffs))")
pv = final_mean * exp(-(prod(forward_structure)-1)*(T))
@printf("Present value of Phoenix with memory note: \$%.2f", pv)
Mean of simulation 1: $108.8612; Simulation time: 5.89s
Mean of simulation 2: $109.0226; Simulation time: 5.90s
Mean of simulation 3: $108.9175; Simulation time: 5.92s
Mean of simulation 4: $108.9426; Simulation time: 5.94s
Mean of simulation 5: $108.8087; Simulation time: 6.06s
Mean over 5 simulations: 108.91052564051816
Present value of Phoenix with memory note: $100.09