From de242a0c9cb32ebdf6e25c887130cf7ebe24e7d1 Mon Sep 17 00:00:00 2001 From: Nathan Wagner Date: Wed, 9 Jul 2014 09:23:14 +0000 Subject: [PATCH] initial commit from pause --- .cvsignore | 10 ++ Changes | 28 ++++ MANIFEST | 13 ++ META.yml | 14 ++ Makefile.PL | 16 +++ README | 131 +++++++++++++++++++ examples/htmlamtable | 34 +++++ lib/Finance/Amortization.pm | 250 ++++++++++++++++++++++++++++++++++++ t/00-load.t | 9 ++ t/basic.t | 17 +++ t/boilerplate.t | 48 +++++++ t/pod-coverage.t | 6 + t/pod.t | 6 + 13 files changed, 582 insertions(+) create mode 100644 .cvsignore create mode 100644 Changes create mode 100644 MANIFEST create mode 100644 META.yml create mode 100644 Makefile.PL create mode 100644 README create mode 100755 examples/htmlamtable create mode 100644 lib/Finance/Amortization.pm create mode 100644 t/00-load.t create mode 100644 t/basic.t create mode 100644 t/boilerplate.t create mode 100644 t/pod-coverage.t create mode 100644 t/pod.t diff --git a/.cvsignore b/.cvsignore new file mode 100644 index 0000000..19b4806 --- /dev/null +++ b/.cvsignore @@ -0,0 +1,10 @@ +blib* +Makefile +Makefile.old +Build +_build* +pm_to_blib* +*.tar.gz +.lwpcookies +Finance-Amortization-* +cover_db diff --git a/Changes b/Changes new file mode 100644 index 0000000..4e7a0a6 --- /dev/null +++ b/Changes @@ -0,0 +1,28 @@ +0.5 + +Changes to improve "kwalitee" rating on cpants. + +0.4 + +Fixed doc bug in BUGS section. BUGS now notes that there is a test script, +which should be better. + +Fixed documentation of new() constructor. It now matches the behavior. + +Added TODO about locale dependent rounding. + +0.3 + +Added t directory and a test program. + +Added precision field to round results. + +Fixed some bugs. + +0.2 + +Version number bump to get around PAUSE naming issue. + +0.1 + +Initial release. diff --git a/MANIFEST b/MANIFEST new file mode 100644 index 0000000..aab0b30 --- /dev/null +++ b/MANIFEST @@ -0,0 +1,13 @@ +.cvsignore +Changes +examples/htmlamtable +lib/Finance/Amortization.pm +Makefile.PL +MANIFEST This list of files +META.yml +README +t/00-load.t +t/basic.t +t/boilerplate.t +t/pod-coverage.t +t/pod.t diff --git a/META.yml b/META.yml new file mode 100644 index 0000000..2d9b4c5 --- /dev/null +++ b/META.yml @@ -0,0 +1,14 @@ +--- #YAML:1.0 +name: Finance-Amortization +version: 0.5 +abstract: Simple Amortization Schedules +license: ~ +generated_by: ExtUtils::MakeMaker version 6.32 +distribution_type: module +requires: + Test::More: 0 +meta-spec: + url: http://module-build.sourceforge.net/META-spec-v1.2.html + version: 1.2 +author: + - Nathan Wagner diff --git a/Makefile.PL b/Makefile.PL new file mode 100644 index 0000000..01782c6 --- /dev/null +++ b/Makefile.PL @@ -0,0 +1,16 @@ +use strict; +use warnings; +use ExtUtils::MakeMaker; + +WriteMakefile( + NAME => 'Finance::Amortization', + AUTHOR => 'Nathan Wagner ', + VERSION_FROM => 'lib/Finance/Amortization.pm', + ABSTRACT_FROM => 'lib/Finance/Amortization.pm', + PL_FILES => {}, + PREREQ_PM => { + 'Test::More' => 0, + }, + dist => { COMPRESS => 'gzip -9f', SUFFIX => 'gz', }, + clean => { FILES => 'Finance-Amortization-*' }, +); diff --git a/README b/README new file mode 100644 index 0000000..b8436c8 --- /dev/null +++ b/README @@ -0,0 +1,131 @@ +Finance::Amortization(3U)ser Contributed Perl DocumentatiFoinnance::Amortization(3) + + + +NNAAMMEE + Finance::Amortization - Simple Amortization Schedules + +SSYYNNOOPPSSIISS + use Finance::Amortization + + # make a new schedule + + $amortization = new Finance::Amortization(principal => 100000, rate = + 0.06/12, periods = 360); + + # get the balance after a the twelveth period + + $balance = $amortization->balance(12) + + # get the interest paid during the twelfth period + + $interest = $amortization->interest(12); + +DDEESSCCRRIIPPTTIIOONN + Finance::Amortization is a simple object oriented interface to an amor- + tization table. Pass in the principal to be amortized, the number of + payments to be made, and the interest rate per payment. It will calcu- + late the rest on demand, and provides a few methods to ask for the + state of the table after a given number of periods. + + Finance::Amortization is written in pure perl and does not depend on + any other modules. It exports no functions; all access is via methods + called on an amortization object. (Except for _n_e_w_(_), of course.) + + _n_e_w_(_) + + $am = Finance::Amortization->new(principal => 0, rate => 0, periods => + 0, compounding => 12, precision => 2); + + Creates a new amortization object. Calling interface is hash style. + The fields principal, rate, and periods are available, all defaulting + to zero. + + Compounding is a parameter which sets how many periods the rate is com- + pounded over. Thus, if each amortization period is one month, setting + compounding to 12 (the default), will make the rate an annual rate. + That is, the interest rate per period is the rate specified, divided by + the compounding. + + So, to get an amortization for 30 years on 200000, with a 6% annual + rate, you would call new(principal => 200000, periods => 12*30, rate => + 0.06), the compounding will default to 12, and so the rate will work + out right for monthly payments. + + precision is used to specify the number of decimal places to round to + when returning answers. It defaults to 2, which is appropriate for US + currency and many others. + + _r_a_t_e_(_) + + $rate_per_period = $am->_r_a_t_e_(_) + + returns the interest rate per period. Ignores any arguments. + + _p_r_i_n_c_i_p_a_l_(_) + + $initial_value = $am->_p_r_i_n_c_i_p_a_l_(_) + + returns the initial principal being amortized. Ignores any arguments. + + _p_e_r_i_o_d_s_(_) + + $number_of_periods = $am->_p_e_r_i_o_d_s_(_) + + returns the number of periods in which the principal is being amor- + tized. Ignores any arguments. + + _p_a_y_m_e_n_t_(_) + + $pmt = $am->_p_a_y_m_e_n_t_(_) + + returns the payment per period. This method will cache the value the + first time it is called. + + bbaallaannccee((nn)) + + $balance = $am->balance(12); + + Returns the balance of the amortization after the period given in the + argument + + iinntteerreesstt((nn)) + + $interest = $am->interest(12); + + Returns the interest paid in the period given in the argument + +BBUUGGSS + This module uses perl's floating point for financial calculations. + This may introduce inaccuracies and/or make this module unsuitable for + serious financial applications. + +TTOODDOO + Use Math::BigRat for the calculations. + + Provide amortizers for present value, future value, annuities, etc. + + Allow for caching calculated values. + + Provide output methods and converters to various table modules. + HTML::Table, Text::Table, and Data::Table come to mind. + + Write better test scripts. + + Better checking for errors and out of range input. Return undef in + these cases. + + Use a locale dependent value to set an appropriate default for preci- + sion in the _n_e_w_(_) method. + +LLIICCEENNSSEE + None. This entire module is in the public domain. + +AAUUTTHHOORR + Nathan Wagner + + This entire module is written by me and placed into the public domain. + + + +perl v5.8.6 2007-05-27 Finance::Amortization(3) diff --git a/examples/htmlamtable b/examples/htmlamtable new file mode 100755 index 0000000..4b27998 --- /dev/null +++ b/examples/htmlamtable @@ -0,0 +1,34 @@ +#!/usr/bin/perl + +use strict; +use warnings; + +use lib '../blib/lib'; + +use Finance::Amortization; +use HTML::Table; + +# this program generates a 30 year amortization table on a $200,000 +# loan at 7% + +my $am = Finance::Amortization->new(principal => 200000, rate => .07, + periods => 30*12, compounding => 12, precision => 2); + + +#balance, interest, payment + +my @cols = qw(Period Payment Interest Principal Balance); + +my $table = HTML::Table->new(0, 5); + +$table->addRow(@cols); +$table->setRowHead(1); + +my $pmt = $am->payment; + +for(1 .. 30*12) { + my ($int, $bal) = ($am->interest($_),$am->balance($_)); + $table->addRow($_, $pmt, $int, $pmt-$int, $bal); +} + +$table->print; diff --git a/lib/Finance/Amortization.pm b/lib/Finance/Amortization.pm new file mode 100644 index 0000000..f752f66 --- /dev/null +++ b/lib/Finance/Amortization.pm @@ -0,0 +1,250 @@ +package Finance::Amortization; + +use strict; +use warnings; + +our $VERSION = '0.5'; + +=head1 NAME + +Finance::Amortization - Simple Amortization Schedules + +=head1 SYNOPSIS + +use Finance::Amortization + +# make a new schedule + +$amortization = new Finance::Amortization(principal => 100000, rate = 0.06/12, + periods = 360); + +# get the balance after a the twelveth period + +$balance = $amortization->balance(12) + +# get the interest paid during the twelfth period + +$interest = $amortization->interest(12); + +=head1 DESCRIPTION + +Finance::Amortization is a simple object oriented interface to an +amortization table. Pass in the principal to be amortized, the number +of payments to be made, and the interest rate per payment. It will +calculate the rest on demand, and provides a few methods to ask +for the state of the table after a given number of periods. + +Finance::Amortization is written in pure perl and does not depend +on any other modules. It exports no functions; all access is via +methods called on an amortization object. (Except for new(), of course.) + +=cut + +=head2 new() + +$am = Finance::Amortization->new(principal => 0, rate => 0, periods => 0, + compounding => 12, precision => 2); + +Creates a new amortization object. Calling interface is hash style. +The fields principal, rate, and periods are available, all defaulting +to zero. + +Compounding is a parameter which sets how many periods the rate is compounded +over. Thus, if each amortization period is one month, setting compounding +to 12 (the default), will make the rate an annual rate. That is, the +interest rate per period is the rate specified, divided by the compounding. + +So, to get an amortization for 30 years on 200000, with a 6% annual rate, +you would call new(principal => 200000, periods => 12*30, rate => 0.06), +the compounding will default to 12, and so the rate will work out right +for monthly payments. + +precision is used to specify the number of decimal places to round to +when returning answers. It defaults to 2, which is appropriate for +US currency and many others. + +=cut + +sub new { + my $pkg = shift; + # bless package variables + my %conf = ( + principal => 0.00, + rate => 0.00, + compounding => 12, + precision => 2, # how many decimals to round + @_ + ); + if (!defined $conf{'periods'}) { + $conf{'periods'} = $conf{'length'} * $conf{'compounding'}; + } + if (defined($conf{'compounding'})) { + $conf{'rate'} /= $conf{'compounding'}; + } + + bless { + %conf + }, $pkg; +} + +=head2 rate() + +$rate_per_period = $am->rate() + +returns the interest rate per period. Ignores any arguments. + +=cut + +sub rate { + my $am = shift; + return $am->{'rate'}; +} + +=head2 principal() + +$initial_value = $am->principal() + +returns the initial principal being amortized. Ignores any arguments. + +=cut + +sub principal { + my $am = shift; + return sprintf('%.*f', $am->{'precision'}, $am->{'principal'}); +} + +=head2 periods() + +$number_of_periods = $am->periods() + +returns the number of periods in which the principal is being amortized. +Ignores any arguments. + +=cut + +sub periods { + my $am = shift; + return $am->{'periods'}; +} + +#P = r*L*(1+r)^n/{(1+r)^n - 1} + +=head2 payment() + +$pmt = $am->payment() + +returns the payment per period. This method will cache the value the +first time it is called. + +=cut + +sub payment { + my $am = shift; + + if ($am->{'payment'}) { + return $am->{'payment'} + } + + my $r = $am->rate; + my $r1 = $r + 1; + my $n = $am->periods(); + my $p = $am->principal; + + if ($r == 0) { + return $am->{'payment'} = $p / $n; + } + + $am->{'payment'} = sprintf('%.2f', $r * $p * $r1**$n / ($r1**$n-1)); +} + +=head2 balance(n) + +$balance = $am->balance(12); + +Returns the balance of the amortization after the period given in the +argument + +=cut + +sub balance { + my $am = shift; + my $period = shift; + return $am->principal() if $period == 0; + + return 0 if ($period < 1 or $period > $am->periods); + + my $rate = $am->rate; + my $rate1 = $rate + 1; + my $periods = $am->periods(); + my $principal = $am->principal; + my $pmt = $am->payment(); + + return sprintf('%.*f', $am->{'precision'}, + $principal*$rate1**$period-$pmt*($rate1**$period - 1)/$rate); + +} + +=head2 interest(n) + +$interest = $am->interest(12); + +Returns the interest paid in the period given in the argument + +=cut + +sub interest { + my $am = shift; + my $period = shift; + + return 0 if ($period < 1 or $period > $am->periods); + + my $rate = $am->rate; + + return sprintf('%.*f', $am->{'precision'}, + $rate * $am->balance($period - 1)); +} + +=head1 BUGS + +This module uses perl's floating point for financial calculations. This +may introduce inaccuracies and/or make this module unsuitable for serious +financial applications. + +Please report any bugs or feature requests to +C, or through the web interface at +L. + +=head1 TODO + +Use Math::BigRat for the calculations. + +Provide amortizers for present value, future value, annuities, etc. + +Allow for caching calculated values. + +Provide output methods and converters to various table modules. +HTML::Table, Text::Table, and Data::Table come to mind. + +Write better test scripts. + +Better checking for errors and out of range input. Return undef +in these cases. + +Use a locale dependent value to set an appropriate default for precision +in the new() method. + +=head1 LICENSE + +None. This entire module is in the public domain. + +=head1 AUTHOR + +Nathan Wagner + +This entire module is written by me and placed into the public domain. + +=cut + +1; + +__END__ diff --git a/t/00-load.t b/t/00-load.t new file mode 100644 index 0000000..2116354 --- /dev/null +++ b/t/00-load.t @@ -0,0 +1,9 @@ +#!perl -T + +use Test::More tests => 1; + +BEGIN { + use_ok( 'Finance::Amortization' ); +} + +diag( "Testing Finance::Amortization $Finance::Amortization::VERSION, Perl $], $^X" ); diff --git a/t/basic.t b/t/basic.t new file mode 100644 index 0000000..bcf874b --- /dev/null +++ b/t/basic.t @@ -0,0 +1,17 @@ +#!/usr/bin/perl + +use Test::More tests => 7; + +BEGIN { use_ok('Finance::Amortization') } + +my $am = Finance::Amortization->new(principal => 10000, rate => 0.12, + periods => 12 * 5); + +isa_ok($am, 'Finance::Amortization'); + +is($am->periods(), 60, 'periods'); +is($am->principal, '10000.00', 'principal'); +is($am->rate, 0.01, 'rate'); +is($am->balance(1), 9877.56, 'balance 1'); +is($am->interest(1), '100.00', 'interest 1'); + diff --git a/t/boilerplate.t b/t/boilerplate.t new file mode 100644 index 0000000..fa70ec2 --- /dev/null +++ b/t/boilerplate.t @@ -0,0 +1,48 @@ +#!perl -T + +use strict; +use warnings; +use Test::More tests => 3; + +sub not_in_file_ok { + my ($filename, %regex) = @_; + open my $fh, "<", $filename + or die "couldn't open $filename for reading: $!"; + + my %violated; + + while (my $line = <$fh>) { + while (my ($desc, $regex) = each %regex) { + if ($line =~ $regex) { + push @{$violated{$desc}||=[]}, $.; + } + } + } + + if (%violated) { + fail("$filename contains boilerplate text"); + diag "$_ appears on lines @{$violated{$_}}" for keys %violated; + } else { + pass("$filename contains no boilerplate text"); + } +} + +not_in_file_ok(README => + "The README is used..." => qr/The README is used/, + "'version information here'" => qr/to provide version information/, +); + +not_in_file_ok(Changes => + "placeholder date/time" => qr(Date/time) +); + +sub module_boilerplate_ok { + my ($module) = @_; + not_in_file_ok($module => + 'the great new $MODULENAME' => qr/ - The great new /, + 'boilerplate description' => qr/Quick summary of what the module/, + 'stub function definition' => qr/function[12]/, + ); +} + +module_boilerplate_ok('lib/Finance/Amortization.pm'); diff --git a/t/pod-coverage.t b/t/pod-coverage.t new file mode 100644 index 0000000..703f91d --- /dev/null +++ b/t/pod-coverage.t @@ -0,0 +1,6 @@ +#!perl -T + +use Test::More; +eval "use Test::Pod::Coverage 1.04"; +plan skip_all => "Test::Pod::Coverage 1.04 required for testing POD coverage" if $@; +all_pod_coverage_ok(); diff --git a/t/pod.t b/t/pod.t new file mode 100644 index 0000000..976d7cd --- /dev/null +++ b/t/pod.t @@ -0,0 +1,6 @@ +#!perl -T + +use Test::More; +eval "use Test::Pod 1.14"; +plan skip_all => "Test::Pod 1.14 required for testing POD" if $@; +all_pod_files_ok(); -- 2.40.0