Skip to content

Commit

Permalink
Add functions for Vanilla asset option simulation
Browse files Browse the repository at this point in the history
  - asset_variance functions for models and path
  - VanillaAssetOption payoff
  - VanillaAssetOptionFlow cash flow
  • Loading branch information
FrameConsult authored and sschlenkrich committed Feb 24, 2024
1 parent 0f605e9 commit 492df05
Show file tree
Hide file tree
Showing 13 changed files with 413 additions and 3 deletions.
2 changes: 2 additions & 0 deletions src/DiffFusion.jl
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,10 @@ include("payoffs/BinaryNodes.jl")
include("payoffs/RatesPayoffs.jl")
include("payoffs/RatesOptions.jl")
include("payoffs/AmcPayoffs.jl")
include("payoffs/AssetOptions.jl")

include("products/Cashflows.jl")
include("products/AssetOptionFlows.jl")
include("products/RatesCoupons.jl")
include("products/CashFlowLeg.jl")
include("products/SwaptionLeg.jl")
Expand Down
69 changes: 69 additions & 0 deletions src/models/hybrid/CompositeModel.jl
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,75 @@ function forward_rate_variance(
)
end

"""
asset_variance(
m::CompositeModel,
ast_alias::Union{String, Nothing},
dom_alias::Union{String, Nothing},
for_alias::Union{String, Nothing},
t::ModelTime,
T::ModelTime,
X::ModelState,
)
Calculate the lognormal model variance of an asset spot price
over the time period [t,T].
"""
function asset_variance(
m::CompositeModel,
ast_alias::Union{String, Nothing},
dom_alias::Union{String, Nothing},
for_alias::Union{String, Nothing},
t::ModelTime,
T::ModelTime,
X::ModelState,
)
# we implement a staged approach to determine the correlation holder
ch = nothing
ast_model = nothing
dom_model = nothing
for_model = nothing
if !isnothing(for_alias)
for_model = m.models[m.model_dict[for_alias]]
ch = correlation_holder(for_model)
end
if !isnothing(dom_alias)
dom_model = m.models[m.model_dict[dom_alias]]
if !isnothing(ch)
# this is only a plausibility check
alias(ch) == alias(correlation_holder(dom_model))
end
ch = correlation_holder(dom_model)
end
if !isnothing(ast_alias)
ast_model = m.models[m.model_dict[ast_alias]]
if !isnothing(ch)
# this is only a plausibility check
alias(ch) == alias(correlation_holder(ast_model))
end
ch = correlation_holder(ast_model)
end
if isnothing(ch)
# in this case all models are deterministic
return 0.0
end
if !state_dependent_Sigma(m)
# We need to make sure that the Sigma_T(...) call in covaiance(...)
# method can be executed.
X = nothing
end
return asset_variance(
ast_model,
dom_model,
for_model,
ch,
t,
T,
X,
)
end


"""
state_dependent_Theta(m::CompositeModel)
Expand Down
44 changes: 43 additions & 1 deletion src/paths/PathMethods.jl
Original file line number Diff line number Diff line change
Expand Up @@ -503,7 +503,7 @@ function forward_rate_variance(
return zeros(length(p)) # deterministic model
end
#
return ones(length(p)) * forward_rate_variance(
return ones(length(p)) .* forward_rate_variance(
p.sim.model,
entry.model_alias,
t,
Expand All @@ -512,3 +512,45 @@ function forward_rate_variance(
T1,
)
end


"""
asset_variance(
p::Path,
t::ModelTime,
T::ModelTime,
key::String,
)
Calculate the lognormal model variance of an asset spot price
over the time period [t,T].
"""
function asset_variance(
p::Path,
t::ModelTime,
T::ModelTime,
key::String,
)
(context_key, ts_key_1, ts_key_2, op) = context_keys(key)
@assert op in (_empty_context_key, "-")
entry = p.context.assets[context_key]
#
if isnothing(entry.asset_model_alias) &&
isnothing(entry.domestic_model_alias) &&
isnothing(entry.foreign_model_alias)
return zeros(length(p)) # deterministic model
end
#
X = state_variable(p.sim, t, p.interpolation)
SX = model_state(X, p.state_alias_dict)
#
return ones(length(p)) .* asset_variance(
p.sim.model,
entry.asset_model_alias,
entry.domestic_model_alias,
entry.foreign_model_alias,
t,
T,
SX,
)
end
106 changes: 106 additions & 0 deletions src/payoffs/AssetOptions.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@

"""
struct VanillaAssetOption <: Payoff
obs_time::ModelTime
expiry_time::ModelTime
forward_price::ForwardAsset
strike_price::Payoff
call_put::ModelValue
end
The time-t forward price of an option paying [ϕ(F-K)]^+. Forward asset price *F* is determined
at `expiry_time`.
Option forward price is calculated as expectation in T-forward measure where T corresponds
to the expiry time. Conditioning (for time-t price) is on information at `obs_time`.
"""
struct VanillaAssetOption <: Payoff
obs_time::ModelTime
expiry_time::ModelTime
forward_price::ForwardAsset
strike_price::Payoff
call_put::ModelValue
end

"""
VanillaAssetOption(
forward_price::ForwardAsset,
strike_price::Payoff,
call_put::ModelValue,
)
Create a `VanillaAssetOption` payoff.
"""
function VanillaAssetOption(
forward_price::ForwardAsset,
strike_price::Payoff,
call_put::ModelValue,
)
#
@assert obs_time(strike_price) forward_price.obs_time # payoff must be time-t measurable
@assert call_put in (+1.0, -1.0)
return VanillaAssetOption(
forward_price.obs_time,
forward_price.maturity_time,
forward_price,
strike_price,
call_put,
)
end


"""
obs_time(p::VanillaAssetOption)
Return VanillaAssetOption observation time.
"""
function obs_time(p::VanillaAssetOption)
return p.obs_time
end


"""
obs_times(p::VanillaAssetOption)
Return all VanillaAssetOption observation times.
"""
function obs_times(p::VanillaAssetOption)
times = Set(obs_time(p))
times = union(times, obs_times(p.strike_price))
return times
end


"""
at(p::VanillaAssetOption, path::AbstractPath)
Evaluate a `VanillaAssetOption` at a given `path`, *X(omega)*.
"""
function at(p::VanillaAssetOption, path::AbstractPath)
F = at(p.forward_price, path)
K = at(p.strike_price, path)
ν² = asset_variance(path, p.obs_time, p.expiry_time, p.forward_price.key)
if all(ν² .≤ 0.0)
# calculate intrinsic value
return max.(p.call_put*(F - K), 0.0)
end
V = black_price(K, F, sqrt.(ν²), p.call_put)
return V
end


"""
string(p::VanillaAssetOption)
Formatted (and shortened) output for VanillaAssetOption payoff.
"""
string(p::VanillaAssetOption) = begin
type = ""
if p.call_put == 1.0
type = "Call"
end
if p.call_put == -1.0
type = "Put"
end
@sprintf("%s(%s, %s)", type, string(p.forward_price), string(p.strike_price))
end
48 changes: 48 additions & 0 deletions src/products/AssetOptionFlows.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@

"""
struct VanillaAssetOptionFlow <: CashFlow
expiry_time::ModelTime
pay_time::ModelTime
strike_price::ModelValue
call_put::ModelValue
asset_key::String
end
A `CashFlow` representing a Call or Put option on an `Asset`.
"""
struct VanillaAssetOptionFlow <: CashFlow
expiry_time::ModelTime
pay_time::ModelTime
strike_price::ModelValue
call_put::ModelValue
asset_key::String
end


"""
amount(cf::VanillaAssetOptionFlow)
Return the payoff of the `VanillaAssetOptionFlow`.
"""
function amount(cf::VanillaAssetOptionFlow)
S = Asset(cf.expiry_time, cf.asset_key)
return Max(cf.call_put*(S - cf.strike_price), 0.0)
end


"""
expected_amount(cf::VanillaAssetOptionFlow, obs_time::ModelTime)
Return the payoff representing the simulated expected amount of the `VanillaAssetOptionFlow`.
This implementation is an approximation and does not capture convexity adjustments.
"""
function expected_amount(cf::VanillaAssetOptionFlow, obs_time::ModelTime)
if obs_time cf.expiry_time
return amount(cf)
end
F = ForwardAsset(obs_time, cf.expiry_time, cf.asset_key)
K = Fixed(cf.strike_price)
return VanillaAssetOption(F, K, cf.call_put)
end

5 changes: 3 additions & 2 deletions test/componenttests/componenttests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ using Test

include("calibration/swap_rate_calibration.jl")

include("scenarios/scenarios.jl")
include("scenarios/rates_option.jl")
include("scenarios/asset_options.jl")
include("scenarios/bermudan_swaption.jl")
include("scenarios/rates_option.jl")
include("scenarios/scenarios.jl")
include("scenarios/swaptions_expected_exposure.jl")

include("sensitivities/forwards_deltas.jl")
Expand Down
53 changes: 53 additions & 0 deletions test/componenttests/scenarios/asset_options.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@

using DiffFusion
using Test
using UnicodePlots

@testset "Test asset option simulation" begin

if !@isdefined(TestModels)
include("../../test_models.jl")
end

function plot_scens(scens, title)
plt = lineplot(scens.times, scens.X[1,:,:],
title = title,
name = scens.leg_aliases,
xlabel = "obs_time",
ylabel = "price (EUR)",
width = 80,
height = 30,
)
println()
display(plt)
println()
end


@testset "Test VanillaAssetOption simulation" begin
model = TestModels.hybrid_model_full
ch = TestModels.ch_full
times = 0.0:1.0:6.0
n_paths = 2^13
sim = DiffFusion.simple_simulation(model, ch, times, n_paths, with_progress_bar = false)
path = DiffFusion.path(sim, TestModels.ts_list, TestModels.context, DiffFusion.LinearPathInterpolation)
#
call_leg = DiffFusion.cashflow_leg(
"leg/C/EUR-USD/5y/1.25",
[ DiffFusion.VanillaAssetOptionFlow(5.0, 5.0, 1.25, +1.0, "EUR-USD"), ],
[ 1.0, ],
"USD"
)
put_leg = DiffFusion.cashflow_leg(
"leg/P/EUR-USD/5y/1.25",
[ DiffFusion.VanillaAssetOptionFlow(5.0, 5.0, 1.25, -1.0, "EUR-USD"), ],
[ 1.0, ],
"USD"
)
scens = DiffFusion.scenarios([call_leg, put_leg], times, path, "")
mv = DiffFusion.aggregate(scens, true, false)
#
plot_scens(mv, "EUR-USD option mv")
end

end
8 changes: 8 additions & 0 deletions test/unittests/models/hybrid/simple_model.jl
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,14 @@ using Test
@test DiffFusion.swap_rate_variance(m, "EUR", yts, 1.0, 8.0, [8.0, 9.0, 10.0], [1.0, 1.0], SX) == DiffFusion.swap_rate_variance(hjm_model_for, "EUR", yts, 1.0, 8.0, [8.0, 9.0, 10.0], [1.0, 1.0], SX)
@test DiffFusion.forward_rate_variance(m, "USD", 1.0, 8.0, 8.0, 9.0) == DiffFusion.forward_rate_variance(hjm_model_dom, "USD", 1.0, 8.0, 8.0, 9.0)
#
@test DiffFusion.asset_variance(m, "EUR-USD", "USD", "EUR", 1.0, 8.0, SX) == DiffFusion.asset_variance(asset_model, hjm_model_dom, hjm_model_for, ch, 1.0, 8.0)
@test DiffFusion.asset_variance(m, "EUR-USD", "USD", "EUR", 1.0, 8.0, SX) == DiffFusion.asset_variance(asset_model, hjm_model_dom, hjm_model_for, ch, 1.0, 8.0)
@test DiffFusion.asset_variance(m, "EUR-USD", nothing, "EUR", 1.0, 8.0, SX) == DiffFusion.asset_variance(asset_model, nothing, hjm_model_for, ch, 1.0, 8.0)
@test DiffFusion.asset_variance(m, "EUR-USD", "USD", nothing, 1.0, 8.0, SX) == DiffFusion.asset_variance(asset_model, hjm_model_dom, nothing, ch, 1.0, 8.0)
@test DiffFusion.asset_variance(m, "EUR-USD", nothing, nothing, 1.0, 8.0, SX) == DiffFusion.asset_variance(asset_model, nothing, nothing, ch, 1.0, 8.0)
@test DiffFusion.asset_variance(m, nothing, "USD", "EUR", 1.0, 8.0, SX) == DiffFusion.asset_variance(nothing, hjm_model_dom, hjm_model_for, ch, 1.0, 8.0)
@test DiffFusion.asset_variance(m, nothing, nothing, nothing, 1.0, 8.0, SX) == 0.0
#
@test_throws KeyError DiffFusion.log_asset(m, "WrongAlias", 1.0, SX)
@test_throws KeyError DiffFusion.log_bank_account(m, "WrongAlias", 1.0, SX)
@test_throws KeyError DiffFusion.log_zero_bond(m, "WrongAlias", 4.0, 8.0, SX)
Expand Down
Loading

0 comments on commit 492df05

Please sign in to comment.