finance_rb 0.1.0 → 0.2.2

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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6a3ce7fbe55b1715dc8b2b636326a1bb99eb4b8638f2a3c111e0decfe1b4d126
4
- data.tar.gz: a5568d0deadc00bdd55ffac1cb05dee7a7d8abb8bee87015d321916f59694705
3
+ metadata.gz: 07e154f5ba5d444fe1e2c782c39a3d21a79b9fd3d413fba6e7c5285fa5f07df4
4
+ data.tar.gz: bef7aea3a7f54df1fc69d05619e9416637c717ce7e8b4d3a211573fbfbf5a6df
5
5
  SHA512:
6
- metadata.gz: cd349f4f6aa4cf20fab8db9064da6b77f1199b61b927af170f491dd447cfdb3889aeea97d07de34706ca12493cfae56d1089aa8cfb2ff80e351ddd951d3e92e9
7
- data.tar.gz: fdef6ff2746503b68a2a83494b6a006f05a7216f87125a71967dfdf7de05d4dcae80563f4cfbac0cf8ce7b91c5510cfd4cc4111cbe49f6573e43dc37fc21d4f2
6
+ metadata.gz: bf67fafbda3defb85d73581d02b3dc00b26f83c6b03e4f35bd5c2d3a0ac661a823cd737d8055c54309b8a6d6feba8be7d03675b39ddb95b2e8f7ceaa94a7e472
7
+ data.tar.gz: 4f429be0368ac5e986c689fcd6afd146cec1676741d149ced828ae6d03e38e2d2e1ebdfeae88fce1d76de1a31c51a08fb0809577362f07f1de0e70e761bfa8b1
data/CHANGELOG.md CHANGED
@@ -1,3 +1,31 @@
1
+ ## [0.2.2] - 2021-05-01
2
+
3
+ ### Added
4
+ * Implement `Finance::Loan#pv`
5
+
6
+ ## [0.2.1] - 2021-05-01
7
+
8
+ ### Added
9
+ * Implement `Finance::Loan#nper`
10
+
11
+ ## [0.2.0] - 2021-04-30
12
+
13
+ ### Added
14
+ * Implement `Finance::Loan#ppmt`
15
+
16
+ ### Changed
17
+ * By default `Finance::Loan#pmt` returns negative values.
18
+
19
+ ## [0.1.2] - 2021-04-05
20
+
21
+ ### Added
22
+ * Implement `Finance::Loan#ipmt`
23
+
24
+ ## [0.1.1] - 2021-03-30
25
+
26
+ ### Added
27
+ * Implement `Finance::Loan#fv`
28
+
1
29
  ## [0.1.0] - 2021-03-28
2
30
 
3
31
  ### Added
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|
14
+ | fv || Computes the future value|
15
+ | ipmt || Computes interest payment for a loan|
16
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
- | pv | | Computes the present value of a payment|
17
+ | ppmt || Computes principal payment for a loan|
18
+ | nper || Computes the number of periodic payments|
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
@@ -5,6 +5,7 @@ module Finance
5
5
  PAYMENT_TYPE_MAPPING = { end: 0, beginning: 1 }.freeze
6
6
 
7
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.
8
9
  # Defaults to 0.
9
10
  attr_accessor :amount
10
11
 
@@ -24,19 +25,31 @@ module Finance
24
25
 
25
26
  # @return [Float] The number of periods to be compounded for. (I.e. Nper())
26
27
  # Defaults to 1.
28
+ # You can use #nper method to calculate value if param is not defined.
27
29
  attr_accessor :duration
28
30
 
29
31
  # @return [Float] Future value.
32
+ # You can use #fv method to calculate value if param is not defined.
30
33
  # Defaults to 0.
31
34
  attr_accessor :future_value
32
35
 
36
+ # @return [Float] The (fixed) periodic payment.
37
+ # You can use #pmt method to calculate value if param is not defined.
38
+ attr_accessor :payment
39
+
40
+ # @return [Float] Period under consideration.
41
+ attr_accessor :period
42
+
43
+ # Create a new Loan instance.
33
44
  def initialize(**options)
34
45
  initialize_payment_type(options[:ptype])
35
46
  @nominal_rate = options.fetch(:nominal_rate, 0).to_f
36
47
  @duration = options.fetch(:duration, 1).to_f
37
48
  @amount = options.fetch(:amount, 0).to_f
38
49
  @future_value = options.fetch(:future_value, 0).to_f
39
- @monthly_rate = @nominal_rate / 12
50
+ @period = options[:period]
51
+ @payment = options[:payment]
52
+ @monthly_rate = @nominal_rate / 12
40
53
  end
41
54
 
42
55
  # Pmt computes the payment against a loan principal plus interest (future_value = 0).
@@ -44,6 +57,8 @@ module Finance
44
57
  # a certain future value given an initial deposit,
45
58
  # a fixed periodically compounded interest rate, and the total number of periods.
46
59
  #
60
+ # Required Loan arguments: nominal_rate, duration, amount, future_value*
61
+ #
47
62
  # @return [Numeric] The (fixed) periodic payment.
48
63
  #
49
64
  # @example
@@ -66,7 +81,125 @@ module Finance
66
81
  (factor - 1) * (1 + monthly_rate * ptype) / monthly_rate
67
82
  end
68
83
 
69
- (-future_value + amount * factor) / second_factor
84
+ -((future_value + amount * factor) / second_factor)
85
+ end
86
+
87
+ # IPmt computes interest payment for a loan under a given period.
88
+ #
89
+ # Required Loan arguments: period, nominal_rate, duration, amount, future_value*
90
+ #
91
+ # @return [Float] Interest payment for a loan.
92
+ #
93
+ # @example
94
+ # require 'finance_rb'
95
+ # Finance::Loan.new(nominal_rate: 0.0824, duration: 12, amount: 2500, period: 1).ipmt
96
+ # #=> -17.166666666666668
97
+ #
98
+ # @see http://www.oasis-open.org/committees/documents.php?wg_abbrev=office-formulaOpenDocument-formula-20090508.odt
99
+ # @see [WRW] Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May).
100
+ # Open Document Format for Office Applications (OpenDocument)v1.2,
101
+ # Part 2: Recalculated Formula (OpenFormula) Format - Annotated Version,
102
+ # Pre-Draft 12. Organization for the Advancement of Structured Information
103
+ # Standards (OASIS). Billerica, MA, USA. [ODT Document].
104
+ def ipmt
105
+ raise ArgumentError, 'no period given' if period.nil?
106
+
107
+ ipmt_val = remaining_balance * monthly_rate
108
+ if ptype == PAYMENT_TYPE_MAPPING[:beginning]
109
+ period == 1 ? 0.0 : (ipmt_val / 1 + monthly_rate)
110
+ else
111
+ ipmt_val
112
+ end
113
+ end
114
+
115
+ # PPmt computes principal payment for a loan under a given period.
116
+ #
117
+ # Required Loan arguments: period, nominal_rate, duration, amount, future_value*
118
+ #
119
+ # @return [Float] Principal payment for a loan under a given period.
120
+ #
121
+ # @example
122
+ # require 'finance_rb'
123
+ # Finance::Loan.new(nominal_rate: 0.0824, duration: 12, amount: 2500, period: 1).ppmt
124
+ # #=> -200.58192368678277
125
+ #
126
+ # @see http://www.oasis-open.org/committees/documents.php?wg_abbrev=office-formulaOpenDocument-formula-20090508.odt
127
+ # @see [WRW] Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May).
128
+ # Open Document Format for Office Applications (OpenDocument)v1.2,
129
+ # Part 2: Recalculated Formula (OpenFormula) Format - Annotated Version,
130
+ # Pre-Draft 12. Organization for the Advancement of Structured Information
131
+ # Standards (OASIS). Billerica, MA, USA. [ODT Document].
132
+ def ppmt
133
+ pmt - ipmt
134
+ end
135
+
136
+ # Nper computes the number of periodic payments.
137
+ #
138
+ # Required Loan arguments: payment, nominal_rate, period, amount, future_value*
139
+ #
140
+ # @return [Float] Number of periodic payments.
141
+ #
142
+ # @example
143
+ # require 'finance_rb'
144
+ # Finance::Loan.new(nominal_rate: 0.07, amount: 8000, payment: -150).nper
145
+ # #=> 64.0733487706618586
146
+ def nper
147
+ z = payment * (1.0 + monthly_rate * ptype) / monthly_rate
148
+
149
+ Math.log(-future_value + z / (amount + z)) / Math.log(1.0 + monthly_rate)
150
+ end
151
+
152
+ # Fv computes future value at the end of some periods (duration).
153
+ # Required Loan arguments: nominal_rate, duration, payment, amount*
154
+ #
155
+ # @param payment [Float] The (fixed) periodic payment.
156
+ # In case you don't want to modify the original loan, use this parameter to recalculate fv.
157
+ #
158
+ # @return [Float] The value at the end of the `duration` periods.
159
+ #
160
+ # @example
161
+ # require 'finance_rb'
162
+ # Finance::Loan.new(nominal_rate: 0.05, duration: 120, amount: -100, payment: -200).fv
163
+ # #=> 15692.928894335748
164
+ #
165
+ # @see http://www.oasis-open.org/committees/documents.php?wg_abbrev=office-formulaOpenDocument-formula-20090508.odt
166
+ # @see [WRW] Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May).
167
+ # Open Document Format for Office Applications (OpenDocument)v1.2,
168
+ # Part 2: Recalculated Formula (OpenFormula) Format - Annotated Version,
169
+ # Pre-Draft 12. Organization for the Advancement of Structured Information
170
+ # Standards (OASIS). Billerica, MA, USA. [ODT Document].
171
+ def fv(payment: nil)
172
+ raise ArgumentError, 'no payment given' if self.payment.nil? && payment.nil?
173
+
174
+ final_payment = payment || self.payment
175
+
176
+ factor = (1.0 + monthly_rate)**duration
177
+ second_factor = (factor - 1) * (1 + monthly_rate * ptype) / monthly_rate
178
+
179
+ -((amount * factor) + (final_payment.to_f * second_factor))
180
+ end
181
+
182
+ # Pv computes present value.
183
+ # Required Loan arguments: nominal_rate, duration, payment, future_value, *ptype
184
+ #
185
+ # @return [Float] The present value.
186
+ #
187
+ # @example
188
+ # require 'finance_rb'
189
+ # Finance::Loan.new(nominal_rate: 0.24, duration: 12, future_value: 1000, payment: -300, ptype: :ending).pv
190
+ # #=> 2384.1091906935
191
+ #
192
+ # @see http://www.oasis-open.org/committees/documents.php?wg_abbrev=office-formulaOpenDocument-formula-20090508.odt
193
+ # @see [WRW] Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May).
194
+ # Open Document Format for Office Applications (OpenDocument)v1.2,
195
+ # Part 2: Recalculated Formula (OpenFormula) Format - Annotated Version,
196
+ # Pre-Draft 12. Organization for the Advancement of Structured Information
197
+ # Standards (OASIS). Billerica, MA, USA. [ODT Document].
198
+ def pv
199
+ factor = (1.0 + monthly_rate)**duration
200
+ second_factor = (factor - 1) * (1 + monthly_rate * ptype) / monthly_rate
201
+
202
+ -(future_value + (payment.to_f * second_factor)) / factor
70
203
  end
71
204
 
72
205
  private
@@ -79,5 +212,12 @@ module Finance
79
212
  PAYMENT_TYPE_MAPPING[ptype]
80
213
  end
81
214
  end
215
+
216
+ def remaining_balance
217
+ self.class.new(
218
+ nominal_rate: nominal_rate.to_f, duration: period - 1.0,
219
+ amount: amount.to_f, ptype: PAYMENT_TYPE_MAPPING.key(ptype)
220
+ ).fv(payment: pmt)
221
+ end
82
222
  end
83
223
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Finance
4
- VERSION = "0.1.0"
4
+ VERSION = "0.2.2"
5
5
  end
@@ -5,37 +5,185 @@ RSpec.describe Finance::Loan do
5
5
  context 'w/o a full set of params' do
6
6
  it 'calculates correct pmt value w/o :ptype' do
7
7
  loan = Finance::Loan.new(nominal_rate: 0.1, duration: 12, amount: 1000)
8
- expect(loan.pmt).to eq(87.9158872300099)
8
+ expect(loan.pmt).to eq(-87.9158872300099)
9
9
  end
10
10
 
11
11
  it 'calculates correct pmt value w/o :nominal_rate' do
12
12
  loan = Finance::Loan.new(duration: 12, amount: 1200, ptype: :end)
13
- expect(loan.pmt).to eq(100)
13
+ expect(loan.pmt).to eq(-100)
14
14
  end
15
15
  end
16
16
 
17
17
  context 'with zero rates' do
18
18
  it 'calculates correct pmt value for 3 years' do
19
19
  loan = Finance::Loan.new(nominal_rate: 0, duration: 36, amount: 10_000, ptype: :end)
20
- expect(loan.pmt).to eq(277.77777777777777)
20
+ expect(loan.pmt).to eq(-277.77777777777777)
21
21
  end
22
22
 
23
23
  it 'calculates correct pmt value for 6 months' do
24
24
  loan = Finance::Loan.new(nominal_rate: 0, duration: 6, amount: 10_000, ptype: :end)
25
- expect(loan.pmt).to eq(1666.6666666666667)
25
+ expect(loan.pmt).to eq(-1666.6666666666667)
26
26
  end
27
27
  end
28
28
 
29
29
  context 'with :beginning ptype' do
30
30
  it 'calculates correct pmt value' do
31
31
  loan = Finance::Loan.new(nominal_rate: 0.12, duration: 6, amount: 1000, ptype: :beginning)
32
- expect(loan.pmt).to eq(170.8399670404763)
32
+ expect(loan.pmt).to eq(-170.8399670404763)
33
33
  end
34
34
  end
35
35
 
36
36
  it 'calculates correct pmt value' do
37
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)
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 '#pv' do
75
+ context 'when :ptype == beginning' do
76
+ it 'calculates correct pv value' do
77
+ loan = Finance::Loan.new(
78
+ nominal_rate: 0.24, duration: 12, future_value: 1000, payment: -300, ptype: :beginning
79
+ )
80
+ expect(loan.pv).to eq(2447.5612380190028)
81
+ end
82
+ end
83
+
84
+ context 'when :ptype == ending' do
85
+ it 'calculates correct pv value' do
86
+ loan = Finance::Loan.new(
87
+ nominal_rate: 0.24, duration: 12, future_value: 1000, payment: -300, ptype: :ending
88
+ )
89
+ expect(loan.pv).to eq(2384.1091906935)
90
+ end
91
+ end
92
+ end
93
+
94
+ describe '#ipmt' do
95
+ context 'when 1 period' do
96
+ it 'calculates correct ipmt value' do
97
+ loan = Finance::Loan.new(
98
+ nominal_rate: 0.0824, duration: 12, amount: 2500, period: 1
99
+ )
100
+ expect(loan.ipmt).to eq(-17.166666666666668)
101
+ end
102
+ end
103
+
104
+ context 'when 2 periods' do
105
+ it 'calculates correct ipmt value' do
106
+ loan = Finance::Loan.new(
107
+ nominal_rate: 0.0824, duration: 12, amount: 2500, period: 2
108
+ )
109
+ expect(loan.ipmt).to eq(-15.789337457350777)
110
+ end
111
+ end
112
+
113
+ context 'when 3 periods' do
114
+ it 'calculates correct ipmt value' do
115
+ loan = Finance::Loan.new(
116
+ nominal_rate: 0.0824, duration: 12.0, amount: 2500.0, period: 3.0, fv: 0.0
117
+ )
118
+ expect(loan.ipmt).to eq(-14.402550587464257)
119
+ end
120
+ end
121
+ end
122
+
123
+ describe '#ppmt' do
124
+ context 'when 1 period' do
125
+ it 'calculates correct ppmt value' do
126
+ loan = Finance::Loan.new(
127
+ nominal_rate: 0.0824, duration: 12, amount: 2500, period: 1
128
+ )
129
+ expect(loan.ppmt).to eq(-200.58192368678277)
130
+ end
131
+ end
132
+
133
+ context 'when 2 periods' do
134
+ it 'calculates correct ppmt value' do
135
+ loan = Finance::Loan.new(
136
+ nominal_rate: 0.0824, duration: 12, amount: 2500, period: 2
137
+ )
138
+ expect(loan.ppmt).to eq(-201.95925289609866)
139
+ end
140
+ end
141
+
142
+ context 'when 3 periods' do
143
+ it 'calculates correct ppmt value' do
144
+ loan = Finance::Loan.new(
145
+ nominal_rate: 0.0824, duration: 12, amount: 2500, period: 3
146
+ )
147
+ expect(loan.ppmt).to eq(-203.34603976598518)
148
+ end
149
+ end
150
+
151
+ context 'when 4 periods' do
152
+ it 'calculates correct ppmt value' do
153
+ loan = Finance::Loan.new(
154
+ nominal_rate: 0.0824, duration: 12, amount: 2500, period: 4
155
+ )
156
+ expect(loan.ppmt).to eq(-204.7423492390449)
157
+ end
158
+ end
159
+
160
+ context 'when 5 periods' do
161
+ it 'calculates correct ppmt value' do
162
+ loan = Finance::Loan.new(
163
+ nominal_rate: 0.0824, duration: 12, amount: 2500, period: 5
164
+ )
165
+ expect(loan.ppmt).to eq(-206.1482467038197)
166
+ end
167
+ end
168
+ end
169
+
170
+ describe '#nper' do
171
+ context 'with normal arguments' do
172
+ it 'calculates correct nper value' do
173
+ loan = Finance::Loan.new(
174
+ nominal_rate: 0.07, amount: 8000, payment: -150, future_value: 0
175
+ )
176
+ expect(loan.nper).to eq(64.0733487706618586)
177
+ end
178
+ end
179
+
180
+ context 'with incorrect arguments' do
181
+ it 'raises Math::DomainError' do
182
+ loan = Finance::Loan.new(
183
+ nominal_rate: 1e100, amount: 8000, payment: -150, future_value: 0
184
+ )
185
+ expect { loan.nper }.to raise_error(Math::DomainError)
186
+ end
39
187
  end
40
188
  end
41
189
  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.1.0
4
+ version: 0.2.2
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-28 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