1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266

;;;; * Mode: LISP; Syntax: ANSICommonLisp; Base: 10 *
;;;; *************************************************************************
;;;; FILE IDENTIFICATION
;;;;
;;;; Name: finance.lisp
;;;; Purpose: Financial Load Utilities.
;;;; Author: R. Scott McIntire
;;;; Date Started: Aug 2003
;;;;
;;;; $Id: finance.lisp,v 1.3 2003/10/03 17:36:37 rscottmcintire Exp $
;;;; *************************************************************************
(inpackage rsm.finance)
(evalwhen (:compiletoplevel)
(declaim (optimize (speed 3) (debug 0) (safety 1) (space 0))))
(defvar *checkforplus* t
"Variable that controls whether the calc functions should check their args for
positive numbers.")
(definecondition nonsolveable ()
((message :reader message :initform "" :initarg :message))
(:report (lambda (condition stream)
(format stream "Warning: ~a~%" (message condition))))
(:documentation "Condition to be signaled when a financial problem
cannot be solved. This may be because the problem has no solution, or that
a solution may exist but be numerically unstable to compute."))
;;; Round a <number> to <digit> digits.
(defun roundto (number digit)
"Round <number> to <digit> digits after the decimal point."
(let ((level (/ 1.0d0 (expt 10.0d0 digit))))
(* (ftruncate (+ number (/ level 2.0d0)) level) level)))
(defun checkforplusp (args)
"Check that the odd elements of the list, <args>, are valid positive
numbers. Use the even elements as identifiers. If an argument is found that is
not a positive number throw an error."
(if *checkforplus*
(loop for (val valstring) on args by #'cddr do
(unless (and (numberp val) (plusp val))
(error "The ~a, ~s, is not a positive number.~%"
valstring val)))))
(defun computelineardifferenceeqn (x0 a b n)
"Solve (by iteration) the linear difference equation:
x_(k+1) = a * x_k + b; x_0 = x0.
Returns x_n."
(let ((acc (coerce x0 'doublefloat)))
(dotimes (i n acc)
(setf acc (+ b (* acc a))))))
(defun solvelineardiffeqn (x0 a b n)
"Solve (by formula) the linear difference equation:
x_(k+1) = a * x_k + b; x_0 = x0.
Returns x_n."
(/ (+ b (* ( (* x0 ( 1 a)) b) (expt a n))) ( 1 a)))
(defun debtcalc (initialdebt annualinterestrate
monthlypayment numberofpayments)
"Compute debt at the end of n (numberofpayments) payments,
based on the initial amount; the (annual) interest rate; and the
monthly payment. Compounding is assumed to be monthly."
(solvelineardiffeqn initialdebt
(+ 1.0d0 (/ annualinterestrate 1200.0d0))
( monthlypayment) numberofpayments))
(defun iterationnumberwhenzero (x0 a b)
"Finds the iteration at which the solution to the difference equation:
x_(k+1) = a * x_k + b; x_0 = x0 is 0.
Returns a number which may be a fraction. Raises a signal of type
nonsolveable if the equation is not solveable."
(let ((iterationnum
(/ (log (/ b ( b (* x0 ( 1 a))))) (log a))))
(if (complexp iterationnum)
(signal (makecondition 'nonsolveable :message
"Can't solve difference equation."))
iterationnum)))
(defun calcnumpayments (initialdebt annualinterestrate monthlypayment)
"Calculates the number of payments to pay off an initial debt with
a given annual interest rate and monthly payment. The debt is assumed
to compound monthly. Returns a multiple value: The number of payments;
the number of whole years this represents; the number of months after the
number of whole years; the total payout; and the last payment."
(checkforplusp (list initialdebt "debt"
annualinterestrate "annual interest rate"
monthlypayment "monthly payment"))
(handlercase
(let ((num (floor
(iterationnumberwhenzero
initialdebt
(+ 1.0d0
(/ annualinterestrate 1200.0d0))
( monthlypayment)))))
(let* ((debt (debtcalc initialdebt
annualinterestrate
monthlypayment
num))
(payout (+ (* num monthlypayment) debt)))
(when (> debt 0.0d0)
(incf num))
(multiplevaluebind (years months)
(truncate num 12)
(values num years (floor months) payout debt))))
(nonsolveable ()
(signal (makecondition 'nonsolveable
:message
(format nil
"A monthly payment of
\"~s\" is too low, a solution does not exist." monthlypayment))))
(:noerror (num years months payout debt)
(returnfrom calcnumpayments
(values num years months payout debt)))))
(defun displaynumpayments (initialdebt annualinterestrate monthlypayment)
"Displays the number of payments to pay off an initial debt with a given
annual interest rate and monthly payment. The debt is assumed to compound
monthly."
(checkforplusp (list initialdebt "debt"
annualinterestrate "annual interest rate"
monthlypayment "monthly payment"))
(let ((*checkforplus* nil))
(handlercase
(multiplevaluebind (numpayments years months
totalpayout lastpayment)
(calcnumpayments initialdebt
annualinterestrate
monthlypayment)
(format t "Number of payments = ~a " numpayments)
(if (> years 0)
(if (= years 1)
(format t "(That's ~a year" years)
(format t "(That's ~a years" years)))
(if (> months 0)
(if (= months 1)
(format t " and ~a month.)~%" months)
(format t " and ~a months.)~%" months))
(format t ".)~%"))
(format t "Total payout = $~2$~%" totalpayout)
(when (> lastpayment 0.0)
(format t "Last payment = $~2$~%" lastpayment))
(values))
(nonsolveable (obj) (format t "~a~%" (message obj)) (values))
(:noerror () (returnfrom displaynumpayments (values))))))
(defun calcinitialdebt (yearstopay annualinterestrate monthlypayment)
"Calculates the initial debt that one can pay off in <yearstopay> years at a
monthly payment rate of <monthlypayment> with an annual interest rate of
<annualinterestrate>. Compounding is assumed to occur monthly."
(checkforplusp (list yearstopay "years to pay"
annualinterestrate "annual interest rate"
monthlypayment "monthly payment"))
(let ((n (* yearstopay 12.0d0))
(a (+ 1.0d0 (/ annualinterestrate 1200.0d0)))
(b ( (coerce monthlypayment 'doublefloat))))
(roundto (* b (/ ( 1.0d0 (expt a ( n))) ( 1 a))) 2.0d0)))
(defun displayinitialdebt (yearstopay annualinterestrate monthlypayment)
"Displays the initial debt that one can pay off in <yearstopay> years at a
monthly payment rate of <monthlypayment> with an annual interest rate of
<annualinterestrate>. Compounding is assumed to occur monthly."
(checkforplusp (list yearstopay "years to pay"
annualinterestrate "annual interest rate"
monthlypayment "monthly payment"))
(let ((*checkforplus* nil))
(format t "Initial debt = $~$~%"
(calcinitialdebt yearstopay
annualinterestrate monthlypayment))
(values)))
(defun calcmonthlypayment (debt yearstopay annualinterestrate)
"Calculates the monthly payment that one needs in order to pay off a debt of
<debt> in <yearstopay> years at an interest rate of <annualinterestrate>.
Compounding is assumed to occur monthly."
(checkforplusp (list debt "initial debt"
yearstopay "years to pay"
annualinterestrate "annual interest rate"))
(let ((a (+ 1.0d0 (/ annualinterestrate 1200.0d0)))
(x0 (coerce debt 'doublefloat))
(n (* yearstopay 12.0d0)))
(roundto (/ (* (expt a n) x0 ( a 1.0d0)) ( (expt a n) 1.0d0)) 2.0d0)))
(defun displaymonthlypayment (debt yearstopay annualinterestrate)
"Displays the monthly payment that one needs in order to pay off a debt of
<debt> in <yearstopay> years at an interest rate of <annualinterestrate>.
Compounding is assumed to occur monthly."
(checkforplusp (list debt "initial debt"
yearstopay "years to pay"
annualinterestrate "annual interest rate"))
(let ((*checkforplus* nil))
(format t "Monthly payment = $~$~%"
(calcmonthlypayment debt yearstopay annualinterestrate))
(values)))
(defun calcinterestrate (initialdebt monthlypayment years)
"Calculates the interest rate at which an initial debt of <initialdebt> will
be paid off in <years> years with monthly payment, <monthlypayment>.
Compounding is assumed to occur monthly."
(checkforplusp (list initialdebt "initial debt"
monthlypayment "monthly payment"
years "years"))
(when (> ( initialdebt (* monthlypayment years 12.0d0)) 0.0d0)
(returnfrom calcinterestrate
(signal (makecondition 'nonsolveable
:message
"Monthly payment is too low to obtain a solution."))))
(let ((n (* (coerce years 'doublefloat) 12.0d0))
(b (coerce monthlypayment 'doublefloat))
(x0 (coerce initialdebt 'doublefloat)))
(let ((r (/ (+ n (/ x0 b)) (+ n (/ (* x0 x0) (* b b))))))
(do ((rlast 0.0d0)
(itercount 0 (incf itercount)))
((< (abs ( r rlast)) 1.0d6))
(when (> itercount 100)
(returnfrom calcinterestrate
(signal (makecondition 'nonsolveable
:message "Algorithm not converging to a solution."))))
(setf rlast r)
(setf r ( r (/ (+ ( (* (expt (1+ r) (1+ n))
x0)
(* (expt (1+ r) n)
(+ x0 b)))
b)
( (* (1+ n)
(expt (1+ r) n)
x0)
(* n
(expt (1+ r) (1 n))
(+ x0 b)))))))
(if (< (/ (abs (+ (* ( x0 (/ b r))
(expt (+ 1.0d0 r) n))
(/ b r))) b) 1.0d0)
(* r 1200.0d0)
(signal (makecondition 'nonsolveable
:message "Monthly payment is too low.
Cannot accurately compute the annual interest rate."))))))
(defun displayinterestrate (initialdebt monthlypayment years)
"Displays the interest rate at which an initial debt of <initialdebt> will be
paid off in <years> years with monthly payment, <monthlypayment>.
Compounding is assumed to occur monthly."
(checkforplusp (list initialdebt "initial debt"
monthlypayment "monthly payment"
years "years"))
(handlercase
(let ((*checkforplus* nil))
(let ((rate (calcinterestrate initialdebt monthlypayment years)))
rate))
(nonsolveable (obj)
(format t "~a~%" (message obj))
(values))
(:noerror (rate)
(format t "Annual Interest Rate = ~3$~%" rate)
(values))))
