diff --git a/Project.toml b/Project.toml index 5288022..4679c99 100644 --- a/Project.toml +++ b/Project.toml @@ -1,12 +1,12 @@ name = "FinanceModels" uuid = "77f2ae65-bdde-421f-ae9d-22f1af19dd76" authors = ["Alec Loudenback and contributors"] -version = "4.7.0" +version = "4.8.0" [deps] AccessibleOptimization = "d88a00a0-4a21-4fe4-a515-e2123c37b885" Accessors = "7d9f7c33-5ae7-4f3b-8dc6-eff91059b697" -BSplineKit = "093aae92-e908-43d7-9660-e50ee39d5a0a" +DataInterpolations = "82cc6244-b520-54b8-b5a6-8a565e85f1d0" Dates = "ade2ca70-3891-5945-98fb-dc099432e06a" FinanceCore = "b9b1ffdd-6612-4b69-8227-7663be06e089" IntervalSets = "8197267c-284f-5f27-9208-e0e47529a953" @@ -29,7 +29,7 @@ FinanceModelsMakieCoreExt = "MakieCore" [compat] AccessibleOptimization = "^0.1.1" Accessors = "^0.1" -BSplineKit = "^0.16, 0.17" +DataInterpolations = "^4.7.1" Dates = "^1.6" FinanceCore = "^2.1" IntervalSets = "^0.7" diff --git a/src/Contract.jl b/src/Contract.jl index 332037a..70c71f6 100644 --- a/src/Contract.jl +++ b/src/Contract.jl @@ -319,7 +319,7 @@ coupon_times(b::AbstractBond) = coupon_times(b.maturity, b.frequency.frequency) for op = (:ZCBPrice, :ZCBYield, :ParYield, :ParSwapYield, :CMTYield, :ForwardYield) eval(quote - $op(x::Vector; kwargs...) = $op.(x, eachindex(x); kwargs...) + $op(x::Vector; kwargs...) = $op.(x, float.(eachindex(x)); kwargs...) end) end diff --git a/src/FinanceModels.jl b/src/FinanceModels.jl index 812f5d9..117c0af 100644 --- a/src/FinanceModels.jl +++ b/src/FinanceModels.jl @@ -11,7 +11,7 @@ using AccessibleOptimization using Accessors using LinearAlgebra using Transducers -import BSplineKit +import DataInterpolations import UnicodePlots using Transducers: @next, complete, __foldl__, asfoldable import SpecialFunctions diff --git a/src/fit.jl b/src/fit.jl index b80e07f..77fa95c 100644 --- a/src/fit.jl +++ b/src/fit.jl @@ -281,7 +281,7 @@ function fit(mod0, quotes, method::F=Fit.Loss(x -> x^2); end -function fit(mod0::Spline.BSpline, quotes, method::Fit.Bootstrap) +function fit(mod0::T, quotes, method::Fit.Bootstrap) where {T<:Spline.SplineCurve} discount_vector = [0.0] times = [maturity(quotes[1])] diff --git a/src/model/Spline.jl b/src/model/Spline.jl index 7e1b963..0adcb46 100644 --- a/src/model/Spline.jl +++ b/src/model/Spline.jl @@ -3,23 +3,28 @@ Spline is a module which offers various degree splines used for fitting or boots Available methods: -- `Spline.BSpline(n)` where n is the nth order. A spline function of order n is a piecewise polynomial function of degree n − 1. This means that, e.g., cubic polynomial is a fourth degree B-Spline. +- `Spline.BSpline(n)` where n is the nth order. A nth-order B-Spline is analagous to an (n-1)th order polynomial spline. That is, a 3rd/4th order BSpline is very similar to a quadratic/cubic spline respectively. BSplines are global in that a change in one point affects the entire spline (though the spline still passes through the other given points still). +- `Spline.PolynomialSpline(n)` where n is the nth order. This object is not a fitted spline itself, rather it is a placeholder object which will be a spline representing the data only after using within [`fit`](@ref FinanceModels.fit-Union{Tuple{F}, Tuple{Any, Any}, Tuple{Any, Any, F}} where F<:FinanceModels.Fit.Loss). and convienience methods which create a `Spline.BSpline` object of the appropriate order. -- `Spline.Linear()` -- `Spline.Quadratic()` -- `Spline.Cubic()` +- `Spline.Linear()` equals `BSpline(2)` +- `Spline.Quadratic()` equals `BSpline(3)` +- `Spline.Cubic()` equals `BSpline(4)` """ module Spline import ..FinanceCore -import ..BSplineKit import ..AbstractModel +abstract type SplineCurve end -struct BSpline +struct PolynomialSpline <: SplineCurve + order::Int +end + +struct BSpline <: SplineCurve order::Int end diff --git a/src/model/Yield.jl b/src/model/Yield.jl index 8947789..021e137 100644 --- a/src/model/Yield.jl +++ b/src/model/Yield.jl @@ -2,7 +2,7 @@ module Yield import ..AbstractModel import ..FinanceCore import ..Spline as Sp -import ..BSplineKit +import ..DataInterpolations import UnicodePlots import ..Bond: coupon_times @@ -33,8 +33,8 @@ Constant() = Constant(0.0) FinanceCore.discount(c::Constant, t) = FinanceCore.discount(c.rate, t) # used as the object which gets optmized before finally returning a completed spline -struct IntermediateYieldCurve{U,V} <: AbstractYieldModel - b::Sp.BSpline +struct IntermediateYieldCurve{T<:Sp.SplineCurve,U,V} <: AbstractYieldModel + b::T xs::Vector{U} ys::Vector{V} # here, ys are the discount factors end @@ -60,12 +60,28 @@ function FinanceCore.discount(c::Spline, time) end function Spline(b::Sp.BSpline, xs, ys) - order = min(length(xs), b.order) # in case the length of xs is less than the spline order - int = BSplineKit.interpolate(xs, ys, BSplineKit.BSplineOrder(order)) - return Spline(BSplineKit.extrapolate(int, BSplineKit.Smooth())) + order = min(length(xs) - 1, b.order) # in case the length of xs is less than the spline order + xs = float.(xs) + knot_type = if length(xs) < 3 + :Uniform + else + :Average + end + + return Spline(DataInterpolations.BSplineInterpolation(ys, xs, order, :Uniform, knot_type; extrapolate=true)) +end + +function Spline(b::Sp.PolynomialSpline, xs, ys) + order = min(length(xs) - 1, b.order) # in case the length of xs is less than the spline order + if order == 1 + return Spline(DataInterpolations.LinearInterpolation(ys, xs; extrapolate=true)) + elseif order == 2 + return Spline(DataInterpolations.QuadraticSpline(ys, xs; extrapolate=true)) + else + return Spline(DataInterpolations.CubicSpline(ys, xs; extrapolate=true)) + end end - include("Yield/SmithWilson.jl") include("Yield/NelsonSiegelSvensson.jl") diff --git a/test/Yield.jl b/test/Yield.jl index 009e219..ab32b87 100644 --- a/test/Yield.jl +++ b/test/Yield.jl @@ -111,8 +111,19 @@ end maturity = [0.5, 1.0, 1.5, 2.0] zero = [5.0, 5.8, 6.4, 6.8] ./ 100 zs = ZCBYield.(zero, maturity) - @testset "$i" for (i, curve) in enumerate([fit(Spline.Cubic(), zs, Fit.Bootstrap()), fit(Spline.Linear(), zs, Fit.Bootstrap()), fit(Spline.Quadratic(), zs, Fit.Bootstrap()), fit(Spline.BSpline(5), zs, Fit.Bootstrap())]) - + variants = [ + Spline.Cubic(), + Spline.Linear(), + Spline.Quadratic(), + Spline.BSpline(5), + Spline.BSpline(3), + Spline.BSpline(1), + Spline.PolynomialSpline(1), + Spline.PolynomialSpline(2), + Spline.PolynomialSpline(3), + ] + @testset "Constructor $i" for (i, m) in enumerate(variants) + curve = fit(m, zs, Fit.Bootstrap()) @test discount(curve, 1) ≈ 1 / 1.058 @test discount(curve, 1.5) ≈ 1 / 1.064^1.5 @test discount(curve, 2) ≈ 1 / 1.068^2