finance_rb 0.0.4 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 82235bcca3f51afff5ec345707da9d1ed2e8472a20488bb3847a4967baf6932c
4
- data.tar.gz: '02029b9b9151d52ad860b1f985cc708199d734f8f210e574cda50fc7290f4231'
3
+ metadata.gz: 52d806b7797622aa42c8a3d13197ddd3e66817dd41199751cc402d2e2ede4741
4
+ data.tar.gz: 3404f5b73ededdd707d82f976a61bed2b575a3e00b448c1b056a06c92169bd5a
5
5
  SHA512:
6
- metadata.gz: 38348b526019d3124c6cff67c4cd39427600d7a52ea584ea9b583328bedcfceb6680783bbbfdb8f9b45b686ff612e6295e670f1a09d882e09f4540fcf23a3aea
7
- data.tar.gz: 879a9b9b6e01efd654e32a1bbc77a9e07e8a9b1cebbbb1061f6d20f3e0aa723b8150fbf8a56e66dee87bf3eb69a14301dc4d9a51fd62f87e9414f69f47aab5d6
6
+ metadata.gz: b2114092617e4120fc0b4e61d5e98cd0c227c882605e10fb0bfafdeb761f8c6dc8124a82ac729b548f4365f1a7fa17b061ad14a1e8d1befb6608ced7f4d68634
7
+ data.tar.gz: c42f87903359390b44fdb6472baacf24f6fc60934961cd16c007675e1adb231c0122bfca514c4ceffab7506f66b13b95736474c85ae23b4d4d103259f747f718
data/CHANGELOG.md CHANGED
@@ -1,12 +1,42 @@
1
+ ## [0.2.1] - 2021-05-01
2
+
3
+ ### Added
4
+ * Implement `Finance::Loan#nper`
5
+
6
+ ## [0.2.0] - 2021-04-30
7
+
8
+ ### Added
9
+ * Implement `Finance::Loan#ppmt`
10
+
11
+ ### Changed
12
+ * By default `Finance::Loan#pmt` returns negative values.
13
+
14
+ ## [0.1.2] - 2021-04-05
15
+
16
+ ### Added
17
+ * Implement `Finance::Loan#ipmt`
18
+
19
+ ## [0.1.1] - 2021-03-30
20
+
21
+ ### Added
22
+ * Implement `Finance::Loan#fv`
23
+
24
+ ## [0.1.0] - 2021-03-28
25
+
26
+ ### Added
27
+ * Create a basic structure for `Finance::Loan`
28
+ * Implement `Finance::Loan#pmt`
29
+
30
+
1
31
  ## [0.0.4] - 2021-03-25
2
32
 
3
33
  ### Added
4
- * Implement Finance::Calculations#mirr
34
+ * Implement `Finance::Calculations#mirr`
5
35
 
6
36
  ## [0.0.3] - 2021-03-23
7
37
 
8
38
  ### Added
9
- * Implement Finance::Calculations#irr using Newton's method
39
+ * Implement `Finance::Calculations#irr` using Newton's method
10
40
 
11
41
  ## [0.0.2] - 2021-03-22
12
42
 
data/README.md CHANGED
@@ -4,20 +4,48 @@ This package is a ruby native port of the numpy-financial package with some help
4
4
 
5
5
  The functions in this package are a scalar version of their vectorised counterparts in the [numpy-financial](https://github.com/numpy/numpy-financial) library.
6
6
 
7
- [![Release](https://img.shields.io/github/v/release/wowinter13/finance_rb.svg?style=flat-square)](https://github.com/wowinter13/finance_rb/releases) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com)
7
+ [![Release](https://img.shields.io/github/v/release/wowinter13/finance_rb.svg?style=flat-square)](https://github.com/wowinter13/finance_rb/releases) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) [![Maintainability](https://api.codeclimate.com/v1/badges/bbca82ad7815794c6718/maintainability)](https://codeclimate.com/github/wowinter13/finance_rb/maintainability)
8
8
 
9
9
  Currently, only some functions are ported,
10
10
  which are as follows:
11
11
 
12
12
  | numpy-financial function | ruby native function ported? | info|
13
13
  |:------------------------: |:------------------: | :------------------|
14
- | fv | | Computes the future value|
15
- | ipmt | | Computes interest payment for a loan|
16
- | pmt | | Computes the fixed periodic payment(principal + interest) made against a loan amount|
17
- | ppmt | | Computes principal payment for a loan|
18
- | nper | | Computes the number of periodic payments|
14
+ | fv || Computes the future value|
15
+ | ipmt || Computes interest payment for a loan|
16
+ | pmt || Computes the fixed periodic payment(principal + interest) made against a loan amount|
17
+ | ppmt || Computes principal payment for a loan|
18
+ | nper || Computes the number of periodic payments|
19
19
  | pv | | Computes the present value of a payment|
20
20
  | rate | | Computes the rate of interest per period|
21
21
  | irr | ✅ | Computes the internal rate of return|
22
22
  | npv | ✅ | Computes the net present value of a series of cash flow|
23
- | mirr | ✅ | Computes the modified internal rate of return|
23
+ | mirr | ✅ | Computes the modified internal rate of return|
24
+
25
+ ## Installation
26
+
27
+ finance_rb is available as a gem, to install it just install the gem:
28
+
29
+ gem install finance_rb
30
+
31
+ If you're using Bundler, add the gem to Gemfile.
32
+
33
+ gem 'finance_rb'
34
+
35
+ Run `bundle install`.
36
+
37
+ ## Running tests
38
+
39
+ bundle exec rspec spec/
40
+
41
+ ## Contributing
42
+
43
+ 1. Fork it ( https://github.com/wowinter13/finance_rb/fork )
44
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
45
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
46
+ 4. Push to the branch (`git push origin my-new-feature`)
47
+ 5. Create a new Pull Request
48
+
49
+ ## License
50
+
51
+ MIT License. See LICENSE for details.
data/lib/finance/loan.rb CHANGED
@@ -2,24 +2,198 @@
2
2
 
3
3
  module Finance
4
4
  class Loan
5
+ PAYMENT_TYPE_MAPPING = { end: 0, beginning: 1 }.freeze
6
+
7
+ # @return [Float] The amount of loan request (I.e. a present value)
8
+ # You can use #pv method to calculate value if param is not defined.
9
+ # Defaults to 0.
10
+ attr_accessor :amount
11
+
12
+ # @return [Integer] Specification of whether payment is made
13
+ # at the beginning (ptype = 1) or the end (ptype = 0) of each period.
14
+ # Defaults to {:end, 0}.
15
+ attr_accessor :ptype
16
+
17
+ # @return [Float] The nominal annual rate of interest as decimal (not per cent).
18
+ # (e.g., 13% -> 0.13)
19
+ # Defaults to 0.
20
+ attr_accessor :nominal_rate
21
+
22
+ # @return [Float] The monthly rate is the nominal annual rate divided by 12.
23
+ # Defaults to 0.
24
+ attr_reader :monthly_rate
25
+
26
+ # @return [Float] The number of periods to be compounded for. (I.e. Nper())
27
+ # Defaults to 1.
28
+ attr_accessor :duration
29
+
30
+ # @return [Float] Future value.
31
+ # You can use #fv method to calculate value if param is not defined.
32
+ # Defaults to 0.
33
+ attr_accessor :future_value
34
+
35
+ # @return [Float] The (fixed) periodic payment.
36
+ # You can use #pmt method to calculate value if param is not defined.
37
+ attr_accessor :payment
38
+
39
+ # @return [Float] Period under consideration.
40
+ attr_accessor :period
41
+
42
+ # Create a new Loan instance.
5
43
  def initialize(**options)
44
+ initialize_payment_type(options[:ptype])
45
+ @nominal_rate = options.fetch(:nominal_rate, 0).to_f
46
+ @duration = options.fetch(:duration, 1).to_f
47
+ @amount = options.fetch(:amount, 0).to_f
48
+ @future_value = options.fetch(:future_value, 0).to_f
49
+ @period = options[:period]
50
+ @payment = options[:payment]
51
+ @monthly_rate = @nominal_rate / 12
52
+ end
53
+
54
+ # Pmt computes the payment against a loan principal plus interest (future_value = 0).
55
+ # It can also be used to calculate the recurring payments needed to achieve
56
+ # a certain future value given an initial deposit,
57
+ # a fixed periodically compounded interest rate, and the total number of periods.
58
+ #
59
+ # Required Loan arguments: nominal_rate, duration, amount, future_value*
60
+ #
61
+ # @return [Numeric] The (fixed) periodic payment.
62
+ #
63
+ # @example
64
+ # require 'finance_rb'
65
+ # Finance::Loan.new(nominal_rate: 0.1, duration: 12, amount: 1000, ptype: :end).pmt
66
+ # #=> 87.9158872300099
67
+ #
68
+ # @see http://www.oasis-open.org/committees/documents.php?wg_abbrev=office-formulaOpenDocument-formula-20090508.odt
69
+ # @see [WRW] Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May).
70
+ # Open Document Format for Office Applications (OpenDocument)v1.2,
71
+ # Part 2: Recalculated Formula (OpenFormula) Format - Annotated Version,
72
+ # Pre-Draft 12. Organization for the Advancement of Structured Information
73
+ # Standards (OASIS). Billerica, MA, USA. [ODT Document].
74
+ def pmt
75
+ factor = (1.0 + monthly_rate)**duration
76
+ second_factor =
77
+ if monthly_rate.zero?
78
+ duration
79
+ else
80
+ (factor - 1) * (1 + monthly_rate * ptype) / monthly_rate
81
+ end
82
+
83
+ -((future_value + amount * factor) / second_factor)
6
84
  end
7
85
 
8
- def pmt()
86
+ # IPmt computes interest payment for a loan under a given period.
87
+ #
88
+ # Required Loan arguments: period, nominal_rate, duration, amount, future_value*
89
+ #
90
+ # @return [Float] Interest payment for a loan.
91
+ #
92
+ # @example
93
+ # require 'finance_rb'
94
+ # Finance::Loan.new(nominal_rate: 0.0824, duration: 12, amount: 2500, period: 1).ipmt
95
+ # #=> -17.166666666666668
96
+ #
97
+ # @see http://www.oasis-open.org/committees/documents.php?wg_abbrev=office-formulaOpenDocument-formula-20090508.odt
98
+ # @see [WRW] Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May).
99
+ # Open Document Format for Office Applications (OpenDocument)v1.2,
100
+ # Part 2: Recalculated Formula (OpenFormula) Format - Annotated Version,
101
+ # Pre-Draft 12. Organization for the Advancement of Structured Information
102
+ # Standards (OASIS). Billerica, MA, USA. [ODT Document].
103
+ def ipmt
104
+ raise ArgumentError, 'no period given' if period.nil?
105
+
106
+ ipmt_val = remaining_balance * monthly_rate
107
+ if ptype == PAYMENT_TYPE_MAPPING[:beginning]
108
+ period == 1 ? 0.0 : (ipmt_val / 1 + monthly_rate)
109
+ else
110
+ ipmt_val
111
+ end
9
112
  end
10
113
 
11
- def ipmt()
114
+ # PPmt computes principal payment for a loan under a given period.
115
+ #
116
+ # Required Loan arguments: period, nominal_rate, duration, amount, future_value*
117
+ #
118
+ # @return [Float] Principal payment for a loan under a given period.
119
+ #
120
+ # @example
121
+ # require 'finance_rb'
122
+ # Finance::Loan.new(nominal_rate: 0.0824, duration: 12, amount: 2500, period: 1).ppmt
123
+ # #=> -200.58192368678277
124
+ #
125
+ # @see http://www.oasis-open.org/committees/documents.php?wg_abbrev=office-formulaOpenDocument-formula-20090508.odt
126
+ # @see [WRW] Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May).
127
+ # Open Document Format for Office Applications (OpenDocument)v1.2,
128
+ # Part 2: Recalculated Formula (OpenFormula) Format - Annotated Version,
129
+ # Pre-Draft 12. Organization for the Advancement of Structured Information
130
+ # Standards (OASIS). Billerica, MA, USA. [ODT Document].
131
+ def ppmt
132
+ pmt - ipmt
12
133
  end
13
134
 
14
- def ppmt()
135
+ # Nper computes the number of periodic payments.
136
+ #
137
+ # Required Loan arguments: payment, nominal_rate, period, amount, future_value*
138
+ #
139
+ # @return [Float] Number of periodic payments.
140
+ #
141
+ # @example
142
+ # require 'finance_rb'
143
+ # Finance::Loan.new(nominal_rate: 0.07, amount: 8000, payment: -150).nper
144
+ # #=> 64.0733487706618586
145
+ def nper
146
+ z = payment * (1.0 + monthly_rate * ptype) / monthly_rate
147
+
148
+ Math.log(-future_value + z / (amount + z)) / Math.log(1.0 + monthly_rate)
15
149
  end
16
150
 
17
- def fv();end
151
+ # Fv computes future value at the end of some periods (duration).
152
+ # Required Loan arguments: nominal_rate, duration, payment, amount*
153
+ #
154
+ # @param payment [Float] The (fixed) periodic payment.
155
+ # In case you don't want to modify the original loan, use this parameter to recalculate fv.
156
+ #
157
+ # @return [Float] The value at the end of the `duration` periods.
158
+ #
159
+ # @example
160
+ # require 'finance_rb'
161
+ # Finance::Loan.new(nominal_rate: 0.05, duration: 120, amount: -100, payment: -200).fv
162
+ # #=> 15692.928894335748
163
+ #
164
+ # @see http://www.oasis-open.org/committees/documents.php?wg_abbrev=office-formulaOpenDocument-formula-20090508.odt
165
+ # @see [WRW] Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May).
166
+ # Open Document Format for Office Applications (OpenDocument)v1.2,
167
+ # Part 2: Recalculated Formula (OpenFormula) Format - Annotated Version,
168
+ # Pre-Draft 12. Organization for the Advancement of Structured Information
169
+ # Standards (OASIS). Billerica, MA, USA. [ODT Document].
170
+ def fv(payment: nil)
171
+ raise ArgumentError, 'no payment given' if self.payment.nil? && payment.nil?
172
+
173
+ final_payment = payment || self.payment
18
174
 
19
- def pv();end
175
+ factor = (1.0 + monthly_rate)**duration
176
+ second_factor = (factor - 1) * (1 + monthly_rate * ptype) / monthly_rate
20
177
 
21
- def nper();end
178
+ -((amount * factor) + (final_payment.to_f * second_factor))
179
+ end
180
+
181
+ private
22
182
 
23
- def rate();end
183
+ def initialize_payment_type(ptype)
184
+ @ptype =
185
+ if ptype.nil? || !PAYMENT_TYPE_MAPPING.keys.include?(ptype)
186
+ PAYMENT_TYPE_MAPPING[:end]
187
+ else
188
+ PAYMENT_TYPE_MAPPING[ptype]
189
+ end
190
+ end
191
+
192
+ def remaining_balance
193
+ self.class.new(
194
+ nominal_rate: nominal_rate.to_f, duration: period - 1.0,
195
+ amount: amount.to_f, ptype: PAYMENT_TYPE_MAPPING.key(ptype)
196
+ ).fv(payment: pmt)
197
+ end
24
198
  end
25
199
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Finance
4
- VERSION = "0.0.4"
4
+ VERSION = "0.2.1"
5
5
  end
@@ -0,0 +1,169 @@
1
+ require 'spec_helper'
2
+
3
+ RSpec.describe Finance::Loan do
4
+ describe '#pmt' do
5
+ context 'w/o a full set of params' do
6
+ it 'calculates correct pmt value w/o :ptype' do
7
+ loan = Finance::Loan.new(nominal_rate: 0.1, duration: 12, amount: 1000)
8
+ expect(loan.pmt).to eq(-87.9158872300099)
9
+ end
10
+
11
+ it 'calculates correct pmt value w/o :nominal_rate' do
12
+ loan = Finance::Loan.new(duration: 12, amount: 1200, ptype: :end)
13
+ expect(loan.pmt).to eq(-100)
14
+ end
15
+ end
16
+
17
+ context 'with zero rates' do
18
+ it 'calculates correct pmt value for 3 years' do
19
+ loan = Finance::Loan.new(nominal_rate: 0, duration: 36, amount: 10_000, ptype: :end)
20
+ expect(loan.pmt).to eq(-277.77777777777777)
21
+ end
22
+
23
+ it 'calculates correct pmt value for 6 months' do
24
+ loan = Finance::Loan.new(nominal_rate: 0, duration: 6, amount: 10_000, ptype: :end)
25
+ expect(loan.pmt).to eq(-1666.6666666666667)
26
+ end
27
+ end
28
+
29
+ context 'with :beginning ptype' do
30
+ it 'calculates correct pmt value' do
31
+ loan = Finance::Loan.new(nominal_rate: 0.12, duration: 6, amount: 1000, ptype: :beginning)
32
+ expect(loan.pmt).to eq(-170.8399670404763)
33
+ end
34
+ end
35
+
36
+ it 'calculates correct pmt value' do
37
+ loan = Finance::Loan.new(nominal_rate: 0.13, duration: 90, amount: 1_000_000, ptype: :end)
38
+ expect(loan.pmt).to eq(-17_449.90775727763)
39
+ end
40
+ end
41
+
42
+ describe '#fv' do
43
+ context 'with loan arguments' do
44
+ it 'calculates correct fv value' do
45
+ loan = Finance::Loan.new(nominal_rate: 0.05, duration: 120, amount: -100, payment: -100)
46
+ expect(loan.fv).to eq(15_692.928894335748)
47
+ end
48
+
49
+ context 'with :ptype' do
50
+ it 'calculates correct fv value' do
51
+ loan = Finance::Loan.new(
52
+ nominal_rate: 0.9, duration: 20, amount: 0, payment: -2000, ptype: :beginning
53
+ )
54
+ expect(loan.fv).to eq(93_105.06487352113)
55
+ end
56
+ end
57
+ end
58
+
59
+ context 'with an optional :payment argument' do
60
+ it 'calculates correct fv value' do
61
+ loan = Finance::Loan.new(nominal_rate: 0.05, duration: 120, amount: -100, payment: -200)
62
+ expect(loan.fv(payment: -100)).to eq(15_692.928894335748)
63
+ end
64
+ end
65
+
66
+ context 'w/o any payments' do
67
+ it 'raises an ArgumentError exception w/o loan arguments' do
68
+ loan = Finance::Loan.new(nominal_rate: 0.05, duration: 120, amount: -100)
69
+ expect { loan.fv }.to raise_error(ArgumentError, "no payment given")
70
+ end
71
+ end
72
+ end
73
+
74
+ describe '#ipmt' do
75
+ context 'when 1 period' do
76
+ it 'calculates correct ipmt value' do
77
+ loan = Finance::Loan.new(
78
+ nominal_rate: 0.0824, duration: 12, amount: 2500, period: 1
79
+ )
80
+ expect(loan.ipmt).to eq(-17.166666666666668)
81
+ end
82
+ end
83
+
84
+ context 'when 2 periods' do
85
+ it 'calculates correct ipmt value' do
86
+ loan = Finance::Loan.new(
87
+ nominal_rate: 0.0824, duration: 12, amount: 2500, period: 2
88
+ )
89
+ expect(loan.ipmt).to eq(-15.789337457350777)
90
+ end
91
+ end
92
+
93
+ context 'when 3 periods' do
94
+ it 'calculates correct ipmt value' do
95
+ loan = Finance::Loan.new(
96
+ nominal_rate: 0.0824, duration: 12.0, amount: 2500.0, period: 3.0, fv: 0.0
97
+ )
98
+ expect(loan.ipmt).to eq(-14.402550587464257)
99
+ end
100
+ end
101
+ end
102
+
103
+ describe '#ppmt' do
104
+ context 'when 1 period' do
105
+ it 'calculates correct ppmt value' do
106
+ loan = Finance::Loan.new(
107
+ nominal_rate: 0.0824, duration: 12, amount: 2500, period: 1
108
+ )
109
+ expect(loan.ppmt).to eq(-200.58192368678277)
110
+ end
111
+ end
112
+
113
+ context 'when 2 periods' do
114
+ it 'calculates correct ppmt value' do
115
+ loan = Finance::Loan.new(
116
+ nominal_rate: 0.0824, duration: 12, amount: 2500, period: 2
117
+ )
118
+ expect(loan.ppmt).to eq(-201.95925289609866)
119
+ end
120
+ end
121
+
122
+ context 'when 3 periods' do
123
+ it 'calculates correct ppmt value' do
124
+ loan = Finance::Loan.new(
125
+ nominal_rate: 0.0824, duration: 12, amount: 2500, period: 3
126
+ )
127
+ expect(loan.ppmt).to eq(-203.34603976598518)
128
+ end
129
+ end
130
+
131
+ context 'when 4 periods' do
132
+ it 'calculates correct ppmt value' do
133
+ loan = Finance::Loan.new(
134
+ nominal_rate: 0.0824, duration: 12, amount: 2500, period: 4
135
+ )
136
+ expect(loan.ppmt).to eq(-204.7423492390449)
137
+ end
138
+ end
139
+
140
+ context 'when 5 periods' do
141
+ it 'calculates correct ppmt value' do
142
+ loan = Finance::Loan.new(
143
+ nominal_rate: 0.0824, duration: 12, amount: 2500, period: 5
144
+ )
145
+ expect(loan.ppmt).to eq(-206.1482467038197)
146
+ end
147
+ end
148
+ end
149
+
150
+ describe '#nper' do
151
+ context 'with normal arguments' do
152
+ it 'calculates correct nper value' do
153
+ loan = Finance::Loan.new(
154
+ nominal_rate: 0.07, amount: 8000, payment: -150, future_value: 0
155
+ )
156
+ expect(loan.nper).to eq(64.0733487706618586)
157
+ end
158
+ end
159
+
160
+ context 'with incorrect arguments' do
161
+ it 'raises Math::DomainError' do
162
+ loan = Finance::Loan.new(
163
+ nominal_rate: 1e100, amount: 8000, payment: -150, future_value: 0
164
+ )
165
+ expect { loan.nper }.to raise_error(Math::DomainError)
166
+ end
167
+ end
168
+ end
169
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: finance_rb
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Vlad Dyachenko
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-03-24 00:00:00.000000000 Z
11
+ date: 2021-05-01 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: A ruby port of numpy-financial functions. This library provides a Ruby
14
14
  interface for working with interest rates, mortgage amortization, and cashflows
@@ -27,6 +27,7 @@ files:
27
27
  - lib/finance/version.rb
28
28
  - lib/finance_rb.rb
29
29
  - spec/finance/calculations_spec.rb
30
+ - spec/finance/loan_spec.rb
30
31
  - spec/spec_helper.rb
31
32
  homepage: https://github.com/wowinter13/finance_rb
32
33
  licenses:
@@ -57,4 +58,5 @@ specification_version: 4
57
58
  summary: A library for finance manipulations in Ruby.
58
59
  test_files:
59
60
  - spec/finance/calculations_spec.rb
61
+ - spec/finance/loan_spec.rb
60
62
  - spec/spec_helper.rb