REBOL [
Title: "Financial Library"
Date: 17-Apr-2002
Name: 'fin
Version: 0.0.2
File: %fin.r
;Home: http://www.pointillistic.com/
Author: "Gregg Irwin"
Email: gregg@pointillistic.com
;Owner: "Pointillistic Software"
;Rights: "Copyright © Pointillistic Software 2002. All Rights Reserved."
;Tabs: 4
;Need: 0.1.4
;Language: 'English
;Charset: 'ANSI
;Category: [script 1]
Purpose: {Financial module for standard library.}
Comment: {Prototype stage.}
History: [
0.0.1 [13-Mar-2002 "First whack" "Gregg"]
0.0.2 [17-Apr-2002 {Added NPV and a functional, if ugly, IRR.
BRACKET-FN supports IRR.} "Gregg"]
]
Example: {do %fin.r}
]
fin: make lib-kernel [
; Future Value
fv: func [
{Computes future value based on present value.}
value [number! money!] "Present value"
rate [number!] "Interest Rate"
periods [number!] "Number of time periods"
][
1 + rate ** periods * value
]
;lib/fin/fv 1000 .1 12
;lib/fin/fv 1000 .1 24
;lib/fin/fv 1000 .1 36
; Compound Interest
interest: func [
{Computes accumulated compound interest.}
value [number! money!] "Present value"
rate [number!] "Interest Rate"
periods [number!] "Number of time periods"
][
1 + rate ** periods - 1 * value
]
;lib/fin/interest 1000 .1 12
;lib/fin/interest 1000 .1 24
;lib/fin/interest 1000 .1 36
; Interest Rate
rate: func [
{Computes interest rate for a given future value.}
value [number! money!] "Present value"
future-value [number! money!] "Future value"
periods [number!] "Number of time periods"
][
future-value / value ** (1 / periods) - 1
]
;lib/fin/rate 1000 future-value 1000 .1 12 12
;lib/fin/rate 1000 future-value 1000 .1 24 24
;lib/fin/rate 1000 future-value 1000 .1 36 36
; Present Value
pv: func [
{Computes present value based on future value.}
value [number! money!] "Future value"
rate [number!] "Interest Rate"
periods [number!] "Number of time periods"
][
1 + rate ** (negate periods) * value
; - or -
;value / (1 + rate ** periods)
]
;lib/fin/pv future-value 1000 .1 12 .1 12
;lib/fin/pv future-value 1000 .1 24 .1 24
;lib/fin/pv future-value 1000 .1 36 .1 36
npv: func [
{Computes net present value based on future cash flow values
using continuous discounting.}
values [any-block!] "Cash flow values"
rate [number!] "Interest Rate"
/local result
][
result: $0.0
repeat i length? values [
result: result + pv values/:i rate i
]
result
]
;lib/fin/npv [1000 2000 3000] .1
;lib/fin/npv [-10000, 3000, 4200, 6800] .1
;[-$70'000 $12'000 $15'000 $18'000 $21'000 $26'000]
; Time Periods
nper: func [
{Computes the number of time periods from the present value
to a given future value.}
value [number! money!] "Present value"
future-value [number! money!] "Future value"
rate [number!] "Interest Rate"
][
; 2 versions. Both work.
(log-e (future-value / value)) / (log-e (rate + 1))
;divide log-e divide future-value value log-e add rate 1
]
;lib/fin/nper 1000 future-value 1000 .1 12 .1
;lib/fin/nper 1000 future-value 1000 .1 24 .1
;lib/fin/nper 1000 future-value 1000 .1 36 .1
irr: func [
{Computes the internal rate of return based on future cash flow values.
The internal rate of return of a cash flow is the interest rate that
makes the present value of a cash flow equal to zero. THIS IS A QUICKLY
HACKED VERSION AND NEEDS TO BE CLEANED UP! HERE FOR TESTING.}
values [any-block!] "Cash flow values"
/guess
guess-val
/local rates rate npv-result result
][
tries: 50
ACCURACY: $.000001
range: none
rtb: dx: x-mid: 0.0
f-mid: $0
if not guess [guess-val: .2]
if range: bracket-fn :npv values 0 guess-val [
either ((npv values range/1) < $0) [
rtb: range/1
dx: range/2 - range/1
][
rtb: range/2
dx: range/1 - range/2
]
loop tries [
dx: dx * 0.5
x-mid: rtb + dx
f-mid: npv values x-mid
if (f-mid <= $0) [rtb: x-mid]
if any [
(abs f-mid < ACCURACY)
(abs dx < second ACCURACY)
] [return x-mid]
]
return none
]
]
bracket-fn: func [
{Adapted from Numerical Recipes. Given a user supplied function and a
given 'guess' range (x1 to x2) it expands the range geometrically until
a root is bracketed by the returned values x1 and x2, in which case
the range is returned as a block of those two values. If it doesn't
succeed after 50 tries, it returns false. This was built to support
IRR so it's designed to call NPV, which takes one arg. It should be
generalized.}
fn arg x1 x2
/local factor tries f1 f2
][
tries: 50
factor: 1.6
f1: fn arg x1
f2: fn arg x2
loop tries [
; We have to use SECOND on f2 because it's a money! value
; and the numeric component is returned by SECOND.
if negative? multiply f1 second f2 [
return reduce [x1 x2]
]
either (abs f1) < (abs f2) [
x1: x1 + (factor * (x1 - x2))
f1: fn arg x1
][
x2: x2 + (factor * (x2 - x1))
f2: fn arg x2
]
]
false
]
;bracket-fn get in lib/fin 'npv values 0 .2
;values: [-$70'000 $12'000 $15'000 $18'000 $21'000 $26'000]
;lib/fin/irr values
;lib/fin/irr copy/part values 5
;lib/fin/irr copy/part values 3
;lib/fin/irr/guess copy/part values 3 -.1
;values: [-10 1.8 1.8 1.8 1.8 1.8 1.8 1.8 2.8]
;lib/fin/irr values
;values: [0 -1073 -1459 -1364 -1247 -1110 31789]
;lib/fin/irr values
; irr: func [
; {Computes the internal rate of return based on future cash flow values.
; The internal rate of return of a cash flow is the interest rate that
; makes the present value of a cash flow equal to zero. THIS IS A QUICKLY
; HACKED VERSION AND NEEDS TO BE CLEANED UP! HERE FOR TESTING.}
; values [any-block!] "Cash flow values"
; /guess
; guess-val
; /local rates rate npv-result result
; ][
; ACCURACY: $.000001
; tries: 50
; range: none
; rtb: dx: x-mid: 0.0
; f-mid: $0
; if not guess [guess-val: .2]
; if range: bracket-fn :npv values 0 guess-val [
; f: npv values range/1
; either (f < $0) [
; rtb: range/1
; dx: range/2 - range/1
; ][
; rtb: range/2
; dx: range/1 - range/2
; ]
; loop tries [
; dx: dx * 0.5
; x-mid: rtb + dx
; f-mid: npv values x-mid
; if (f-mid <= $0) [rtb: x-mid]
; if any [
; ((abs f-mid) < ACCURACY)
; ((abs dx) < (second ACCURACY))
; ] [return x-mid]
; ]
; return none
; ]
; ]
;
; bracket-fn: func [
; {Adapted from Numerical Recipes. Given a user supplied function and a
; given 'guess' range (x1 to x2) it expands the range geometrically until
; a root is bracketed by the returned values x1 and x2, in which case
; the range is returned as a block of those two values. If it doesn't
; succeed after 50 tries, it returns false. This was built to support
; IRR so it's designed to call NPV, which takes one arg. It should be
; generalized.}
; fn arg x1 x2
; /local factor tries f1 f2
; ][
; tries: 50
; factor: 1.6
; f1: fn arg x1
; f2: fn arg x2
; loop tries [
; ; We have to use SECOND on f2 because it's a money! value
; ; and the numeric component is returned by SECOND.
; if negative? multiply f1 second f2 [
; return reduce [x1 x2]
; ]
; either (abs f1) < (abs f2) [
; x1: x1 + (factor * (x1 - x2))
; f1: fn arg x1
; ][
; x2: x2 + (factor * (x2 - x1))
; f2: fn arg x2
; ]
; ]
; false
; ]
]