Skip to content

Commit

Permalink
some progress
Browse files Browse the repository at this point in the history
  • Loading branch information
alecloudenback committed Jun 23, 2024
1 parent 946bca6 commit d21bdeb
Show file tree
Hide file tree
Showing 3 changed files with 111 additions and 19 deletions.
12 changes: 7 additions & 5 deletions src/fit.jl
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ __default_optic(m::MyModel) = OptArgs([
"""
__default_optic(m::Yield.Constant) = OptArgs(@optic(_.rate.value) => -1.0 .. 1.0)
__default_optic(m::Yield.MonotoneConvex) = OptArgs(@optic(_.rates[*]) => -1.0 .. 1.0)
__default_optic(m::Yield.MonotoneConvexUnInit) = OptArgs(@optic(_.rates) .=> -1.0 .. 1.0)
__default_optic(m::Yield.MonotoneConvex) = OptArgs(@optic(_.rates) .=> -1.0 .. 1.0)

Check warning on line 134 in src/fit.jl

View check run for this annotation

Codecov / codecov/patch

src/fit.jl#L133-L134

Added lines #L133 - L134 were not covered by tests
__default_optic(m::Yield.IntermediateYieldCurve) = OptArgs(@optic(_.ys[end]) => 0.0 .. 1.0)
__default_optic(m::Yield.NelsonSiegel) = OptArgs([
@optic(_.τ₁) => 0.0 .. 100.0
Expand Down Expand Up @@ -282,18 +283,19 @@ function fit(mod0, quotes, method::F=Fit.Loss(x -> x^2);

end

function fit(mod0::Yield.MonotoneConvexUnInit, quotes, method;
function fit(mod0::Yield.MonotoneConvexUnInit, quotes, method::F=Fit.Loss(x -> x^2);
variables=__default_optic(mod0),
optimizer=__default_optim(mod0)
)
) where
{F<:Fit.Loss}
# use maturities as the times
# fit a vector of rates to the times
times = [maturity(q.contract) for q in quotes]
times = [maturity(q.instrument) for q in quotes]
if !iszero(first(times))
pushfirst!(times, zero(eltype(times)))
end
mc = mod0(times)
rates = fit(mc, quotes, method)
rates = fit(mc, quotes, method; variables, optimizer)
end


Expand Down
65 changes: 51 additions & 14 deletions src/model/Yield/MonotoneConvex.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,19 @@ end
struct MonotoneConvexUnInit
end

struct MonotoneConvexUnoptimized{T,U}
rates::Vector{T}
times::Vector{U}
end

MonotoneConvex() = MonotoneConvexUnInit()
function (m::MonotoneConvexUnInit)(times)
rates = zeros(length(times))
MonotoneConvex(rates, times)

Check warning on line 30 in src/model/Yield/MonotoneConvex.jl

View check run for this annotation

Codecov / codecov/patch

src/model/Yield/MonotoneConvex.jl#L27-L30

Added lines #L27 - L30 were not covered by tests
end



function __issector1(g0, g1)
a = (g0 > 0) && (g1 >= -2 * g0) && (-0.5 * g0 >= g1)
b = (g0 < 0) && (g1 <= -2 * g0) && (-0.5 * g0 <= g1)
Expand Down Expand Up @@ -148,25 +154,35 @@ function __monotone_convex_init(t, rates, times)

return t, i_time, rates, times

Check warning on line 155 in src/model/Yield/MonotoneConvex.jl

View check run for this annotation

Codecov / codecov/patch

src/model/Yield/MonotoneConvex.jl#L155

Added line #L155 was not covered by tests
end

"""
returns a pair of vectors (f and fᵈ) used in Monotone Convex Yield Curve fitting
"""
function __monotone_convex_fs(rates, times)

Check warning on line 161 in src/model/Yield/MonotoneConvex.jl

View check run for this annotation

Codecov / codecov/patch

src/model/Yield/MonotoneConvex.jl#L161

Added line #L161 was not covered by tests
# step 1
fᵈ = map(2:length(times)) do i
(times[i] * rates[i] - times[i-1] * rates[i-1]) / (times[i] - times[i-1])
fᵈ = deepcopy(rates)
for i in 2:length(times)
fᵈ[i] = (times[i] * rates[i] - times[i-1] * rates[i-1]) / (times[i] - times[i-1])
end

Check warning on line 166 in src/model/Yield/MonotoneConvex.jl

View check run for this annotation

Codecov / codecov/patch

src/model/Yield/MonotoneConvex.jl#L163-L166

Added lines #L163 - L166 were not covered by tests
pushfirst!(fᵈ, 0)
# step 2
f = map(2:length(times)-1) do i
(times[i] - times[i-1]) / (times[i+1] - times[i-1]) * fᵈ[i+1] +
(times[i+1] - times[i]) / (times[i+1] - times[i-1]) * fᵈ[i]
f = similar(rates, length(rates) + 1)

Check warning on line 168 in src/model/Yield/MonotoneConvex.jl

View check run for this annotation

Codecov / codecov/patch

src/model/Yield/MonotoneConvex.jl#L168

Added line #L168 was not covered by tests
# fill in middle elements first, then do 1st and last
for i in 1:length(rates)-1
t_prior = if i == 1
0

Check warning on line 172 in src/model/Yield/MonotoneConvex.jl

View check run for this annotation

Codecov / codecov/patch

src/model/Yield/MonotoneConvex.jl#L170-L172

Added lines #L170 - L172 were not covered by tests
else
times[i-1]

Check warning on line 174 in src/model/Yield/MonotoneConvex.jl

View check run for this annotation

Codecov / codecov/patch

src/model/Yield/MonotoneConvex.jl#L174

Added line #L174 was not covered by tests
end

weight1 = (times[i] - t_prior) / (times[i+1] - t_prior)
weight2 = (times[i+1] - times[i]) / (times[i+1] - t_prior)
f[i+1] = weight1 * fᵈ[i+1] + weight2 * fᵈ[i]
end

Check warning on line 180 in src/model/Yield/MonotoneConvex.jl

View check run for this annotation

Codecov / codecov/patch

src/model/Yield/MonotoneConvex.jl#L177-L180

Added lines #L177 - L180 were not covered by tests
# step 3
# collar(a,b,c) = clamp(b, a, c)
pushfirst!(f, fᵈ[2] - 0.5 * (f[1] - fᵈ[2]))
fᵈ[end], f[end-1], fᵈ[end]
push!(f, fᵈ[end] - 0.5 * (f[end] - fᵈ[end]))
f[1] = fᵈ[1] - 0.5 * (f[2] - fᵈ[1])

Check warning on line 183 in src/model/Yield/MonotoneConvex.jl

View check run for this annotation

Codecov / codecov/patch

src/model/Yield/MonotoneConvex.jl#L183

Added line #L183 was not covered by tests

f[end] = fᵈ[end] - 0.5 * (f[end-1] - fᵈ[end])
f[1] = clamp(f[1], 0, 2 * fᵈ[2])
f[end] = clamp(f[end], 0, 2 * fᵈ[end])

Check warning on line 187 in src/model/Yield/MonotoneConvex.jl

View check run for this annotation

Codecov / codecov/patch

src/model/Yield/MonotoneConvex.jl#L185-L187

Added lines #L185 - L187 were not covered by tests

Expand All @@ -176,7 +192,8 @@ function __monotone_convex_fs(rates, times)

return f, fᵈ

Check warning on line 193 in src/model/Yield/MonotoneConvex.jl

View check run for this annotation

Codecov / codecov/patch

src/model/Yield/MonotoneConvex.jl#L193

Added line #L193 was not covered by tests
end
function myzero(t, rates, times)

function myzero(t, rates, times, f, fᵈ)
lt = last(times)

Check warning on line 197 in src/model/Yield/MonotoneConvex.jl

View check run for this annotation

Codecov / codecov/patch

src/model/Yield/MonotoneConvex.jl#L196-L197

Added lines #L196 - L197 were not covered by tests
# if the time is greater than the last input time then extrapolate using the forwards
if t > lt
Expand All @@ -185,7 +202,6 @@ function myzero(t, rates, times)
end

t, i_time, rates, times = __monotone_convex_init(t, rates, times)
f, fᵈ = __monotone_convex_fs(rates, times)
x = (t - times[i_time]) / (times[i_time+1] - times[i_time])
G = g_rate(x, f[i_time], f[i_time+1], fᵈ[i_time+1])
return 1 / t * (times[i_time] * rates[i_time] + (t - times[i_time]) * fᵈ[i_time+1] + (times[i_time+1] - times[i_time]) * G)

Check warning on line 207 in src/model/Yield/MonotoneConvex.jl

View check run for this annotation

Codecov / codecov/patch

src/model/Yield/MonotoneConvex.jl#L204-L207

Added lines #L204 - L207 were not covered by tests
Expand All @@ -196,22 +212,43 @@ function myzero(t, rates, times)
end

function Base.zero(mc::MonotoneConvex, t)
lt = last(mc.times)
f, fᵈ = mc.f, mc.fᵈ
t, i_time, rates, times = __monotone_convex_init(t, mc.rates, mc.times)

Check warning on line 217 in src/model/Yield/MonotoneConvex.jl

View check run for this annotation

Codecov / codecov/patch

src/model/Yield/MonotoneConvex.jl#L214-L217

Added lines #L214 - L217 were not covered by tests
# if the time is greater than the last input time then extrapolate using the forwards
if t > lt
r = myzero(lt, rates, times, f, fᵈ)
return r * lt / t + forward(lt, rates, times) * (1 - lt / t)

Check warning on line 221 in src/model/Yield/MonotoneConvex.jl

View check run for this annotation

Codecov / codecov/patch

src/model/Yield/MonotoneConvex.jl#L219-L221

Added lines #L219 - L221 were not covered by tests
end

x = (t - times[i_time]) / (times[i_time+1] - times[i_time])
G = g_rate(x, f[i_time], f[i_time+1], fᵈ[i_time+1])
return Continuous(1 / t * (times[i_time] * rates[i_time] + (t - times[i_time]) * fᵈ[i_time+1] + (times[i_time+1] - times[i_time]) * G))

Check warning on line 226 in src/model/Yield/MonotoneConvex.jl

View check run for this annotation

Codecov / codecov/patch

src/model/Yield/MonotoneConvex.jl#L224-L226

Added lines #L224 - L226 were not covered by tests

end

function FinanceCore.discount(mc::MonotoneConvex, t)
r = zero(mc, t)
return discount(r, t)

Check warning on line 232 in src/model/Yield/MonotoneConvex.jl

View check run for this annotation

Codecov / codecov/patch

src/model/Yield/MonotoneConvex.jl#L230-L232

Added lines #L230 - L232 were not covered by tests
end

function Base.zero(mc::MonotoneConvexUnoptimized, t)
lt = last(times)

Check warning on line 236 in src/model/Yield/MonotoneConvex.jl

View check run for this annotation

Codecov / codecov/patch

src/model/Yield/MonotoneConvex.jl#L235-L236

Added lines #L235 - L236 were not covered by tests
# if the time is greater than the last input time then extrapolate using the forwards
if t > lt
r = myzero(lt, rates, times)
r = myzero(lt, rates, times, f, fᵈ)
return r * lt / t + forward(lt, rates, times) * (1 - lt / t)

Check warning on line 240 in src/model/Yield/MonotoneConvex.jl

View check run for this annotation

Codecov / codecov/patch

src/model/Yield/MonotoneConvex.jl#L238-L240

Added lines #L238 - L240 were not covered by tests
end

t, i_time, rates, times = __monotone_convex_init(t, rates, times)
f, fᵈ = mc.f, mc.fᵈ
f, fᵈ = __monotone_convex_fs(mc.rates, mc.times)
x = (t - times[i_time]) / (times[i_time+1] - times[i_time])
G = g_rate(x, f[i_time], f[i_time+1], fᵈ[i_time+1])
return 1 / t * (times[i_time] * rates[i_time] + (t - times[i_time]) * fᵈ[i_time+1] + (times[i_time+1] - times[i_time]) * G)

Check warning on line 247 in src/model/Yield/MonotoneConvex.jl

View check run for this annotation

Codecov / codecov/patch

src/model/Yield/MonotoneConvex.jl#L243-L247

Added lines #L243 - L247 were not covered by tests

end

function FinanceCore.discount(mc::MonotoneConvex, t)
function FinanceCore.discount(mc::MonotoneConvexUnoptimized, t)
r = zero(mc, t)
return exp(-r * t)

Check warning on line 253 in src/model/Yield/MonotoneConvex.jl

View check run for this annotation

Codecov / codecov/patch

src/model/Yield/MonotoneConvex.jl#L251-L253

Added lines #L251 - L253 were not covered by tests
end
Expand Down
53 changes: 53 additions & 0 deletions test/MonotoneConvex.jl
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
@testset "Monotone Convex" begin

# Interpolation of the yield curve, Gustaf Dehlbom
# http://uu.diva-portal.org/smash/get/diva2:1477828/FULLTEXT01.pdf

prices = [0.98, 0.955, 0.92, 0.88, 0.830]
times = [1, 2, 3, 4, 5]
quotes = ZCBPrice.(prices, times)
rates = @. -log(prices) / times
c = Yield.MonotoneConvex(rates, times)
# fit(Yield.MonotoneConvex(), quotes)

f, fᵈ = Yield.__monotone_convex_fs(rates, times)

@test fᵈ[1] 0.0202 atol = 0.0001
@test fᵈ[2] 0.0258 atol = 0.0001
@test fᵈ[3] 0.0373 atol = 0.0001
@test fᵈ[4] 0.0445 atol = 0.0001
@test fᵈ[5] 0.0585 atol = 0.0001

@test f[1] 0.0188 atol = 0.0001
@test f[2] 0.023 atol = 0.0001
@test f[3] 0.0316 atol = 0.0001
@test f[4] 0.0409 atol = 0.0001
@test f[5] 0.0515 atol = 0.0001
@test f[6] 0.0620 atol = 0.0001

function r(t)
if 0 <= t <= 1
return 0.0014t^2 + 0.0188
elseif 1 <= t <= 1.0233
return -0.0028 / t + 0.0230
elseif 1.0233 <= t <= 2
return 0.0029t^2 - 0.0088t - 0.0058 / t + 0.0319
elseif 2 <= t <= 3
return -0.0022t^2 + 0.0212t + 0.0324 / t - 0.0268
elseif 3 <= t <= 4
return 0.0031t^2 - 0.0274t - 0.1188 / t + 0.1217
elseif 4 <= t <= 5
return -0.0035t^2 + 0.0525t + 0.314 / t - 0.2005
else
error("t is out of the defined range")
end
end


@testset for t in range(0, 5, 30)
@test zero(c, t) r(t)
end


# https://repository.up.ac.za/bitstream/handle/2263/25882/dissertation.pdf?sequence=1&isAllowed=y
end

0 comments on commit d21bdeb

Please sign in to comment.