From 0e8b9ad37184b50d53f4fe20815445ac4f49c789 Mon Sep 17 00:00:00 2001 From: alecloudenback Date: Thu, 25 Aug 2022 19:01:10 -0500 Subject: [PATCH] define multiplication and division of yield objects (#136) --- README.md | 2 +- src/RateCombination.jl | 89 +++++++++++++++++++++++++++++++++++++++++ test/RateCombination.jl | 37 +++++++++++++++-- 3 files changed, 124 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 41e8f4b7..51aec07a 100644 --- a/README.md +++ b/README.md @@ -84,7 +84,7 @@ convert(Yields.Continuous(),r) # convert monthly rate to continuous #### Arithmetic -Adding, substracting, and comparing rates is supported. +Adding, substracting, multiplying, dividing, and comparing rates is supported. ### Curves diff --git a/src/RateCombination.jl b/src/RateCombination.jl index 64b32d04..b5eb964e 100644 --- a/src/RateCombination.jl +++ b/src/RateCombination.jl @@ -1,4 +1,13 @@ ## Curve Manipulations +""" + RateCombination(curve1,curve2,operation) + +Creates a datastructure that will perform the given `operation` after independently calculating the effects of the two curves. +Can only be created via the public API by using the `+`, `-`, `*`, and `/` operatations on `AbstractYield` objects. + +As this is double the normal operations when performing calculations, if you are using the curve in performance critical locations, you should consider transforming the inputs and +constructing a single curve object ahead of time. +""" struct RateCombination{T,U,V} <: AbstractYieldCurve r1::T r2::U @@ -48,6 +57,46 @@ function Base.:+(a, b::T) where {T<:AbstractYieldCurve} return Constant(a) + b end +""" + Yields.AbstractYieldCurve * Yields.AbstractYieldCurve + +The multiplication of two yields will create a `RateCombination`. For `rate`, `discount`, and `accumulation` purposes the spot rates of the two curves will be added together. This can be useful, for example, if you wanted to after-tax a yield. + +# Examples + +```julia-repl +julia> m = Yields.Constant(0.01) * 0.79; + +julia> accumulation(m,1) +1.0079 + +julia> accumulation(.01*.79,1) +1.0079 +``` +""" +function Base.:*(a::AbstractYieldCurve, b::AbstractYieldCurve) + return RateCombination(a, b, *) +end + +function Base.:*(a::Constant, b::Constant) + a_kind = rate(a).compounding + rate_new_basis = rate(convert(a_kind, rate(b))) + return Constant( + Rate( + rate(a.rate) * rate_new_basis, + a_kind + ) + ) +end + +function Base.:*(a::T, b) where {T<:AbstractYieldCurve} + return a * Constant(b) +end + +function Base.:*(a, b::T) where {T<:AbstractYieldCurve} + return Constant(a) * b +end + """ Yields.AbstractYieldCurve - Yields.AbstractYieldCurve @@ -74,4 +123,44 @@ end function Base.:-(a, b::T) where {T<:AbstractYieldCurve} return Constant(a) - b +end + +""" + Yields.AbstractYieldCurve / Yields.AbstractYieldCurve + +The division of two yields will create a `RateCombination`. For `rate`, `discount`, and `accumulation` purposes the spot rates of the two curves will have the first divided by the second. This can be useful, for example, if you wanted to gross-up a yield to be pre-tax. + +# Examples + +```julia-repl +julia> m = Yields.Constant(0.01) / 0.79; + +julia> accumulation(d,1) +1.0126582278481013 + +julia> accumulation(.01/.79,1) +1.0126582278481013 +``` +""" +function Base.:/(a::AbstractYieldCurve, b::AbstractYieldCurve) + return RateCombination(a, b, /) +end + +function Base.:/(a::Constant, b::Constant) + a_kind = rate(a).compounding + rate_new_basis = rate(convert(a_kind, rate(b))) + return Constant( + Rate( + rate(a.rate) / rate_new_basis, + a_kind + ) + ) +end + +function Base.:/(a::T, b) where {T<:AbstractYieldCurve} + return a / Constant(b) +end + +function Base.:/(a, b::T) where {T<:AbstractYieldCurve} + return Constant(a) / b end \ No newline at end of file diff --git a/test/RateCombination.jl b/test/RateCombination.jl index e8b3ce01..9ada421f 100644 --- a/test/RateCombination.jl +++ b/test/RateCombination.jl @@ -1,12 +1,13 @@ @testset "Rate Combinations" begin + riskfree_maturities = [0.5, 1.0, 1.5, 2.0] + riskfree = [5.0, 5.8, 6.4, 6.8] ./ 100 # spot rates + rf_curve = Yields.Zero(riskfree, riskfree_maturities) + @testset "base + spread" begin - riskfree_maturities = [0.5, 1.0, 1.5, 2.0] - riskfree = [5.0, 5.8, 6.4, 6.8] ./ 100 # spot rates spread_maturities = [0.5, 1.0, 1.5, 3.0] # different maturities spread = [1.0, 1.8, 1.4, 1.8] ./ 100 # spot spreads - rf_curve = Yields.Zero(riskfree, riskfree_maturities) spread_curve = Yields.Zero(spread, spread_maturities) yield = rf_curve + spread_curve @@ -16,4 +17,34 @@ @test discount(yield, 1.0) ≈ 1 / (1 + riskfree[2] + spread[2])^1 @test discount(yield, 1.5) ≈ 1 / (1 + riskfree[3] + spread[3])^1.5 end + + @testset "multiplicaiton and division" begin + @testset "multiplication" begin + factor = .79 + c = rf_curve * factor + (discount(c,10)-1 * factor) ≈ discount(rf_curve,10) + (accumulation(c,10)-1 * factor) ≈ accumulation(rf_curve,10) + forward(c,5,10) * factor ≈ forward(rf_curve,5,10) + Yields.par(c,10) * factor ≈ Yields.par(rf_curve,10) + + c = factor * rf_curve + (discount(c,10)-1 * factor) ≈ discount(rf_curve,10) + (accumulation(c,10)-1 * factor) ≈ accumulation(rf_curve,10) + forward(c,5,10) * factor ≈ forward(rf_curve,5,10) + Yields.par(c,10) * factor ≈ Yields.par(rf_curve,10) + + @test discount(Yields.Constant(0.1) * Yields.Constant(0.1),10) ≈ discount(Yields.Constant(0.01),10) + end + + @testset "division" begin + factor = .79 + c = rf_curve / (factor^-1) + (discount(c,10)-1 * factor) ≈ discount(rf_curve,10) + (accumulation(c,10)-1 * factor) ≈ accumulation(rf_curve,10) + forward(c,5,10) * factor ≈ forward(rf_curve,5,10) + Yields.par(c,10) * factor ≈ Yields.par(rf_curve,10) + @test discount(Yields.Constant(0.1) / Yields.Constant(0.5),10) ≈ discount(Yields.Constant(0.2),10) + @test discount(0.1 / Yields.Constant(0.5),10) ≈ discount(Yields.Constant(0.2),10) + end + end end \ No newline at end of file