refinance 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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