From 0c22a1f766dc1bcabd84c6b212537d162057ca59 Mon Sep 17 00:00:00 2001 From: Sebastian Schlenkrich Date: Sat, 9 Dec 2023 19:46:19 +0100 Subject: [PATCH] Refactor sensitivity calculation for AMC payoffs - Amend AMC payoffs for smooth step function - Add has_amc_function to identify AMC payoffs - Add regression re-calibration to sensitivity calculation - Add swaption sensitivity component test - Add gradient calculation via FiniteDifferences --- Project.toml | 1 + src/DiffFusion.jl | 1 + src/analytics/Valuations.jl | 45 ++- src/payoffs/AmcPayoffs.jl | 75 +++- src/utils/Gradients.jl | 16 +- test/componenttests/componenttests.jl | 1 + .../sensitivities/swaptions_delta_vega.jl | 321 ++++++++++++++++++ test/unittests/analytics/valuations.jl | 9 + test/unittests/payoffs/amc_payoffs.jl | 23 ++ 9 files changed, 474 insertions(+), 18 deletions(-) create mode 100644 test/componenttests/sensitivities/swaptions_delta_vega.jl diff --git a/Project.toml b/Project.toml index 5e2d850c..4ae578b9 100644 --- a/Project.toml +++ b/Project.toml @@ -7,6 +7,7 @@ version = "0.3.0" ChainRulesCore = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4" DelimitedFiles = "8bb1440f-4735-579b-a4ab-409b98df4dab" Distributions = "31c24e10-a181-5473-b8eb-7969acd0382f" +FiniteDifferences = "26cc04aa-876d-5657-8c51-4c34ba976000" ForwardDiff = "f6369f11-7733-5829-9624-2563aa707210" Interpolations = "a98d9a8b-a2ab-59e6-89dd-64a1c18fca59" LinearAlgebra = "37e2e46d-f89d-539d-b4ee-838fcccc9c8e" diff --git a/src/DiffFusion.jl b/src/DiffFusion.jl index b62e593e..42a238bd 100644 --- a/src/DiffFusion.jl +++ b/src/DiffFusion.jl @@ -3,6 +3,7 @@ module DiffFusion using ChainRulesCore using DelimitedFiles using Distributions +using FiniteDifferences using ForwardDiff using Interpolations using LinearAlgebra diff --git a/src/analytics/Valuations.jl b/src/analytics/Valuations.jl index 724d9d83..9c3863e9 100644 --- a/src/analytics/Valuations.jl +++ b/src/analytics/Valuations.jl @@ -79,8 +79,11 @@ function model_price_and_deltas( payoffs::AbstractVector, path_obj::Path, pay_time::Union{ModelTime, Nothing} = nothing, - discount_curve_key::Union{String,Nothing} = nothing + discount_curve_key::Union{String,Nothing} = nothing, ) + if has_amc_payoff(payoffs) + @warn "Zygote cannot properly handle AMC payoffs." + end # pay_time = _effective_pay_time(payoffs, pay_time, discount_curve_key) # @@ -97,7 +100,7 @@ function model_price_and_deltas( return mean( X ) end # res = obj_function(path_obj.ts_dict) - (v, g) = _function_value_and_gradient(obj_function, path_obj.ts_dict) + (v, g) = _function_value_and_gradient(obj_function, path_obj.ts_dict, Zygote) return (v, g) end @@ -108,7 +111,7 @@ end path_obj::Path, pay_time::Union{ModelTime, Nothing} = nothing, discount_curve_key::Union{String,Nothing} = nothing, - ad_module::Module = Zygote, + ad_module::Module = ForwardDiff, ) Calculate model price and curve sensitivities. Sensitivities are @@ -120,15 +123,23 @@ Here, payoffs is a vector of `Payoff` objects and `path_obj` is a simulated `Pat numeraire calculation. `ad_module` can be `Zygote` or `ForwardDiff`. + +For AMC payoffs we need to update the regression path and trigger a +recalibration. For sensitivity calculation, we impose the constraint that +regression calibration uses the same paths as valuation. """ function model_price_and_deltas_vector( payoffs::AbstractVector, path_obj::Path, pay_time::Union{ModelTime, Nothing} = nothing, discount_curve_key::Union{String,Nothing} = nothing, - ad_module::Module = Zygote, + ad_module::Module = ForwardDiff, ) # + if has_amc_payoff(payoffs) && (ad_module == Zygote) + @warn "Zygote cannot properly handle AMC payoffs." + end + # pay_time = _effective_pay_time(payoffs, pay_time, discount_curve_key) ts_dict = deepcopy(path_obj.ts_dict) # maybe we don't want (or need) this (ts_labels, ts_values) = termstructure_values(ts_dict) @@ -136,6 +147,11 @@ function model_price_and_deltas_vector( obj_function(ts_values_) = begin termstructure_dictionary!(ts_dict, ts_labels, ts_values_) path_ = path(path_obj.sim, ts_dict, path_obj.context, path_obj.interpolation) + # we need to update regression paths for AMC payoffs + for p in payoffs + reset_regression!(p, path_) + end + # X = zeros(length(path_)) if length(payoffs) > 0 X += sum(( p(path_) for p in payoffs )) @@ -185,6 +201,9 @@ function model_price_and_vegas( pay_time::Union{ModelTime, Nothing} = nothing, discount_curve_key::Union{String,Nothing} = nothing ) + if has_amc_payoff(payoffs) + @warn "Zygote cannot properly handle AMC payoffs." + end # pay_time = _effective_pay_time(payoffs, pay_time, discount_curve_key) # @@ -220,7 +239,7 @@ function model_price_and_vegas( return price end # res = obj_function(param_dict) - (v, g) = _function_value_and_gradient(obj_function, param_dict) + (v, g) = _function_value_and_gradient(obj_function, param_dict, Zygote) return (v, g) end @@ -234,7 +253,7 @@ end context::Context, pay_time::Union{ModelTime, Nothing} = nothing, discount_curve_key::Union{String,Nothing} = nothing, - ad_module::Module = Zygote, + ad_module::Module = ForwardDiff, ) Calculate model price and model sensitivities. Sensitivities are @@ -252,6 +271,10 @@ signature `simulation(model::Model, ch::CorrelationHolder)`. numeraire calculation. `ad_module` can be `Zygote` or `ForwardDiff`. + +For AMC payoffs we need to update the regression path and trigger a +recalibration. For sensitivity calculation, we impose the constraint that +regression calibration uses the same paths as valuation. """ function model_price_and_vegas_vector( payoffs::AbstractVector, @@ -261,9 +284,13 @@ function model_price_and_vegas_vector( context::Context, pay_time::Union{ModelTime, Nothing} = nothing, discount_curve_key::Union{String,Nothing} = nothing, - ad_module::Module = Zygote, + ad_module::Module = ForwardDiff, ) # + if has_amc_payoff(payoffs) && (ad_module == Zygote) + @warn "Zygote cannot properly handle AMC payoffs." + end + # pay_time = _effective_pay_time(payoffs, pay_time, discount_curve_key) # param_dict = model_parameters(model) @@ -287,6 +314,10 @@ function model_price_and_vegas_vector( end sim = simulation(mdl, ch) path_ = path(sim, ts_list, context, LinearPathInterpolation) + # we need to update regression paths for AMC payoffs + for p in payoffs + reset_regression!(p, path_) + end # X = zeros(length(path_)) if length(payoffs) > 0 diff --git a/src/payoffs/AmcPayoffs.jl b/src/payoffs/AmcPayoffs.jl index 9de72ae3..0c2e4150 100644 --- a/src/payoffs/AmcPayoffs.jl +++ b/src/payoffs/AmcPayoffs.jl @@ -206,6 +206,58 @@ function obs_times(p::AmcPayoff) return times end + +""" + has_amc_payoff(p::AmcPayoff) + +Determine whether a payoff is or contains an AMC payoff. + +AMC payoffs require special treatment e.g. for sensitivity calculation. +""" +has_amc_payoff(p::AmcPayoff) = true + + +""" + has_amc_payoff(p::UnaryNode) + +Determine whether a payoff is or contains an AMC payoff. +""" +has_amc_payoff(p::UnaryNode) = has_amc_payoff(p.x) + +""" + has_amc_payoff(p::BinaryNode) + +Determine whether a payoff is or contains an AMC payoff. +""" +has_amc_payoff(p::BinaryNode) = has_amc_payoff(p.x) || has_amc_payoff(p.y) + +""" + has_amc_payoff(p::Union{Leaf, CompoundedRate, Optionlet, Swaption}) + +Determine whether a payoff is or contains an AMC payoff. +""" +has_amc_payoff(p::Union{Leaf, CompoundedRate, Optionlet, Swaption}) = false + +""" + has_amc_payoff(p::Payoff) + +Determine whether a payoff is or contains an AMC payoff. +""" +has_amc_payoff(p::Payoff) = begin + error("Payoff " * string(typeof(p)) * " needs to implement has_amc_payoff method.") + return false +end + +""" + has_amc_payoff(payoffs::AbstractVector) + +Determine whether any payoff is or contains an AMC payoff. +""" +has_amc_payoff(payoffs::AbstractVector) = begin + return any([ has_amc_payoff(p) for p in payoffs ]) +end + + """ reset_regression!( p::AmcPayoff, @@ -269,7 +321,7 @@ end """ reset_regression!( - p::Leaf, + p::Union{Leaf, CompoundedRate, Optionlet, Swaption}, path::Union{AbstractPath, Nothing} = nothing, make_regression::Union{Function, Nothing} = nothing, ) @@ -304,6 +356,17 @@ function reset_regression!( end +""" + _is_larger_zero(T::AbstractArray) + +Implement a differentiable version of the indicator (T>0). +""" +function _is_larger_zero(T::AbstractArray) + # return 1.0 * (T .> 0.0) + scaling = 1.0e+8 + return 0.5 .+ 0.5 .* tanh.(scaling .* T) +end + """ struct AmcMax <: AmcPayoff links::AmcPayoffLinks @@ -370,8 +433,8 @@ function at(p::AmcMax, path::AbstractPath) end Y = Y .* N end - use_X = (T .> 0.0) - return use_X .* X + (.!use_X) .* Y + use_X = _is_larger_zero(T) + return use_X .* X .+ (1.0 .- use_X) .* Y end """ @@ -450,8 +513,8 @@ function at(p::AmcMin, path::AbstractPath) end Y = Y .* N end - use_X = (T .<= 0.0) - return use_X .* X + (.!use_X) .* Y + use_Y = _is_larger_zero(T) + return (1.0 .- use_Y) .* X .+ use_Y .* Y end """ @@ -516,7 +579,7 @@ Evaluate an AmcOne payoff at a given path. """ function at(p::AmcOne, path::AbstractPath) (X, Y, T) = at(p.links, p.regr, path) - return (T .> 0.0) + return _is_larger_zero(T) end """ diff --git a/src/utils/Gradients.jl b/src/utils/Gradients.jl index d2976489..908543ed 100644 --- a/src/utils/Gradients.jl +++ b/src/utils/Gradients.jl @@ -12,7 +12,7 @@ tuning AD routines. _function_value_and_gradient( f::Function, x::Any, - m::Module = Zygote, + m::Module = ForwardDiff, ) Calculate the function value and gradient of a function. @@ -20,18 +20,24 @@ Calculate the function value and gradient of a function. function _function_value_and_gradient( f::Function, x::Any, - m::Module = Zygote, + m::Module = ForwardDiff, ) # + if m == ForwardDiff + v = f(x) + g = ForwardDiff.gradient(f, x) + return (v, g) + end if m == Zygote (v, g) = withgradient(f, x) @assert length(g) == 1 return (v, g[1]) end - if m == ForwardDiff + if m == FiniteDifferences v = f(x) - g = ForwardDiff.gradient(f, x) - return (v, g) + m = central_fdm(3, 1) + g = grad(m, f, x) + return (v, g[1]) end error("Unknown module " * string(m) * ".") end diff --git a/test/componenttests/componenttests.jl b/test/componenttests/componenttests.jl index 2f5d425f..9732440f 100644 --- a/test/componenttests/componenttests.jl +++ b/test/componenttests/componenttests.jl @@ -16,6 +16,7 @@ using Test include("sensitivities/option_deltas.jl") include("sensitivities/swap_deltas.jl") include("sensitivities/option_vegas.jl") + include("sensitivities/swaptions_delta_vega.jl") end diff --git a/test/componenttests/sensitivities/swaptions_delta_vega.jl b/test/componenttests/sensitivities/swaptions_delta_vega.jl new file mode 100644 index 00000000..56970eb7 --- /dev/null +++ b/test/componenttests/sensitivities/swaptions_delta_vega.jl @@ -0,0 +1,321 @@ +using DiffFusion +using Test +using UnicodePlots + + +@testset "Test European and Bermudan swaption simulation" begin + + # Model + + ch = DiffFusion.correlation_holder("") + δ = DiffFusion.flat_parameter([ 0., ]) + χ = DiffFusion.flat_parameter([ 0.01, ]) + + times = [ 1., 2., 5., 10. ] + values = [ 50., 50., 50., 50., ]' * 1.0e-4 + σ = DiffFusion.backward_flat_volatility("", times, values) + + model = DiffFusion.gaussian_hjm_model("md/EUR", δ, χ, σ, ch, nothing) + + + # Simulation + + times = 0.0:0.25:10.0 + n_paths = 2^10 + + sim = DiffFusion.simple_simulation( + model, + ch, + times, + n_paths, + with_progress_bar = true, + brownian_increments = DiffFusion.sobol_brownian_increments, + ) + + + # Path + + yc_estr = DiffFusion.linear_zero_curve( + "yc/EUR:ESTR", + [1.0, 3.0, 6.0, 10.0], + [1.0, 1.0, 1.0, 1.0] .* 1e-2, + ) + yc_euribor6m = DiffFusion.linear_zero_curve( + "yc/EUR:EURIBOR6M", + [1.0, 3.0, 6.0, 10.0], + [2.0, 2.0, 2.0, 2.0] .* 1e-2, + ) + + # yc_estr = DiffFusion.flat_forward("yc/EUR:ESTR", 0.01) + # yc_euribor6m = DiffFusion.flat_forward("yc/EUR:EURIBOR6M", 0.02) + + ts_list = [ + yc_estr, + yc_euribor6m, + ] + + _empty_key = DiffFusion._empty_context_key + context = DiffFusion.Context( + "Std", + DiffFusion.NumeraireEntry("EUR", "md/EUR", Dict(_empty_key => "yc/EUR:ESTR")), + Dict{String, DiffFusion.RatesEntry}([ + ("EUR", DiffFusion.RatesEntry("EUR", "md/EUR", Dict( + _empty_key => "yc/EUR:ESTR", + "ESTR" => "yc/EUR:ESTR", + "EURIBOR6M" => "yc/EUR:EURIBOR6M", + ))), + ]), + Dict{String, DiffFusion.AssetEntry}(), + Dict{String, DiffFusion.ForwardIndexEntry}(), + Dict{String, DiffFusion.FutureIndexEntry}(), + Dict{String, DiffFusion.FixingEntry}(), + ) + + path = DiffFusion.path(sim, ts_list, context, DiffFusion.LinearPathInterpolation) + + + # Vanilla Swap + + fixed_flows = [ + DiffFusion.FixedRateCoupon( 1.0, 0.02, 1.0), + DiffFusion.FixedRateCoupon( 2.0, 0.02, 1.0), + DiffFusion.FixedRateCoupon( 3.0, 0.02, 1.0), + DiffFusion.FixedRateCoupon( 4.0, 0.02, 1.0), + DiffFusion.FixedRateCoupon( 5.0, 0.02, 1.0), + DiffFusion.FixedRateCoupon( 6.0, 0.02, 1.0), + DiffFusion.FixedRateCoupon( 7.0, 0.02, 1.0), + DiffFusion.FixedRateCoupon( 8.0, 0.02, 1.0), + DiffFusion.FixedRateCoupon( 9.0, 0.02, 1.0), + DiffFusion.FixedRateCoupon(10.0, 0.02, 1.0), + ]; + + libor_flows = [ + DiffFusion.SimpleRateCoupon(0.0, 0.0, 0.5, 0.5, 0.5, "EUR:EURIBOR6M", nothing, nothing), + DiffFusion.SimpleRateCoupon(0.5, 0.5, 1.0, 1.0, 0.5, "EUR:EURIBOR6M", nothing, nothing), + DiffFusion.SimpleRateCoupon(1.0, 1.0, 1.5, 1.5, 0.5, "EUR:EURIBOR6M", nothing, nothing), + DiffFusion.SimpleRateCoupon(1.5, 1.5, 2.0, 2.0, 0.5, "EUR:EURIBOR6M", nothing, nothing), + DiffFusion.SimpleRateCoupon(2.0, 2.0, 2.5, 2.5, 0.5, "EUR:EURIBOR6M", nothing, nothing), + DiffFusion.SimpleRateCoupon(2.5, 2.5, 3.0, 3.0, 0.5, "EUR:EURIBOR6M", nothing, nothing), + DiffFusion.SimpleRateCoupon(3.0, 3.0, 3.5, 3.5, 0.5, "EUR:EURIBOR6M", nothing, nothing), + DiffFusion.SimpleRateCoupon(3.5, 3.5, 4.0, 4.0, 0.5, "EUR:EURIBOR6M", nothing, nothing), + DiffFusion.SimpleRateCoupon(4.0, 4.0, 4.5, 4.5, 0.5, "EUR:EURIBOR6M", nothing, nothing), + DiffFusion.SimpleRateCoupon(4.5, 4.5, 5.0, 5.0, 0.5, "EUR:EURIBOR6M", nothing, nothing), + DiffFusion.SimpleRateCoupon(5.0, 5.0, 5.5, 5.5, 0.5, "EUR:EURIBOR6M", nothing, nothing), + DiffFusion.SimpleRateCoupon(5.5, 5.5, 6.0, 6.0, 0.5, "EUR:EURIBOR6M", nothing, nothing), + DiffFusion.SimpleRateCoupon(6.0, 6.0, 6.5, 6.5, 0.5, "EUR:EURIBOR6M", nothing, nothing), + DiffFusion.SimpleRateCoupon(6.5, 6.5, 7.0, 7.0, 0.5, "EUR:EURIBOR6M", nothing, nothing), + DiffFusion.SimpleRateCoupon(7.0, 7.0, 7.5, 7.5, 0.5, "EUR:EURIBOR6M", nothing, nothing), + DiffFusion.SimpleRateCoupon(7.5, 7.5, 8.0, 8.0, 0.5, "EUR:EURIBOR6M", nothing, nothing), + DiffFusion.SimpleRateCoupon(8.0, 8.0, 8.5, 8.5, 0.5, "EUR:EURIBOR6M", nothing, nothing), + DiffFusion.SimpleRateCoupon(8.5, 8.5, 9.0, 9.0, 0.5, "EUR:EURIBOR6M", nothing, nothing), + DiffFusion.SimpleRateCoupon(9.0, 9.0, 9.5, 9.5, 0.5, "EUR:EURIBOR6M", nothing, nothing), + DiffFusion.SimpleRateCoupon(9.5, 9.5, 10.0, 10.0, 0.5, "EUR:EURIBOR6M", nothing, nothing), + ] + + fixed_notionals = 10_000.00 * ones(length(fixed_flows)) + fixed_leg = DiffFusion.cashflow_leg( + "leg/1", fixed_flows, fixed_notionals, "EUR:ESTR", nothing, 1.0, + ) + + libor_notionals = 10_000.00 * ones(length(libor_flows)) + libor_leg = DiffFusion.cashflow_leg( + "leg/2", libor_flows, libor_notionals, "EUR:ESTR", nothing, -1.0 + ) + + vanilla_swap = [ fixed_leg, libor_leg ] + + + # European Swaptions + + payer_receiver = -1.0 # fixed receiver swaptions; put option on swap rate + swap_disc_curve_key = "EUR:ESTR" + settlement_type = DiffFusion.SwaptionPhysicalSettlement + notional = 10_000.00 + + swpt_disc_curve_key = "EUR:ESTR" + swpt_fx_key = nothing + swpt_long_short = 1.0 + + make_swaption(_alias, _expiry_time, _libor_coupons, _fixed_coupons, ) = DiffFusion.SwaptionLeg( + _alias, + _expiry_time, + _expiry_time, # settlement_time + _libor_coupons, + _fixed_coupons, + payer_receiver, + swap_disc_curve_key, + settlement_type, + notional, + swpt_disc_curve_key, + swpt_fx_key, + swpt_long_short, + ) + + swaption_2y = make_swaption("leg/swpn/2y", 2.0, libor_flows[5:end], fixed_flows[3:end]) + swaption_4y = make_swaption("leg/swpn/4y", 4.0, libor_flows[9:end], fixed_flows[5:end]) + swaption_6y = make_swaption("leg/swpn/6y", 6.0, libor_flows[13:end], fixed_flows[7:end]) + swaption_8y = make_swaption("leg/swpn/8y", 8.0, libor_flows[17:end], fixed_flows[9:end]) + + + # Bermudan Swaption + + make_regression_variables(t) = [ DiffFusion.LiborRate(t, t, 10.0, "EUR:EURIBOR6M"), ] + + swap_2y_10y = [ + DiffFusion.cashflow_leg("leg/fixed/2y-10y",fixed_flows[3:end], fixed_notionals[3:end], "EUR:ESTR", nothing, 1.0), # receiver + DiffFusion.cashflow_leg("leg/libor/2y-10y",libor_flows[5:end], libor_notionals[5:end], "EUR:ESTR", nothing, -1.0), # payer + ] + + swap_4y_10y = [ + DiffFusion.cashflow_leg("leg/fixed/4y-10y",fixed_flows[5:end], fixed_notionals[5:end], "EUR:ESTR", nothing, 1.0), # receiver + DiffFusion.cashflow_leg("leg/libor/4y-10y",libor_flows[9:end], libor_notionals[9:end], "EUR:ESTR", nothing, -1.0), # payer + ] + + swap_6y_10y = [ + DiffFusion.cashflow_leg("leg/fixed/6y-10y",fixed_flows[7:end], fixed_notionals[7:end], "EUR:ESTR", nothing, 1.0), # receiver + DiffFusion.cashflow_leg("leg/libor/6y-10y",libor_flows[13:end], libor_notionals[13:end], "EUR:ESTR", nothing, -1.0), # payer + ] + + swap_8y_10y = [ + DiffFusion.cashflow_leg("leg/fixed/6y-10y",fixed_flows[9:end], fixed_notionals[9:end], "EUR:ESTR", nothing, 1.0), # receiver + DiffFusion.cashflow_leg("leg/libor/6y-10y",libor_flows[17:end], libor_notionals[17:end], "EUR:ESTR", nothing, -1.0), # payer + ] + + exercise_2y = DiffFusion.bermudan_exercise(2.0, swap_2y_10y, make_regression_variables) + exercise_4y = DiffFusion.bermudan_exercise(4.0, swap_4y_10y, make_regression_variables) + exercise_6y = DiffFusion.bermudan_exercise(6.0, swap_6y_10y, make_regression_variables) + exercise_8y = DiffFusion.bermudan_exercise(8.0, swap_8y_10y, make_regression_variables) + + berm_10nc2 = DiffFusion.bermudan_swaption_leg( + "berm/10-nc-2", + [ exercise_2y, exercise_4y, exercise_6y, exercise_8y, ], + 1.0, # long option + "", # default discounting (curve key) + make_regression_variables, + nothing, # path + nothing, # make_regression + ) + + berm_4y = DiffFusion.bermudan_swaption_leg( + "berm/4y", + [ exercise_4y, ], + 1.0, # long option + "", # default discounting (curve key) + make_regression_variables, + nothing, # path + nothing, # make_regression + ) + + # AMC Regression + + make_regression = (C, O) -> DiffFusion.polynomial_regression(C, O, 2) + # make_regression = (C, O) -> DiffFusion.piecewise_regression(C, O, 2, [3]) + + DiffFusion.reset_regression!(berm_10nc2, path, make_regression) + DiffFusion.reset_regression!(berm_4y, path, make_regression) + + + # Scenario Calculation + portfolo = [ + vanilla_swap..., + swaption_2y, + swaption_4y, + swaption_6y, + swaption_8y, + berm_10nc2, + ] + + function print_results(v, g, ts_labels) + println("NPV: " * string(v)) + for (l, d) in zip(ts_labels, g) + println(l * ": " * string(d)) + end + end + + @testset "Test Delta calculation" begin + @info "Start Delta calculation..." + # + (v1, g1, ts_labels) = DiffFusion.model_price_and_deltas_vector( + DiffFusion.discounted_cashflows(swaption_4y, 1.0), + path, + 1.0, + "", + DiffFusion.ForwardDiff + ) + print_results(v1, g1, ts_labels) + # + (v2, g2, ts_labels) = DiffFusion.model_price_and_deltas_vector( + DiffFusion.discounted_cashflows(berm_4y, 1.0), + path, + 1.0, + "", + DiffFusion.ForwardDiff + ) + print_results(v2, g2, ts_labels) + # + (v3, g3, ts_labels) = DiffFusion.model_price_and_deltas_vector( + DiffFusion.discounted_cashflows(berm_10nc2, 1.0), + path, + 1.0, + "", + DiffFusion.ForwardDiff + ) + print_results(v3, g3, ts_labels) + @info "Finished Delta calculation." + # + @test abs(sum(g2[1:4])/sum(g1[1:4]) - 1.0) < 1.5e-2 # berm_4y EURIBOR6M Delta + @test abs(sum(g2[5:8])/sum(g1[5:8]) - 1.0) < 7.5e-2 # berm_4y ESTR + # + @test abs(sum(g3[1:4])/sum(g1[1:4]) - 1.0) < 0.27 # berm_10nc2 EURIBOR6M Delta + @test abs(sum(g3[5:8])/sum(g1[5:8]) - 1.0) < 0.17 # berm_10nc2 ESTR + end + + + @testset "Test Vega calculation" begin + @info "Start Vega calculation..." + # + hyb_model = DiffFusion.simple_model("Std", [model]) + sim(model, ch) = DiffFusion.simple_simulation(model, ch, times, n_paths, with_progress_bar = false, brownian_increments = DiffFusion.sobol_brownian_increments) + # + (v1, g1, ts_labels) = DiffFusion.model_price_and_vegas_vector( + DiffFusion.discounted_cashflows(swaption_4y, 1.0), + hyb_model, + sim, + ts_list, + context, + 1.0, + "", + DiffFusion.ForwardDiff + ) + print_results(v1, g1, ts_labels) + # + (v2, g2, ts_labels) = DiffFusion.model_price_and_vegas_vector( + DiffFusion.discounted_cashflows(berm_4y, 1.0), + hyb_model, + sim, + ts_list, + context, + 1.0, + "", + DiffFusion.ForwardDiff + ) + print_results(v2, g2, ts_labels) + # + (v3, g3, ts_labels) = DiffFusion.model_price_and_vegas_vector( + DiffFusion.discounted_cashflows(berm_10nc2, 1.0), + hyb_model, + sim, + ts_list, + context, + 1.0, + "", + DiffFusion.ForwardDiff + ) + print_results(v3, g3, ts_labels) + @info "Finished Vega calculation." + # + @test abs(sum(g2)/sum(g1) - 1.0) < 1.0e-1 # berm_4y IR Vega + end + + +end \ No newline at end of file diff --git a/test/unittests/analytics/valuations.jl b/test/unittests/analytics/valuations.jl index 3ff01cf8..f27b87e3 100644 --- a/test/unittests/analytics/valuations.jl +++ b/test/unittests/analytics/valuations.jl @@ -52,16 +52,22 @@ using ForwardDiff model_price = DiffFusion.model_price(payoffs, path, nothing, "") (v1, g1, l1) = DiffFusion.model_price_and_deltas_vector(payoffs, path, nothing, "", Zygote) (v2, g2, l2) = DiffFusion.model_price_and_deltas_vector(payoffs, path, nothing, "", ForwardDiff) + (v3, g3, l3) = DiffFusion.model_price_and_deltas_vector(payoffs, path, nothing, "", FiniteDifferences) @test v1 == model_price @test v2 == model_price + @test v2 == model_price @test isapprox(g1, g2, atol=1.0e-12) + @test isapprox(g2, g3, atol=1.0e-10) # model_price = DiffFusion.model_price(payoffs, path, nothing, nothing) (v1, g1, l1) = DiffFusion.model_price_and_deltas_vector(payoffs, path, nothing, nothing, Zygote) (v2, g2, l2) = DiffFusion.model_price_and_deltas_vector(payoffs, path, nothing, nothing, ForwardDiff) + (v3, g3, l3) = DiffFusion.model_price_and_deltas_vector(payoffs, path, nothing, nothing, FiniteDifferences) @test v1 == model_price @test v2 == model_price + @test v3 == model_price @test isapprox(g1, g2, atol=1.0e-12) + @test isapprox(g2, g3, atol=1.0e-9) @info "Finished." #println(g2) end @@ -79,9 +85,12 @@ using ForwardDiff model_price = DiffFusion.model_price(payoffs, path, nothing, "") (v1, g1, l1) = DiffFusion.model_price_and_vegas_vector(payoffs, model, sim_func, ts_list, context, nothing, "", Zygote) (v2, g2, l2) = DiffFusion.model_price_and_vegas_vector(payoffs, model, sim_func, ts_list, context, nothing, "", ForwardDiff) + (v3, g3, l3) = DiffFusion.model_price_and_vegas_vector(payoffs, model, sim_func, ts_list, context, nothing, "", FiniteDifferences) @test v1 == model_price @test v2 == model_price + @test v3 == model_price @test isapprox(g1, g2, atol=1.0e-12) + @test isapprox(g2, g3, atol=1.0e-10) @info "Finished." # println(g1) end diff --git a/test/unittests/payoffs/amc_payoffs.jl b/test/unittests/payoffs/amc_payoffs.jl index d0c14b8d..6d8fb824 100644 --- a/test/unittests/payoffs/amc_payoffs.jl +++ b/test/unittests/payoffs/amc_payoffs.jl @@ -38,6 +38,29 @@ using Test @test string(p4) == "AmcSum(5.00, [S(Std, 6.00), S(Std, 7.00)], [], [S(Std, 3.00), S(Std, 4.00)])" end + @testset "Has AMC property" begin + x = [ DiffFusion.Asset(6.0, "Std"), DiffFusion.Asset(7.0, "Std") ] + y = [ DiffFusion.Asset(8.0, "Std"), DiffFusion.Asset(9.0, "Std") ] + z = [ DiffFusion.Asset(3.0, "Std"), DiffFusion.Asset(4.0, "Std") ] + # + p1 = DiffFusion.AmcMax(5.0, x, y, z, nothing, nothing, "Std") + p2 = DiffFusion.AmcMin(5.0, x, y, z, nothing, nothing, "Std") + p3 = DiffFusion.AmcOne(5.0, x, y, z, nothing, nothing, "Std") + p4 = DiffFusion.AmcSum(5.0, x, z, nothing, nothing, "Std") + # + @test DiffFusion.has_amc_payoff(x) == false + # + @test DiffFusion.has_amc_payoff(p1) == true + @test DiffFusion.has_amc_payoff(p2) == true + @test DiffFusion.has_amc_payoff(p3) == true + @test DiffFusion.has_amc_payoff(p4) == true + # + @test DiffFusion.has_amc_payoff(p1 + x[1]) == true + @test DiffFusion.has_amc_payoff(p1 + p2) == true + @test DiffFusion.has_amc_payoff(DiffFusion.Pay(p1, 5.0)) == true + @test DiffFusion.has_amc_payoff([p1, p2, p3, p4]) == true + end + @testset "Payoff calibration." begin x = [ DiffFusion.Asset(6.0, "Std"), DiffFusion.Asset(7.0, "Std") ] y = [ DiffFusion.Asset(8.0, "Std"), DiffFusion.Asset(9.0, "Std") ]