initial commit from pause
[amortization] / lib / Finance / Amortization.pm
1 package Finance::Amortization;
2
3 use strict;
4 use warnings;
5
6 our $VERSION = '0.5';
7
8 =head1 NAME
9
10 Finance::Amortization - Simple Amortization Schedules
11
12 =head1 SYNOPSIS
13
14 use Finance::Amortization
15
16 # make a new schedule
17
18 $amortization = new Finance::Amortization(principal => 100000, rate = 0.06/12,
19         periods = 360);
20
21 # get the balance after a the twelveth period
22
23 $balance = $amortization->balance(12)
24
25 # get the interest paid during the twelfth period
26
27 $interest = $amortization->interest(12);
28
29 =head1 DESCRIPTION
30
31 Finance::Amortization is a simple object oriented interface to an
32 amortization table.  Pass in the principal to be amortized, the number
33 of payments to be made, and the interest rate per payment.  It will
34 calculate the rest on demand, and provides a few methods to ask
35 for the state of the table after a given number of periods.
36
37 Finance::Amortization is written in pure perl and does not depend
38 on any other modules.  It exports no functions; all access is via
39 methods called on an amortization object.  (Except for new(), of course.)
40
41 =cut
42
43 =head2 new()
44
45 $am = Finance::Amortization->new(principal => 0, rate => 0, periods => 0,
46         compounding => 12, precision => 2);
47
48 Creates a new amortization object.  Calling interface is hash style.
49 The fields principal, rate, and periods are available, all defaulting
50 to zero.
51
52 Compounding is a parameter which sets how many periods the rate is compounded
53 over.  Thus, if each amortization period is one month, setting compounding
54 to 12 (the default), will make the rate an annual rate.  That is, the
55 interest rate per period is the rate specified, divided by the compounding.
56
57 So, to get an amortization for 30 years on 200000, with a 6% annual rate,
58 you would call new(principal => 200000, periods => 12*30, rate => 0.06),
59 the compounding will default to 12, and so the rate will work out right
60 for monthly payments.
61
62 precision is used to specify the number of decimal places to round to
63 when returning answers.  It defaults to 2, which is appropriate for
64 US currency and many others.
65
66 =cut
67
68 sub new {
69         my $pkg = shift;
70         # bless package variables
71         my %conf = (
72                 principal => 0.00,
73                 rate => 0.00,
74                 compounding => 12,
75                 precision => 2, # how many decimals to round
76                 @_
77         );
78         if (!defined $conf{'periods'}) {
79                 $conf{'periods'} = $conf{'length'} * $conf{'compounding'};
80         }
81         if (defined($conf{'compounding'})) {
82                 $conf{'rate'} /= $conf{'compounding'};
83         }
84
85         bless {
86                 %conf
87         }, $pkg;
88 }
89
90 =head2 rate()
91
92 $rate_per_period = $am->rate()
93
94 returns the interest rate per period.  Ignores any arguments.
95
96 =cut
97
98 sub rate {
99         my $am = shift;
100         return $am->{'rate'};
101 }       
102
103 =head2 principal()
104
105 $initial_value = $am->principal()
106
107 returns the initial principal being amortized.  Ignores any arguments.
108
109 =cut
110
111 sub principal {
112         my $am = shift;
113         return sprintf('%.*f', $am->{'precision'}, $am->{'principal'});
114 }       
115
116 =head2 periods()
117
118 $number_of_periods = $am->periods()
119
120 returns the number of periods in which the principal is being amortized.
121 Ignores any arguments.
122
123 =cut
124
125 sub periods {
126         my $am = shift;
127         return $am->{'periods'};
128 }       
129
130 #P = r*L*(1+r)^n/{(1+r)^n - 1}
131
132 =head2 payment()
133
134 $pmt = $am->payment()
135
136 returns the payment per period.  This method will cache the value the
137 first time it is called.
138
139 =cut
140
141 sub payment {
142         my $am = shift;
143
144         if ($am->{'payment'}) {
145                 return $am->{'payment'}
146         }
147
148         my $r = $am->rate;
149         my $r1 = $r + 1;
150         my $n = $am->periods();
151         my $p = $am->principal;
152
153         if ($r == 0) {
154                 return $am->{'payment'} = $p / $n;
155         }
156
157         $am->{'payment'} = sprintf('%.2f', $r * $p * $r1**$n / ($r1**$n-1));
158 }
159
160 =head2 balance(n)
161
162 $balance = $am->balance(12);
163
164 Returns the balance of the amortization after the period given in the
165 argument
166
167 =cut
168
169 sub balance {
170         my $am = shift;
171         my $period = shift;
172         return $am->principal() if $period == 0;
173
174         return 0 if ($period < 1 or $period > $am->periods);
175
176         my $rate = $am->rate;
177         my $rate1 = $rate + 1;
178         my $periods = $am->periods();
179         my $principal = $am->principal;
180         my $pmt = $am->payment();
181
182         return sprintf('%.*f', $am->{'precision'},
183                  $principal*$rate1**$period-$pmt*($rate1**$period - 1)/$rate);
184
185 }
186
187 =head2 interest(n)
188
189 $interest = $am->interest(12);
190
191 Returns the interest paid in the period given in the argument
192
193 =cut
194
195 sub interest {
196         my $am = shift;
197         my $period = shift;
198
199         return 0 if ($period < 1 or $period > $am->periods);
200
201         my $rate = $am->rate;
202
203         return sprintf('%.*f', $am->{'precision'},
204                 $rate * $am->balance($period - 1));
205 }
206
207 =head1 BUGS
208
209 This module uses perl's floating point for financial calculations.  This
210 may introduce inaccuracies and/or make this module unsuitable for serious
211 financial applications.
212
213 Please report any bugs or feature requests to
214 C<bug-finance-amortization at rt.cpan.org>, or through the web interface at
215 L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Finance-Amortization>.
216
217 =head1 TODO
218
219 Use Math::BigRat for the calculations.
220
221 Provide amortizers for present value, future value, annuities, etc.
222
223 Allow for caching calculated values.
224
225 Provide output methods and converters to various table modules.
226 HTML::Table, Text::Table, and Data::Table come to mind.
227
228 Write better test scripts.
229
230 Better checking for errors and out of range input.  Return undef
231 in these cases.
232
233 Use a locale dependent value to set an appropriate default for precision
234 in the new() method.
235
236 =head1 LICENSE
237
238 None.  This entire module is in the public domain.
239
240 =head1 AUTHOR
241
242 Nathan Wagner <nw@hydaspes.if.org>
243
244 This entire module is written by me and placed into the public domain.
245
246 =cut
247
248 1;
249
250 __END__