bankroll 0.1.0 → 0.1.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 +4 -4
- data/.rubocop.yml +24 -2
- data/Gemfile +9 -5
- data/Gemfile.lock +51 -4
- data/README.md +10 -1
- data/bankroll.gemspec +8 -6
- data/lib/bankroll/amortization_schedule.rb +25 -18
- data/lib/bankroll/annuity_factor.rb +4 -2
- data/lib/bankroll/callable.rb +2 -0
- data/lib/bankroll/cumulative_interest.rb +4 -2
- data/lib/bankroll/decimal.rb +11 -7
- data/lib/bankroll/future_value.rb +6 -4
- data/lib/bankroll/interest_payment.rb +4 -2
- data/lib/bankroll/interest_rate.rb +10 -6
- data/lib/bankroll/payment.rb +2 -0
- data/lib/bankroll/present_value.rb +2 -0
- data/lib/bankroll/total_periods.rb +7 -5
- data/lib/bankroll/types.rb +5 -5
- data/lib/bankroll/unpaid_balance.rb +4 -2
- data/lib/bankroll/version.rb +1 -1
- data/lib/bankroll.rb +2 -3
- metadata +19 -18
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3d60a1c7c561cbb9945f94a7913cb116dba1c56d050ae0f26d34543c811a1fbe
|
|
4
|
+
data.tar.gz: 80a97fd7345e676473515dff20c449053502a5457f653d615061ca5d27a8e508
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e6fa504c7d3a0be597686abf78ac31ea7ac17d51c551d8b03737b242fd0d3eb79750d67efa430bd7d0cfa580dae50156ddd17381c410c66fdfaeb9b3d8e73275
|
|
7
|
+
data.tar.gz: 42793b333497660e72d893c46ab57b6ef4506c16994d34ddc17ad6d605df7b56e9160aee2826a57662e6ff2778e108202f4a39af6135e3b668f3bc06868f36de
|
data/.rubocop.yml
CHANGED
|
@@ -5,6 +5,8 @@ require:
|
|
|
5
5
|
|
|
6
6
|
# ==================== Main ==========================
|
|
7
7
|
AllCops:
|
|
8
|
+
TargetRubyVersion: 3.1
|
|
9
|
+
NewCops: enable
|
|
8
10
|
DisplayCopNames: true
|
|
9
11
|
Exclude:
|
|
10
12
|
- "bin/**/*"
|
|
@@ -68,6 +70,8 @@ Layout/LineEndStringConcatenationIndentation:
|
|
|
68
70
|
IndentationWidth: 0
|
|
69
71
|
Layout/LineLength:
|
|
70
72
|
Max: 100
|
|
73
|
+
Exclude:
|
|
74
|
+
- "lib/bankroll/interest_rate.rb"
|
|
71
75
|
Layout/MultilineArrayLineBreaks:
|
|
72
76
|
Enabled: true
|
|
73
77
|
Layout/MultilineAssignmentLayout:
|
|
@@ -118,7 +122,7 @@ Lint/ElseLayout:
|
|
|
118
122
|
Lint/EmptyBlock:
|
|
119
123
|
Enabled: true
|
|
120
124
|
AllowComments: false
|
|
121
|
-
AllowEmptyLambdas:
|
|
125
|
+
AllowEmptyLambdas: true
|
|
122
126
|
Lint/EmptyClass:
|
|
123
127
|
Enabled: true
|
|
124
128
|
Lint/EmptyConditionalBody:
|
|
@@ -199,6 +203,16 @@ Metrics/BlockLength:
|
|
|
199
203
|
- "spec/**/*"
|
|
200
204
|
Metrics/ParameterLists:
|
|
201
205
|
Max: 3
|
|
206
|
+
Exclude:
|
|
207
|
+
- "lib/bankroll/interest_rate.rb"
|
|
208
|
+
Metrics/AbcSize:
|
|
209
|
+
Enabled: true
|
|
210
|
+
Exclude:
|
|
211
|
+
- "lib/bankroll/interest_rate.rb"
|
|
212
|
+
Metrics/MethodLength:
|
|
213
|
+
Enabled: true
|
|
214
|
+
Exclude:
|
|
215
|
+
- "lib/bankroll/interest_rate.rb"
|
|
202
216
|
Naming/BlockForwarding:
|
|
203
217
|
Enabled: true
|
|
204
218
|
Naming/InclusiveLanguage:
|
|
@@ -223,8 +237,12 @@ Naming/VariableNumber:
|
|
|
223
237
|
EnforcedStyle: snake_case
|
|
224
238
|
AllowedIdentifiers:
|
|
225
239
|
- capture3
|
|
240
|
+
Exclude:
|
|
241
|
+
- "lib/bankroll/interest_rate.rb"
|
|
226
242
|
Naming/MethodParameterName:
|
|
227
243
|
AllowNamesEndingInNumbers: false
|
|
244
|
+
Exclude:
|
|
245
|
+
- "lib/bankroll/interest_rate.rb"
|
|
228
246
|
Security/IoMethods:
|
|
229
247
|
Enabled: true
|
|
230
248
|
Style/AccessorGrouping:
|
|
@@ -311,7 +329,7 @@ Style/MethodCallWithArgsParentheses:
|
|
|
311
329
|
AllowParenthesesInMultilineCall: true
|
|
312
330
|
IgnoreMacros: false
|
|
313
331
|
Style/MethodDefParentheses:
|
|
314
|
-
EnforcedStyle:
|
|
332
|
+
EnforcedStyle: require_parentheses
|
|
315
333
|
Style/MissingElse:
|
|
316
334
|
Enabled: false
|
|
317
335
|
Style/MultilineInPatternThen:
|
|
@@ -438,6 +456,10 @@ Performance/Sum:
|
|
|
438
456
|
Enabled: true
|
|
439
457
|
|
|
440
458
|
# ==================== RSpec ============================
|
|
459
|
+
RSpec/BeEq:
|
|
460
|
+
Enabled: true
|
|
461
|
+
RSpec/BeNil:
|
|
462
|
+
Enabled: true
|
|
441
463
|
RSpec/ContextWording:
|
|
442
464
|
Prefixes:
|
|
443
465
|
- when
|
data/Gemfile
CHANGED
|
@@ -7,8 +7,12 @@ gemspec
|
|
|
7
7
|
|
|
8
8
|
gem "rake", "~> 13.0"
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
gem "
|
|
13
|
-
|
|
14
|
-
gem "
|
|
10
|
+
group :development, :test do
|
|
11
|
+
gem "byebug"
|
|
12
|
+
gem "rspec", "~> 3.0"
|
|
13
|
+
gem "rubocop", "~> 1.21"
|
|
14
|
+
gem "rubocop-performance"
|
|
15
|
+
gem "rubocop-rake"
|
|
16
|
+
gem "rubocop-rspec"
|
|
17
|
+
gem "solargraph"
|
|
18
|
+
end
|
data/Gemfile.lock
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
PATH
|
|
2
2
|
remote: .
|
|
3
3
|
specs:
|
|
4
|
-
bankroll (0.1.
|
|
5
|
-
dry-equalizer
|
|
6
|
-
dry-initializer
|
|
7
|
-
dry-types
|
|
4
|
+
bankroll (0.1.1)
|
|
5
|
+
dry-equalizer (~> 0.3.0)
|
|
6
|
+
dry-initializer (~> 3.1.1)
|
|
7
|
+
dry-types (~> 1.5.1)
|
|
8
8
|
|
|
9
9
|
GEM
|
|
10
10
|
remote: https://rubygems.org/
|
|
11
11
|
specs:
|
|
12
12
|
ast (2.4.2)
|
|
13
|
+
backport (1.2.0)
|
|
14
|
+
benchmark (0.2.0)
|
|
13
15
|
byebug (11.1.3)
|
|
14
16
|
concurrent-ruby (1.1.9)
|
|
15
17
|
diff-lcs (1.5.0)
|
|
@@ -33,12 +35,25 @@ GEM
|
|
|
33
35
|
dry-core (~> 0.5, >= 0.5)
|
|
34
36
|
dry-inflector (~> 0.1, >= 0.1.2)
|
|
35
37
|
dry-logic (~> 1.0, >= 1.0.2)
|
|
38
|
+
e2mmap (0.1.0)
|
|
39
|
+
jaro_winkler (1.5.4)
|
|
40
|
+
kramdown (2.3.1)
|
|
41
|
+
rexml
|
|
42
|
+
kramdown-parser-gfm (1.1.0)
|
|
43
|
+
kramdown (~> 2.0)
|
|
44
|
+
nokogiri (1.13.3-x86_64-darwin)
|
|
45
|
+
racc (~> 1.4)
|
|
46
|
+
nokogiri (1.13.3-x86_64-linux)
|
|
47
|
+
racc (~> 1.4)
|
|
36
48
|
parallel (1.21.0)
|
|
37
49
|
parser (3.1.0.0)
|
|
38
50
|
ast (~> 2.4.1)
|
|
51
|
+
racc (1.6.0)
|
|
39
52
|
rainbow (3.1.1)
|
|
40
53
|
rake (13.0.6)
|
|
41
54
|
regexp_parser (2.2.0)
|
|
55
|
+
reverse_markdown (2.1.1)
|
|
56
|
+
nokogiri
|
|
42
57
|
rexml (3.2.5)
|
|
43
58
|
rspec (3.10.0)
|
|
44
59
|
rspec-core (~> 3.10.0)
|
|
@@ -64,11 +79,39 @@ GEM
|
|
|
64
79
|
unicode-display_width (>= 1.4.0, < 3.0)
|
|
65
80
|
rubocop-ast (1.15.1)
|
|
66
81
|
parser (>= 3.0.1.1)
|
|
82
|
+
rubocop-performance (1.13.2)
|
|
83
|
+
rubocop (>= 1.7.0, < 2.0)
|
|
84
|
+
rubocop-ast (>= 0.4.0)
|
|
85
|
+
rubocop-rake (0.6.0)
|
|
86
|
+
rubocop (~> 1.0)
|
|
87
|
+
rubocop-rspec (2.8.0)
|
|
88
|
+
rubocop (~> 1.19)
|
|
67
89
|
ruby-progressbar (1.11.0)
|
|
90
|
+
solargraph (0.44.3)
|
|
91
|
+
backport (~> 1.2)
|
|
92
|
+
benchmark
|
|
93
|
+
bundler (>= 1.17.2)
|
|
94
|
+
diff-lcs (~> 1.4)
|
|
95
|
+
e2mmap
|
|
96
|
+
jaro_winkler (~> 1.5)
|
|
97
|
+
kramdown (~> 2.3)
|
|
98
|
+
kramdown-parser-gfm (~> 1.1)
|
|
99
|
+
parser (~> 3.0)
|
|
100
|
+
reverse_markdown (>= 1.0.5, < 3)
|
|
101
|
+
rubocop (>= 0.52)
|
|
102
|
+
thor (~> 1.0)
|
|
103
|
+
tilt (~> 2.0)
|
|
104
|
+
yard (~> 0.9, >= 0.9.24)
|
|
105
|
+
thor (1.2.1)
|
|
106
|
+
tilt (2.0.10)
|
|
68
107
|
unicode-display_width (2.1.0)
|
|
108
|
+
webrick (1.7.0)
|
|
109
|
+
yard (0.9.27)
|
|
110
|
+
webrick (~> 1.7.0)
|
|
69
111
|
|
|
70
112
|
PLATFORMS
|
|
71
113
|
x86_64-darwin-20
|
|
114
|
+
x86_64-linux
|
|
72
115
|
|
|
73
116
|
DEPENDENCIES
|
|
74
117
|
bankroll!
|
|
@@ -76,6 +119,10 @@ DEPENDENCIES
|
|
|
76
119
|
rake (~> 13.0)
|
|
77
120
|
rspec (~> 3.0)
|
|
78
121
|
rubocop (~> 1.21)
|
|
122
|
+
rubocop-performance
|
|
123
|
+
rubocop-rake
|
|
124
|
+
rubocop-rspec
|
|
125
|
+
solargraph
|
|
79
126
|
|
|
80
127
|
BUNDLED WITH
|
|
81
128
|
2.2.32
|
data/README.md
CHANGED
|
@@ -46,8 +46,9 @@ puts payment #=> Bankroll::Decimal["3_216.40"]
|
|
|
46
46
|
original_amount = Bankroll.present_value(
|
|
47
47
|
periods: 360, # 30 years
|
|
48
48
|
monthly_payment: 3_216.40,
|
|
49
|
-
interest_rate: 0.01 / 12.0, #
|
|
49
|
+
interest_rate: 0.01 / 12.0, # 1% APR
|
|
50
50
|
).round
|
|
51
|
+
|
|
51
52
|
puts original_amount #=> Bankroll::Decimal["1_000_001.49"]
|
|
52
53
|
|
|
53
54
|
# What does the balance look like after a single mortgage payment?
|
|
@@ -58,6 +59,7 @@ unpaid_balance = Bankroll.unpaid_balance(
|
|
|
58
59
|
period: 1,
|
|
59
60
|
payment: 3_216.40
|
|
60
61
|
).round
|
|
62
|
+
|
|
61
63
|
puts unpaid_balance #=> Bankroll::Decimal["997_616.94"]
|
|
62
64
|
|
|
63
65
|
# What was the interest rate?
|
|
@@ -66,6 +68,7 @@ interest_rate = Bankroll.interest_rate(
|
|
|
66
68
|
periods: 360,
|
|
67
69
|
payment: -3_216.40,
|
|
68
70
|
).round(3)
|
|
71
|
+
|
|
69
72
|
puts interest_rate #=> Bankroll::Decimal["0.833e-3"]
|
|
70
73
|
|
|
71
74
|
# What is the annuity factor of a rate for n periods?
|
|
@@ -73,6 +76,7 @@ annuity_factor = Bankroll.annuity_factor(
|
|
|
73
76
|
interest_rate: 0.01,
|
|
74
77
|
periods: 2
|
|
75
78
|
).round(4)
|
|
79
|
+
|
|
76
80
|
puts annuity_factor #=> Bankroll::Decimal["1.9704"]
|
|
77
81
|
|
|
78
82
|
# What is the total interest paid on a 500_000 1% mortgage
|
|
@@ -82,6 +86,7 @@ interest_paid = Bankroll.cumulative_interest(
|
|
|
82
86
|
payment: 1_608.20,
|
|
83
87
|
present_value: 500_000
|
|
84
88
|
).round
|
|
89
|
+
|
|
85
90
|
puts interest_paid #=> Bankroll::Decimal["832.34"]
|
|
86
91
|
|
|
87
92
|
# What is the total cost of a 500_000 1% loan with interest?
|
|
@@ -91,6 +96,7 @@ total_amount = Bankroll.future_value(
|
|
|
91
96
|
periods: 360,
|
|
92
97
|
payment: 1_608.20
|
|
93
98
|
).round
|
|
99
|
+
|
|
94
100
|
puts total_amount #=> Bankroll::Decimal["647_846.10"]
|
|
95
101
|
|
|
96
102
|
# What is the interest payment on the 17th period of a $165,000 3% mortgage?
|
|
@@ -100,6 +106,7 @@ interest_payment = Bankroll.interest_payment(
|
|
|
100
106
|
period: 17,
|
|
101
107
|
present_value: 165_000
|
|
102
108
|
).round
|
|
109
|
+
|
|
103
110
|
puts interest_payment #=> Bankroll::Decimal["400.96"]
|
|
104
111
|
|
|
105
112
|
# How many periods until a -$1000 account with -$100/month reaches $10_000 with
|
|
@@ -110,6 +117,7 @@ total_periods = Bankroll.total_periods(
|
|
|
110
117
|
present_value: -1_000,
|
|
111
118
|
future_value: 10_000
|
|
112
119
|
)
|
|
120
|
+
|
|
113
121
|
puts total_periods #=> Bankroll::Decimal["59.67"]
|
|
114
122
|
|
|
115
123
|
# What is the amortization schedule of a $165_000 3% 30 year mortgage?
|
|
@@ -118,6 +126,7 @@ amortization_schedule = Bankroll.amortization_schedule(
|
|
|
118
126
|
interest_rate: 0.03 / 12.0,
|
|
119
127
|
periods: 360
|
|
120
128
|
)
|
|
129
|
+
|
|
121
130
|
puts amortization_schedule.payments #=> [
|
|
122
131
|
# Bankroll::AmortizationSchedule::Payment.new(
|
|
123
132
|
# payment: Bankroll::Decimal["695.65"],
|
data/bankroll.gemspec
CHANGED
|
@@ -12,7 +12,7 @@ Gem::Specification.new do |spec|
|
|
|
12
12
|
spec.description = "Mortgage and refinance calculations for ruby"
|
|
13
13
|
spec.homepage = "https://github.com/nolantait/bankroll"
|
|
14
14
|
spec.license = "MIT"
|
|
15
|
-
spec.required_ruby_version = ">=
|
|
15
|
+
spec.required_ruby_version = ">= 3.1.0"
|
|
16
16
|
|
|
17
17
|
spec.metadata["homepage_uri"] = spec.homepage
|
|
18
18
|
spec.metadata["source_code_uri"] = "https://github.com/nolantait/bankroll"
|
|
@@ -22,18 +22,20 @@ Gem::Specification.new do |spec|
|
|
|
22
22
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
23
23
|
spec.files = Dir.chdir(File.expand_path(__dir__)) do
|
|
24
24
|
`git ls-files -z`.split("\x0").reject do |f|
|
|
25
|
-
(f == __FILE__) ||
|
|
25
|
+
(f == __FILE__) ||
|
|
26
|
+
f.match(%r{\A(?:(?:test|spec|features)/|\.(?:git|travis|circleci)|appveyor)})
|
|
26
27
|
end
|
|
27
28
|
end
|
|
28
29
|
spec.bindir = "exe"
|
|
29
|
-
spec.executables = spec.files.grep(%r
|
|
30
|
+
spec.executables = spec.files.grep(%r(\Aexe/)) { |f| File.basename(f) }
|
|
30
31
|
spec.require_paths = ["lib"]
|
|
31
32
|
|
|
32
33
|
# Uncomment to register a new dependency of your gem
|
|
33
|
-
spec.add_dependency "dry-
|
|
34
|
-
spec.add_dependency "dry-initializer"
|
|
35
|
-
spec.add_dependency "dry-
|
|
34
|
+
spec.add_dependency "dry-equalizer", "~> 0.3.0"
|
|
35
|
+
spec.add_dependency "dry-initializer", "~> 3.1.1"
|
|
36
|
+
spec.add_dependency "dry-types", "~> 1.5.1"
|
|
36
37
|
|
|
37
38
|
# For more information and examples about making a new gem, checkout our
|
|
38
39
|
# guide at: https://bundler.io/guides/creating_gem.html
|
|
40
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
|
39
41
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bankroll
|
|
2
4
|
class AmortizationSchedule
|
|
3
5
|
extend Dry::Initializer
|
|
@@ -18,22 +20,14 @@ module Bankroll
|
|
|
18
20
|
|
|
19
21
|
def each
|
|
20
22
|
Enumerator.new do |yielder|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
+
current_payment = Payment.new(
|
|
24
|
+
payment: monthly_payment.round,
|
|
25
|
+
total_interest: Decimal["0"],
|
|
26
|
+
balance: @present_value
|
|
27
|
+
)
|
|
23
28
|
|
|
24
29
|
periods.times do |period|
|
|
25
|
-
|
|
26
|
-
principal = payment - interest
|
|
27
|
-
total_interest += interest
|
|
28
|
-
balance -= principal
|
|
29
|
-
|
|
30
|
-
yielder << Payment.new(
|
|
31
|
-
payment: payment.round,
|
|
32
|
-
principal: principal.round,
|
|
33
|
-
interest: interest.round,
|
|
34
|
-
total_interest: total_interest.round,
|
|
35
|
-
balance: balance.round
|
|
36
|
-
)
|
|
30
|
+
yielder << current_payment = payment_for_period(period, current_payment)
|
|
37
31
|
end
|
|
38
32
|
end
|
|
39
33
|
end
|
|
@@ -48,18 +42,31 @@ module Bankroll
|
|
|
48
42
|
|
|
49
43
|
private
|
|
50
44
|
|
|
45
|
+
def payment_for_period(period, last_payment)
|
|
46
|
+
interest = interest_payment(period + 1)
|
|
47
|
+
principal = last_payment.payment - interest
|
|
48
|
+
|
|
49
|
+
Payment.new(
|
|
50
|
+
payment: last_payment.payment,
|
|
51
|
+
principal: principal.round,
|
|
52
|
+
interest: interest.round,
|
|
53
|
+
total_interest: (last_payment.total_interest + interest).round,
|
|
54
|
+
balance: (last_payment.balance - principal).round
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
51
58
|
def interest_payment(period)
|
|
52
59
|
InterestPayment.call(
|
|
53
60
|
interest_rate: @interest_rate,
|
|
54
61
|
periods: @periods,
|
|
55
|
-
period
|
|
62
|
+
period:,
|
|
56
63
|
present_value: @present_value,
|
|
57
|
-
payment: -
|
|
64
|
+
payment: -monthly_payment
|
|
58
65
|
)
|
|
59
66
|
end
|
|
60
67
|
|
|
61
|
-
def
|
|
62
|
-
@
|
|
68
|
+
def monthly_payment
|
|
69
|
+
@monthly_payment ||= Bankroll::Payment.call(
|
|
63
70
|
present_value: @present_value,
|
|
64
71
|
interest_rate: @interest_rate,
|
|
65
72
|
periods: @periods
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bankroll
|
|
2
4
|
class AnnuityFactor
|
|
3
5
|
extend Callable
|
|
4
6
|
extend Dry::Initializer
|
|
5
7
|
|
|
6
|
-
ONE = Decimal[
|
|
8
|
+
ONE = Decimal["1"].freeze
|
|
7
9
|
|
|
8
10
|
option :periods, Types["bankroll.decimal"]
|
|
9
11
|
option :interest_rate, Types["bankroll.decimal"]
|
|
@@ -19,7 +21,7 @@ module Bankroll
|
|
|
19
21
|
end
|
|
20
22
|
|
|
21
23
|
def interest_contribution_per_period
|
|
22
|
-
(ONE + @interest_rate)
|
|
24
|
+
(ONE + @interest_rate)**-@periods
|
|
23
25
|
end
|
|
24
26
|
end
|
|
25
27
|
end
|
data/lib/bankroll/callable.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bankroll
|
|
2
4
|
class CumulativeInterest
|
|
3
5
|
# Calculate the total interest paid at any period
|
|
@@ -11,7 +13,7 @@ module Bankroll
|
|
|
11
13
|
option :present_value, Types["bankroll.decimal"]
|
|
12
14
|
|
|
13
15
|
def call
|
|
14
|
-
payments_made +
|
|
16
|
+
payments_made +
|
|
15
17
|
((total_interest - @payment) * percentage_of_interest_per_period)
|
|
16
18
|
end
|
|
17
19
|
|
|
@@ -26,7 +28,7 @@ module Bankroll
|
|
|
26
28
|
end
|
|
27
29
|
|
|
28
30
|
def compound_rate
|
|
29
|
-
(ONE + @interest_rate)
|
|
31
|
+
(ONE + @interest_rate)**@periods
|
|
30
32
|
end
|
|
31
33
|
|
|
32
34
|
def total_interest
|
data/lib/bankroll/decimal.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bankroll
|
|
2
|
-
class Decimal <
|
|
4
|
+
class Decimal < SimpleDelegator
|
|
3
5
|
include Dry::Equalizer.new(:value)
|
|
4
6
|
|
|
5
7
|
ROUNDING = :half_even
|
|
@@ -13,17 +15,19 @@ module Bankroll
|
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
attr_reader :value
|
|
16
|
-
|
|
18
|
+
|
|
19
|
+
alias __getobj__ value
|
|
17
20
|
|
|
18
21
|
def initialize(value)
|
|
19
22
|
decimal = case value
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
when self.class
|
|
24
|
+
value.value.to_s
|
|
25
|
+
else
|
|
26
|
+
value.to_s
|
|
27
|
+
end
|
|
25
28
|
|
|
26
29
|
@value = BigDecimal(Types["string"][decimal])
|
|
30
|
+
super(@value)
|
|
27
31
|
end
|
|
28
32
|
|
|
29
33
|
def round(precision = 2, rounding = ROUNDING)
|
|
@@ -1,7 +1,9 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bankroll
|
|
2
4
|
class FutureValue
|
|
3
|
-
# Future value is the value to which an investment will grow
|
|
4
|
-
# after one or more periods. Usage is with a fixed payment
|
|
5
|
+
# Future value is the value to which an investment will grow
|
|
6
|
+
# after one or more periods. Usage is with a fixed payment
|
|
5
7
|
|
|
6
8
|
extend Callable
|
|
7
9
|
extend Dry::Initializer
|
|
@@ -13,13 +15,13 @@ module Bankroll
|
|
|
13
15
|
|
|
14
16
|
def call
|
|
15
17
|
(effective_rate * present_value) +
|
|
16
|
-
(@payment * (1 + @interest_rate * 0) * (effective_rate - 1) / @interest_rate)
|
|
18
|
+
(@payment * (1 + (@interest_rate * 0)) * (effective_rate - 1) / @interest_rate)
|
|
17
19
|
end
|
|
18
20
|
|
|
19
21
|
private
|
|
20
22
|
|
|
21
23
|
def effective_rate
|
|
22
|
-
(ONE + @interest_rate)
|
|
24
|
+
(ONE + @interest_rate)**@periods
|
|
23
25
|
end
|
|
24
26
|
end
|
|
25
27
|
end
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bankroll
|
|
2
4
|
class InterestPayment
|
|
3
5
|
# The interest portion of a payment at period N
|
|
@@ -9,7 +11,7 @@ module Bankroll
|
|
|
9
11
|
option :periods, Types["bankroll.decimal"]
|
|
10
12
|
option :period, Types["integer"]
|
|
11
13
|
option :present_value, Types["bankroll.decimal"]
|
|
12
|
-
option :payment, Types["bankroll.decimal"] | Types["nil"], default: -> {
|
|
14
|
+
option :payment, Types["bankroll.decimal"] | Types["nil"], default: -> {}
|
|
13
15
|
|
|
14
16
|
def call
|
|
15
17
|
future_loan_balance * @interest_rate
|
|
@@ -20,7 +22,7 @@ module Bankroll
|
|
|
20
22
|
def future_loan_balance
|
|
21
23
|
FutureValue.call(
|
|
22
24
|
periods: @period - 1,
|
|
23
|
-
payment
|
|
25
|
+
payment:,
|
|
24
26
|
present_value: @present_value,
|
|
25
27
|
interest_rate: @interest_rate
|
|
26
28
|
)
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bankroll
|
|
2
4
|
class InterestRate
|
|
3
5
|
extend Dry::Initializer
|
|
@@ -15,7 +17,9 @@ module Bankroll
|
|
|
15
17
|
significance_required = 1e-6
|
|
16
18
|
stop = false
|
|
17
19
|
|
|
18
|
-
|
|
20
|
+
next_guess = 0
|
|
21
|
+
|
|
22
|
+
loop do
|
|
19
23
|
temp = newton_iteration(
|
|
20
24
|
guess,
|
|
21
25
|
@periods,
|
|
@@ -28,20 +32,20 @@ module Bankroll
|
|
|
28
32
|
difference = (next_guess - guess).abs
|
|
29
33
|
stop = difference < significance_required
|
|
30
34
|
guess = next_guess
|
|
31
|
-
|
|
35
|
+
break if stop
|
|
36
|
+
end
|
|
32
37
|
|
|
33
38
|
next_guess
|
|
34
39
|
end
|
|
35
40
|
|
|
36
41
|
private
|
|
37
42
|
|
|
38
|
-
|
|
39
43
|
# This method was borrowed from the NumPy rate formula
|
|
40
44
|
# which was generated by Sage
|
|
41
45
|
def newton_iteration(r, n, p, x, y, w)
|
|
42
|
-
t1 = (r+1)**n
|
|
43
|
-
t2 = (r+1)**(n-1)
|
|
44
|
-
((y + t1*x + p*(t1 - 1)*(r*w + 1)/r) / (n*t2*x - p*(t1 - 1)*(r*w + 1)/(r**2) + n*p*t2*(r*w + 1)/r + p*(t1 - 1)*w/r))
|
|
46
|
+
t1 = (r + 1)**n
|
|
47
|
+
t2 = (r + 1)**(n - 1)
|
|
48
|
+
((y + (t1 * x) + (p * (t1 - 1) * ((r * w) + 1) / r)) / ((n * t2 * x) - (p * (t1 - 1) * ((r * w) + 1) / (r**2)) + (n * p * t2 * ((r * w) + 1) / r) + (p * (t1 - 1) * w / r)))
|
|
45
49
|
end
|
|
46
50
|
end
|
|
47
51
|
end
|
data/lib/bankroll/payment.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bankroll
|
|
2
4
|
class TotalPeriods
|
|
3
5
|
extend Callable
|
|
@@ -28,16 +30,16 @@ module Bankroll
|
|
|
28
30
|
end
|
|
29
31
|
|
|
30
32
|
def percentage_of_total_interest_on_principal
|
|
31
|
-
(-@future_value + initial_payment_interest) /
|
|
33
|
+
(-@future_value + initial_payment_interest) /
|
|
32
34
|
(@present_value + initial_payment_interest)
|
|
33
35
|
end
|
|
34
36
|
|
|
35
37
|
def initial_payment_interest
|
|
36
38
|
case type
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
when :ordinary
|
|
40
|
+
(@payment * (1 + @interest_rate)) / @interest_rate
|
|
41
|
+
else
|
|
42
|
+
0
|
|
41
43
|
end
|
|
42
44
|
end
|
|
43
45
|
end
|
data/lib/bankroll/types.rb
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bankroll
|
|
2
4
|
module Types
|
|
3
5
|
include Dry.Types()
|
|
@@ -13,11 +15,9 @@ module Bankroll
|
|
|
13
15
|
end
|
|
14
16
|
|
|
15
17
|
ConvertToDecimal = lambda do |value|
|
|
16
|
-
if value.is_a? Bankroll::Decimal
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
Bankroll::Decimal[value.to_s]
|
|
20
|
-
end
|
|
18
|
+
return value if value.is_a? Bankroll::Decimal
|
|
19
|
+
|
|
20
|
+
Bankroll::Decimal[value.to_s]
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
register("bankroll.decimal", Constructor[Bankroll::Decimal, fn: ConvertToDecimal])
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
1
3
|
module Bankroll
|
|
2
4
|
class UnpaidBalance
|
|
3
5
|
extend Callable
|
|
4
6
|
extend Dry::Initializer
|
|
5
7
|
|
|
6
|
-
ONE = Decimal[
|
|
8
|
+
ONE = Decimal["1"].freeze
|
|
7
9
|
|
|
8
10
|
option :present_value, Types["bankroll.decimal"]
|
|
9
11
|
option :interest_rate, Types["bankroll.decimal"]
|
|
@@ -30,7 +32,7 @@ module Bankroll
|
|
|
30
32
|
|
|
31
33
|
def annuity_factor
|
|
32
34
|
AnnuityFactor.call(
|
|
33
|
-
interest_rate: @interest_rate,
|
|
35
|
+
interest_rate: @interest_rate,
|
|
34
36
|
periods: remaining
|
|
35
37
|
)
|
|
36
38
|
end
|
data/lib/bankroll/version.rb
CHANGED
data/lib/bankroll.rb
CHANGED
|
@@ -25,9 +25,8 @@ require_relative "bankroll/interest_rate"
|
|
|
25
25
|
BigDecimal.mode(BigDecimal::ROUND_MODE, Bankroll::Decimal::ROUNDING)
|
|
26
26
|
|
|
27
27
|
module Bankroll
|
|
28
|
-
ZERO = Decimal[
|
|
29
|
-
ONE = Decimal[
|
|
30
|
-
|
|
28
|
+
ZERO = Decimal["0"].freeze
|
|
29
|
+
ONE = Decimal["1"].freeze
|
|
31
30
|
|
|
32
31
|
class Error < StandardError; end
|
|
33
32
|
# Your code goes here...
|
metadata
CHANGED
|
@@ -1,57 +1,57 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bankroll
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.1.
|
|
4
|
+
version: 0.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Nolan J Tait
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: exe
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2022-02
|
|
11
|
+
date: 2022-03-02 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
|
-
name: dry-
|
|
14
|
+
name: dry-equalizer
|
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
|
16
16
|
requirements:
|
|
17
|
-
- - "
|
|
17
|
+
- - "~>"
|
|
18
18
|
- !ruby/object:Gem::Version
|
|
19
|
-
version:
|
|
19
|
+
version: 0.3.0
|
|
20
20
|
type: :runtime
|
|
21
21
|
prerelease: false
|
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
|
23
23
|
requirements:
|
|
24
|
-
- - "
|
|
24
|
+
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
|
-
version:
|
|
26
|
+
version: 0.3.0
|
|
27
27
|
- !ruby/object:Gem::Dependency
|
|
28
28
|
name: dry-initializer
|
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
|
30
30
|
requirements:
|
|
31
|
-
- - "
|
|
31
|
+
- - "~>"
|
|
32
32
|
- !ruby/object:Gem::Version
|
|
33
|
-
version:
|
|
33
|
+
version: 3.1.1
|
|
34
34
|
type: :runtime
|
|
35
35
|
prerelease: false
|
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
|
37
37
|
requirements:
|
|
38
|
-
- - "
|
|
38
|
+
- - "~>"
|
|
39
39
|
- !ruby/object:Gem::Version
|
|
40
|
-
version:
|
|
40
|
+
version: 3.1.1
|
|
41
41
|
- !ruby/object:Gem::Dependency
|
|
42
|
-
name: dry-
|
|
42
|
+
name: dry-types
|
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
|
44
44
|
requirements:
|
|
45
|
-
- - "
|
|
45
|
+
- - "~>"
|
|
46
46
|
- !ruby/object:Gem::Version
|
|
47
|
-
version:
|
|
47
|
+
version: 1.5.1
|
|
48
48
|
type: :runtime
|
|
49
49
|
prerelease: false
|
|
50
50
|
version_requirements: !ruby/object:Gem::Requirement
|
|
51
51
|
requirements:
|
|
52
|
-
- - "
|
|
52
|
+
- - "~>"
|
|
53
53
|
- !ruby/object:Gem::Version
|
|
54
|
-
version:
|
|
54
|
+
version: 1.5.1
|
|
55
55
|
description: Mortgage and refinance calculations for ruby
|
|
56
56
|
email:
|
|
57
57
|
- nolanjtait@gmail.com
|
|
@@ -94,6 +94,7 @@ metadata:
|
|
|
94
94
|
homepage_uri: https://github.com/nolantait/bankroll
|
|
95
95
|
source_code_uri: https://github.com/nolantait/bankroll
|
|
96
96
|
changelog_uri: https://github.com/nolantait/bankroll/CHANGELOG.md
|
|
97
|
+
rubygems_mfa_required: 'true'
|
|
97
98
|
post_install_message:
|
|
98
99
|
rdoc_options: []
|
|
99
100
|
require_paths:
|
|
@@ -102,14 +103,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
102
103
|
requirements:
|
|
103
104
|
- - ">="
|
|
104
105
|
- !ruby/object:Gem::Version
|
|
105
|
-
version:
|
|
106
|
+
version: 3.1.0
|
|
106
107
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
107
108
|
requirements:
|
|
108
109
|
- - ">="
|
|
109
110
|
- !ruby/object:Gem::Version
|
|
110
111
|
version: '0'
|
|
111
112
|
requirements: []
|
|
112
|
-
rubygems_version: 3.
|
|
113
|
+
rubygems_version: 3.3.3
|
|
113
114
|
signing_key:
|
|
114
115
|
specification_version: 4
|
|
115
116
|
summary: Mortgage and refinance calculations for ruby
|