1
- defmodule Finance do
1
+ defmodule ExXirr do
2
2
@ moduledoc """
3
- Library to calculate XIRR through the Newton Raphson method.
3
+ Library to calculate XIRR and absolute rate of return
4
+ through the Newton Raphson method.
4
5
"""
5
6
6
- @ type date :: Date . t ( )
7
-
8
7
@ max_error 1.0e-3
9
8
@ days_in_a_year 365
10
9
11
- defp pmap ( collection , function ) do
12
- me = self
13
-
14
- collection
15
- |> Enum . map ( fn element -> spawn_link ( fn -> send ( me , { self , function . ( element ) } ) end ) end )
16
- |> Enum . map ( fn pid ->
17
- receive do
18
- { ^ pid , result } -> result
19
- end
20
- end )
21
- end
22
-
23
- @ spec power_of ( float ( ) , Fraction . t ( ) ) :: float ( )
24
- def power_of ( rate , fraction ) when rate < 0 do
25
- :math . pow ( - rate , Fraction . to_float ( fraction ) ) * :math . pow ( - 1 , fraction . num )
26
- end
10
+ # Public API
27
11
28
- def power_of ( rate , fraction ) do
29
- :math . pow ( rate , Fraction . to_float ( fraction ) )
30
- end
31
-
32
- @ spec xirr_reduction ( { Fraction . t ( ) , float ( ) , float ( ) } ) :: float ( )
33
- defp xirr_reduction ( { fraction , value , rate } ) do
34
- value / power_of ( 1.0 + rate , fraction )
35
- end
12
+ @ doc """
13
+ Function to calculate the rate of return for a given array of
14
+ dates and values.
36
15
37
- @ spec dxirr_reduction ( { Fraction . t ( ) , float ( ) , float ( ) } ) :: float ( )
38
- defp dxirr_reduction ( { fraction , value , rate } ) do
39
- - value * Fraction . to_float ( fraction ) * power_of ( 1.0 + rate , Fraction . negative ( fraction ) ) *
40
- :math . pow ( 1.0 + rate , - 1 )
41
- end
16
+ ## Examples
42
17
43
- @ doc """
44
- iex> d = [{1985, 1, 1}, {1990, 1, 1}, {1995, 1, 1}]
45
- iex> v = [1000, -600, -200]
46
- iex> Finance.xirr(d,v)
47
- {:ok, -0.034592}
18
+ iex> d = [{1985, 1, 1}, {1990, 1, 1}, {1995, 1, 1}]
19
+ iex> v = [1000, -600, -200]
20
+ iex> ExXirr.xirr(d,v)
21
+ {:ok, -0.034592}
48
22
"""
49
- @ spec xirr ( [ date ] , [ number ] ) :: float
23
+ @ spec xirr ( [ Date . t ( ) ] , [ number ] ) :: float
50
24
def xirr ( dates , values ) when length ( dates ) != length ( values ) do
51
25
{ :error , "Date and Value collections must have the same size" }
52
26
end
@@ -75,8 +49,20 @@ defmodule Finance do
75
49
{ :error , 0.0 }
76
50
end
77
51
52
+ @ doc """
53
+ Function to calculate the absolute rate of return for a given array
54
+ of dates and values.
55
+
56
+ ## Examples
57
+
58
+ iex> d = [{1985, 1, 1}, {1990, 1, 1}, {1995, 1, 1}]
59
+ iex> v = [1000, -600, -200]
60
+ iex> {:ok, rate} = ExXirr.xirr(d,v)
61
+ iex> ExXirr.absolute_rate(rate, 50)
62
+ {:ok, -0.48}
63
+ """
78
64
@ spec absolute_rate ( float ( ) , integer ( ) ) :: { :ok , float ( ) } | { :error , String . t ( ) }
79
- def absolute_rate ( 0 , days ) , do: { :error , "Rate is 0" }
65
+ def absolute_rate ( 0 , _ ) , do: { :error , "Rate is 0" }
80
66
81
67
def absolute_rate ( rate , days ) do
82
68
try do
@@ -91,11 +77,48 @@ defmodule Finance do
91
77
end
92
78
end
93
79
80
+ # Private API
81
+
82
+ @ spec pmap ( list ( tuple ( ) ) , fun ( ) ) :: Enum . t ( )
83
+ defp pmap ( collection , function ) do
84
+ me = self ( )
85
+
86
+ collection
87
+ |> Enum . map ( fn element -> spawn_link ( fn -> send ( me , { self ( ) , function . ( element ) } ) end ) end )
88
+ |> Enum . map ( fn pid ->
89
+ receive do
90
+ { ^ pid , result } -> result
91
+ end
92
+ end )
93
+ end
94
+
95
+ @ spec power_of ( float ( ) , Fraction . t ( ) ) :: float ( )
96
+ defp power_of ( rate , fraction ) when rate < 0 do
97
+ :math . pow ( - rate , Fraction . to_float ( fraction ) ) * :math . pow ( - 1 , fraction . num )
98
+ end
99
+
100
+ defp power_of ( rate , fraction ) do
101
+ :math . pow ( rate , Fraction . to_float ( fraction ) )
102
+ end
103
+
104
+ @ spec xirr_reduction ( { Fraction . t ( ) , float ( ) , float ( ) } ) :: float ( )
105
+ defp xirr_reduction ( { fraction , value , rate } ) do
106
+ value / power_of ( 1.0 + rate , fraction )
107
+ end
108
+
109
+ @ spec dxirr_reduction ( { Fraction . t ( ) , float ( ) , float ( ) } ) :: float ( )
110
+ defp dxirr_reduction ( { fraction , value , rate } ) do
111
+ - value * Fraction . to_float ( fraction ) * power_of ( 1.0 + rate , Fraction . negative ( fraction ) ) *
112
+ :math . pow ( 1.0 + rate , - 1 )
113
+ end
114
+
115
+ @ spec compact_flow ( list ( ) , Date . t ( ) ) :: tuple ( )
94
116
defp compact_flow ( dates_values , min_date ) do
95
117
flow = Enum . reduce ( dates_values , % { } , & organize_value ( & 1 , & 2 , min_date ) )
96
118
{ Map . keys ( flow ) , Map . values ( flow ) , Enum . filter ( flow , & ( elem ( & 1 , 1 ) != 0 ) ) }
97
119
end
98
120
121
+ @ spec organize_value ( tuple ( ) , map ( ) , Date . t ( ) ) :: map ( )
99
122
defp organize_value ( date_value , dict , min_date ) do
100
123
{ date , value } = date_value
101
124
@@ -104,15 +127,16 @@ defmodule Finance do
104
127
den: 365.0
105
128
}
106
129
107
- Dict . update ( dict , fraction , value , & ( value + & 1 ) )
130
+ Map . update ( dict , fraction , value , & ( value + & 1 ) )
108
131
end
109
132
133
+ @ spec verify_flow ( list ( float ( ) ) ) :: boolean ( )
110
134
defp verify_flow ( values ) do
111
135
{ min , max } = Enum . min_max ( values )
112
136
min < 0 && max > 0
113
137
end
114
138
115
- @ spec guess_rate ( [ date ] , [ number ] ) :: float
139
+ @ spec guess_rate ( [ Date . t ( ) ] , [ number ] ) :: float
116
140
defp guess_rate ( dates , values ) do
117
141
{ min_value , max_value } = Enum . min_max ( values )
118
142
period = 1 / ( length ( dates ) - 1 )
@@ -121,11 +145,10 @@ defmodule Finance do
121
145
Float . round ( rate , 6 )
122
146
end
123
147
148
+ @ spec reduce_date_values ( list ( ) , float ( ) ) :: tuple ( )
124
149
defp reduce_date_values ( dates_values , rate ) do
125
- list = Dict . to_list ( dates_values )
126
-
127
150
calculated_xirr =
128
- list
151
+ dates_values
129
152
|> pmap ( fn x ->
130
153
{
131
154
elem ( x , 0 ) ,
@@ -138,7 +161,7 @@ defmodule Finance do
138
161
|> Float . round ( 6 )
139
162
140
163
calculated_dxirr =
141
- list
164
+ dates_values
142
165
|> pmap ( fn x ->
143
166
{
144
167
elem ( x , 0 ) ,
@@ -153,28 +176,25 @@ defmodule Finance do
153
176
{ calculated_xirr , calculated_dxirr }
154
177
end
155
178
179
+ @ spec calculate ( atom ( ) , list ( ) , float ( ) , float ( ) , integer ( ) ) ::
180
+ { :ok , float ( ) } | { :error , String . t ( ) }
156
181
defp calculate ( :xirr , _ , 0.0 , rate , _ ) , do: { :ok , Float . round ( rate , 6 ) }
157
182
defp calculate ( :xirr , _ , _ , - 1.0 , _ ) , do: { :error , "Could not converge" }
158
183
defp calculate ( :xirr , _ , _ , _ , 300 ) , do: { :error , "I give up" }
159
184
160
185
defp calculate ( :xirr , dates_values , _ , rate , tries ) do
161
186
{ xirr , dxirr } = reduce_date_values ( dates_values , rate )
162
187
163
- if dxirr < 0.0 do
164
- new_rate = rate
165
- else
166
- new_rate = rate - xirr / dxirr
167
- end
188
+ new_rate =
189
+ if dxirr < 0.0 do
190
+ rate
191
+ else
192
+ rate - xirr / dxirr
193
+ end
168
194
169
195
diff = Kernel . abs ( new_rate - rate )
170
-
171
- if diff < @ max_error do
172
- diff = 0.0
173
- end
174
-
196
+ diff = if diff < @ max_error , do: 0.0
175
197
tries = tries + 1
176
198
calculate ( :xirr , dates_values , diff , new_rate , tries )
177
199
end
178
200
end
179
-
180
- # defmodule Finance
0 commit comments