Skip to content

Commit

Permalink
add InterestRate Swap and one layer of unwrapping Transducers from Co…
Browse files Browse the repository at this point in the history
…ntracts (#166)

* add InterestRate Swap and one layer of unwrapping Transducers from Contracts

* add tests, docs, and more generic `__rewrap`

* add IRS example to docs

* FC compat bound

* fix doc
  • Loading branch information
alecloudenback authored Nov 3, 2023
1 parent 649ddf5 commit 90187c1
Show file tree
Hide file tree
Showing 8 changed files with 163 additions and 8 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ FinanceModelsMakieCoreExt = "MakieCore"
AccessibleOptimization = "^0.1.1"
Accessors = "^0.1"
BSplineKit = "^0.16"
FinanceCore = "^2"
FinanceCore = "^2.1"
IntervalSets = "^0.7"
MakieCore = "0.6"
Optimization = "^3.15"
Expand Down
43 changes: 42 additions & 1 deletion docs/src/contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,48 @@ 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)
```

Another example of this is how `InterestRateSwap`[@ref] is implemented. It's simply a `Composite` contract of a positive fixed rate bond and a negative floating rate bond:

```julia
function InterestRateSwap(curve, tenor; model_key="OIS")
fixed_rate = par(curve, tenor; frequency=4)
fixed_leg = Bond.Fixed(rate(fixed_rate), Periodic(4), tenor)
float_leg = Bond.Floating(0.0, Periodic(4), tenor, model_key) |> Map(-)
return Composite(fixed_leg, float_leg)
end
```

##### 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
38 changes: 37 additions & 1 deletion src/Contract.jl
Original file line number Diff line number Diff line change
Expand Up @@ -424,4 +424,40 @@ end

function cashflows_timepoints(qs::Vector{Q}) where {Q<:Quote}
cashflows_timepoints([q.instrument for q in qs])
end
end

"""
InterestRateSwap(curve, tenor; model_key="OIS")
A convenience method for creating an interest rate swap given a curve and a tenor via a `Composite` contract consisting of receiving a [fixed bond](@ref Bond.Fixed) and paying (i.e. the negative of) a [floating bond](@ref Bond.Floating).
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)
fixed_leg = Bond.Fixed(rate(fixed_rate), Periodic(4), tenor)
float_leg = Bond.Floating(0.0, Periodic(4), tenor, model_key) |> Map(-)
return Composite(fixed_leg, float_leg)
end
2 changes: 1 addition & 1 deletion src/FinanceModels.jl
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ include("model/Model.jl")
include("Projection.jl")
include("fit.jl")

export Cashflow, Quote, Forward, CommonEquity, Option
export Cashflow, Quote, Forward, CommonEquity, Option, InterestRateSwap

using .Bond: ZCBYield, ZCBPrice, ParSwapYield, ParYield, CMTYield, ForwardYields, OISYield
export Bond, ZCBYield, ZCBPrice, ParSwapYield, ParYield, CMTYield, ForwardYields, OISYield
Expand Down
56 changes: 52 additions & 4 deletions src/Projection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ struct CashflowProjection <: ProjectionKind end
# collecting a Projection gives your the reducible defined below with __foldl__
Base.collect(p::P) where {P<:AbstractProjection} = p |> Map(identity) |> collect
# collecting a contract wraps the contract in with the default Projection, defined next
Base.collect(c::C) where {C<:FinanceCore.AbstractContract} = Projection(c) |> Map(identity) |> collect
Base.collect(c::C) where {C<:FinanceCore.AbstractContract} = Projection(c) |> collect

# Default Projections ##########################

Expand All @@ -73,7 +73,7 @@ Projection(c, m) = Projection(c, m, CashflowProjection())

# Reducibles ###################################

# a more composible, efficient way to create a collection of things that you can apply subsequent transformations to
# a more composable, efficient way to create a collection of things that you can apply subsequent transformations to
# (and those transformations can be Transducers).
# https://juliafolds2.github.io/Transducers.jl/stable/howto/reducibles/
# https://www.youtube.com/watch?v=6mTbuzafcII
Expand All @@ -84,7 +84,7 @@ Projection(c, m) = Projection(c, m, CashflowProjection())
# `__foldl__` where you can define the collection using a `for` loop
# and `foldl__` you can also define state that is used within the loop

# this wraps a contract in a default proejction and makes a contract a reducible collection of cashflows
# this wraps a contract in a default projection and makes a contract a reducible collection of cashflows
function Transducers.asfoldable(c::C) where {C<:FinanceCore.AbstractContract}
Projection(c) |> Map(identity)
end
Expand All @@ -97,6 +97,14 @@ end
return complete(rf, val)
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}
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

#
@inline function Transducers.__foldl__(rf, val, p::Projection{C,M,K}) where {C<:Bond.Fixed,M,K}
b = p.contract
Expand Down Expand Up @@ -159,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 90187c1

Please sign in to comment.