Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add functions for Vanilla asset option simulation #66

Merged
merged 1 commit into from
Feb 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading