finance_rb 0.0.4 → 0.2.1

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