Skip to content

Commit

Permalink
Add interest rate payoffs
Browse files Browse the repository at this point in the history
  • Loading branch information
FrameConsult committed Aug 14, 2023
1 parent dbf2c7f commit 6408c44
Show file tree
Hide file tree
Showing 4 changed files with 263 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/DiffFusion.jl
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ include("payoffs/Payoff.jl")
include("payoffs/Leafs.jl")
include("payoffs/UnaryNodes.jl")
include("payoffs/BinaryNodes.jl")
include("payoffs/RatesPayoffs.jl")

include("utils/Integrations.jl")
include("utils/InterpolationMethods.jl")
Expand Down
208 changes: 208 additions & 0 deletions src/payoffs/RatesPayoffs.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@

"""
struct LiborRate <: Leaf
obs_time::ModelTime
start_time::ModelTime
end_time::ModelTime
year_fraction::ModelValue
key::String
end
A simple compounded forward Libor rate.
"""
struct LiborRate <: Leaf
obs_time::ModelTime
start_time::ModelTime
end_time::ModelTime
year_fraction::ModelValue
key::String
end

"""
LiborRate(
obs_time::ModelTime,
start_time::ModelTime,
end_time::ModelTime,
key::String,
)
A simple compounded forward Libor rate with year fraction from model time.
"""
function LiborRate(
obs_time::ModelTime,
start_time::ModelTime,
end_time::ModelTime,
key::String,
)
return LiborRate(obs_time, start_time, end_time, end_time-start_time, key)
end

"""
at(p::LiborRate, path::AbstractPath)
Derive the forward Libor rate at a given path.
"""
function at(p::LiborRate, path::AbstractPath)
df1 = zero_bond(path, p.obs_time, p.start_time, p.key)
df2 = zero_bond(path, p.obs_time, p.end_time, p.key)
return (df1 ./ df2 .- 1.0) ./ p.year_fraction
end

"""
string(p::LiborRate)
Formatted (and shortened) output for LiborRate payoff.
"""
string(p::LiborRate) = @sprintf("L(%s, %.2f; %.2f, %.2f)", p.key, p.obs_time, p.start_time, p.end_time)


"""
struct CompoundedRate <: Payoff
obs_time::ModelTime
start_time::ModelTime
end_time::ModelTime
year_fraction::ModelValue
fixed_compounding::Union{Payoff, Nothing}
key::String
fixed_type::DataType # distinguish from constructors
end
A continuously compounded backward looking rate.
This is a proxy for daily compounded RFR coupon rates.
For obs_time less start_time it is equivalent to a Libor rate.
"""
struct CompoundedRate <: Payoff
obs_time::ModelTime
start_time::ModelTime
end_time::ModelTime
year_fraction::ModelValue
fixed_compounding::Union{Payoff, Nothing}
key::String
fixed_type::DataType # distinguish from constructors
end

"""
CompoundedRate(
obs_time_::ModelTime,
start_time::ModelTime,
end_time::ModelTime,
year_fraction::ModelValue,
key::String,
fixed_compounding::Union{Payoff, Nothing} = nothing,
)
A continuously compounded backward looking rate.
"""
function CompoundedRate(
obs_time_::ModelTime,
start_time::ModelTime,
end_time::ModelTime,
year_fraction::ModelValue,
key::String,
fixed_compounding::Union{Payoff, Nothing} = nothing,
)
@assert isnothing(fixed_compounding) || obs_time(fixed_compounding) == 0.0
return CompoundedRate(
obs_time_,
start_time,
end_time,
year_fraction,
fixed_compounding,
key,
typeof(fixed_compounding),
)
end


"""
CompoundedRate(
obs_time::ModelTime,
start_time::ModelTime,
end_time::ModelTime,
key::String,
fixed_compounding::Union{Payoff, Nothing} = nothing,
)
A continuously compounded backward looking rate with year fraction from model time.
"""
function CompoundedRate(
obs_time::ModelTime,
start_time::ModelTime,
end_time::ModelTime,
key::String,
fixed_compounding::Union{Payoff, Nothing} = nothing,
)
return CompoundedRate(
obs_time,
start_time,
end_time,
end_time-start_time,
key,
fixed_compounding,
)
end


"""
at(p::CompoundedRate, path::AbstractPath)
Derive the compounded backward looking rate at a given path.
"""
function at(p::CompoundedRate, path::AbstractPath)
fixed_cmp = 1.0
if !isnothing(p.fixed_compounding)
fixed_cmp = at(p.fixed_compounding, path)
end
if p.obs_time p.start_time
df1 = zero_bond(path, p.obs_time, p.start_time, p.key)
df2 = zero_bond(path, p.obs_time, p.end_time, p.key)
return (fixed_cmp .* df1 ./ df2 .- 1.0) ./ p.year_fraction
end
if p.obs_time < p.end_time
cmp = bank_account(path, p.obs_time, p.key) ./ bank_account(path, p.start_time, p.key)
df2 = zero_bond(path, p.obs_time, p.end_time, p.key)
return (fixed_cmp .* cmp ./ df2 .- 1.0) ./ p.year_fraction
end
# p.obs_time ≥ end p.end_time
cmp = bank_account(path, p.end_time, p.key) ./ bank_account(path, p.start_time, p.key)
return (fixed_cmp .* cmp .- 1.0) ./ p.year_fraction
end

"""
string(p::CompoundedRate)
Formatted (and shortened) output for CompoundedRate payoff.
"""
string(p::CompoundedRate) = begin
if isnothing(p.fixed_compounding)
return @sprintf("R(%s, %.2f; %.2f, %.2f)", p.key, p.obs_time, p.start_time, p.end_time)
else
return @sprintf("R(%s, %.2f; %.2f, %.2f; %s)", p.key, p.obs_time, p.start_time, p.end_time, string(p.fixed_compounding))
end
end

"""
obs_time(p::CompoundedRate)
Calculate observation time for CompoundedRate payoff.
"""
obs_time(p::CompoundedRate) = min(p.obs_time, p.end_time)

"""
obs_times(p::CompoundedRate)
Calculate all observation times (i.e. event times) for CompoundedRate payoff.
"""
function obs_times(p::CompoundedRate)
fix_times = Set()
if !isnothing(p.fixed_compounding)
fix_times = obs_times(p.fixed_compounding)
end
if p.obs_time p.start_time
return union(Set(p.obs_time), fix_times)
else
return union(Set((p.start_time, obs_time(p))), fix_times)
end
end
1 change: 1 addition & 0 deletions test/unittests/payoffs/payoffs.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@ using Test
end

include("nodes_and_leafs.jl")
include("rates_payoffs.jl")

end
53 changes: 53 additions & 0 deletions test/unittests/payoffs/rates_payoffs.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using DiffFusion
using Test

@testset "Rates payoffs." begin

"A trivial path for testing."
struct ConstantPath <: DiffFusion.AbstractPath end
DiffFusion.numeraire(p::ConstantPath, t::DiffFusion.ModelTime, curve_key::String) = t * ones(5)
DiffFusion.bank_account(p::ConstantPath, t::DiffFusion.ModelTime, key::String) = t * ones(5)
DiffFusion.zero_bond(p::ConstantPath, t::DiffFusion.ModelTime, T::DiffFusion.ModelTime, key::String) = 1.0 * ones(5)
DiffFusion.length(p::ConstantPath) = 5

@testset "Libor Rate payoff" begin
path = ConstantPath()
#
L = DiffFusion.LiborRate(1.0, 2.0, 3.0, "USD-Lib-3m")
@test DiffFusion.obs_time(L) == 1.0
@test DiffFusion.at(L, path) == zeros(5)
@test string(L) == "L(USD-Lib-3m, 1.00; 2.00, 3.00)"
@test DiffFusion.obs_time(L) == 1.0
@test DiffFusion.obs_times(L) == Set([1.0])
end

@testset "Compounded Rate payoff" begin
path = ConstantPath()
#
L = DiffFusion.CompoundedRate(1.0, 2.0, 3.0, "SOFR")
@test DiffFusion.obs_time(L) == 1.0
@test DiffFusion.at(L, path) == zeros(5)
@test string(L) == "R(SOFR, 1.00; 2.00, 3.00)"
#
@test DiffFusion.at(DiffFusion.CompoundedRate(2.0, 2.0, 3.0, "SOFR"), path) == zeros(5)
@test DiffFusion.at(DiffFusion.CompoundedRate(2.5, 2.0, 3.0, "SOFR"), path) == 0.25 * ones(5)
@test DiffFusion.at(DiffFusion.CompoundedRate(3.0, 2.0, 3.0, "SOFR"), path) == 0.50 * ones(5)
#
@test DiffFusion.obs_time(DiffFusion.CompoundedRate(1.0, 2.0, 3.0, "SOFR")) == 1.0
@test DiffFusion.obs_time(DiffFusion.CompoundedRate(2.0, 2.0, 3.0, "SOFR")) == 2.0
@test DiffFusion.obs_time(DiffFusion.CompoundedRate(2.5, 2.0, 3.0, "SOFR")) == 2.5
@test DiffFusion.obs_time(DiffFusion.CompoundedRate(3.0, 2.0, 3.0, "SOFR")) == 3.0
#
@test DiffFusion.obs_times(DiffFusion.CompoundedRate(1.0, 2.0, 3.0, "SOFR")) == Set([1.0])
@test DiffFusion.obs_times(DiffFusion.CompoundedRate(2.0, 2.0, 3.0, "SOFR")) == Set([2.0])
@test DiffFusion.obs_times(DiffFusion.CompoundedRate(2.5, 2.0, 3.0, "SOFR")) == Set([2.0, 2.5])
@test DiffFusion.obs_times(DiffFusion.CompoundedRate(3.0, 2.0, 3.0, "SOFR")) == Set([2.0, 3.0])
@test DiffFusion.obs_times(DiffFusion.CompoundedRate(4.0, 2.0, 3.0, "SOFR")) == Set([2.0 3.0])
#
fixed_compounding = 1.0 + 0.01 * DiffFusion.Fixing(-0.01, "SOFR")
R = DiffFusion.CompoundedRate(0.5, 0.0, 1.0, "SOFR", fixed_compounding)
@test string(R) == "R(SOFR, 0.50; 0.00, 1.00; (1.0000 + 0.0100 * Idx(SOFR, -0.01)))"
@test DiffFusion.obs_times(R) == Set((-0.01, 0.0, 0.5))
end

end

0 comments on commit 6408c44

Please sign in to comment.