Skip to content

Commit 2d9c828

Browse files
use Optim instead of Roots because it solves more robustly
1 parent 4f5728d commit 2d9c828

File tree

4 files changed

+48
-31
lines changed

4 files changed

+48
-31
lines changed

Project.toml

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,11 @@ version = "0.2.0"
55

66
[deps]
77
Dates = "ade2ca70-3891-5945-98fb-dc099432e06a"
8-
Roots = "f2b01f46-fcfa-551c-844a-d8ac1e96c665"
8+
Optim = "429524aa-4258-5aef-a3af-852621145aeb"
99

1010
[compat]
1111
julia = "^1.1"
12-
Roots = "1"
13-
12+
Optim = "≥ 0.20.0"
1413
[extras]
1514
Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40"
1615

src/ActuaryUtilities.jl

Lines changed: 28 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ module ActuaryUtilities
22

33
using Dates
44
using Roots
5+
using Optim
56

67
"""
78
Years_Between(d1::Date, d2::Date)
@@ -82,36 +83,48 @@ function duration(issue_date::Date, proj_date::Date)
8283
end
8384

8485
"""
85-
internal_rate_of_return(cashflows::vector; search_interval::Tuple{Real,Real})
86+
internal_rate_of_return(cashflows::vector)
8687
8788
Calculate the internal_rate_of_return of a series of equally spaced cashflows, assuming the first
88-
element occurs at time zero. By default searches the `search_interval`in the numeric range `[-1,1]`.
89+
element occurs at time zero. First tries to find a positive rate in the interval `[0.0,1.0]`. If none is found,
90+
will extend search to [-1.0,1.0]. If still not found, will return `nothing`.
8991
9092
"""
91-
function internal_rate_of_return(cashflows;search_interval::Tuple{Real,Real}=(-1.0,1.0))
92-
93-
f(i) = pv(i,cashflows[2:end]) + cashflows[1]
93+
function internal_rate_of_return(cashflows)
94+
9495

95-
return find_zero(f,search_interval)
96+
return internal_rate_of_return(cashflows,[t for t in 0:(length(cashflows)-1)])
97+
9698
end
9799

98100
"""
99-
internal_rate_of_return(cashflows::Vector, timepoints::Vector; search_interval::Tuple{Real,Real})
101+
internal_rate_of_return(cashflows::Vector, timepoints::Vector)
100102
101103
Calculate the internal_rate_of_return with given timepoints.
102-
By default searches the `search_interval`in the numeric range `[-1,1]`.
104+
First tries to find a positive rate in the interval `[0.0,1.0]`. If none is found,
105+
will extend search to [-1.0,1.0]. If still not found, will return `nothing`.
103106
104107
```jldoctest
105108
julia> internal_rate_of_return([-100,110],[0,1]) # e.g. cashflows at time 0 and 1
106-
0.10000000000000005
109+
0.10000000001652906
107110
```
108111
"""
109-
function internal_rate_of_return(cashflows,times;search_interval::Tuple{Real,Real}=(-1.0,1.0))
110-
111-
f(i) = sum(cashflows[1:end] .* [1/(1+i)^t for t in times])
112-
113-
return find_zero(f,search_interval)
114-
112+
function internal_rate_of_return(cashflows,times)
113+
# Optim requires the optimizing variable to be an array, thus the i[1]
114+
f(i) = sum(cashflows .* [1/(1+i[1])^t for t in times])
115+
result = optimize(x -> f(x)^2, 0.0,1.0)
116+
if abs(f(result.minimizer)) < 1.0e-3 # arbitrary that seems to work
117+
return result.minimizer
118+
else
119+
# try finding a negative irr
120+
result = optimize(x -> f(x)^2, -1.0,1.0)
121+
if abs(f(result.minimizer)) < 1.0e-3 # arbitrary that seems to work
122+
return result.minimizer
123+
else
124+
return nothing
125+
end
126+
end
127+
115128
end
116129

117130
"""

test/run_doctests.jl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# using Documenter
2+
# doctest(ActuaryUtilities)

test/runtests.jl

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -47,16 +47,16 @@ end
4747
v = [-70000,12000,15000,18000,21000,26000]
4848

4949
# per Excel (example comes from Excel help text)
50-
@test irr(v[1:2]) -0.8285714285714
51-
@test irr(v[1:3]) -0.4435069413346
52-
@test irr(v[1:4]) -0.1821374641455
53-
@test irr(v[1:5]) -0.0212448482734
54-
@test irr(v[1:6]) 0.0866309480365
50+
@test isapprox(irr(v[1:2]), -0.8285714285714,atol = 0.001)
51+
@test isapprox(irr(v[1:3]), -0.4435069413346,atol = 0.001)
52+
@test isapprox(irr(v[1:4]), -0.1821374641455,atol = 0.001)
53+
@test isapprox(irr(v[1:5]), -0.0212448482734,atol = 0.001)
54+
@test isapprox(irr(v[1:6]), 0.0866309480365,atol = 0.001)
5555

5656
# much more challenging to solve b/c of the overflow below zero
5757
cfs = [t % 10 == 0 ? -10 : 1.5 for t in 0:99]
5858

59-
@test irr(cfs) 0.06463163963925866
59+
@test isapprox(irr(cfs), 0.06463163963925866,atol=0.001)
6060

6161
# test the unsolvable
6262

@@ -67,8 +67,8 @@ end
6767
@testset "xirr with float times" begin
6868

6969

70-
@test irr([-100,100],[0,1]) 0.0
71-
@test irr([-100,110],[0,1]) 0.1
70+
@test isapprox(irr([-100,100],[0,1]), 0.0, atol =0.001)
71+
@test isapprox(irr([-100,110],[0,1]), 0.1, atol =0.001)
7272

7373
end
7474

@@ -78,11 +78,11 @@ end
7878
dates = Date(2019,12,31):Year(1):Date(2024,12,31)
7979
times = yearfrac.(dates[1],dates,Thirty360)
8080
# per Excel (example comes from Excel help text)
81-
@test irr(v[1:2], times[1:2]) -0.8285714285714
82-
@test irr(v[1:3], times[1:3]) -0.4435069413346
83-
@test irr(v[1:4], times[1:4]) -0.1821374641455
84-
@test irr(v[1:5], times[1:5]) -0.0212448482734
85-
@test irr(v[1:6], times[1:6]) 0.0866309480365
81+
@test isapprox(irr(v[1:2], times[1:2]), -0.8285714285714, atol = 0.001)
82+
@test isapprox(irr(v[1:3], times[1:3]), -0.4435069413346, atol = 0.001)
83+
@test isapprox(irr(v[1:4], times[1:4]), -0.1821374641455, atol = 0.001)
84+
@test isapprox(irr(v[1:5], times[1:5]), -0.0212448482734, atol = 0.001)
85+
@test isapprox(irr(v[1:6], times[1:6]), 0.0866309480365, atol = 0.001)
8686

8787
end
8888
end
@@ -104,3 +104,6 @@ end
104104
@test isnothing(breakeven([-10,-15,2,3,4,8],times,0.10))
105105
end
106106
end
107+
108+
109+
include("run_doctests.jl")

0 commit comments

Comments
 (0)