Loading [MathJax]/extensions/tex2jax.js
MRFFL: MR Fortran Finance Library 2024-12-28
Computational Tools For Finance
All Namespaces Files Functions Variables
mrffl_tvm12.f90
Go to the documentation of this file.
1! -*- Mode:F90; Coding:us-ascii-unix; fill-column:129 -*-
2!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!.H.S.!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!.H.E.!!
3!>
4!! @file mrffl_tvm12.f90
5!! @author Mitch Richling http://www.mitchr.me/
6!! @date 2024-12-19
7!! @brief TVM solver with functionality similar to financial calculators.@EOL
8!! @keywords finance fortran monte carlo inflation cashflow time value of money tvm percentages taxes stock market
9!! @std F2023
10!! @see https://github.com/richmit/FortranFinance
11!! @copyright
12!! @parblock
13!! Copyright (c) 2024, Mitchell Jay Richling <http://www.mitchr.me/> All rights reserved.
14!!
15!! Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following
16!! conditions are met:
17!!
18!! 1. Redistributions of source code must retain the above copyright notice, this list of conditions, and the following
19!! disclaimer.
20!!
21!! 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions, and the following
22!! disclaimer in the documentation and/or other materials provided with the distribution.
23!!
24!! 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products
25!! derived from this software without specific prior written permission.
26!!
27!! THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
28!! INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
29!! DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
30!! EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
31!! USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
32!! LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
33!! OF THE POSSIBILITY OF SUCH DAMAGE.
34!! @endparblock
35!!
36
37
38!##################################################################################################################################
39!> Provides a TVM solver with functionality similar to modern financial calculators.
40!!
41!! Most financial calculators use the following relationship to implement TVM functionality:
42!! @f[ 0=PV+(1+i\cdot b)\cdot\frac{PMT}{i}\cdot\left(1-\frac{1}{(1+i)^n}\right)+\frac{FV}{(1+i)^n} @f]
43!!
44!! This single equation defines several relationships, and can be used to solve a great many different kinds of TVM problems.
45!!
46!! The modular approach taken in tvm.f90 and cashflows.f90 is a far more flexible to and powerful way to solve TVM problems;
47!! however, the simplicity and familiarity of the classical calculator approach is sometimes more comfortable and direct.
48!!
50 use mrffl_config, only: rk=>mrfflrk, ik=>mrfflik, cnfmt=>mrfflcnfmt, ctfmt=>mrfflctfmt, zero_epsilon
51 use mrffl_bitset;
54 ! use mrffl_solver_ne, only: multi_bisection
56 implicit none
57 private
58
59 ! Quite a lot of code depends on the values being 1 & 0. Do not change them!
60 integer(kind=ik), parameter, public :: pmt_at_beginning = 1
61 integer(kind=ik), parameter, public :: pmt_at_end = 0
62
63 public :: tvm12_solve, tvm12_print
64 public :: var_i, var_n, var_pv, var_fv, var_pmt
65
66contains
67
68 !------------------------------------------------------------------------------------------------------------------------------
69 !> Solve TVM Equation.
70 !!
71 !! Value returned in status:
72 !! - 0 - Everything worked
73 !! - 1 - ERROR(tvm_solve): n==0!
74 !! - 2 - ERROR(tvm_solve): n<0!
75 !! - 3 - ERROR(tvm_solve): Unsupported value for pmt_time (must be one of pmt_at_beginning or pmt_at_end)
76 !! - 4 - ERROR(tvm_solve): Unsupported value for action!
77 !! - 5 - ERROR(tvm_solve): Unable to solve for i
78 !! - 6 - ERROR(tvm_solve): Can not solve for n!
79 !! - 7 - ERROR(tvm_solve): i near -1!
80 !! - 8 - ERROR(tvm_solve): i near zero!
81 !! - 9 - Reserved
82 !!
83 !! WARNING: Solving for i is not entirely reliable. Only values between -1 and 1 may be found. Additionally it is
84 !! possible a solution in that range might not be found.
85 !!
86 !! @param n Number of compounding periods
87 !! @param i Interest as a fraction (not a percentage)
88 !! @param pv Present Value
89 !! @param pmt Payment
90 !! @param fv Future Value
91 !! @param pmt_time Payments at beginning or end of period. Allowed parameters: pmt_at_beginning or pmt_at_end
92 !! @param unknown The unknown variable to solve for. Allowed parameters: var_pmt var_i, var_n, var_pv, or var_fv.
93 !! @param status Returns status of computation. 0 if everything worked. Range: 0 & 3001-3032.
94 subroutine tvm12_solve(n, i, pv, pmt, fv, pmt_time, unknown, status)
95 integer(kind=ik), intent(inout) :: n
96 real(kind=rk), intent(inout) :: i, pv, pmt, fv
97 integer(kind=ik), intent(in) :: pmt_time, unknown
98 integer(kind=ik), intent(out) :: status
99 real (kind=rk) :: ip1tn, tmp1, tmp2, islvivl0(3), islvivl1(3), r_dat(4)
100 integer(kind=ik) :: i_dat(2)
101 if (unknown /= var_n) then
102 if (n == 0) then
103 status = 3001 ! "ERROR(tvm_solve): n==0!"
104 return
105 end if
106 if (n < 0) then
107 status = 3002 ! "ERROR(tvm_solve): n<0!"
108 return
109 end if
110 end if
111 if (unknown /= var_i) then
112 if (abs(i) < zero_epsilon) then
113 status = 3003 ! "ERROR(tvm_solve): i near zero!"
114 return
115 else if (abs(1+i) < zero_epsilon) then
116 status = 3004 ! "ERROR(tvm_solve): i near -1!"
117 return
118 end if
119 end if
120 r_dat = [ i, pv, pmt, fv ]
121 i_dat = [ n, pmt_time ]
122 islvivl0 = [ 0.0_rk+zero_epsilon, -100.0_rk+zero_epsilon, -99999.0_rk]
123 islvivl1 = [ 99999.0_rk, 0.0_rk-zero_epsilon, -100.0_rk-zero_epsilon]
124 ip1tn = (1+i) ** n
125 if (pmt_time == pmt_at_beginning) then
126 if (unknown == var_pmt) then
127 pmt = i * (fv + ip1tn * pv) / (i + 1 - (1+i) * ip1tn)
128 status = 0
129 else if (unknown == var_i) then
130 call multi_bisection(i, islvivl0, islvivl1, i_slv_func, 1.0e-5_rk, 1.0e-5_rk, 1000_ik, status, .false.)
131 !call multi_bisection(i, islvivl0, islvivl1, i_slv_func, r_dat, i_dat, 1.0e-5_rk, 1.0e-5_rk, 1000, status, .false.)
132 if (status /= 0) then
133 status = 3005 ! "ERROR(tvm_solve): Unable to solve for i!"
134 end if
135 else if (unknown == var_n) then
136 tmp1 = ((-1 - i) * pmt - pv * i) / ((-1 - i) * pmt + fv * i)
137 tmp2 = 1 + i
138 if ((tmp1 < zero_epsilon) .or. (tmp2 < zero_epsilon)) then
139 status = 3006 ! "ERROR(tvm_solve): Can not solve for n!"
140 else
141 n = nint(-log(tmp1) / log(tmp2), kind=ik)
142 status = 0
143 end if
144 else if (unknown == var_pv) then
145 pv = ((pmt * (1 + i) - fv * i) * (1 + i) ** (-n) - pmt * (1 + i)) / i
146 status = 0
147 else if (unknown == var_fv) then
148 fv = (((-1 - i) * pmt - pv * i) * ip1tn + pmt * (1 + i)) / i
149 status = 0
150 end if
151 else if (pmt_time == pmt_at_end) then
152 if (unknown == var_pmt) then
153 pmt = -i / (ip1tn - 1) * (fv + ip1tn * pv)
154 status = 0
155 else if (unknown == var_i) then
156 call multi_bisection(i, islvivl0, islvivl1, i_slv_func, 1.0e-5_rk, 1.0e-5_rk, 1000_ik, status, .false.)
157 !call multi_bisection(i, islvivl0, islvivl1, i_slv_func, r_dat, i_dat, 1.0e-5_rk, 1.0e-5_rk, 1000, status, .false.)
158 if (status /= 0) then
159 status = 3007 ! "ERROR(tvm_solve): Unable to solve for i!"
160 end if
161 else if (unknown == var_n) then
162 tmp1 = (-pv * i - pmt) / (fv * i - pmt)
163 tmp2 = 1 + i
164 if ((tmp1 < zero_epsilon) .or. (tmp2 < zero_epsilon)) then
165 status = 3008 ! "ERROR(tvm_solve): Can not solve for n!"
166 else
167 n = nint(-log(tmp1) / log(tmp2), kind=ik)
168 status = 0
169 end if
170 else if (unknown == var_pv) then
171 pv = ((-fv * i + pmt) / ip1tn - pmt) / i
172 status = 0
173 else if (unknown == var_fv) then
174 fv = ((-pv * i - pmt) * ip1tn + pmt) / i
175 status = 0
176 else
177 status = 3009 ! "ERROR(tvm_solve): Unsupported value for unknown!"
178 end if
179 else
180 status = 3010 ! "ERROR(tvm_solve): Unsupported value for pmt_time (must be one of pmt_at_beginning or pmt_at_end)"
181 end if
182 contains
183 real(kind=rk) function i_slv_func(x)
184 implicit none
185 real(kind=rk), intent(in) :: x
186 i_slv_func = (((-pmt_time * pmt + fv) * x - pmt) * (1 + x) ** (-n) + (pmt_time * pmt + pv) * x + pmt) / x
187 end function i_slv_func
188 end subroutine tvm12_solve
189
190
191 ! real(kind=rk) function i_slv_func(x, r_dat, i_dat)
192 ! implicit none
193 ! real(kind=rk), intent(in) :: x
194 ! real(kind=rk), intent(in) :: r_dat(:)
195 ! integer(kind=ik), intent(in) :: i_dat(:)
196 ! integer(kind=ik) :: n, pmt_time
197 ! real(kind=rk) :: i, pv, pmt, fv
198 ! i = r_dat(1)
199 ! pv = r_dat(2)
200 ! pmt = r_dat(3)
201 ! fv = r_dat(4)
202 ! n = i_dat(1)
203 ! pmt_time = i_dat(2)
204 ! i_slv_func = (((-pmt_time * pmt + fv) * x - pmt) * (1 + x) ** (-n) + (pmt_time * pmt + pv) * x + pmt) / x
205 ! end function i_slv_func
206
207
208 !------------------------------------------------------------------------------------------------------------------------------
209 !> Print TVM Problem (variables and/or table)
210 !!
211 !! @param n Number of compounding periods
212 !! @param i Interest as a fraction (not a percentage)
213 !! @param pv Present Value
214 !! @param pmt Payment
215 !! @param fv Future Value
216 !! @param pmt_time Payments at beginning or end of period. Allowed parameters: pmt_at_beginning or pmt_at_end
217 !! @param print_out Set made from the following constants: prt_param, prt_table, prt_title
218 subroutine tvm12_print(n, i, pv, pmt, fv, pmt_time, print_out)
219 implicit none
220 integer(kind=ik), intent(in) :: n, pmt_time, print_out
221 real(kind=rk), intent(in) :: i, pv, fv, pmt
222 real (kind=rk) :: tot_pmt, cur_pv
223 integer(kind=ik) :: k
224
225 if ((pmt_time /= pmt_at_beginning) .and. (pmt_time /= pmt_at_end)) then
226 stop "ERROR(tvm_solve): Unsupported value for pmt_time (must be one of pmt_at_beginning or pmt_at_end)"
227 end if
228 if (bitset_intersectp(prt_param+prt_table, print_out)) then
229 print *, ""
230 end if
231 if (bitset_subsetp(prt_param, print_out)) then
232 print "(a20,i30)", "n:", n
233 print "(a20,f30.8)", "i:", i
234 print "(a20,f30.8)", "pv:", pv
235 print "(a20,f30.8)", "fv:", fv
236 print "(a20,f30.8)", "pmt:", pmt
237 if (pmt_time == pmt_at_beginning) then
238 print "(a20,a30)", "pmt_time:", "BEGIN"
239 else
240 print "(a20,a30)", "pmt_time:", "END"
241 end if
242 end if
243 if (bitset_intersectp(prt_param+prt_table, print_out)) then
244 print *
245 end if
246 if (bitset_subsetp(prt_table, print_out)) then
247 if (bitset_subsetp(prt_title, print_out)) print "(a8,2(1x,"//ctfmt//"))", "period", "cur_pv", "tot_pmt"
248 cur_pv = pv
249 tot_pmt = 0
250 do k=0,n
251 if (pmt_time == pmt_at_beginning) then
252 print "(i8,2(1x,"//cnfmt//"))", k, cur_pv, tot_pmt
253 tot_pmt = tot_pmt + pmt
254 cur_pv = cur_pv + pmt
255 cur_pv = cur_pv + cur_pv * i
256 else if (pmt_time == pmt_at_end) then
257 print "(i8,2(1x,"//cnfmt//"))", k, cur_pv, tot_pmt
258 cur_pv = cur_pv + cur_pv * i
259 tot_pmt = tot_pmt + pmt
260 cur_pv = cur_pv + pmt
261 end if
262 end do
263 end if
264 if (bitset_intersectp(prt_param+prt_table, print_out)) then
265 print *, ""
266 end if
267 end subroutine tvm12_print
268
269end module mrffl_tvm12
real(kind=rk) function i_slv_func(x)
Simple sets (using the bits of an integer to indicate element existence).
logical pure function, public bitset_intersectp(bitset1, bitset2)
logical pure function, public bitset_subsetp(bitset1, bitset2)
Configuration for MRFFL (MR Fortran Finance Library).
integer, parameter, public mrfflrk
Real kind used in interfaces.
real(kind=mrfflrk), parameter, public zero_epsilon
Used to test for zero.
integer, parameter, public mrfflik
Integer kinds used in interfaces.
character(len=5), public mrfflcnfmt
Used to print cash values.
character(len=5), public mrfflctfmt
Used to print cash titles.
Constants to select what *_print subroutines will print.
integer(kind=ik), parameter, public prt_table
Print a table.
integer(kind=ik), parameter, public prt_param
Print parameters before the table/titles.
integer(kind=ik), parameter, public prt_title
Print titles on the tables.
Root solvers.
subroutine, public multi_bisection(xc, x0_init, x1_init, f, x_epsilon, y_epsilon, max_itr, status, progress)
Use bisection() to search for a root for the function f in a list of intervals returning the first ro...
Provides a TVM solver with functionality similar to modern financial calculators.
subroutine, public tvm12_print(n, i, pv, pmt, fv, pmt_time, print_out)
Print TVM Problem (variables and/or table)
integer(kind=ik), parameter, public pmt_at_beginning
subroutine, public tvm12_solve(n, i, pv, pmt, fv, pmt_time, unknown, status)
Solve TVM Equation.
integer(kind=ik), parameter, public pmt_at_end
Constants to to identify TVM variables.
integer(kind=ik), parameter, public var_pv
Present value.
integer(kind=ik), parameter, public var_fv
Future value.
integer(kind=ik), parameter, public var_n
Number of periods.
integer(kind=ik), parameter, public var_pmt
Currently this is only used by the tvm12 module.
integer(kind=ik), parameter, public var_i
Interest/rate (First rate for geometric annuity)