bankroll 0.1.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 +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +485 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +14 -0
- data/Gemfile.lock +81 -0
- data/LICENSE.txt +21 -0
- data/README.md +152 -0
- data/Rakefile +12 -0
- data/bankroll.gemspec +39 -0
- data/bin/bundle +114 -0
- data/bin/console +15 -0
- data/bin/rspec +29 -0
- data/bin/setup +8 -0
- data/lib/bankroll/amortization_schedule.rb +69 -0
- data/lib/bankroll/annuity_factor.rb +25 -0
- data/lib/bankroll/callable.rb +7 -0
- data/lib/bankroll/cumulative_interest.rb +36 -0
- data/lib/bankroll/decimal.rb +59 -0
- data/lib/bankroll/future_value.rb +25 -0
- data/lib/bankroll/interest_payment.rb +37 -0
- data/lib/bankroll/interest_rate.rb +47 -0
- data/lib/bankroll/payment.rb +28 -0
- data/lib/bankroll/present_value.rb +28 -0
- data/lib/bankroll/total_periods.rb +44 -0
- data/lib/bankroll/types.rb +26 -0
- data/lib/bankroll/unpaid_balance.rb +42 -0
- data/lib/bankroll/version.rb +5 -0
- data/lib/bankroll.rb +70 -0
- metadata +116 -0
@@ -0,0 +1,28 @@
|
|
1
|
+
module Bankroll
|
2
|
+
class Payment
|
3
|
+
extend Callable
|
4
|
+
extend Dry::Initializer
|
5
|
+
|
6
|
+
option :present_value, Types["bankroll.decimal"]
|
7
|
+
option :interest_rate, Types["bankroll.decimal"]
|
8
|
+
option :periods, Types["bankroll.decimal"]
|
9
|
+
|
10
|
+
def call
|
11
|
+
payment
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def payment
|
17
|
+
@present_value * mortgage_constant
|
18
|
+
end
|
19
|
+
|
20
|
+
def mortgage_constant
|
21
|
+
ONE / annuity_factor
|
22
|
+
end
|
23
|
+
|
24
|
+
def annuity_factor
|
25
|
+
AnnuityFactor.call(interest_rate: @interest_rate, periods: @periods)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Bankroll
|
2
|
+
class PresentValue
|
3
|
+
extend Callable
|
4
|
+
extend Dry::Initializer
|
5
|
+
|
6
|
+
# Calculates the present value of an annuity, e.g. the minimal loan amount
|
7
|
+
# required for a certain home price
|
8
|
+
|
9
|
+
option :periods, Types["bankroll.decimal"]
|
10
|
+
option :payment, Types["bankroll.decimal"]
|
11
|
+
option :future_value, Types["bankroll.decimal"], default: -> { ZERO }
|
12
|
+
option :interest_rate, Types["bankroll.decimal"], default: -> { ZERO }
|
13
|
+
|
14
|
+
def call
|
15
|
+
present_value
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def present_value
|
21
|
+
@payment * annuity_factor
|
22
|
+
end
|
23
|
+
|
24
|
+
def annuity_factor
|
25
|
+
AnnuityFactor.call(periods: @periods, interest_rate: @interest_rate)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,44 @@
|
|
1
|
+
module Bankroll
|
2
|
+
class TotalPeriods
|
3
|
+
extend Callable
|
4
|
+
extend Dry::Initializer
|
5
|
+
|
6
|
+
# Equivalent to Excel's NPER
|
7
|
+
# Calculates the total periods of an investment given a fixed payment,
|
8
|
+
# an interest rate and the loan's present value.
|
9
|
+
# Future value can be 0 for the end of a loan or any intermediate value
|
10
|
+
|
11
|
+
option :interest_rate, Types["bankroll.decimal"]
|
12
|
+
option :payment, Types["bankroll.decimal"]
|
13
|
+
option :present_value, Types["bankroll.decimal"]
|
14
|
+
option :future_value, Types["bankroll.decimal"]
|
15
|
+
option :type, Types["annuity_type"], default: -> { :ordinary }
|
16
|
+
|
17
|
+
def call
|
18
|
+
return (-@present_value - @future_value) / @payment if interest_rate.zero?
|
19
|
+
|
20
|
+
Bankroll::Decimal[periods]
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def periods
|
26
|
+
Math.log(percentage_of_total_interest_on_principal) /
|
27
|
+
Math.log((1 + @interest_rate))
|
28
|
+
end
|
29
|
+
|
30
|
+
def percentage_of_total_interest_on_principal
|
31
|
+
(-@future_value + initial_payment_interest) /
|
32
|
+
(@present_value + initial_payment_interest)
|
33
|
+
end
|
34
|
+
|
35
|
+
def initial_payment_interest
|
36
|
+
case type
|
37
|
+
when :ordinary
|
38
|
+
(@payment * (1 + @interest_rate)) / @interest_rate
|
39
|
+
else
|
40
|
+
0
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Bankroll
|
2
|
+
module Types
|
3
|
+
include Dry.Types()
|
4
|
+
|
5
|
+
Constructor = Dry::Types::Constructor
|
6
|
+
|
7
|
+
def self.[](key)
|
8
|
+
Dry::Types[key]
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.register(key, value)
|
12
|
+
Dry::Types.register(key, value)
|
13
|
+
end
|
14
|
+
|
15
|
+
ConvertToDecimal = lambda do |value|
|
16
|
+
if value.is_a? Bankroll::Decimal
|
17
|
+
return value
|
18
|
+
else
|
19
|
+
Bankroll::Decimal[value.to_s]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
register("bankroll.decimal", Constructor[Bankroll::Decimal, fn: ConvertToDecimal])
|
24
|
+
register("annuity_type", Types["symbol"].enum(:ordinary, :due))
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module Bankroll
|
2
|
+
class UnpaidBalance
|
3
|
+
extend Callable
|
4
|
+
extend Dry::Initializer
|
5
|
+
|
6
|
+
ONE = Decimal['1'].freeze
|
7
|
+
|
8
|
+
option :present_value, Types["bankroll.decimal"]
|
9
|
+
option :interest_rate, Types["bankroll.decimal"]
|
10
|
+
option :periods, Types["bankroll.decimal"]
|
11
|
+
option :period, Types["bankroll.decimal"]
|
12
|
+
|
13
|
+
def call
|
14
|
+
unpaid_balance
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def unpaid_balance
|
20
|
+
payment * annuity_factor
|
21
|
+
end
|
22
|
+
|
23
|
+
def payment
|
24
|
+
Payment.call(
|
25
|
+
present_value: @present_value,
|
26
|
+
interest_rate: @interest_rate,
|
27
|
+
periods: @periods
|
28
|
+
)
|
29
|
+
end
|
30
|
+
|
31
|
+
def annuity_factor
|
32
|
+
AnnuityFactor.call(
|
33
|
+
interest_rate: @interest_rate,
|
34
|
+
periods: remaining
|
35
|
+
)
|
36
|
+
end
|
37
|
+
|
38
|
+
def remaining
|
39
|
+
@periods - @period
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/bankroll.rb
ADDED
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "bigdecimal"
|
4
|
+
require "dry-types"
|
5
|
+
require "dry-initializer"
|
6
|
+
require "dry-equalizer"
|
7
|
+
require "delegate"
|
8
|
+
|
9
|
+
require_relative "bankroll/version"
|
10
|
+
require_relative "bankroll/callable"
|
11
|
+
require_relative "bankroll/decimal"
|
12
|
+
require_relative "bankroll/types"
|
13
|
+
|
14
|
+
require_relative "bankroll/cumulative_interest"
|
15
|
+
require_relative "bankroll/present_value"
|
16
|
+
require_relative "bankroll/future_value"
|
17
|
+
require_relative "bankroll/annuity_factor"
|
18
|
+
require_relative "bankroll/payment"
|
19
|
+
require_relative "bankroll/unpaid_balance"
|
20
|
+
require_relative "bankroll/interest_payment"
|
21
|
+
require_relative "bankroll/amortization_schedule"
|
22
|
+
require_relative "bankroll/total_periods"
|
23
|
+
require_relative "bankroll/interest_rate"
|
24
|
+
|
25
|
+
BigDecimal.mode(BigDecimal::ROUND_MODE, Bankroll::Decimal::ROUNDING)
|
26
|
+
|
27
|
+
module Bankroll
|
28
|
+
ZERO = Decimal['0'].freeze
|
29
|
+
ONE = Decimal['1'].freeze
|
30
|
+
|
31
|
+
|
32
|
+
class Error < StandardError; end
|
33
|
+
# Your code goes here...
|
34
|
+
|
35
|
+
def self.payment(**kwargs)
|
36
|
+
Payment.call(**kwargs)
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.interest_rate(**kwargs)
|
40
|
+
InterestRate.call(**kwargs)
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.unpaid_balance(**kwargs)
|
44
|
+
UnpaidBalance.call(**kwargs)
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.present_value(**kwargs)
|
48
|
+
PresentValue.call(**kwargs)
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.future_value(**kwargs)
|
52
|
+
FutureValue.call(**kwargs)
|
53
|
+
end
|
54
|
+
|
55
|
+
def self.cumulative_interest(**kwargs)
|
56
|
+
CumulativeInterest.call(**kwargs)
|
57
|
+
end
|
58
|
+
|
59
|
+
def self.annuity_factor(**kwargs)
|
60
|
+
AnnuityFactor.call(**kwargs)
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.total_periods(**kwargs)
|
64
|
+
TotalPeriods.call(**kwargs)
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.amortization_schedule(**kwargs)
|
68
|
+
AmortizationSchedule.call(**kwargs)
|
69
|
+
end
|
70
|
+
end
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: bankroll
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Nolan J Tait
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-02-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: dry-types
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: dry-initializer
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: dry-equalizer
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: Mortgage and refinance calculations for ruby
|
56
|
+
email:
|
57
|
+
- nolanjtait@gmail.com
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- ".rspec"
|
63
|
+
- ".rubocop.yml"
|
64
|
+
- CHANGELOG.md
|
65
|
+
- Gemfile
|
66
|
+
- Gemfile.lock
|
67
|
+
- LICENSE.txt
|
68
|
+
- README.md
|
69
|
+
- Rakefile
|
70
|
+
- bankroll.gemspec
|
71
|
+
- bin/bundle
|
72
|
+
- bin/console
|
73
|
+
- bin/rspec
|
74
|
+
- bin/setup
|
75
|
+
- lib/bankroll.rb
|
76
|
+
- lib/bankroll/amortization_schedule.rb
|
77
|
+
- lib/bankroll/annuity_factor.rb
|
78
|
+
- lib/bankroll/callable.rb
|
79
|
+
- lib/bankroll/cumulative_interest.rb
|
80
|
+
- lib/bankroll/decimal.rb
|
81
|
+
- lib/bankroll/future_value.rb
|
82
|
+
- lib/bankroll/interest_payment.rb
|
83
|
+
- lib/bankroll/interest_rate.rb
|
84
|
+
- lib/bankroll/payment.rb
|
85
|
+
- lib/bankroll/present_value.rb
|
86
|
+
- lib/bankroll/total_periods.rb
|
87
|
+
- lib/bankroll/types.rb
|
88
|
+
- lib/bankroll/unpaid_balance.rb
|
89
|
+
- lib/bankroll/version.rb
|
90
|
+
homepage: https://github.com/nolantait/bankroll
|
91
|
+
licenses:
|
92
|
+
- MIT
|
93
|
+
metadata:
|
94
|
+
homepage_uri: https://github.com/nolantait/bankroll
|
95
|
+
source_code_uri: https://github.com/nolantait/bankroll
|
96
|
+
changelog_uri: https://github.com/nolantait/bankroll/CHANGELOG.md
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options: []
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - ">="
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: 2.6.0
|
106
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - ">="
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '0'
|
111
|
+
requirements: []
|
112
|
+
rubygems_version: 3.2.32
|
113
|
+
signing_key:
|
114
|
+
specification_version: 4
|
115
|
+
summary: Mortgage and refinance calculations for ruby
|
116
|
+
test_files: []
|