/* financial.mac Maxima by Example, Ch. 15, Financial Mathematics Edwin L. (Ted) Woollett Oct. 20, 2020 This file (financial.mac), financial.html, financial.wxm, and financial_brief.wxm are available for download at Ted's CSULB webpage: http://web.csulb.edu/~woollett/ financial.mac began as a modification of Nicolas Guarin Zapata's original file finance.mac in /share. */ /***************** from Zapata's finance.mac: *****************************/ /* COPYRIGHT NOTICE Copyright (C) 2008 Nicolas Guarin Zapata This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details at http://www.gnu.org/copyleft/gpl.html */ /* **************************************************** * FINANCE PACKAGE V.0.1 * **************************************************** *This is the Finance Package. * *Actually, there are only basic functions, we hope * *you could improve the package with brand new lines* **************************************************** *In all the functions rate is the compound interest* *rate, num is the number of periods and must be * *postive & "flow" refers to cash flow so if you * *have an Output the flow is negative & positive * *for Inputs. * **************************************************** */ /********************** end of "from finance.mac" ************************/ /***************** start of financial.mac functions *********************/ /************** Rounding Floating Point Numbers and Printing All Matrix Rows *****/ /* two ways to print elements of a matrix as a (crude) ordinary table */ /* print_matrix (AA) := for k thru length(AA) do apply ('print,AA[k])$ */ /* print_matrix */ print_matrix (AA) := map (lambda([xx], apply ('print,xx)), args(AA))$ /* rnddp, rnd_list_dp, rnd_matrix_dp are based on a stackoverflow suggested function to round real number to dp decimal digits; dp stands for decimal places. */ /* rnddp */ rnddp(expr,dp):= if floatnump (expr) then float((round(expr*10^dp)/10^dp)) else expr$ /* related function to round a list of floats */ /* rnd_list_dp */ rnd_list_dp(alist,dp) := map (lambda([xx], rnddp(xx, dp)), alist)$ /* rnd_matrix_dp */ rnd_matrix_dp(%Amatrix,dp) := matrixmap (lambda([xx], rnddp(xx, dp)), %Amatrix)$ /* Example: (%i13) ma : matrix([1.2345,2.3456],[3.4567,4.5678])$ (%i15) rnd_matrix_dp(ma,2); (%o15) matrix( [1.23, 2.35], [3.46, 4.57] ) */ /**************** Time Value of Money Functions *************************/ /* fv(i,PV,n) The future value fv of a **single** payment made num compounding periods in the past in a financial environment in which investment oportunities are available which yield a rate of interest "rate" is gotten by compounding the past payment by num factors of the compounding factor (1+rate). */ fv(rate, past_payment, num) := past_payment*(1+rate)^num$ /* Examples: (%i7) fv(0.05,100,1); (%o7) 105.0 (%i8) fv(0.05,100,2); (%o8) 110.25 */ /* pv(i,FV,n) The present value pv of a **single** payment made num compounding periods in the future in a financial environment in which investment oportunities are available which yield a rate of interest "rate" is gotten by "discounting" the future payment by num factors of the "discount factor" v = 1/(1+rate). */ pv(rate,future_payment,num):=future_payment/(1+rate)^num$ /* Examples (%i10) pv(0.05,105,1); (%o10) 100.0 (%i11) pv(0.05,105,2); (%o11) 95.23809523809524 (%i12) rnddp(%,2); (%o12) 95.24 */ /* npv (rate, flowValues) assumes flowValues is a list of cash flows which includes as the first element the cash flow at t = 0. npv then returns the net present value at t = 0 of this stream of cash flows, with the rest of the cash flows occurring at the ends of a compounding period. uses pv(rate,FV,num) */ npv (%rate, %flowValues) := sum ( pv (%rate, %flowValues[k], k-1), k, 1,length (%flowValues) )$ /* Example (%i7) cfL : [0, 100, 500, 323, 124, 300]; (cfL) [0,100,500,323,124,300] (%i8) npv (0.25, cfL ); (%o8) 714.4703999999999 */ /* irr(...) The IRR (Internal Rate of Return), is the value of rate which makes npr(rate, cash_flow_list) zero. Our Maxima function irr (flowValues, IO) returns the internal rate of return (IRR) based on a cash flow list of signed values, with the first element of the list representing t = 0, and also based on a possibly separate accounting of an initial investment (IO) made at t = 0. */ irr(flowValues,I0):= block([rr, asum, realonly: true], asum : npv (rr, flowValues) - I0, algsys ([asum = 0], [rr]), rhs ( %%[1][1]))$ /* Examples: (%i17) irr ([-5000, 0, 800, 1300, 1500, 2000], 0); (%o17) 0.03009250374237132 (%i11) irr ([0, 0, 800, 1300, 1500, 2000], 5000); (%o11) 0.03009250374237132 */ /* benefit_cost Calculate the Benefit/Cost ratio where "Benefit" is the Net Present Value (NPV) of the cash flow in, and "Cost" is the Net Present Value (NPV) of the cash flow out. */ benefit_cost (rate, CFin, CFout) := if length (CFin) # length (CFout) then error("CFin & CFout lists must have the same length") else rnddp ( npv (rate, CFin) / npv (rate, CFout), 2)$ /* Example with 24% interest rate per compounding period. CFin = cash flow in list = [0,300,500,150] where first list element applies to t = 0 (k = 0). CFout = cash flow out list = [100,320,0,180] where first list element applies to t = 0. (%i57) benefit_cost(0.24,[0,300,500,150],[100,320,0,180]); (%o57) 1.43 */ /* days360 (....) Calculate the number of days between 2 dates, assuming 360 day years, and 30 day months. */ days360(year1, month1, day1, year2, month2, day2):= block([years, months, days], if month2 > month1 then ( years : year2 - year1, if day2 > day1 then ( months : month2 - month1, days : day2 - day1) else ( months : month2 - month1 - 1, days : day2 + 30 - day1)) else ( years : year2 - year1 - 1, if day2 > day1 then ( months : month2 + 12 - month1, days : day2 - day1) else ( months : month2 + 11 - month1, days : day2 + 30 - day1)), days + years*360 + months*30)$ /*Example (%i16) days360 (2007,3,25,2008,12,16); (%o16) 621 */ /* plot_CF (cash_flow_list, lineWidth) cash_flow_list can have two forms: 1. if the list looks like [100, -50, 200, 300, -100] for example, then a positive cash flow is assumed to occur at t = 0 (k = 0), a negative cash flow occurs at k = 1 (the end of the first compounding period), and so on, with a negative cash flow (-100) at k = 4 (the end of the fourth compounding period). 2. If, instead, the list looks like [ [0.5,100], [1,-50], [2,200], [2.5,300], [4,-100] ] for example, then a positive cash flow (100) occurs at k = 0.5 (halfway thru the first compounding period), a negative cash flow (-50) occurs at k = 1 (the end of the first compounding period), and so on, with cash flow (-100) at k = 4 (the end of the 4th compounding period). */ plot_CF(CFL, lineWidth) := block([vectors, options], if listp (CFL[1]) then /* assume each element of CFL is a two element list */ vectors: flatten(makelist([color = if CFL[k][2] < 0 then 'red else 'blue, vector ( [CFL[k][1], 0], [0, CFL[k][2]] ) ], k,1, length(CFL))) else vectors: flatten(makelist([color = if CFL[k]<0 then 'red else 'blue, vector ( [k-1, 0], [0, CFL[k]] ) ], k,1, length(CFL))), options: [head_length=0.05, line_width = lineWidth, xaxis=true, yaxis=false, xrange=[-1,length(CFL) ]], apply(draw2d,append(options,vectors)))$ /* Examples of use (%i2) myList : [100, -50, 200, 300, -100]; (myList) [100,\-50,200,300,\-100] (%i3) plot_CF (myList, 6)$ (%i4) myList2 : [ [0.5,100],[1,-50],[2,200],[2.5,300],[4,-100] ]; (myList2) [[0.5,100],[1,\-50],[2,200],[2.5,300],[4,\-100]] (%i5) plot_CF (myList2, 6)$ */ /***************** Ordinary Annuity Functions *******************/ /* FV_stream Future Value of an Ordinary Annuity Immediate Stream of Payments each made at the end of a compounding period. */ FV_stream (rate,payment,num) := payment*( (1+rate)^num -1) / rate$ /* Example (%i12) FV_stream (0.05, 1000, 5); (%o12) 5525.631250000007 */ /* annuity_fv(i, FV, n) Returns the ordinary annuity (the constant and periodic payment) paid at the end of each compounding period at a given interest rate per period to achieve a desired future value FV_stream of a stream of payments with "rate" being interest rate per period, and a total number "num" of compounding periods. */ annuity_fv(%rate, %FV_stream, %num) := %FV_stream*%rate/((1+%rate)^%num-1)$ /* example: (%i12) annuity_fv(0.05,5525.64,5); (%o12) 1000.001583529482 (%i13) rnddp(%,2); (%o13) 1000.0 */ /* savingA and savingB constant periodic deposits in a savings account over num periods to achieve a savings goal of Amount, given the periodic interest rate. */ /* savingA (rate, Amount, num) assumes first savings deposit occurs at k = 1, and last occurs at k = num to reach savings goal = Amount. */ savingA (rate, Amount, num) := block ([MM, %payment], if num < 0 then error ("num must be a positive value") else MM : genmatrix (a, num + 2, 4), %payment : annuity_fv (rate, Amount, num), MM[1] : [k, Balance, Interest, Payment], MM[2] : [0, 0, 0, 0], for k : 3 thru num+2 do( MM[k, 4] : %payment, MM[k, 1] : k - 2, MM[k, 3] : MM[k - 1, 2]*rate, MM[k, 2] : MM[k - 1, 2] + MM[k, 3] + MM[k, 4] ), rnd_matrix_dp (MM, 2))$ /* Example (%i15) savingA (0.05, 1000, 6); (%o15) matrix( [k, Balance, Interest, Payment], [0, 0, 0, 0], [1, 147.02, 0, 147.02], [2, 301.39, 7.35, 147.02], [3, 463.47, 15.07, 147.02], [4, 633.66, 23.17, 147.02], [5, 812.36, 31.68, 147.02], [6, 1000.0, 40.62, 147.02] ) */ /* savingB (rate, Amount, num) assumes the first savings deposit occurs at t = 0 and immediately starts making interest ---------------- note: If A is the constant periodic mayment, and i is the periodic interest rate, and the first savings deposit is made at t = 0 then the future value at k = n of the stream of deposits made at k = 0, 1, 2,..., n-1 is A (1+i)^n + A (1+i)^(n-1) + A (1+i)^(n-2) + .... + A (1+i) */ savingB (rate, Amount, num) := block ([MM, %payment], if num < 0 then error ("num must be a positive value") else MM : genmatrix (a, num + 2, 4), %payment : rate*Amount / ( (1+rate)^(num+1) -rate -1), MM[1] : [k, Balance, Interest, Payment], MM[2] : [0, %payment, 0, %payment], /* t = 0 */ for k : 3 thru num+2 do( MM[k, 4] : %payment, MM[k, 1] : k - 2, MM[k, 3] : MM[k - 1, 2]*rate, MM[k, 2] : MM[k - 1, 2] + MM[k, 3] + MM[k, 4] ), rnd_matrix_dp (MM, 2))$ /* Example (%i16) savingB (0.05, 1000, 6); (%o16) matrix( [k, Balance, Interest, Payment], [0, 140.02, 0, 140.02], [1, 287.03, 7.0, 140.02], [2, 441.4, 14.35, 140.02], [3, 603.49, 22.07, 140.02], [4, 773.68, 30.17, 140.02], [5, 952.38, 38.68, 140.02], [6, 1140.02, 47.62, 140.02] ) */ /* PV_stream Present Value (t = 0) of an Ordinary Annuity Immediate Stream of Constant Payments each made at the end of a compounding period. */ PV_stream (rate,payment,num) :=block ([vv : 1/(1+rate)], payment * (1-vv^num) / rate )$ /* Example (%i17) PV_stream (0.05, 1000, 5); (%o17) 4329.476670630823 */ /* annuity_pv (i, PV, n) Returns the annuity (the constant periodic payment at the end of each of num compounding periods) knowing the present value (PV) of the stream of deposits in an ordinary annuity, and the interest rate per compounding period. Note that the present value of a stream of deposits in an ordinary annuity (PVA) is related to the future value of that same stream of deposits (FVA) by the equation: PVA = FVA/(1+rate)^num */ annuity_pv(%rate, %PV_stream, %num) := block ([ %vv : 1/(1+%rate)], %PV_stream*%rate/ (1 - %vv^%num) )$ /* Example here is the same stream of deposits as used in annuity_fv example above. (%i16) pv_example : PV_stream (0.05, 1000, 5); (pv_example) 4329.476670630823 (%i17) annuity_pv (0.05, pv_example, 5); (%o17) 1000.0 (%i18) fv_example : FV_stream (0.05, 1000, 5); (fv_example) 5525.631250000007 */ /* amortization (i, PV, n) Returns the amortization matrix determined by (interest) rate, (loan) Amount, and number of compounding periods num. Note syntax: genmatrix(a,2,3) creates a matrix with 2 rows and 3 columns of the form (a is an undefined 2 dimensional array): |a11, a12, a13 | |a21, a22, a23 | */ amortization (rate, Amount, num) := block ([%payment, MM], %payment : annuity_pv (rate, Amount, num), MM : genmatrix(a, num + 2, 5), MM[1] : [m, Prin_balance, Interest_portion, Principal_portion, Payment], MM[2] : [0,0,0,0,0], MM[2,2] : Amount, for k : 3 thru num + 2 do ( MM[k,1] : k - 2, MM[k,5] : %payment, MM[k,3] : rate * MM[k-1, 2], /* interest paid */ MM[k,4] : %payment - MM[k,3], /* amortization */ MM[k,2] : MM[k-1,2] - MM[k,4] ), /* new balance */ rnd_matrix_dp (MM,2 ) )$ /* example: amortization (0.05,56000,3); --> matrix([m,Prin_balance,Interest_portion,Principal_portion,Payment], [0,56000,0,0,0], [1,38236.32,2800.0,17763.68,20563.68], [2,19584.46,1911.82,18651.86,20563.68], [3,0.0,979.22,19584.46,20563.68])$ */ /************* Arithmetic Increasing Annuity Functions ************/ /* PV_arith(i,R,Q, n) is a brute force calculation of the present value of a stream of increasing payments in arithmetic progression using the Maxima function sum for finite sums, with i = interest rate per compounding period, n = number of payments, R = first payment, Q = constant payment increment. v = 1/(1+i) is the "discount factor". */ PV_arith (i, R, Q, n) := block ([v:1/(1+i)], R*sum (v^k,k,1,n) + Q*sum( (k-1)*v^k,k,2,n))$ /* simple example (%i78) PV_arith (0.05,1,2,3); (%o78) 7.992657380412481 */ /* PV_AI_stream (i,R,Q,n) present value (t=0) of a stream of arithmetic increasing payments which are made at the end of each compounding period, beginning with the payment R, then R+Q, then R+2*Q etc. See pdf p 24, "annuity-immediate with general arithmetic progression payment amounts" in: http://users.stat.ufl.edu/~rrandles/sta4183/4183lectures/chapter04/chapter04R.pdf R = first payment, Q = constant increment in payment, rate = i = interest rate per compounding period, num compounding periods. abn stands for "a bracket n", v = 1/(1+i) = "discount factor". */ PV_AI_stream (rate, R, Q, num) := block ([vv:1/(1+rate), abn], abn : (1-vv^num)/rate, R*abn + (Q/rate)*(abn - num*vv^num))$ /* example 3 payments 1,3,5 with i = 5%. (%i80) PV_AI_stream (0.05, 1,2,3); (%o80) 7.99265738041266 */ /* AI_annuity_pv used by amortization_arith turn PV_AI_stream formula into a formula for the first payment, given the Amount = PV, rate = i, increment = Q and number of compounding periods num = n */ AI_annuity_pv (%rate, %Amount, %increment, %num) := block ([vv:1/(1+%rate), abn], abn : (1 - vv^%num)/%rate, (%Amount/abn) - (%increment/%rate)*(1 - (%num*vv^%num) / abn))$ /* example of use: (%i82) AI_annuity_pv (0.05,7.99265738041266, 2, 3); (%o82) 1.000000000000002 (%i83) rnddp (%, 2); (%o83) 1.0 */ /* amortization_arith (i, PV, Q, n) matrix Payments are in an arithmetic progression: Returns the amortization table determinated by rate, "Amount" = PV = valuation of the stream at t = 0, num = number of interest compounding periods, and the constant increment we call Q. R is given by "first_payment". The payments are: m = 1, payment = R, m = 2, payment = R + Q, m = 3, payment = R + 2*Q, m = 4, payment = R + 3*Q, etc up to the payment R+(num-1)*Q at m = num. The integer "m" defines the compounding period. */ amortization_arith(rate, Amount, increment, num):= block([MM,%payment], if num < 0 then error("num must be a positive value") else MM : genmatrix(a, num+2, 5), %payment : AI_annuity_pv(rate, Amount, increment, num), MM[1] : [m, prin_balance, int_portion, prin_portion,Payment], MM[2] : [0,0,0,0,0], MM[2,2] : Amount, MM[3,5] : %payment, /* first payment */ /* subsequent payments */ for k:4 thru num+2 do (MM[k,5]:MM[k-1,5] + increment), /* remainder of matrix elements */ for k:3 thru num+2 do ( MM[k,1] : k-2, MM[k,3] : MM[k-1,2]*rate, /* interest paid */ MM[k,4] : MM[k,5] - MM[k,3], /* amortization */ MM[k,2] : MM[k-1,2] - MM[k,4]), /* new loan balance */ rnd_matrix_dp (MM,2) )$ /* Example: output looks better inside wxmaxima worksheet, since the matrix function automatically spaces things out to get an attractive and balanced looking output. (%i86) amortization_arith (0.05, 7.992657380412481, 2, 3); (%o86) matrix( [m, prin_balance, int_portion, prin_portion, Payment], [0, 7.99, 0, 0, 0], [1, 7.39, 0.4, 0.6, 1.0], [2, 4.76, 0.37, 2.63, 3.0], [3, 0.0, 0.24, 4.76, 5.0]) (%i87) grind(%)$ matrix([m,prin_balance,int_portion,prin_portion,Payment],[0,7.99,0,0,0], [1,7.39,0.4,0.6,1.0],[2,4.76,0.37,2.63,3.0],[3,0.0,0.24,4.76,5.0])$ */ /*************** Growing ("Geometric Increasing") Annuity Functions *********************/ /* PV_geom In this brute force calculation we simply used the Maxima function sum, without using the finite sum geometric sum formula. Note also that the last line inside the block uses rnddp (%%,2) to round to two decimals the previous result. present value of a geometrically increasing stream of payments. Let R be the first payment at the end of the first compounding period, i be the interest rate per period, let v = 1/ (1+i) = "discount factor", let g be the "growth rate", such that the second payment is R*(1+g), the third payment is R*(1+g)^2, etc. Let the number of payments be n. Then using the discount factor to get the present value of each payment, we get: */ PV_geom (i, g, R, n) := block ([v : 1/(1+i) ], R*v* sum ( ( v*(1+g) )^k, k, 0, n-1 ))$ /* Example (%i28) PV_geom (0.05, 0.1, 100, 3); (%o28) 299.5356872907893 */ /* PV_GI_stream present value using geometric series summation formula */ PV_GI_stream (i,g,R, n) := R * (1 - ( (1+g)/(1+i) )^n ) / (i - g)$ /* Example: (%i30) PV_GI_stream (0.05, 0.1,100, 3); (%o30) 299.5356872907897 */ /* GI_annuity_pv This function returns the first payment of a geometric increasing stream of payments, given the desired present value PV */ GI_annuity_pv (rate,growing_rate,PV,num) := PV*(rate - growing_rate)/(1 - (1 + growing_rate)^(num)/((1 + rate)^num))$ /*Example: (%i32) GI_annuity_pv(0.05, 0.1, 299.54, 3); (%o32) 100.0014397981253 */ /* amortization_geom amortization matrix for a geometrically increasing annuity. Returns the amortization table determinated by rate, the growing_rate, the Amount, and number of periods. payment[k+1] = (1 + growing_rate)*payment[k]. */ amortization_geom (rate, growing_rate, Amount, num) := block([MM, %payment], if num<0 then error("num must be a positive value") else MM : genmatrix(a, num + 2, 5), %payment : GI_annuity_pv (rate, growing_rate, Amount, num), MM[1] : [ k, Balance, Interest, Amortization,Payment], MM[2] : [0, Amount, 0, 0, 0], MM[3, 5] : %payment, for k : 4 thru num+2 do MM[k, 5] : MM[k - 1, 5]*(1+growing_rate), for k : 3 thru num+2 do ( MM[k, 1] : k - 2, MM[k, 3] : MM[k - 1, 2]*rate, MM[k, 4] : MM[k, 5] - MM[k, 3], MM[k, 2] : MM[k - 1, 2] - MM[k, 4] ), rnd_matrix_dp (MM, 2))$ /* Example: $56,000 loan at 5% interest per compounding period, with periodic payment increasing by 3% each period: (%i42) amortization_geom (0.05, 0.03, 56000, 3); (%o42) matrix( [k, Balance, Interest, Amortization, Payment], [0, 56000, 0, 0, 0], [1, 38821.88, 2800.0, 17178.12, 19978.12], [2, 20185.51, 1941.09, 18636.37, 20577.46], [3, 0.0, 1009.28, 20185.51, 21194.79] ) */ /* FV_geom Brute force calculation of future value of a stream of n payments with R = first payment at time k = 1, R(1+g) the second payment at k = 2, etc, and R (1+g)^(n-1) being the last payment at k = n, assuming the growth rate g, periodic interest rate i. */ FV_geom (i, g, R, n) := R*sum ( (1+g)^(k-1) * (1+i)^(n-k), k, 1, n)$ /* example (%i49) FV_geom (0.05, 0.1, 100, 3); (%o49) 346.75 */ /* FV_GI_stream future value of a stream of n payments with R = first payment at time k = 1, R(1+g) the second payment at k = 2, etc, and R (1+g)^(n-1) being the last payment at k = n, assuming the growth rate g, periodic interest rate i, which makes use of the summation of the finite sum series, giving the formula FV = ( R / (i-g) ) * ( (1+i)^n - (1+g)^n ) */ FV_GI_stream (i, g,R, n) := R * ( (1+i)^n - (1+g)^n ) / (i - g)$ /* example (%i43) FV_GI_stream (0.05, 0.1, 100, 3); (%o43) 346.7500000000005 */ /* GI_annuity_fv This formula gives the first payment (at k = 1) of a geometric increasing stream of payments, given the future value FV (at k = n), the interest rate i, the growth rate g, and the number n of compound interest periods. */ GI_annuity_fv (i, g, FV, n) := FV*(i - g)/( (1 + i)^n - (1 + g)^n )$ /* Example (%i29) GI_annuity_fv (0.05, 0.1, 346.7500000000005, 3); (%o29) 100.0 */ /* savingA and savingB constant periodic deposits in a savings account over num periods to achieve a savings goal of Amount, given the periodic interest rate. */ /************************ list utilities **************/ /* fll [first, last, length] */ fll(L) := [first(L),last(L),length(L)]$ declare(fll,evfun)$ /* head first three elements */ head(L) := rest (L, - (length (L) - 3) )$ declare(head,evfun)$ /* tail last three elements */ tail (L) := rest (L, length (L) - 3 )$ declare(tail,evfun)$ /* examples (%i7) myL : makelist ( j^2, j, 1, 15); (myL) [1,4,9,16,25,36,49,64,81,100,121,144,169,196,225] (%i8) fll (myL); (%o8) [1,225,15] (%i9) myL, fll; (%o9) [1,225,15] (%i10) head (myL); (%o10) [1,4,9] (%i11) myL, head; (%o11) [1,4,9] (%i12) tail (myL); (%o12) [169,196,225] (%i13) myL, tail; (%o13) [169,196,225] */ /* set fpprintprec and load draw package */ fpprintprec:0$ load(draw)$