finance_rb 0.1.1 → 1.0.0

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: 98c8d72b54d7c7342331cf06086b5e5b1c5ec7c9fe9e3f60315fc78a94254885
4
- data.tar.gz: a5ce132889634c03e72dc3c884e3549071867e3a297de2ab1a040c2627b876bd
3
+ metadata.gz: 0abc599e1b7158a3095f3b9ac1627c54a0a85a9808a9afcc915a98fadfaff42d
4
+ data.tar.gz: f4417a96a3226bf76574e37fcf4c7f7e2bba9a92ea6586f43087444f4ecc239a
5
5
  SHA512:
6
- metadata.gz: 8938c6f7d84419716cc1ee728a372388f1956f9ab1be75c80bbbed70e744711a505aa252b19269c3655db04c1f73884280cf11cc1a483f2d4a24b64d231fbbcf
7
- data.tar.gz: a75066c79654c3eb3812db62f5e59bd130c12b87de2229da69a3ad6ba82d116089e95807c3a204c9c93780c243cc21b92953d224c4c532a400c4e30e22986af1
6
+ metadata.gz: fd17e47f2b8418ef8e1e1c7b24385bb6aff9f6d4342915a901f5590a4b3dc25c0d6154bf8af273bc377fa743e5104391ce05a67c80fbd93b9509f38bbf678ec4
7
+ data.tar.gz: 69ed50d7d91a8b416af2796ef0232dbf32bcae226e7cc71c174de163f4ddab4169af9f09952b7b5104367848b892fcafa971c737b50384bba8019dc0c71fa6bd
data/CHANGELOG.md CHANGED
@@ -1,3 +1,31 @@
1
+ ## [1.0.0] - 2021-05-01
2
+
3
+ ### Added
4
+ * Implement `Finance::Loan#rate`
5
+
6
+ ## [0.2.2] - 2021-05-01
7
+
8
+ ### Added
9
+ * Implement `Finance::Loan#pv`
10
+
11
+ ## [0.2.1] - 2021-05-01
12
+
13
+ ### Added
14
+ * Implement `Finance::Loan#nper`
15
+
16
+ ## [0.2.0] - 2021-04-30
17
+
18
+ ### Added
19
+ * Implement `Finance::Loan#ppmt`
20
+
21
+ ### Changed
22
+ * By default `Finance::Loan#pmt` returns negative values.
23
+
24
+ ## [0.1.2] - 2021-04-05
25
+
26
+ ### Added
27
+ * Implement `Finance::Loan#ipmt`
28
+
1
29
  ## [0.1.1] - 2021-03-30
2
30
 
3
31
  ### Added
data/README.md CHANGED
@@ -12,16 +12,24 @@ which are as follows:
12
12
  | numpy-financial function | ruby native function ported? | info|
13
13
  |:------------------------: |:------------------: | :------------------|
14
14
  | fv | ✅ | Computes the future value|
15
- | ipmt | | Computes interest payment for a loan|
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|
20
- | rate | | Computes the rate of interest per period|
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
+ | 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
23
  | mirr | ✅ | Computes the modified internal rate of return|
24
24
 
25
+
26
+ Things to be done:
27
+
28
+ 1. Xirr
29
+ 2. More specs for edge cases
30
+ 3. Fee, currency protection and other cool stuff for advanced usage
31
+ 4. Better errors
32
+
25
33
  ## Installation
26
34
 
27
35
  finance_rb is available as a gem, to install it just install the gem:
@@ -98,6 +98,7 @@ module Finance
98
98
 
99
99
  private
100
100
 
101
+ # @api private
101
102
  def correct_cashflows?(values)
102
103
  inflows, outflows = values.partition{ |i| i >= 0 }
103
104
  !(inflows.empty? || outflows.empty?)
@@ -105,6 +106,7 @@ module Finance
105
106
 
106
107
  # Base class for working with Newton's Method.
107
108
  # For more details, see Bigdecimal::Newton.
109
+ #
108
110
  # @api private
109
111
  class Function
110
112
  def initialize(values)
data/lib/finance/loan.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Finance
4
4
  class Loan
5
- PAYMENT_TYPE_MAPPING = { end: 0, beginning: 1 }.freeze
5
+ PAYMENT_TYPE_MAPPING = { end: 0.0, beginning: 1.0 }.freeze
6
6
 
7
7
  # @return [Float] The amount of loan request (I.e. a present value)
8
8
  # You can use #pv method to calculate value if param is not defined.
@@ -25,6 +25,7 @@ module Finance
25
25
 
26
26
  # @return [Float] The number of periods to be compounded for. (I.e. Nper())
27
27
  # Defaults to 1.
28
+ # You can use #nper method to calculate value if param is not defined.
28
29
  attr_accessor :duration
29
30
 
30
31
  # @return [Float] Future value.
@@ -36,6 +37,9 @@ module Finance
36
37
  # You can use #pmt method to calculate value if param is not defined.
37
38
  attr_accessor :payment
38
39
 
40
+ # @return [Float] Period under consideration.
41
+ attr_accessor :period
42
+
39
43
  # Create a new Loan instance.
40
44
  def initialize(**options)
41
45
  initialize_payment_type(options[:ptype])
@@ -43,6 +47,7 @@ module Finance
43
47
  @duration = options.fetch(:duration, 1).to_f
44
48
  @amount = options.fetch(:amount, 0).to_f
45
49
  @future_value = options.fetch(:future_value, 0).to_f
50
+ @period = options[:period]
46
51
  @payment = options[:payment]
47
52
  @monthly_rate = @nominal_rate / 12
48
53
  end
@@ -76,7 +81,72 @@ module Finance
76
81
  (factor - 1) * (1 + monthly_rate * ptype) / monthly_rate
77
82
  end
78
83
 
79
- (-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] The 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] The 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] The 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)
80
150
  end
81
151
 
82
152
  # Fv computes future value at the end of some periods (duration).
@@ -109,8 +179,82 @@ module Finance
109
179
  -((amount * factor) + (final_payment.to_f * second_factor))
110
180
  end
111
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
203
+ end
204
+
205
+ # Rate computes the interest rate per period
206
+ # by running Newton Rapson to find an approximate value.
207
+ #
208
+ # @return [Float] The interest rate.
209
+ #
210
+ # @param tolerance [Float] Required tolerance for the solution.
211
+ # @param initial_guess [Float] Starting guess for solving the rate of interest.
212
+ # @param iterations [Integer] Maximum iterations in finding the solution.
213
+ #
214
+ # @example
215
+ # require 'finance_rb'
216
+ # Finance::Loan.new(nominal_rate: 10, amount: -3500, payment: 0, duration: 10, future_value: 10000).rate
217
+ # #=> 0.11069085371426901
218
+ #
219
+ # @see http://www.oasis-open.org/committees/documents.php?wg_abbrev=office-formulaOpenDocument-formula-20090508.odt
220
+ # @see [WRW] Wheeler, D. A., E. Rathke, and R. Weir (Eds.) (2009, May).
221
+ # Open Document Format for Office Applications (OpenDocument)v1.2,
222
+ # Part 2: Recalculated Formula (OpenFormula) Format - Annotated Version,
223
+ # Pre-Draft 12. Organization for the Advancement of Structured Information
224
+ # Standards (OASIS). Billerica, MA, USA. [ODT Document].
225
+ def rate(tolerance: 1e-6, iterations: 100, initial_guess: 0.1)
226
+ next_iteration_rate = nil
227
+ current_iteration_rate = initial_guess
228
+
229
+ (0..iterations).each do |iteration|
230
+ next_iteration_rate = current_iteration_rate - rate_ratio(current_iteration_rate)
231
+ break if (next_iteration_rate - current_iteration_rate).abs <= tolerance
232
+ current_iteration_rate = next_iteration_rate
233
+ end
234
+
235
+ next_iteration_rate
236
+ end
237
+
112
238
  private
113
239
 
240
+ # rate_ratio computes the ratio
241
+ # that is used to find a single value that sets the non-liner equation to zero.
242
+ #
243
+ # @api private
244
+ def rate_ratio(rate)
245
+ t1 = (rate+1.0) ** duration
246
+ t2 = (rate+1.0) ** (duration-1.0)
247
+ g = future_value + t1 * amount + payment * (t1 - 1.0) * (rate * ptype + 1.0) / rate
248
+ derivative_g = \
249
+ (duration * t2 * amount)
250
+ - (payment * (t1 - 1.0) * (rate * ptype + 1.0) / (rate ** 2.0))
251
+ + (duration * payment * t2 * (rate * ptype + 1.0) / rate)
252
+ + (payment * (t1 - 1.0) * ptype/rate)
253
+
254
+ g / derivative_g
255
+ end
256
+
257
+ # @api private
114
258
  def initialize_payment_type(ptype)
115
259
  @ptype =
116
260
  if ptype.nil? || !PAYMENT_TYPE_MAPPING.keys.include?(ptype)
@@ -119,5 +263,13 @@ module Finance
119
263
  PAYMENT_TYPE_MAPPING[ptype]
120
264
  end
121
265
  end
266
+
267
+ # @api private
268
+ def remaining_balance
269
+ self.class.new(
270
+ nominal_rate: nominal_rate.to_f, duration: period - 1.0,
271
+ amount: amount.to_f, ptype: PAYMENT_TYPE_MAPPING.key(ptype)
272
+ ).fv(payment: pmt)
273
+ end
122
274
  end
123
275
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Finance
4
- VERSION = "0.1.1"
4
+ VERSION = "1.0.0"
5
5
  end
@@ -5,37 +5,37 @@ 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
39
  end
40
40
  end
41
41
 
@@ -70,4 +70,131 @@ RSpec.describe Finance::Loan do
70
70
  end
71
71
  end
72
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
187
+ end
188
+ end
189
+
190
+ describe '#rate' do
191
+ context 'with default arguments' do
192
+ it 'calculates correct rate value' do
193
+ loan = Finance::Loan.new(
194
+ nominal_rate: 10, amount: -3500, payment: 0, duration: 10, future_value: 10000
195
+ )
196
+ expect(loan.rate).to eq(0.11069085371426901)
197
+ end
198
+ end
199
+ end
73
200
  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.1
4
+ version: 1.0.0
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-30 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