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.
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bankroll
4
+ VERSION = "0.1.0"
5
+ 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: []