1
2 REBOL [
3 Title: "Financial Library"
4 Date: 17-Apr-2002
5 Name: 'fin
6 Version: 0.0.2
7 File: %fin.r
8 ;Home: http://www.pointillistic.com/
9 Author: "Gregg Irwin"
10 Email: gregg@pointillistic.com
11 ;Owner: "Pointillistic Software"
12 ;Rights: "Copyright © Pointillistic Software 2002. All Rights Reserved."
13 ;Tabs: 4
14 ;Need: 0.1.4
15 ;Language: 'English
16 ;Charset: 'ANSI
17 ;Category: [script 1]
18 Purpose: {Financial module for standard library.}
19
20 Comment: {Prototype stage.}
21
22 History: [
23 0.0.1 [13-Mar-2002 "First whack" "Gregg"]
24 0.0.2 [17-Apr-2002 {Added NPV and a functional, if ugly, IRR.
25 BRACKET-FN supports IRR.} "Gregg"]
26 ]
27
28 Example: {do %fin.r}
29 ]
30
31 fin: make lib-kernel [
32
33 ; Future Value
34 fv: func [
35 {Computes future value based on present value.}
36 value [number! money!] "Present value"
37 rate [number!] "Interest Rate"
38 periods [number!] "Number of time periods"
39 ][
40 1 + rate ** periods * value
41 ]
42 ;lib/fin/fv 1000 .1 12
43 ;lib/fin/fv 1000 .1 24
44 ;lib/fin/fv 1000 .1 36
45
46
47 ; Compound Interest
48 interest: func [
49 {Computes accumulated compound interest.}
50 value [number! money!] "Present value"
51 rate [number!] "Interest Rate"
52 periods [number!] "Number of time periods"
53 ][
54 1 + rate ** periods - 1 * value
55 ]
56 ;lib/fin/interest 1000 .1 12
57 ;lib/fin/interest 1000 .1 24
58 ;lib/fin/interest 1000 .1 36
59
60
61 ; Interest Rate
62 rate: func [
63 {Computes interest rate for a given future value.}
64 value [number! money!] "Present value"
65 future-value [number! money!] "Future value"
66 periods [number!] "Number of time periods"
67 ][
68 future-value / value ** (1 / periods) - 1
69 ]
70 ;lib/fin/rate 1000 future-value 1000 .1 12 12
71 ;lib/fin/rate 1000 future-value 1000 .1 24 24
72 ;lib/fin/rate 1000 future-value 1000 .1 36 36
73
74
75 ; Present Value
76 pv: func [
77 {Computes present value based on future value.}
78 value [number! money!] "Future value"
79 rate [number!] "Interest Rate"
80 periods [number!] "Number of time periods"
81 ][
82 1 + rate ** (negate periods) * value
83 ; - or -
84 ;value / (1 + rate ** periods)
85 ]
86 ;lib/fin/pv future-value 1000 .1 12 .1 12
87 ;lib/fin/pv future-value 1000 .1 24 .1 24
88 ;lib/fin/pv future-value 1000 .1 36 .1 36
89
90 npv: func [
91 {Computes net present value based on future cash flow values
92 using continuous discounting.}
93 values [any-block!] "Cash flow values"
94 rate [number!] "Interest Rate"
95 /local result
96 ][
97 result: $0.0
98 repeat i length? values [
99 result: result + pv values/:i rate i
100 ]
101 result
102 ]
103 ;lib/fin/npv [1000 2000 3000] .1
104 ;lib/fin/npv [-10000, 3000, 4200, 6800] .1
105
106 ;[-$70'000 $12'000 $15'000 $18'000 $21'000 $26'000]
107
108 ; Time Periods
109 nper: func [
110 {Computes the number of time periods from the present value
111 to a given future value.}
112 value [number! money!] "Present value"
113 future-value [number! money!] "Future value"
114 rate [number!] "Interest Rate"
115 ][
116 ; 2 versions. Both work.
117 (log-e (future-value / value)) / (log-e (rate + 1))
118 ;divide log-e divide future-value value log-e add rate 1
119 ]
120 ;lib/fin/nper 1000 future-value 1000 .1 12 .1
121 ;lib/fin/nper 1000 future-value 1000 .1 24 .1
122 ;lib/fin/nper 1000 future-value 1000 .1 36 .1
123
124 irr: func [
125 {Computes the internal rate of return based on future cash flow values.
126 The internal rate of return of a cash flow is the interest rate that
127 makes the present value of a cash flow equal to zero. THIS IS A QUICKLY
128 HACKED VERSION AND NEEDS TO BE CLEANED UP! HERE FOR TESTING.}
129 values [any-block!] "Cash flow values"
130 /guess
131 guess-val
132 /local rates rate npv-result result
133 ][
134 tries: 50
135 ACCURACY: $.000001
136 range: none
137 rtb: dx: x-mid: 0.0
138 f-mid: $0
139
140 if not guess [guess-val: .2]
141
142 if range: bracket-fn :npv values 0 guess-val [
143 either ((npv values range/1) < $0) [
144 rtb: range/1
145 dx: range/2 - range/1
146 ][
147 rtb: range/2
148 dx: range/1 - range/2
149 ]
150 loop tries [
151 dx: dx * 0.5
152 x-mid: rtb + dx
153 f-mid: npv values x-mid
154 if (f-mid <= $0) [rtb: x-mid]
155 if any [
156 (abs f-mid < ACCURACY)
157 (abs dx < second ACCURACY)
158 ] [return x-mid]
159 ]
160 return none
161 ]
162 ]
163
164 bracket-fn: func [
165 {Adapted from Numerical Recipes. Given a user supplied function and a
166 given 'guess' range (x1 to x2) it expands the range geometrically until
167 a root is bracketed by the returned values x1 and x2, in which case
168 the range is returned as a block of those two values. If it doesn't
169 succeed after 50 tries, it returns false. This was built to support
170 IRR so it's designed to call NPV, which takes one arg. It should be
171 generalized.}
172 fn arg x1 x2
173 /local factor tries f1 f2
174 ][
175 tries: 50
176 factor: 1.6
177 f1: fn arg x1
178 f2: fn arg x2
179 loop tries [
180 ; We have to use SECOND on f2 because it's a money! value
181 ; and the numeric component is returned by SECOND.
182 if negative? multiply f1 second f2 [
183 return reduce [x1 x2]
184 ]
185 either (abs f1) < (abs f2) [
186 x1: x1 + (factor * (x1 - x2))
187 f1: fn arg x1
188 ][
189 x2: x2 + (factor * (x2 - x1))
190 f2: fn arg x2
191 ]
192 ]
193 false
194 ]
195 ;bracket-fn get in lib/fin 'npv values 0 .2
196 ;values: [-$70'000 $12'000 $15'000 $18'000 $21'000 $26'000]
197 ;lib/fin/irr values
198 ;lib/fin/irr copy/part values 5
199 ;lib/fin/irr copy/part values 3
200 ;lib/fin/irr/guess copy/part values 3 -.1
201 ;values: [-10 1.8 1.8 1.8 1.8 1.8 1.8 1.8 2.8]
202 ;lib/fin/irr values
203 ;values: [0 -1073 -1459 -1364 -1247 -1110 31789]
204 ;lib/fin/irr values
205
206 ; irr: func [
207 ; {Computes the internal rate of return based on future cash flow values.
208 ; The internal rate of return of a cash flow is the interest rate that
209 ; makes the present value of a cash flow equal to zero. THIS IS A QUICKLY
210 ; HACKED VERSION AND NEEDS TO BE CLEANED UP! HERE FOR TESTING.}
211 ; values [any-block!] "Cash flow values"
212 ; /guess
213 ; guess-val
214 ; /local rates rate npv-result result
215 ; ][
216 ; ACCURACY: $.000001
217 ; tries: 50
218 ; range: none
219 ; rtb: dx: x-mid: 0.0
220 ; f-mid: $0
221 ; if not guess [guess-val: .2]
222 ; if range: bracket-fn :npv values 0 guess-val [
223 ; f: npv values range/1
224 ; either (f < $0) [
225 ; rtb: range/1
226 ; dx: range/2 - range/1
227 ; ][
228 ; rtb: range/2
229 ; dx: range/1 - range/2
230 ; ]
231 ; loop tries [
232 ; dx: dx * 0.5
233 ; x-mid: rtb + dx
234 ; f-mid: npv values x-mid
235 ; if (f-mid <= $0) [rtb: x-mid]
236 ; if any [
237 ; ((abs f-mid) < ACCURACY)
238 ; ((abs dx) < (second ACCURACY))
239 ; ] [return x-mid]
240 ; ]
241 ; return none
242 ; ]
243 ; ]
244 ;
245 ; bracket-fn: func [
246 ; {Adapted from Numerical Recipes. Given a user supplied function and a
247 ; given 'guess' range (x1 to x2) it expands the range geometrically until
248 ; a root is bracketed by the returned values x1 and x2, in which case
249 ; the range is returned as a block of those two values. If it doesn't
250 ; succeed after 50 tries, it returns false. This was built to support
251 ; IRR so it's designed to call NPV, which takes one arg. It should be
252 ; generalized.}
253 ; fn arg x1 x2
254 ; /local factor tries f1 f2
255 ; ][
256 ; tries: 50
257 ; factor: 1.6
258 ; f1: fn arg x1
259 ; f2: fn arg x2
260 ; loop tries [
261 ; ; We have to use SECOND on f2 because it's a money! value
262 ; ; and the numeric component is returned by SECOND.
263 ; if negative? multiply f1 second f2 [
264 ; return reduce [x1 x2]
265 ; ]
266 ; either (abs f1) < (abs f2) [
267 ; x1: x1 + (factor * (x1 - x2))
268 ; f1: fn arg x1
269 ; ][
270 ; x2: x2 + (factor * (x2 - x1))
271 ; f2: fn arg x2
272 ; ]
273 ; ]
274 ; false
275 ; ]
276
277 ]