finance_rb 0.1.0 → 0.2.2

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: 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