finance_rb 0.1.1 → 1.0.0

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