REBOL [
Title: "Cash Flow Functions"
File: %cash-flow-functions.r
Purpose: "Provide some financial functions."
Author: "Brett Handley"
Date: 23-Apr-2002
Version: 0.1.1
History: [
0.1.1 [23-Apr-2002 "Work around 0 <> $0. Allow IRR to handle money!" "Brett Handley"]
0.1.0 [21-Apr-2002 "Initial version." "Brett Handley"]
]
Comment: {
(0) This is NOT rigoursly tested. Please send me any bugs you find and
preferrably solutions to them. :^)
(1) Throws in a solve function which could be used to simulate "Goal Seek".
(2) The input to the DCF function has been chosen with a database application
in mind - that is the amounts may not be ordered in the block and there
could be other fields in the records. I'm not sure yet if I'll change
this later to a more simple raw format.
(3) The DCF function employs some "self destroying functions". I did this
as a performance enhancement in the event the function needs to process
many records. Note that DCF will be called iteratively by someone wanting
to do an IRR.
}
Rights: {Copyright (c) 2002 Brett Handley
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, subrights,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.}
]
npv: function [
{Returns the net present value for a sequence of periodic cash flows.
Note: (1) The date of the first amount is considered the value date.
(2) The datatype of the result is the same as the first amount
however the currency part is ignored.
(3) Amounts should be decimal! or money! only.}
rate [decimal!]
amounts [block!] "Cash flows - first is in today's money."
] [cf sum factor amt] [
if empty? amounts [return 0.00]
sum: 0.00 factor: 1 + rate
cf: tail amounts
until [
if money? amt: first cf: back cf [amt: second amt]
sum: add divide sum factor amt
same? cf amounts
]
make first amounts sum
]
dcf: function [
{Returns the net present/discounted value for a sequence of cash flows.
^- Note: (1) Each cash-flow must be either an object or a
^- context specification. Fields in use are DATE and AMOUNT
^- although they are not mandatory.
^- (2) The datatype of the result is the same as the first amount.
^- (3) The value-date is defaulted to the first date encountered.
^- (4) If the cash flow has no date - value date is assumed.
^- (5) If the cash flow element has no amount - it is skipped without error.}
rate [decimal!]
cash-flows [block!] "Cash flows - first is in today's money."
/as-at value-date "The date to which all cash flows should be discounted to."
/days ann-days [integer! decimal!] "Number of days considered to be in a year (Default 365)."
/spec "Returns result in context specification format."
][sum factor amt date-check sum-check][
if not days [ann-days: 365]
factor: 1 + rate
date-check: do [
; Create self destroying function - performance enhancement.
func[flow][
if all [none? value-date in flow 'date][
value-date: flow/date
date-check: none
]
]
]
sum-check: do [
; Create self destroying function - performance enhancement.
func[flow][
if all [none? sum in flow 'amount][
sum: make flow/amount 0
sum-check: none
]
]
]
repeat flow cash-flows [
if not object? flow [flow: context flow]
date-check flow
if in flow 'amount [
sum-check flow
amt: flow/amount
if in flow 'date [
amt: factor ** ((value-date - flow/date/date) / ann-days) * amt
]
sum: sum + amt
]
]
either spec [ compose [date: (value-date) amount: (sum)] ][sum]
]
irr: function [
{Returns the internal rate of return for a sequence of amounts.
^- Note: (1) The first amount represents the value date.
^- (2) The amounts are are considered to be periodic net cash flow amounts.
^- If you want to calculate on non-periodic cash flows then you must
^- supply a function that takes rate as an argument and
^- returns the NPV for your amounts.}
cash-flow [block! function!] "A block of amounts or a function that returns NPV for given rate."
/guess "Initial guess interval"
guess1 [decimal!]
guess2 [decimal!]
][npv-func][
if not guess [guess1: 0.10 guess2: 1.00]
if all [block? :cash-flow lesser? length? cash-flow 2] [return none]
npv-func: either function? :cash-flow [:cash-flow][
func [rate][npv rate cash-flow]
]
solve/limit :npv-func guess1 guess2 0.00000001 0.00000001 100
]
solve: function [
"Solve for x such that F(x)=0 using secant iteration."
F "Function to solve." [any-function!]
x1 "First initial point."
x2 "Second initial point."
x-tol "Input point tolerance."
f-tol "Output value tolerance."
/limit max-tries "Maximum number of iterations."
][x3 f1 f2 f3 slope tries zero][
if not limit [max-tries: 100]
f1: f x1 f2: f x2
x-tol: make x1 x-tol ; Make datatypes compatible.
f-tol: make f1 f-tol ; Make datatypes compatible.
if lesser? abs f1 f-tol [return x1]
if lesser? abs f2 f-tol [return x2]
zero: make f1 0 ; Workaround 0 <> $0.
tries: 0
while [greater? abs (x2 - x1) x-tol] [
if tries > max-tries [return none]
slope: (f2 - f1) / (x2 - x1)
if slope = zero [return none]
x3: x2 - (f2 / slope)
f3: f x3
if (abs f3) <= f-tol [break]
x1: x2 f1: f2
x2: x3 f2: f3
tries: tries + 1
]
x3
]
pmt: func [
"Periodical Payment (e.g Standard Mortgage payment)."
principal [decimal! money!]
rate [decimal!] "Rate (per period)."
periods [integer! decimal!] "Number of periods."
][
Principal * (
rate / (
1.0 - ((1.0 + rate) ** (-1 * periods))
)
)
]
dcf-irr-example: does [
amounts: [
[date: 31-dec-2000 amount: -100]
[date: 31-dec-2001 amount: 110]
]
irr func [rate][dcf rate amounts]
]