finance_velocity 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/COPYING.LESSER ADDED
@@ -0,0 +1,167 @@
1
+ # @title COPYING.LESSER
2
+ # @markup none
3
+ GNU LESSER GENERAL PUBLIC LICENSE
4
+ Version 3, 29 June 2007
5
+
6
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
7
+ Everyone is permitted to copy and distribute verbatim copies
8
+ of this license document, but changing it is not allowed.
9
+
10
+
11
+ This version of the GNU Lesser General Public License incorporates
12
+ the terms and conditions of version 3 of the GNU General Public
13
+ License, supplemented by the additional permissions listed below.
14
+
15
+ 0. Additional Definitions.
16
+
17
+ As used herein, "this License" refers to version 3 of the GNU Lesser
18
+ General Public License, and the "GNU GPL" refers to version 3 of the GNU
19
+ General Public License.
20
+
21
+ "The Library" refers to a covered work governed by this License,
22
+ other than an Application or a Combined Work as defined below.
23
+
24
+ An "Application" is any work that makes use of an interface provided
25
+ by the Library, but which is not otherwise based on the Library.
26
+ Defining a subclass of a class defined by the Library is deemed a mode
27
+ of using an interface provided by the Library.
28
+
29
+ A "Combined Work" is a work produced by combining or linking an
30
+ Application with the Library. The particular version of the Library
31
+ with which the Combined Work was made is also called the "Linked
32
+ Version".
33
+
34
+ The "Minimal Corresponding Source" for a Combined Work means the
35
+ Corresponding Source for the Combined Work, excluding any source code
36
+ for portions of the Combined Work that, considered in isolation, are
37
+ based on the Application, and not on the Linked Version.
38
+
39
+ The "Corresponding Application Code" for a Combined Work means the
40
+ object code and/or source code for the Application, including any data
41
+ and utility programs needed for reproducing the Combined Work from the
42
+ Application, but excluding the System Libraries of the Combined Work.
43
+
44
+ 1. Exception to Section 3 of the GNU GPL.
45
+
46
+ You may convey a covered work under sections 3 and 4 of this License
47
+ without being bound by section 3 of the GNU GPL.
48
+
49
+ 2. Conveying Modified Versions.
50
+
51
+ If you modify a copy of the Library, and, in your modifications, a
52
+ facility refers to a function or data to be supplied by an Application
53
+ that uses the facility (other than as an argument passed when the
54
+ facility is invoked), then you may convey a copy of the modified
55
+ version:
56
+
57
+ a) under this License, provided that you make a good faith effort to
58
+ ensure that, in the event an Application does not supply the
59
+ function or data, the facility still operates, and performs
60
+ whatever part of its purpose remains meaningful, or
61
+
62
+ b) under the GNU GPL, with none of the additional permissions of
63
+ this License applicable to that copy.
64
+
65
+ 3. Object Code Incorporating Material from Library Header Files.
66
+
67
+ The object code form of an Application may incorporate material from
68
+ a header file that is part of the Library. You may convey such object
69
+ code under terms of your choice, provided that, if the incorporated
70
+ material is not limited to numerical parameters, data structure
71
+ layouts and accessors, or small macros, inline functions and templates
72
+ (ten or fewer lines in length), you do both of the following:
73
+
74
+ a) Give prominent notice with each copy of the object code that the
75
+ Library is used in it and that the Library and its use are
76
+ covered by this License.
77
+
78
+ b) Accompany the object code with a copy of the GNU GPL and this license
79
+ document.
80
+
81
+ 4. Combined Works.
82
+
83
+ You may convey a Combined Work under terms of your choice that,
84
+ taken together, effectively do not restrict modification of the
85
+ portions of the Library contained in the Combined Work and reverse
86
+ engineering for debugging such modifications, if you also do each of
87
+ the following:
88
+
89
+ a) Give prominent notice with each copy of the Combined Work that
90
+ the Library is used in it and that the Library and its use are
91
+ covered by this License.
92
+
93
+ b) Accompany the Combined Work with a copy of the GNU GPL and this license
94
+ document.
95
+
96
+ c) For a Combined Work that displays copyright notices during
97
+ execution, include the copyright notice for the Library among
98
+ these notices, as well as a reference directing the user to the
99
+ copies of the GNU GPL and this license document.
100
+
101
+ d) Do one of the following:
102
+
103
+ 0) Convey the Minimal Corresponding Source under the terms of this
104
+ License, and the Corresponding Application Code in a form
105
+ suitable for, and under terms that permit, the user to
106
+ recombine or relink the Application with a modified version of
107
+ the Linked Version to produce a modified Combined Work, in the
108
+ manner specified by section 6 of the GNU GPL for conveying
109
+ Corresponding Source.
110
+
111
+ 1) Use a suitable shared library mechanism for linking with the
112
+ Library. A suitable mechanism is one that (a) uses at run time
113
+ a copy of the Library already present on the user's computer
114
+ system, and (b) will operate properly with a modified version
115
+ of the Library that is interface-compatible with the Linked
116
+ Version.
117
+
118
+ e) Provide Installation Information, but only if you would otherwise
119
+ be required to provide such information under section 6 of the
120
+ GNU GPL, and only to the extent that such information is
121
+ necessary to install and execute a modified version of the
122
+ Combined Work produced by recombining or relinking the
123
+ Application with a modified version of the Linked Version. (If
124
+ you use option 4d0, the Installation Information must accompany
125
+ the Minimal Corresponding Source and Corresponding Application
126
+ Code. If you use option 4d1, you must provide the Installation
127
+ Information in the manner specified by section 6 of the GNU GPL
128
+ for conveying Corresponding Source.)
129
+
130
+ 5. Combined Libraries.
131
+
132
+ You may place library facilities that are a work based on the
133
+ Library side by side in a single library together with other library
134
+ facilities that are not Applications and are not covered by this
135
+ License, and convey such a combined library under terms of your
136
+ choice, if you do both of the following:
137
+
138
+ a) Accompany the combined library with a copy of the same work based
139
+ on the Library, uncombined with any other library facilities,
140
+ conveyed under the terms of this License.
141
+
142
+ b) Give prominent notice with the combined library that part of it
143
+ is a work based on the Library, and explaining where to find the
144
+ accompanying uncombined form of the same work.
145
+
146
+ 6. Revised Versions of the GNU Lesser General Public License.
147
+
148
+ The Free Software Foundation may publish revised and/or new versions
149
+ of the GNU Lesser General Public License from time to time. Such new
150
+ versions will be similar in spirit to the present version, but may
151
+ differ in detail to address new problems or concerns.
152
+
153
+ Each version is given a distinguishing version number. If the
154
+ Library as you received it specifies that a certain numbered version
155
+ of the GNU Lesser General Public License "or any later version"
156
+ applies to it, you have the option of following the terms and
157
+ conditions either of that published version or of any later version
158
+ published by the Free Software Foundation. If the Library as you
159
+ received it does not specify a version number of the GNU Lesser
160
+ General Public License, you may choose any version of the GNU Lesser
161
+ General Public License ever published by the Free Software Foundation.
162
+
163
+ If the Library as you received it specifies that a proxy can decide
164
+ whether future versions of the GNU Lesser General Public License shall
165
+ apply, that proxy's public statement of acceptance of any version is
166
+ permanent authorization for you to choose that version for the
167
+ Library.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source "http://rubygems.org"
2
+
3
+ gemspec
data/HISTORY ADDED
@@ -0,0 +1,47 @@
1
+ = Version 2.0.0
2
+ 23 Jul 2013
3
+
4
+ * Removed Integer#months, Integer#years, and replaced Numeric#to_d by Numeric#to_s in the interest of Rails compatibility.
5
+ * Converted unit tests from the shoulda framework to minitest.
6
+ * Removed octal numbers in test_cashflow.rb
7
+ * Thanks to @thadd, @bramswenson, and @xpe for their contributions to this release!
8
+
9
+ = Version 1.1.2
10
+ 16 Jun 2012
11
+
12
+ * Bugfix: Array#irr and Array#xirr check for a valid sequence of cash flows.
13
+ * Bugfix: Integer#months and Integer#years no longer collide with Rails methods.
14
+
15
+ = Version 1.1.0
16
+ 11 Sep 2011
17
+
18
+ * Added XNPV and XIRR functions, with basic testing.
19
+ * Bugfix: Array#sum no longer collides with the Array#sum defined in Rails.
20
+ * Bugfix: Numeric#amortize now correctly calls Finance::Amortization#new.
21
+
22
+ = Version 1.0.0
23
+ 20 Jul 2011
24
+
25
+ * Moved to Ruby 1.9.
26
+ * All classes are now contained within the +Finance+ namespace.
27
+ * LOTS of additional documentation and examples.
28
+ * Introduced _shoulda_ for unit tests, to make things a little more readable.
29
+ * Bugfix: The +amortize+ Numeric method now accepts a variable number of rates.
30
+ * Some code refactoring and clean-up for a small performance increase.
31
+
32
+ = Version 0.2.0
33
+ 28 Jun 2011
34
+
35
+ * Added support for adjustable rate mortgages.
36
+ * Added support for additional payments.
37
+
38
+ = Version 0.1.1
39
+ 21 Jun 2011
40
+
41
+ * Code examples in README now display correctly in the online documentation.
42
+
43
+ = Version 0.1.0
44
+ 21 Jun 2011
45
+
46
+ * Support for fixed-rate mortgage amortization.
47
+ * NPV, IRR array methods for cash flow analysis.
data/README.md ADDED
@@ -0,0 +1,166 @@
1
+ # FINANCE
2
+
3
+ a library for financial modelling in Ruby.
4
+
5
+ ## INSTALL
6
+
7
+ $ sudo gem install finance
8
+
9
+ ## IMPORTANT CHANGES
10
+
11
+ Contributions by [@thadd](https://github.com/thadd) and
12
+ [@bramswenson](https://github.com/bramswenson) have made the `finance`
13
+ library fully compatible with rails, at the cost of the `#years` and
14
+ `#months` convenience methods on `Integer`, as well as the `#to_d` method for
15
+ converting `Numerics` into `DecNums`. These methods have been removed, due to
16
+ conflicts with existing rails methods.
17
+
18
+ Correspondingly, `finance` has been bumped up to version 2.0.
19
+
20
+ ## OVERVIEW
21
+
22
+ ### GETTING STARTED
23
+
24
+ >> require 'finance'
25
+
26
+ *Note:* As of version 1.0.0, the entire library is contained under the
27
+ Finance namespace. Existing code will not work unless you add:
28
+
29
+ >> include Finance
30
+
31
+ for all of the examples below, we'll assume that you have done this.
32
+
33
+ ### AMORTIZATION
34
+
35
+ You are interested in borrowing $250,000 under a 30 year, fixed-rate
36
+ loan with a 4.25% APR.
37
+
38
+ >> rate = Rate.new(0.0425, :apr, :duration => (30 * 12))
39
+ >> amortization = Amortization.new(250000, rate)
40
+
41
+ Find the standard monthly payment:
42
+
43
+ >> amortization.payment
44
+ => DecNum('-1229.91')
45
+
46
+ Find the total cost of the loan:
47
+
48
+ >> amortization.payments.sum
49
+ => DecNum('-442766.55')
50
+
51
+ How much will you pay in interest?
52
+
53
+ >> amortization.interest.sum
54
+ => DecNum('192766.55')
55
+
56
+ How much interest in the first six months?
57
+
58
+ >> amortization.interest[0,6].sum
59
+ => DecNum('5294.62')
60
+
61
+ If your loan has an adjustable rate, no problem. You can pass an
62
+ arbitrary number of rates, and they will be used in the amortization.
63
+ For example, we can look at an amortization of $250000, where the APR
64
+ starts at 4.25%, and increases by 1% every five years.
65
+
66
+ >> values = %w{ 0.0425 0.0525 0.0625 0.0725 0.0825 0.0925 }
67
+ >> rates = values.collect { |value| Rate.new( value, :apr, :duration => (5 * 12) }
68
+ >> arm = Amortization.new(250000, *rates)
69
+
70
+ Since we are looking at an ARM, there is no longer a single "payment" value.
71
+
72
+ >> arm.payment
73
+ => nil
74
+
75
+ But we can look at the different payments over time.
76
+
77
+ >> arm.payments.uniq
78
+ => [DecNum('-1229.85'), DecNum('-1360.41'), DecNum('-1475.65'), DecNum('-1571.07'), ... snipped ... ]
79
+
80
+ The other methods previously discussed can be accessed in the same way:
81
+
82
+ >> arm.interest.sum
83
+ => DecNum('287515.45')
84
+ >> arm.payments.sum
85
+ => DecNum('-537515.45')
86
+
87
+ Last, but not least, you may pass a block when creating an Amortization
88
+ which returns a modified monthly payment. For example, to increase your
89
+ payment by $150, do:
90
+
91
+ >> rate = Rate.new(0.0425, :apr, :duration => (30 * 12))
92
+ >> extra_payments = 250000.amortize(rate){ |period| period.payment - 150 }
93
+
94
+ Disregarding the block, we have used the same parameters as the first
95
+ example. Notice the difference in the results:
96
+
97
+ >> amortization.payments.sum
98
+ => DecNum('-442745.98')
99
+ >> extra_payments.payments.sum
100
+ => DecNum('-400566.24')
101
+ >> amortization.interest.sum
102
+ => DecNum('192745.98')
103
+ >> extra_payments.interest.sum
104
+ => DecNum('150566.24')
105
+
106
+ You can also increase your payment to a specific amount:
107
+
108
+ >> extra_payments_2 = 250000.amortize(rate){ -1500 }
109
+
110
+ ## ABOUT
111
+
112
+ I started developing `finance` while analyzing mortgages as a personal
113
+ project. Spreadsheets have convenient formulas for doing this type of
114
+ work, until you want to do something semi-complex (like ARMs or extra
115
+ payments), at which point you need to create your own amortization
116
+ table. I thought I could create a better interface for this type of
117
+ work in Ruby, and since I couldn't find an existing resource for these
118
+ tools, I am hoping to save other folks some time by releasing what I
119
+ have as a gem.
120
+
121
+ More broadly, I believe there are many calculations that are necessary
122
+ for the effective management of personal finances, but are difficult
123
+ (or impossible) to do with spreadsheets or other existing open source
124
+ tools. My hope is that the `finance` library will grow to provide a set
125
+ of open, tested tools to fill this gap.
126
+
127
+ If you have used `finance` and find it useful, I would enjoy hearing
128
+ about it!
129
+
130
+ ## FEATURES
131
+
132
+ Currently implemented features include:
133
+
134
+ * Uses the [flt](http://flt.rubyforge.org/) library to ensure precision decimal arithmetic in all calculations.
135
+ * Fixed-rate mortgage amortization (30/360).
136
+ * Interest rates
137
+ * Various cash flow computations, such as NPV and IRR.
138
+ * Adjustable rate mortgage amortization.
139
+ * Payment modifications (i.e., how does paying an additional $75 per month affect the amortization?)
140
+
141
+ ## RESOURCES
142
+
143
+ * [RubyGems Page](https://rubygems.org/gems/finance)
144
+ * [Source Code](http://github.com/wkranec/finance)
145
+ * [Bug Tracker](https://github.com/wkranec/finance/issues)
146
+ * [Google Group](http://groups.google.com/group/finance-gem/topics?pli=1)
147
+
148
+ ## COPYRIGHT
149
+
150
+ This library is released under the terms of the LGPL license.
151
+
152
+ Copyright (c) 2011, William Kranec.
153
+ All rights reserved.
154
+
155
+ This program is free software: you can redistribute it and/or modify it
156
+ under the terms of the GNU Lesser General Public License as published by the
157
+ Free Software Foundation, either version 3 of the License, or (at your
158
+ option) any later version.
159
+
160
+ This program is distributed in the hope that it will be useful,
161
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
162
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
163
+ Lesser General Public License for more details.
164
+
165
+ You should have received a copy of the GNU Lesser General Public License along
166
+ with this program. If not, see <http://www.gnu.org/licenses/>.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'rake'
2
+ require 'rake/testtask'
3
+
4
+ task :default => [:test_units]
5
+
6
+ Rake::TestTask.new("test_units") do |t|
7
+ t.pattern = 'test/*.rb'
8
+ t.verbose = true
9
+ t.warning = true
10
+ end
data/finance.gemspec ADDED
@@ -0,0 +1,19 @@
1
+ SPEC = Gem::Specification.new do |s|
2
+ s.name = "finance_velocity"
3
+ s.version = "2.0.3"
4
+ s.author = "Mani Bhushan"
5
+ s.license = "LGPL-3.0"
6
+ s.email = "manilnct@gmail.com"
7
+ s.platform = Gem::Platform::RUBY
8
+ s.summary = "a library for financial modelling in Ruby."
9
+ s.description = "The finance library provides a Ruby interface for working with interest rates, mortgage amortization, and cashflows (NPV, IRR, etc.)."
10
+ s.homepage = "https://rubygems.org/gems/finance"
11
+
12
+ s.required_ruby_version = '>=1.9'
13
+ s.add_runtime_dependency 'flt', '~> 1.3', '>= 1.3.0'
14
+ s.add_development_dependency 'minitest', '~> 4.7', '>= 4.7.5'
15
+ s.add_development_dependency 'activesupport', '~> 4.0', '>= 4.0.0'
16
+ s.files = `git ls-files`.split("\n")
17
+
18
+ s.extra_rdoc_files = ['README.md', 'COPYING', 'COPYING.LESSER', 'HISTORY']
19
+ end
@@ -0,0 +1,201 @@
1
+ require_relative 'cashflows'
2
+ require_relative 'decimal'
3
+ require_relative 'transaction'
4
+
5
+ module Finance
6
+ # the Amortization class provides an interface for working with loan amortizations.
7
+ # @note There are _two_ ways to create an amortization. The first
8
+ # example uses the amortize method for the Numeric class. The second
9
+ # calls Amortization.new directly.
10
+ # @example Borrow $250,000 under a 30 year, fixed-rate loan with a 4.25% APR
11
+ # rate = Rate.new(0.0425, :apr, :duration => (30 * 12))
12
+ # amortization = 250000.amortize(rate)
13
+ # @example Borrow $250,000 under a 30 year, adjustable rate loan, with an APR starting at 4.25%, and increasing by 1% every five years
14
+ # values = %w{ 0.0425 0.0525 0.0625 0.0725 0.0825 0.0925 }
15
+ # rates = values.collect { |value| Rate.new( value, :apr, :duration = (5 * 12) ) }
16
+ # arm = Amortization.new(250000, *rates)
17
+ # @example Borrow $250,000 under a 30 year, fixed-rate loan with a 4.25% APR, but pay $150 extra each month
18
+ # rate = Rate.new(0.0425, :apr, :duration => (5 * 12))
19
+ # extra_payments = 250000.amortize(rate){ |period| period.payment - 150 }
20
+ # @api public
21
+ class Amortization
22
+ # @return [DecNum] the balance of the loan at the end of the amortization period (usually zero)
23
+ # @api public
24
+ attr_reader :balance
25
+ # @return [DecNum] the required monthly payment. For loans with more than one rate, returns nil
26
+ # @api public
27
+ attr_reader :payment
28
+ # @return [DecNum] the principal amount of the loan
29
+ # @api public
30
+ attr_reader :principal
31
+ # @return [Array] the interest rates used for calculating the amortization
32
+ # @api public
33
+ attr_reader :rates
34
+
35
+ # compare two Amortization instances
36
+ # @return [Numeric] -1, 0, or +1
37
+ # @param [Amortization]
38
+ # @api public
39
+ def ==(amortization)
40
+ self.principal == amortization.principal and self.rates == amortization.rates and self.payments == amortization.payments
41
+ end
42
+
43
+ # @return [Array] the amount of any additional payments in each period
44
+ # @example
45
+ # rate = Rate.new(0.0375, :apr, :duration => (30 * 12))
46
+ # amt = 300000.amortize(rate){ |payment| payment.amount-100}
47
+ # amt.additional_payments #=> [DecNum('-100.00'), DecNum('-100.00'), ... ]
48
+ # @api public
49
+ def additional_payments
50
+ @transactions.find_all(&:payment?).collect{ |p| p.difference }
51
+ end
52
+
53
+ # amortize the balance of loan with the given interest rate
54
+ # @return none
55
+ # @param [Rate] rate the interest rate to use in the amortization
56
+ # @api private
57
+ def amortize(rate)
58
+ # For the purposes of calculating a payment, the relevant time
59
+ # period is the remaining number of periods in the loan, not
60
+ # necessarily the duration of the rate itself.
61
+ periods = @periods - @period
62
+ amount = Amortization.payment @balance, rate.monthly, periods
63
+
64
+ pmt = Payment.new(amount, :period => @period)
65
+ if @block then pmt.modify(&@block) end
66
+
67
+ rate.duration.to_i.times do
68
+ # Do this first in case the balance is zero already.
69
+ if @balance.zero? then break end
70
+
71
+ # Compute and record interest on the outstanding balance.
72
+ int = (@balance * rate.monthly).round(2)
73
+ interest = Interest.new(int, :period => @period)
74
+ @balance += interest.amount
75
+ @transactions << interest.dup
76
+
77
+ # Record payment. Don't pay more than the outstanding balance.
78
+ if pmt.amount.abs > @balance then pmt.amount = -@balance end
79
+ @transactions << pmt.dup
80
+ @balance += pmt.amount
81
+
82
+ @period += 1
83
+ end
84
+ end
85
+
86
+ # compute the amortization of the principal
87
+ # @return none
88
+ # @api private
89
+ def compute
90
+ @balance = @principal
91
+ @transactions = []
92
+
93
+ @rates.each do |rate|
94
+ amortize(rate)
95
+ end
96
+
97
+ # Add any remaining balance due to rounding error to the last payment.
98
+ unless @balance.zero?
99
+ @transactions.find_all(&:payment?)[-1].amount -= @balance
100
+ @balance = 0
101
+ end
102
+
103
+ if @rates.length == 1
104
+ @payment = self.payments[0]
105
+ else
106
+ @payment = nil
107
+ end
108
+
109
+ @transactions.freeze
110
+ end
111
+
112
+ # @return [Integer] the time required to pay off the loan, in months
113
+ # @example In most cases, the duration is equal to the total duration of all rates
114
+ # rate = Rate.new(0.0375, :apr, :duration => (30 * 12))
115
+ # amt = 300000.amortize(rate)
116
+ # amt.duration #=> 360
117
+ # @example Extra payments may reduce the duration
118
+ # rate = Rate.new(0.0375, :apr, :duration => (30 * 12))
119
+ # amt = 300000.amortize(rate){ |payment| payment.amount-100}
120
+ # amt.duration #=> 319
121
+ # @api public
122
+ def duration
123
+ self.payments.length
124
+ end
125
+
126
+ # create a new Amortization instance
127
+ # @return [Amortization]
128
+ # @param [DecNum] principal the initial amount of the loan or investment
129
+ # @param [Rate] rates the applicable interest rates
130
+ # @param [Proc] block
131
+ # @api public
132
+ def initialize(principal, *rates, &block)
133
+ @principal = Flt::DecNum.new(principal.to_s)
134
+ @rates = rates
135
+ @block = block
136
+
137
+ # compute the total duration from all of the rates.
138
+ @periods = (rates.collect { |r| r.duration }).sum
139
+ @period = 0
140
+
141
+ compute
142
+ end
143
+
144
+ # @api public
145
+ def inspect
146
+ "Amortization.new(#{@principal})"
147
+ end
148
+
149
+ # @return [Array] the amount of interest charged in each period
150
+ # @example find the total cost of interest for a loan
151
+ # rate = Rate.new(0.0375, :apr, :duration => (30 * 12))
152
+ # amt = 300000.amortize(rate)
153
+ # amt.interest.sum #=> DecNum('200163.94')
154
+ # @example find the total interest charges in the first six months
155
+ # rate = Rate.new(0.0375, :apr, :duration => (30 * 12))
156
+ # amt = 300000.amortize(rate)
157
+ # amt.interest[0,6].sum #=> DecNum('5603.74')
158
+ # @api public
159
+ def interest
160
+ @transactions.find_all(&:interest?).collect{ |p| p.amount }
161
+ end
162
+
163
+ # @return [DecNum] the periodic payment due on a loan
164
+ # @param [DecNum] principal the initial amount of the loan or investment
165
+ # @param [Rate] rate the applicable interest rate (per period)
166
+ # @param [Integer] periods the number of periods needed for repayment
167
+ # @note in most cases, you will probably want to use rate.monthly when calling this function outside of an Amortization instance.
168
+ # @example
169
+ # rate = Rate.new(0.0375, :apr, :duration => (30 * 12))
170
+ # rate.duration #=> 360
171
+ # Amortization.payment(200000, rate.monthly, rate.duration) #=> DecNum('-926.23')
172
+ # @see http://en.wikipedia.org/wiki/Amortization_calculator
173
+ # @api public
174
+ def Amortization.payment(principal, rate, periods)
175
+ if rate.zero?
176
+ # simplified formula to avoid division-by-zero when interest rate is zero
177
+ return -(principal / periods).round(2)
178
+ else
179
+ return -(principal * (rate + (rate / ((1 + rate) ** periods - 1)))).round(2)
180
+ end
181
+ end
182
+
183
+ # @return [Array] the amount of the payment in each period
184
+ # @example find the total payments for a loan
185
+ # rate = Rate.new(0.0375, :apr, :duration => (30 * 12))
186
+ # amt = 300000.amortize(rate)
187
+ # amt.payments.sum #=> DecNum('-500163.94')
188
+ # @api public
189
+ def payments
190
+ @transactions.find_all(&:payment?).collect{ |p| p.amount }
191
+ end
192
+ end
193
+ end
194
+
195
+ class Numeric
196
+ # @see Amortization#new
197
+ # @api public
198
+ def amortize(*rates, &block)
199
+ Finance::Amortization.new(self, *rates, &block)
200
+ end
201
+ end