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 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
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # Ignore lockfile to catch dependency issues
11
+ /Gemfile.lock
12
+
13
+ # Development console always looks messy
14
+ bin/console
data/CHANGELOG ADDED
@@ -0,0 +1,5 @@
1
+ ## [Unreleased]
2
+
3
+ ## [0.0.1] - 2021-05-01
4
+
5
+ - Initial release
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # Specify your gem's dependencies in apy.gemspec
6
+ gemspec
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
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/testtask"
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << "test"
8
+ t.libs << "lib"
9
+ t.test_files = FileList["test/**/*_test.rb"]
10
+ end
11
+
12
+ task default: :test
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
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/lib/apy.rb ADDED
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "apy/calculable"
4
+ require_relative "apy/interest"
5
+ require_relative "apy/loan"
6
+ require_relative "apy/version"
7
+
8
+ module Apy
9
+ class Error < StandardError; end
10
+ # Your code goes here...
11
+ end
@@ -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
@@ -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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Apy
4
+ VERSION = "0.0.1"
5
+ end
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: []