refinance 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.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in refinance.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 reInteractive
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # Refinance
2
+
3
+ Refinance is a Ruby gem that provides a collection of finance algorithms.
4
+ Currently, it contains algorithms for calculating the properties of ordinary
5
+ annuities: principal, interest rate, number of payment periods, and payment
6
+ amount.
7
+
8
+ The algorithms are simple rather than fast. At present, they deal only with
9
+ _annuities immediate_ (in which the interest is accumulated _before_ the
10
+ payment), not _annuities due_ (in which the interest is accumulated _after_ the
11
+ payment). There are many opportunities for extension and improvement.
12
+
13
+ ## Requirements
14
+
15
+ This library requires Ruby 1.9.3 or newer. (However, it will work with Ruby
16
+ 1.9.2 if you only use Float, not BigDecimal, for floating-point values.)
17
+
18
+ ## Installation
19
+
20
+ Add this line to your application's Gemfile:
21
+
22
+ gem 'refinance'
23
+
24
+ And then execute:
25
+
26
+ $ bundle
27
+
28
+ Or install it yourself as:
29
+
30
+ $ gem install refinance
31
+
32
+ ## Contributing
33
+
34
+ 1. Fork it
35
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
36
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
37
+ 4. Push to the branch (`git push origin my-new-feature`)
38
+ 5. Create new Pull Request
39
+
40
+ ## Authorship and copyright information
41
+
42
+ This software was written by [reInteractive](http://reinteractive.net/), a
43
+ software consulting company in Sydney, Australia. It is distributed under the
44
+ MIT License; see LICENSE.txt for details.
45
+
46
+ ## Acknowledgements
47
+
48
+ Thanks to Stan Brown for his paper _[Loan or Investment
49
+ Formulas](http://oakroadsystems.com/math/loan.htm)_. It is an excellent
50
+ introduction to the mathematics of annuities, and we used some of its examples
51
+ as test cases.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.test_files = FileList["test/**/*_test.rb"]
7
+ t.verbose = true
8
+ end
9
+
10
+ task :default => :test
@@ -0,0 +1,99 @@
1
+ module Refinance
2
+
3
+ ##
4
+ # This module contains methods for calculating the basic properties of
5
+ # annuities. Annuities are assumed to be an annuities immediate (that is,
6
+ # interest is accumulated before the payment).
7
+ #
8
+ # The floating-point numbers you pass in as arguments can be instances of
9
+ # Float or BigDecimal. Actually, thanks to duck typing, they can be any
10
+ # objects that support the necessary operations.
11
+ #
12
+ module Annuities
13
+
14
+ ##
15
+ # Determine an annuity's interest rate over some period, given:
16
+ #
17
+ # * Periodic payment amount
18
+ # * Number of payment periods
19
+ # * Principal
20
+ #
21
+ # This has no closed-form solution, so the answer is iteratively
22
+ # approximated with the Newton-Raphson method. After each improvement, the
23
+ # guess will be rounded; _max_decimals_ is the number of decimal places to
24
+ # keep (if you set this high, the algorithm will be slow). _max_iterations_
25
+ # is the maximum number of iterations that will be attempted. It will stop
26
+ # iterating if the last improvement was less than _precision_ in magnitude.
27
+ #
28
+ def self.interest_rate(payment, periods, principal, initial_guess = 0.1,
29
+ precision = 0.0001, max_decimals = 8, max_iterations = 10)
30
+ guess = initial_guess
31
+
32
+ max_iterations.times do
33
+ new_guess = improve_interest_rate(payment, periods, principal, guess).
34
+ round(max_decimals)
35
+ difference = (guess - new_guess).abs
36
+ guess = new_guess
37
+ break if difference < precision
38
+ end
39
+
40
+ guess
41
+ end
42
+
43
+ ##
44
+ # Iteratively improve an approximated interest rate for an annuity using
45
+ # the Newton-Raphson method. This method is used by ::interest_rate.
46
+ #
47
+ def self.improve_interest_rate(payment, periods, principal, guess)
48
+ top = payment - (payment * ((guess + 1) ** -periods)) -
49
+ (principal * guess)
50
+ bottom = (periods * payment * ((guess + 1) ** (-periods - 1))) -
51
+ principal
52
+ guess - (top / bottom)
53
+ end
54
+
55
+ ##
56
+ # Determine an annuity's periodic payment amount, given:
57
+ #
58
+ # * Interest rate over a period
59
+ # * Number of payment periods
60
+ # * Principal
61
+ #
62
+ def self.payment(interest_rate, periods, principal)
63
+ (interest_rate * principal) / (1 - ((interest_rate + 1) ** -periods))
64
+ end
65
+
66
+ ##
67
+ # Determine the number of payment periods for an annuity, given:
68
+ #
69
+ # * Interest rate over a period
70
+ # * Periodic payment amount
71
+ # * Principal
72
+ #
73
+ def self.periods(interest_rate, payment, principal)
74
+ -Math.log(1 - ((interest_rate * principal) / payment)) /
75
+ Math.log(interest_rate + 1)
76
+ end
77
+
78
+ ##
79
+ # Determine an annuity's principal, given:
80
+ #
81
+ # * Interest rate over a period
82
+ # * Periodic payment amount
83
+ # * Number of payment periods
84
+ #
85
+ def self.principal(interest_rate, payment, periods)
86
+ (payment / interest_rate) * (1 - ((interest_rate + 1) ** -periods))
87
+ end
88
+
89
+ ##
90
+ # Determines the effective interest rate, given:
91
+ #
92
+ # * The nominal annual interest rate
93
+ # * The number of compounding periods per year
94
+ #
95
+ def self.effective_interest_rate(nair, cppy)
96
+ (((nair / cppy) + 1) ** cppy) - 1
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,3 @@
1
+ module Refinance
2
+ VERSION = "0.0.1"
3
+ end
data/lib/refinance.rb ADDED
@@ -0,0 +1,5 @@
1
+ require "refinance/version"
2
+ require "refinance/annuities"
3
+
4
+ module Refinance
5
+ end
data/refinance.gemspec ADDED
@@ -0,0 +1,24 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'refinance/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = "refinance"
8
+ gem.version = Refinance::VERSION
9
+ gem.authors = ["reInteractive"]
10
+ gem.email = ["enquiries@reinteractive.net"]
11
+ gem.description = %q{A collection of finance algorithms related to annuities.}
12
+ gem.summary = %q{Simple annuity algorithms}
13
+ gem.homepage = "https://github.com/reinteractive-open/refinance"
14
+ gem.license = "MIT"
15
+ gem.required_ruby_version = ">= 1.9.3"
16
+
17
+ gem.files = `git ls-files`.split($/)
18
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
19
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
20
+ gem.require_paths = ["lib"]
21
+
22
+ gem.add_development_dependency 'rake', '0.9.2.2'
23
+ gem.add_development_dependency 'minitest', '3.5.0'
24
+ end
@@ -0,0 +1,109 @@
1
+ require 'minitest/autorun'
2
+ require 'bigdecimal'
3
+ require 'refinance'
4
+
5
+ class BigDecimalAnnuitiesTest < MiniTest::Unit::TestCase
6
+
7
+ def test_improve_interest_rate
8
+ # Based on Example 6 in http://oakroadsystems.com/math/loan.htm .
9
+ payment = BigDecimal.new('291')
10
+ periods = BigDecimal.new('48')
11
+ principal = BigDecimal.new('11200')
12
+ guess = BigDecimal.new('0.01')
13
+
14
+ expected = BigDecimal.new('0.0094295242')
15
+ actual = Refinance::Annuities.improve_interest_rate(payment, periods,
16
+ principal, guess)
17
+
18
+ assert_in_delta expected, actual, BigDecimal.new('0.00000001')
19
+ end
20
+
21
+ def test_interest_rate_stops_if_improvement_is_small
22
+ # Based on Example 6 in http://oakroadsystems.com/math/loan.htm .
23
+ payment = BigDecimal.new('291')
24
+ periods = BigDecimal.new('48')
25
+ principal = BigDecimal.new('11200')
26
+ guess = BigDecimal.new('0.01')
27
+ imprecision = BigDecimal.new('0.5')
28
+
29
+ expected = BigDecimal.new('0.0094295242')
30
+ actual = Refinance::Annuities.interest_rate(payment, periods, principal,
31
+ guess, imprecision)
32
+
33
+ assert_in_delta expected, actual, BigDecimal.new('0.00000001')
34
+ end
35
+
36
+ def test_interest_rate_stops_if_max_iterations_reached
37
+ extreme_precision = BigDecimal.new('0')
38
+ guess = BigDecimal.new('0.01')
39
+
40
+ expected = guess
41
+ actual = Refinance::Annuities.interest_rate(0, 0, 0, guess,
42
+ extreme_precision, 1, 0)
43
+
44
+ assert_equal expected, actual
45
+ end
46
+
47
+ def test_interest_rate_does_multiple_iterations
48
+ # Based on Example 6 in http://oakroadsystems.com/math/loan.htm .
49
+ payment = BigDecimal.new('291')
50
+ periods = BigDecimal.new('48')
51
+ principal = BigDecimal.new('11200')
52
+ guess = BigDecimal.new('0.01')
53
+ extreme_precision = BigDecimal.new('0')
54
+ max_iterations = 4
55
+
56
+ expected = BigDecimal.new('0.0094007411')
57
+ actual = Refinance::Annuities.interest_rate(payment, periods, principal,
58
+ guess, extreme_precision, 10, 4)
59
+
60
+ assert_in_delta expected, actual, BigDecimal.new('0.0000000001')
61
+ end
62
+
63
+ def test_payment
64
+ # Based on Example 2 in http://oakroadsystems.com/math/loan.htm .
65
+ interest_rate = BigDecimal.new('0.0065')
66
+ periods = BigDecimal.new('360')
67
+ principal = BigDecimal.new('225000')
68
+
69
+ expected = BigDecimal.new('1619.708627')
70
+ actual = Refinance::Annuities.payment(interest_rate, periods, principal)
71
+
72
+ assert_in_delta expected, actual, BigDecimal.new('0.000001')
73
+ end
74
+
75
+ def test_periods
76
+ # Based on Example 3 in http://oakroadsystems.com/math/loan.htm .
77
+ interest_rate = BigDecimal.new('0.005')
78
+ payment = BigDecimal.new('100')
79
+ principal = BigDecimal.new('3500')
80
+
81
+ expected = BigDecimal.new('38.57')
82
+ actual = Refinance::Annuities.periods(interest_rate, payment, principal)
83
+
84
+ assert_in_delta expected, actual, BigDecimal.new('0.01')
85
+ end
86
+
87
+ def test_principal
88
+ # Based on Example 5 in http://oakroadsystems.com/math/loan.htm .
89
+ interest_rate = BigDecimal.new('0.014083')
90
+ payment = BigDecimal.new('60')
91
+ periods = BigDecimal.new('36')
92
+
93
+ expected = BigDecimal.new('1685.26') # Example fudges to 1685.25.
94
+ actual = Refinance::Annuities.principal(interest_rate, payment, periods)
95
+
96
+ assert_in_delta expected, actual, BigDecimal.new('0.01')
97
+ end
98
+
99
+ def test_effective_interest_rate
100
+ nominal_annual_interest_rate = BigDecimal.new('0.1')
101
+ compounding_periods_per_year = BigDecimal.new('12')
102
+
103
+ expected = BigDecimal.new('0.10471')
104
+ actual = Refinance::Annuities.effective_interest_rate(
105
+ nominal_annual_interest_rate, compounding_periods_per_year)
106
+
107
+ assert_in_delta expected, actual, BigDecimal.new('0.00001')
108
+ end
109
+ end
@@ -0,0 +1,108 @@
1
+ require 'minitest/autorun'
2
+ require 'refinance'
3
+
4
+ class FloatAnnuitiesTest < MiniTest::Unit::TestCase
5
+
6
+ def test_improve_interest_rate
7
+ # Based on Example 6 in http://oakroadsystems.com/math/loan.htm .
8
+ payment = 291.0
9
+ periods = 48.0
10
+ principal = 11200.0
11
+ guess = 0.01
12
+
13
+ expected = 0.0094295242
14
+ actual = Refinance::Annuities.improve_interest_rate(payment, periods,
15
+ principal, guess)
16
+
17
+ assert_in_delta expected, actual, 0.00000001
18
+ end
19
+
20
+ def test_interest_rate_stops_if_improvement_is_small
21
+ # Based on Example 6 in http://oakroadsystems.com/math/loan.htm .
22
+ payment = 291.0
23
+ periods = 48.0
24
+ principal = 11200.0
25
+ guess = 0.01
26
+ imprecision = 0.5
27
+
28
+ expected = 0.0094295242
29
+ actual = Refinance::Annuities.interest_rate(payment, periods, principal,
30
+ guess, imprecision)
31
+
32
+ assert_in_delta expected, actual, 0.00000001
33
+ end
34
+
35
+ def test_interest_rate_stops_if_max_iterations_reached
36
+ extreme_precision = 0.0
37
+ guess = 0.01
38
+
39
+ expected = guess
40
+ actual = Refinance::Annuities.interest_rate(0, 0, 0, guess,
41
+ extreme_precision, 1, 0)
42
+
43
+ assert_equal expected, actual
44
+ end
45
+
46
+ def test_interest_rate_does_multiple_iterations
47
+ # Based on Example 6 in http://oakroadsystems.com/math/loan.htm .
48
+ payment = 291.0
49
+ periods = 48.0
50
+ principal = 11200.0
51
+ guess = 0.01
52
+ extreme_precision = 0.0
53
+ max_iterations = 4
54
+
55
+ expected = 0.0094007411
56
+ actual = Refinance::Annuities.interest_rate(payment, periods, principal,
57
+ guess, extreme_precision, 10, 4)
58
+
59
+ assert_in_delta expected, actual, 0.0000000001
60
+ end
61
+
62
+ def test_payment
63
+ # Based on Example 2 in http://oakroadsystems.com/math/loan.htm .
64
+ interest_rate = 0.0065
65
+ periods = 360.0
66
+ principal = 225000.0
67
+
68
+ expected = 1619.708627
69
+ actual = Refinance::Annuities.payment(interest_rate, periods, principal)
70
+
71
+ assert_in_delta expected, actual, 0.000001
72
+ end
73
+
74
+ def test_periods
75
+ # Based on Example 3 in http://oakroadsystems.com/math/loan.htm .
76
+ interest_rate = 0.005
77
+ payment = 100.0
78
+ principal = 3500.0
79
+
80
+ expected = 38.57
81
+ actual = Refinance::Annuities.periods(interest_rate, payment, principal)
82
+
83
+ assert_in_delta expected, actual, 0.01
84
+ end
85
+
86
+ def test_principal
87
+ # Based on Example 5 in http://oakroadsystems.com/math/loan.htm .
88
+ interest_rate = 0.014083
89
+ payment = 60.0
90
+ periods = 36.0
91
+
92
+ expected = 1685.26 # Example fudges to 1685.25.
93
+ actual = Refinance::Annuities.principal(interest_rate, payment, periods)
94
+
95
+ assert_in_delta expected, actual, 0.01
96
+ end
97
+
98
+ def test_effective_interest_rate
99
+ nominal_annual_interest_rate = 0.1
100
+ compounding_periods_per_year = 12.0
101
+
102
+ expected = 0.10471
103
+ actual = Refinance::Annuities.effective_interest_rate(
104
+ nominal_annual_interest_rate, compounding_periods_per_year)
105
+
106
+ assert_in_delta expected, actual, 0.00001
107
+ end
108
+ end
@@ -0,0 +1,12 @@
1
+ require 'minitest/autorun'
2
+ require 'refinance'
3
+
4
+ class RefinanceTest < MiniTest::Unit::TestCase
5
+ def test_refinance_is_a_module
6
+ assert_kind_of Module, ::Refinance
7
+ end
8
+
9
+ def test_version_is_a_string
10
+ assert_kind_of String, ::Refinance::VERSION
11
+ end
12
+ end
metadata ADDED
@@ -0,0 +1,96 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: refinance
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - reInteractive
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-27 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rake
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - '='
20
+ - !ruby/object:Gem::Version
21
+ version: 0.9.2.2
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - '='
28
+ - !ruby/object:Gem::Version
29
+ version: 0.9.2.2
30
+ - !ruby/object:Gem::Dependency
31
+ name: minitest
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - '='
36
+ - !ruby/object:Gem::Version
37
+ version: 3.5.0
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - '='
44
+ - !ruby/object:Gem::Version
45
+ version: 3.5.0
46
+ description: A collection of finance algorithms related to annuities.
47
+ email:
48
+ - enquiries@reinteractive.net
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - .gitignore
54
+ - Gemfile
55
+ - LICENSE.txt
56
+ - README.md
57
+ - Rakefile
58
+ - lib/refinance.rb
59
+ - lib/refinance/annuities.rb
60
+ - lib/refinance/version.rb
61
+ - refinance.gemspec
62
+ - test/bigdecimal_annuities_test.rb
63
+ - test/float_annuities_test.rb
64
+ - test/refinance_test.rb
65
+ homepage: https://github.com/reinteractive-open/refinance
66
+ licenses:
67
+ - MIT
68
+ post_install_message:
69
+ rdoc_options: []
70
+ require_paths:
71
+ - lib
72
+ required_ruby_version: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: 1.9.3
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ none: false
80
+ requirements:
81
+ - - ! '>='
82
+ - !ruby/object:Gem::Version
83
+ version: '0'
84
+ segments:
85
+ - 0
86
+ hash: 803431032426501235
87
+ requirements: []
88
+ rubyforge_project:
89
+ rubygems_version: 1.8.24
90
+ signing_key:
91
+ specification_version: 3
92
+ summary: Simple annuity algorithms
93
+ test_files:
94
+ - test/bigdecimal_annuities_test.rb
95
+ - test/float_annuities_test.rb
96
+ - test/refinance_test.rb