Skip to content

Commit

Permalink
add tests, docs, and more generic __rewrap
Browse files Browse the repository at this point in the history
  • Loading branch information
alecloudenback committed Nov 3, 2023
1 parent b33d940 commit d2e0fa5
Show file tree
Hide file tree
Showing 6 changed files with 124 additions and 4 deletions.
32 changes: 31 additions & 1 deletion docs/src/contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,37 @@ Note that all contracts in FinanceModels.jl are currently *unit* contracts in th

#### More complex Contracts

**When the cashflow depends on a model**. An example of this is a floating bond where the coupon paid depends on a view of forward rates. See [this section in the overview](@ref Contracts-that-depend-on-the-model-(or-multiple-models)) on projections for how this is handled.
##### Transformations

Contracts (`<:AbstractContract`) and [`Projection`](@ref)s can be modified to be scaled or transformed using the transformations in [Transducers.jl](https://juliafolds2.github.io/Transducers.jl/stable/#List-of-transducers) after importing that package.

Most commonly, this is likely simply chaining `Map(...)` calls. Two use-cases of this may be to (1) scale the contract by a factor or (2) change the sign of the contract to indicate an obligation/liability instead of an asset. Examples of this:

```julia-repl
julia> using Transducers, FinanceModels
julia> Bond.Fixed(0.05,Periodic(1),3) |> collect
3-element Vector{Cashflow{Float64, Float64}}:
Cashflow{Float64, Float64}(0.05, 1.0)
Cashflow{Float64, Float64}(0.05, 2.0)
Cashflow{Float64, Float64}(1.05, 3.0)
julia> Bond.Fixed(0.05,Periodic(1),3) |> Map(-) |> collect
3-element Vector{Cashflow{Float64, Float64}}:
Cashflow{Float64, Float64}(-0.05, 1.0)
Cashflow{Float64, Float64}(-0.05, 2.0)
Cashflow{Float64, Float64}(-1.05, 3.0)
julia> Bond.Fixed(0.05,Periodic(1),3) |> Map(-) |> Map(x->x*2) |> collect
3-element Vector{Cashflow{Float64, Float64}}:
Cashflow{Float64, Float64}(-0.1, 1.0)
Cashflow{Float64, Float64}(-0.1, 2.0)
Cashflow{Float64, Float64}(-2.1, 3.0)
```

##### Cashflows are model dependent

An example of this is a floating bond where the coupon paid depends on a view of forward rates. See [this section in the overview](@ref Contracts-that-depend-on-the-model-(or-multiple-models)) on projections for how this is handled.

## Available Contracts & Modules

Expand Down
21 changes: 21 additions & 0 deletions src/Contract.jl
Original file line number Diff line number Diff line change
Expand Up @@ -433,6 +433,27 @@ A convenience method for creating an interest rate swap given a curve and a teno
The notional is a unit (1.0) amount and assumed to settle four times per period.
A [`Projection`](@ref), with an indexable `model_key` is still needed to project a swap. See examples below for what this looks like.
# Examples
```julia-repl
julia> curve = Yield.Constant(0.05);
julia> swap = InterestRateSwap(curve,10);
julia> Projection(swap,Dict("OIS" => curve),CashflowProjection()) |> collect
80-element Vector{Cashflow{Float64, Float64}}:
Cashflow{Float64, Float64}(0.012272234429039353, 0.25)
Cashflow{Float64, Float64}(0.012272234429039353, 0.5)
Cashflow{Float64, Float64}(-0.012272234429039353, 9.75)
Cashflow{Float64, Float64}(-1.0122722344290391, 10.0)
```
"""
function InterestRateSwap(curve, tenor; model_key="OIS")
fixed_rate = par(curve, tenor; frequency=4)
Expand Down
45 changes: 42 additions & 3 deletions src/Projection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,7 @@ end
# If a Transducer has been combined with a contract into an Eduction
# then unwrap the contract and apply the transducer to the projection
@inline function Transducers.__foldl__(rf, val, p::Projection{C,M,K}) where {C<:Transducers.Eduction,M,K}
rfx = p.contract.rf.xform # get the transducer's "xform" from the projection's contract
rf = Transducers.Reduction(rfx, rf) # compose the xform with any othe existing transducers
rf = __rewrap(p.contract.rf, rf) # compose the xform with any othe existing transducers
p_alt = @set p.contract = p.contract.coll # reset the contract to the underlying contract without transducers
Transducers.__foldl__(rf, val, p_alt) # project with a newly combined reduction
end
Expand Down Expand Up @@ -168,4 +167,44 @@ end

@inline function Transducers.asfoldable(p::Projection{C,M,K}) where {C<:Cashflow,M,K<:CashflowProjection}
Ref(p.contract) |> Map(identity)
end
end

"""
__rewrap(from::Transducers.Reduction, to)
__rewrap(from, to)
Used to unwrap a Reduction which is a composition of contracts and a transducer and apply the transducers to the associated projection instead of the transducer.
For example, on its own a contract is not project-able, but wrapped in a (default) [`Projection`](@ref) it can be. But it may also be a lot more convienent
to construct contracts which have scaling or negated modifications and let that flow into a projection.
# Examples
```julia-repl
julia> Bond.Fixed(0.05,Periodic(1),3) |> collect
3-element Vector{Cashflow{Float64, Float64}}:
Cashflow{Float64, Float64}(0.05, 1.0)
Cashflow{Float64, Float64}(0.05, 2.0)
Cashflow{Float64, Float64}(1.05, 3.0)
julia> Bond.Fixed(0.05,Periodic(1),3) |> Map(-) |> collect
3-element Vector{Cashflow{Float64, Float64}}:
Cashflow{Float64, Float64}(-0.05, 1.0)
Cashflow{Float64, Float64}(-0.05, 2.0)
Cashflow{Float64, Float64}(-1.05, 3.0)
julia> Bond.Fixed(0.05,Periodic(1),3) |> Map(-) |> Map(x->x*2) |> collect
3-element Vector{Cashflow{Float64, Float64}}:
Cashflow{Float64, Float64}(-0.1, 1.0)
Cashflow{Float64, Float64}(-0.1, 2.0)
Cashflow{Float64, Float64}(-2.1, 3.0)
```
"""
function __rewrap(from::Transducers.Reduction, to)
rfx = from.xform # get the transducer's "xform" from the projection's contract
__rewrap(from.inner, Transducers.Reduction(rfx, to)) # compose the xform with any othe existing transducers
end
function __rewrap(from, to)
# we've hit bottom, so return `to`
return to
end
1 change: 1 addition & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ CairoMakie = "13f3f980-e62b-5c42-98c6-ff1f3baf88f0"
DecFP = "55939f99-70c6-5e9b-8bb0-5071ed7d61fd"
FinanceCore = "b9b1ffdd-6612-4b69-8227-7663be06e089"
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
Transducers = "28d57a85-8fef-5791-bfe6-a80928e7c999"
1 change: 1 addition & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ using Accessors

using FinanceModels
using Test
using Transducers

include("generic.jl")
include("sp.jl")
Expand Down
28 changes: 28 additions & 0 deletions test/sp.jl
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,34 @@ end

p = Projection(c)
@test collect(p) == [collect(a); collect(b)]


# a swap value with the same curve used to parameterize should have
# zero value at inception
curve = Yield.Constant(0.05)
swap = InterestRateSwap(curve, 10)
proj = Projection(swap, Dict("OIS" => curve), CashflowProjection())
@test pv(0.04, proj |> collect) 0.0 atol = 1e-12
end

@testset "Transduced Contracts" begin

@test (Bond.Fixed(0.05, Periodic(1), 3) |> Map(-) |> collect) == [
Cashflow{Float64,Float64}(-0.05, 1.0),
Cashflow{Float64,Float64}(-0.05, 2.0),
Cashflow{Float64,Float64}(-1.05, 3.0)
]

@test (Bond.Fixed(0.05, Periodic(1), 3) |> Map(-) |> Map(x -> x * 2) |> collect) == [
Cashflow{Float64,Float64}(-0.1, 1.0),
Cashflow{Float64,Float64}(-0.1, 2.0),
Cashflow{Float64,Float64}(-2.1, 3.0)
]





end

@testset "Fit Models" begin
Expand Down

0 comments on commit d2e0fa5

Please sign in to comment.