apy 0.0.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 +7 -0
- data/.github/workflows/main.yml +18 -0
- data/.gitignore +14 -0
- data/CHANGELOG +5 -0
- data/Gemfile +6 -0
- data/LICENSE +21 -0
- data/README +121 -0
- data/Rakefile +12 -0
- data/apy.gemspec +39 -0
- data/bin/setup +8 -0
- data/lib/apy.rb +11 -0
- data/lib/apy/calculable.rb +87 -0
- data/lib/apy/interest.rb +77 -0
- data/lib/apy/loan.rb +79 -0
- data/lib/apy/version.rb +5 -0
- metadata +116 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 5d474dde3f7bb520eda50aecd1015bfe779a4da3e959b6fa81927c70bb4483e3
|
4
|
+
data.tar.gz: bdeff0d880df581d65eaa374b07467ca5e0faee4726691d65f485cf8b44c8b0d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 36c2b40cb28cb2fc0d92db3a808167d3922b249e943f0f8616bdc6b3c69a2584f3b9b749a56c66175715ec283d9056e3ed1819f90fc9f7b7d0d9f3fa85191e1c
|
7
|
+
data.tar.gz: d47c2cea41c2c964b5b13e9b748f5b02b1c1d8f9a702aa031f6e63858f98e59f43e25119ab0b151167674542673f29a23089692bea5d27561fdfffabf2414719
|
@@ -0,0 +1,18 @@
|
|
1
|
+
name: Ruby
|
2
|
+
|
3
|
+
on: [push,pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
runs-on: ubuntu-latest
|
8
|
+
steps:
|
9
|
+
- uses: actions/checkout@v2
|
10
|
+
- name: Set up Ruby
|
11
|
+
uses: ruby/setup-ruby@v1
|
12
|
+
with:
|
13
|
+
ruby-version: 3.0.1
|
14
|
+
- name: Run the default task
|
15
|
+
run: |
|
16
|
+
gem install bundler -v 2.2.15
|
17
|
+
bundle install
|
18
|
+
bundle exec rake
|
data/.gitignore
ADDED
data/CHANGELOG
ADDED
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2021 Trevor James
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1,121 @@
|
|
1
|
+
# Apy
|
2
|
+
|
3
|
+
Various helpers for calculating interest
|
4
|
+
|
5
|
+
## TODO
|
6
|
+
- [ ] Amortization
|
7
|
+
- [ ] Update method signatures to only use day counts
|
8
|
+
- [ ] Comprehensive examples w/ tests
|
9
|
+
- [ ] Setup ci
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
|
13
|
+
Add this line to your application's Gemfile:
|
14
|
+
|
15
|
+
```ruby
|
16
|
+
gem "apy"
|
17
|
+
```
|
18
|
+
|
19
|
+
And then execute:
|
20
|
+
|
21
|
+
$ bundle install
|
22
|
+
|
23
|
+
Or install it yourself as:
|
24
|
+
|
25
|
+
$ gem install apy
|
26
|
+
|
27
|
+
## Usage
|
28
|
+
|
29
|
+
- [As a module](#as-a-module)
|
30
|
+
- [Method: `weighted_harmonic_mean`](#weighted-harmonic-mean)
|
31
|
+
- [Method: `compound`](#compound)
|
32
|
+
- [Interest class](#interest)
|
33
|
+
- [Loan class](#loan)
|
34
|
+
|
35
|
+
|
36
|
+
### As a Module
|
37
|
+
`Apy::Calculable` is the base module with the most exposed utility. Simply include it in a class which needs the functionality.
|
38
|
+
|
39
|
+
#### `weighted_harmonic_mean`
|
40
|
+
[Link](https://www.investopedia.com/terms/h/harmonicaverage.asp#mntl-sc-block_1-0-9)
|
41
|
+
|
42
|
+
Given a series of invested amounts, this can be used to effectively calculate the average share price, or DCA (dollar cost average) price of a position.
|
43
|
+
|
44
|
+
Given the following:
|
45
|
+
| Amount invested | Share price |
|
46
|
+
| --- | --- |
|
47
|
+
| 1000 | 156.23 |
|
48
|
+
| 1000 | 156.30 |
|
49
|
+
| 1000 | 173.15 |
|
50
|
+
| 1000 | 188.72 |
|
51
|
+
| 1000 | 204.61 |
|
52
|
+
| 1000 | 178.23 |
|
53
|
+
|
54
|
+
Investing a total of 6000 with the above share prices results in `174.57` for the average price paid per share.
|
55
|
+
|
56
|
+
Given the following:
|
57
|
+
| Amount invested | Share price |
|
58
|
+
| --- | --- |
|
59
|
+
| 500 | 200.00 |
|
60
|
+
| 1000 | 100.00 |
|
61
|
+
|
62
|
+
Investing a total of 1500 with the above share prices results in `100.00` for the average price paid per share.
|
63
|
+
|
64
|
+
The module method accepts a matrix, with each array in the set having the following signature `[invested_amount, share_price]`:
|
65
|
+
```ruby
|
66
|
+
dca = [
|
67
|
+
[1000, 156.23],
|
68
|
+
[1000, 156.30],
|
69
|
+
[1000, 173.15],
|
70
|
+
[1000, 188.72],
|
71
|
+
[1000, 204.61],
|
72
|
+
[1000, 178.23]
|
73
|
+
]
|
74
|
+
weighted_harmonic_mean(dca) == 174.5655590168175
|
75
|
+
```
|
76
|
+
|
77
|
+
#### `compound`
|
78
|
+
[Link](https://www.thecalculatorsite.com/articles/finance/compound-interest-formula.php)
|
79
|
+
|
80
|
+
Simple compound interest formula. Variable names correspond to the following:
|
81
|
+
- `principal` The base amount before interest
|
82
|
+
- `rate` The expected interest rate
|
83
|
+
- `times` The number of times interest is calculated per term
|
84
|
+
- `terms` The number of terms to allow the principal accrue interest
|
85
|
+
|
86
|
+
Example: 1200@10% over 1y, interest paid monthly
|
87
|
+
```ruby
|
88
|
+
compound(1200, rate: 0.1, times: 12, terms: 1) == 1325.66
|
89
|
+
```
|
90
|
+
|
91
|
+
#### `dca_compound`
|
92
|
+
A variant of `#compound`, wherein an amount is continually invested at varying interest rates. Similar to `weighted_harmonic_mean`, this method also accepts a matrix. The size of the matrix corresponds to the number of `terms` all funds will accrue.
|
93
|
+
|
94
|
+
Example: 1200@10% over 2y, interest paid monthly
|
95
|
+
```ruby
|
96
|
+
dca = [
|
97
|
+
[1200, 0.1],
|
98
|
+
[1200, 0.1]
|
99
|
+
]
|
100
|
+
dca_compound(dca, times: 12) == 2790.125
|
101
|
+
```
|
102
|
+
|
103
|
+
### Interest
|
104
|
+
todo
|
105
|
+
|
106
|
+
### Loan
|
107
|
+
todo
|
108
|
+
|
109
|
+
## Development
|
110
|
+
|
111
|
+
After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake test` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
|
112
|
+
|
113
|
+
To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and the created tag, and push the `.gem` file to [rubygems.org](https://rubygems.org).
|
114
|
+
|
115
|
+
## Contributing
|
116
|
+
|
117
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/fire-pls/apy.
|
118
|
+
|
119
|
+
## License
|
120
|
+
|
121
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/apy.gemspec
ADDED
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "lib/apy/version"
|
4
|
+
require "date"
|
5
|
+
|
6
|
+
Gem::Specification.new do |gemspec|
|
7
|
+
gemspec.name = "apy"
|
8
|
+
gemspec.version = Apy::VERSION
|
9
|
+
gemspec.authors = ["Trevor James"]
|
10
|
+
gemspec.email = ["trevor@osrs-stat.com"]
|
11
|
+
|
12
|
+
gemspec.summary = "Interest calculators"
|
13
|
+
gemspec.description = "Helpers for calculating various interest scenarios"
|
14
|
+
gemspec.homepage = "https://github.com/fire-pls/apy"
|
15
|
+
gemspec.license = "MIT"
|
16
|
+
gemspec.required_ruby_version = Gem::Requirement.new(">= 2.4.0")
|
17
|
+
|
18
|
+
gemspec.metadata["homepage_uri"] = gemspec.homepage
|
19
|
+
gemspec.metadata["source_code_uri"] = gemspec.homepage
|
20
|
+
gemspec.metadata["changelog_uri"] = [gemspec.homepage, "CHANGELOG"].join("/")
|
21
|
+
|
22
|
+
# Specify which files should be added to the gem when it is released.
|
23
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
24
|
+
gemspec.files = Dir.chdir(File.expand_path(__dir__)) do
|
25
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{\A(?:test|spec|features)/}) }
|
26
|
+
end
|
27
|
+
gemspec.bindir = "exe"
|
28
|
+
gemspec.executables = gemspec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
29
|
+
gemspec.require_paths = ["lib"]
|
30
|
+
|
31
|
+
# Dependencies
|
32
|
+
# None :)
|
33
|
+
|
34
|
+
# Dev Dependencies
|
35
|
+
gemspec.add_development_dependency "pry-byebug", "~> 3.9"
|
36
|
+
gemspec.add_development_dependency "rake", "~> 13"
|
37
|
+
gemspec.add_development_dependency "minitest", "~> 5.0"
|
38
|
+
gemspec.add_development_dependency "standard", "~> 1"
|
39
|
+
end
|
data/bin/setup
ADDED
data/lib/apy.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Apy
|
4
|
+
module Calculable
|
5
|
+
# Calculate the weighted harmonic mean for a set of values
|
6
|
+
# (∑w) / (∑w/i)
|
7
|
+
# @param matrix [Array<Array<(Numeric, Numeric)>>] Matrix whose size is the number of investments, with the following array elements: idx0 invested amount, idx1 purchase price of the asset
|
8
|
+
# @todo Can make this a bit more performant with `inject`
|
9
|
+
# @example Providing a set of DCA values
|
10
|
+
# dca = [[1000,156.23],[1000,156.3], [1000,173.15],[1000,188.72], [1000,204.61],[1000,178.23]]
|
11
|
+
# weighted_harmonic_mean(dca)
|
12
|
+
# # => 174.5655590168175
|
13
|
+
def weighted_harmonic_mean(matrix)
|
14
|
+
sum = matrix.sum { |(n, _)| n }.to_f
|
15
|
+
|
16
|
+
total_weight = []
|
17
|
+
weighted_values = []
|
18
|
+
|
19
|
+
matrix.each do |(investment, price)|
|
20
|
+
weight = investment / sum
|
21
|
+
|
22
|
+
total_weight << weight
|
23
|
+
weighted_values << (weight / price)
|
24
|
+
end
|
25
|
+
|
26
|
+
total_weight.sum / weighted_values.sum
|
27
|
+
end
|
28
|
+
|
29
|
+
# Simple compound, assuming no additional investment over successive maturity terms
|
30
|
+
# @param principal [Numeric] Initial investment
|
31
|
+
# @param rate [Float] Expected interest rate for the length of the period
|
32
|
+
# @param times [Integer] Times the interest will be paid out per term
|
33
|
+
# @param terms [Integer] Number of terms
|
34
|
+
#
|
35
|
+
# @example Given a "10% APY", with interest paid monthly (1y maturity date):
|
36
|
+
# compound(1200, rate: 0.1, times: 12, terms: 1) == 1325.66
|
37
|
+
#
|
38
|
+
# @example Given a "0.1923% WPY", with interest paid weekly (1w maturity date):
|
39
|
+
# compound(1200, rate: 0.1, times: 52, terms: 1) == 1326.07
|
40
|
+
# compound(1200, rate: 0.001923, times: 1, terms: 52) == 1326.07
|
41
|
+
#
|
42
|
+
# @example Given a "0.0274% DPY", with interest paid daily (1d maturity date):
|
43
|
+
# compound(1200, rate: 0.1, times: 365, terms: 1) == 1326.19
|
44
|
+
# compound(1200, rate: 0.000274, times: 1, terms: 365) == 1326.20
|
45
|
+
def compound(principal, rate:, times:, terms:)
|
46
|
+
total_rate = 1 + (rate / times)
|
47
|
+
|
48
|
+
principal * (total_rate**(times * terms))
|
49
|
+
end
|
50
|
+
|
51
|
+
# "DCA" compound, assuming a recurring investment continuously added to the principal amount, this new amount _additionally_ compounded for the next period
|
52
|
+
# @param matrix [Array<Array(Numeric, Numeric)>] Matrix whose size is the number of terms; Each array item has the following elements: idx0 additional investment, idx1 the expected rate for the term
|
53
|
+
# @param times [Integer] Times the interest will be paid out per term
|
54
|
+
# @example Continuously investing 1200 dollars a year into a contract with a "10% APY", interest paid out once a month
|
55
|
+
# dca_compound([[1200, 0.1], [1200, 0.1]], times: 12) == 2790.125
|
56
|
+
#
|
57
|
+
# @todo Clean this up, there is most likely an optimized formula for this 🤨
|
58
|
+
# @see #compound
|
59
|
+
def dca_compound(matrix, times:)
|
60
|
+
result = matrix.each_with_object(
|
61
|
+
total: 0,
|
62
|
+
interest: 0,
|
63
|
+
data: {
|
64
|
+
0 => {
|
65
|
+
ytd: 0,
|
66
|
+
in: 0,
|
67
|
+
interest: 0
|
68
|
+
}
|
69
|
+
}
|
70
|
+
).with_index do |(ary, out), i|
|
71
|
+
additional_investment, rate = ary
|
72
|
+
prev_ytd = out[:data][i][:ytd]
|
73
|
+
|
74
|
+
to_compound = prev_ytd + additional_investment
|
75
|
+
|
76
|
+
current = compound(to_compound, rate: rate, times: times, terms: 1)
|
77
|
+
interest_this_period = current - to_compound
|
78
|
+
|
79
|
+
out[:total] += additional_investment + interest_this_period
|
80
|
+
out[:interest] += interest_this_period
|
81
|
+
out[:data][i + 1] = {ytd: current, in: additional_investment, interest: interest_this_period}
|
82
|
+
end
|
83
|
+
|
84
|
+
result.fetch(:total)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
data/lib/apy/interest.rb
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "date"
|
4
|
+
require_relative "calculable"
|
5
|
+
|
6
|
+
module Apy
|
7
|
+
# An interest object; Given an apy & date range, prepares various methods to calculate with
|
8
|
+
class Interest
|
9
|
+
include Apy::Calculable
|
10
|
+
|
11
|
+
attr_reader :apy
|
12
|
+
|
13
|
+
# @param apy [Float] Annual/Average Percent Yield -- an expected pct return over the course of a _year_
|
14
|
+
# @param start_date [Date] The beginning of the interest period; Defaults to today
|
15
|
+
# @param end_date [Date] The end of the interest period; Defaults to 1y from today
|
16
|
+
# @param days_per_term [Integer] Days per compound interest term; Defaults to 365 days
|
17
|
+
#
|
18
|
+
# @example A 10% APY, intended to be used in calculations over 2y:
|
19
|
+
# d1 = Date.parse "2020-01-01"
|
20
|
+
# d2 = Date.parse "2022-01-01"
|
21
|
+
# Interest.new(apy: 0.1, start_date: d1, end_date: d2)
|
22
|
+
# @example An interest object _yielding 10% over the course of 2y_:
|
23
|
+
# d1 = Date.parse "2020-01-01"
|
24
|
+
# d2 = Date.parse "2022-01-01"
|
25
|
+
# Interest.new(apy: 0.1, start_date: d1, end_date: d2, days_per_term: 730)
|
26
|
+
# @example The above example, but instantiated with an apy of 5%:
|
27
|
+
# d1 = Date.parse "2020-01-01"
|
28
|
+
# d2 = Date.parse "2022-01-01"
|
29
|
+
# Interest.new(apy: 0.05, start_date: d1, end_date: d2)
|
30
|
+
def initialize(apy:, start_date: Date.today, end_date: Date.today.next_year, days_per_term: 365)
|
31
|
+
fail(ArgumentError, "apy must be a positive Float") unless apy.positive? && apy.is_a?(Float)
|
32
|
+
|
33
|
+
@apy = apy
|
34
|
+
@terms = Interest.get_term_size(start_date, end_date, days_per_term)
|
35
|
+
end
|
36
|
+
|
37
|
+
class << self
|
38
|
+
# @param days_per_term [Integer] Number of days that pass before an entire term ends. Defaults to 365
|
39
|
+
# @return [Integer] The actual number of terms completed
|
40
|
+
def get_term_size(start_date, end_date, days_per_term)
|
41
|
+
((end_date - start_date) / days_per_term).round
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
# Given a principal amount, return the ending balance
|
46
|
+
# @param principal [Numeric] Initial investment
|
47
|
+
# @param times [Integer] The number of times per term interest is accrued; Defaults to 1 (flat rate)
|
48
|
+
# @see Calculable#compound
|
49
|
+
def total(principal, times: 1)
|
50
|
+
compound(principal, rate: apy, times: times, terms: @terms)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Given a series of investments, calculate the DCA return
|
54
|
+
# @param in_per_split [Numeric] Value of newly invested funds per term; Will be zipped with #apy & term size
|
55
|
+
# @param times [Integer] The number of times per term interest is accrued; Defaults to 1 (flat rate)
|
56
|
+
# @note This assumes you wish to maximize dca returns; the in_per_split is deposited before the interest is calculated
|
57
|
+
# @note If this method is too inflexible for your use case, you should use the {Calculable} module directly
|
58
|
+
# @see Calculable#dca_compound
|
59
|
+
# @example Investing 3.29 everyday for a year:
|
60
|
+
# Apy::Interest.new(apy: 0.1).dca(3.29, times: 365) == 1263.12
|
61
|
+
# @example Investing 23.08 every week for a year:
|
62
|
+
# Apy::Interest.new(apy: 0.1).dca(23.08, times: 52) == 1263.43
|
63
|
+
# @example Investing 100 every month for a year:
|
64
|
+
# Apy::Interest.new(apy: 0.1).dca(23.08, times: 12) == 1267.29
|
65
|
+
# @example Investing 300 every quarter for a year:
|
66
|
+
# Apy::Interest.new(apy: 0.1).dca(300, times: 4) == 1277.64
|
67
|
+
# @example Investing 600 semi-annualy for a year:
|
68
|
+
# Apy::Interest.new(apy: 0.1).dca(600, times: 2) == 1292.66
|
69
|
+
def dca(in_per_split, times: 1)
|
70
|
+
split = @terms * times
|
71
|
+
adjusted_apy = apy / times
|
72
|
+
range = split.times.map { [in_per_split, adjusted_apy] }
|
73
|
+
|
74
|
+
dca_compound(range, times: times)
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/apy/loan.rb
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "interest"
|
4
|
+
|
5
|
+
module Apy
|
6
|
+
# A loan object; Provides helpers for calculating various debt-related scenarios
|
7
|
+
class Loan
|
8
|
+
attr_reader :borrow, :apy
|
9
|
+
|
10
|
+
# @param borrow [Numeric] Amount of money to be borrowed
|
11
|
+
# @param apy [Float] The interest rate of the borrowed money
|
12
|
+
def initialize(borrow, apy:)
|
13
|
+
fail(ArgumentError, "apy must be a positive Float") unless apy.positive? && apy.is_a?(Float)
|
14
|
+
|
15
|
+
@borrow = borrow
|
16
|
+
@apy = apy
|
17
|
+
end
|
18
|
+
|
19
|
+
# Based on the borrow amount, calculate the payment size required to pay off the principal and accrued interest
|
20
|
+
# @param start_date [Date] Date the loan will begin
|
21
|
+
# @param end_date [Date] Date the loan will be fully paid off
|
22
|
+
# @param times [Integer] The number of times per term interest is accrued; Defaults to 1 (flat rate)
|
23
|
+
# @param days_per_term [Integer] The number of days per interest term
|
24
|
+
# @param payments_per_term [Integer] The number of payments made towards the loan per term; Defaults to `times`
|
25
|
+
# @example Payment size for 1200, interest accrued once, repaid in 1 payment across 365 days:
|
26
|
+
# d1 = Date.parse "2020-01-01"
|
27
|
+
# d2 = Date.parse "2021-01-01"
|
28
|
+
# Loan.new(1200, apy: 0.1).payment_size(start_date: d1, end_date: d2) == 1320.0
|
29
|
+
# @example Payment size for 1200, interest accrued once, repaid in 12 payments across 365 days:
|
30
|
+
# Loan.new(1200, apy: 0.1).payment_size(start_date: d1, end_date: d2, payments_per_term: 12) == 110.0
|
31
|
+
# @example Payment size for 1200, interest accrued 12 times, repaid in 12 payments across 365 days:
|
32
|
+
# Loan.new(1200, apy: 0.1).payment_size(start_date: d1, end_date: d2, times: 12) == 110.47
|
33
|
+
def payment_size(start_date:, end_date:, times: 1, days_per_term: 365, payments_per_term: times)
|
34
|
+
total_owed(
|
35
|
+
start_date: start_date,
|
36
|
+
end_date: end_date,
|
37
|
+
times: times,
|
38
|
+
days_per_term: days_per_term
|
39
|
+
) / (payments_per_term * Interest.get_term_size(start_date, end_date, days_per_term))
|
40
|
+
end
|
41
|
+
|
42
|
+
# Get the total amount owed, based on the start & end date
|
43
|
+
# @param start_date [Date] Date the loan will begin
|
44
|
+
# @param end_date [Date] Date the loan will be fully paid off
|
45
|
+
# @param times [Integer] The number of times per term interest is accrued; Defaults to 1 (flat rate)
|
46
|
+
# @param days_per_term [Integer] The number of days per interest term
|
47
|
+
# @note Because the constructor accepts an APY for the year, an adjusted rate is used here based on days_per_term
|
48
|
+
# @see Interest#total
|
49
|
+
# @see #adjusted_apy
|
50
|
+
def total_owed(start_date:, end_date:, times: 1, days_per_term: 365)
|
51
|
+
Apy::Interest.new(
|
52
|
+
apy: adjusted_apy(days_per_term),
|
53
|
+
start_date: start_date,
|
54
|
+
end_date: end_date,
|
55
|
+
days_per_term: days_per_term
|
56
|
+
).total(borrow, times: times)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Similar to payment_size, except interest accrues based on the remaining debt
|
60
|
+
# @todo Finish this
|
61
|
+
# @todo Once finished, make #payment_size accept less args (lump-sum, _only_ accept payment count)
|
62
|
+
def amortized_payment_size(start_date:, end_date:, times: 1, days_per_term: 365, payments_per_term: times)
|
63
|
+
fail
|
64
|
+
end
|
65
|
+
|
66
|
+
# @todo Finish this
|
67
|
+
# @todo Once finished, make #total_owed accept less args (lump-sum, _only_ accept payment count)
|
68
|
+
def amortized_total_owed(start_date:, end_date:, times: 1, days_per_term: 365, payments_per_term: times)
|
69
|
+
fail
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
# `apy / (365 / days_per_term)`
|
75
|
+
def adjusted_apy(days_per_term)
|
76
|
+
apy / (365 / days_per_term)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
data/lib/apy/version.rb
ADDED
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: apy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Trevor James
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2021-05-01 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: pry-byebug
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.9'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.9'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '13'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '13'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: minitest
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '5.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '5.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: standard
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '1'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '1'
|
69
|
+
description: Helpers for calculating various interest scenarios
|
70
|
+
email:
|
71
|
+
- trevor@osrs-stat.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- ".github/workflows/main.yml"
|
77
|
+
- ".gitignore"
|
78
|
+
- CHANGELOG
|
79
|
+
- Gemfile
|
80
|
+
- LICENSE
|
81
|
+
- README
|
82
|
+
- Rakefile
|
83
|
+
- apy.gemspec
|
84
|
+
- bin/setup
|
85
|
+
- lib/apy.rb
|
86
|
+
- lib/apy/calculable.rb
|
87
|
+
- lib/apy/interest.rb
|
88
|
+
- lib/apy/loan.rb
|
89
|
+
- lib/apy/version.rb
|
90
|
+
homepage: https://github.com/fire-pls/apy
|
91
|
+
licenses:
|
92
|
+
- MIT
|
93
|
+
metadata:
|
94
|
+
homepage_uri: https://github.com/fire-pls/apy
|
95
|
+
source_code_uri: https://github.com/fire-pls/apy
|
96
|
+
changelog_uri: https://github.com/fire-pls/apy/CHANGELOG
|
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.4.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.15
|
113
|
+
signing_key:
|
114
|
+
specification_version: 4
|
115
|
+
summary: Interest calculators
|
116
|
+
test_files: []
|