mortgage_calculations 0.1.9

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/Manifest ADDED
@@ -0,0 +1,12 @@
1
+ Manifest
2
+ README.rdoc
3
+ Rakefile
4
+ Version.yml
5
+ features/apr.feature
6
+ features/step_definitions/apr_steps.rb
7
+ features/support/env.rb
8
+ lib/mortgage_calc.rb
9
+ lib/mortgage_calc/mortgage_util.rb
10
+ mortgage_calculations.gemspec
11
+ spec/mortgage_calc/mortgage_util_spec.rb
12
+ spec/spec_helper.rb
data/README.rdoc ADDED
@@ -0,0 +1,49 @@
1
+ = mortgage_calc
2
+
3
+ http://www.pathf.com/blogs/2010/02/mortcalc-gem/
4
+
5
+ Calculates mortgage APR, monthly payments, and fees.
6
+
7
+ == INSTALL
8
+ $ sudo gem install gemcutter
9
+ $ gem tumble
10
+ $ sudo gem install mortgage_calc
11
+ or add the following to your <b>environment.rb</b>
12
+ config.gem 'mortgage_calc'
13
+
14
+ ==Example:
15
+ loan_amount = 350000
16
+ interest_rate = 4.75
17
+ period = 30 * 12
18
+ lender_fee = 800
19
+ points = 1.0
20
+
21
+ mort_calc = MortgageCalc::MortgageUtil.new(loan_amount, interest_rate, period, lender_fee, points)
22
+
23
+ mort_calc.apr
24
+ mort_calc.monthly_payment
25
+ mort_calc.monthly_payment_with_fees
26
+ mort_calc.total_fees
27
+
28
+ ==Formulas used
29
+ ===Monthly payment with fees
30
+ P = [(C + E) r (1 + r)^N]/[(1 + r)^N - 1]
31
+
32
+ P = monthly payment
33
+ C = Loan amount
34
+ r = Interest rate
35
+ N = Period in months
36
+ E = Lender fees
37
+
38
+ ===Monthly payment without fees is calculated like above with E = 0.
39
+
40
+ ===APR
41
+ [a (1 + a)^N] / [(1 + a)^N - 1] - P/C = 0
42
+ a = A/1200
43
+ N = Period in months
44
+ P = Monthly payment
45
+ C = Loan amount
46
+
47
+ ===Total fees
48
+ Total fees are calculated simply by adding Lender fees to the points paid by borrower.
49
+ T = E + P(C)
data/Rakefile ADDED
@@ -0,0 +1,33 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+ require 'echoe'
4
+ require 'spec'
5
+ require 'cucumber'
6
+ require 'lib/mortgage_calc'
7
+
8
+ require 'cucumber/rake/task'
9
+ require 'spec/rake/spectask'
10
+
11
+ Echoe.new("mortgage_calculations", MortgageCalc::VERSION) do |p|
12
+ p.description = "Utilities for Mortgage related calculations (APR and Monthly Payments)"
13
+ p.url = "http://www.pathf.com/blogs/2010/02/mortcalc-gem/"
14
+ p.author = "Perry Hertler"
15
+ p.email = "perry@hertler.org"
16
+ p.ignore_pattern = ["tmp/*", "script/*, .idea/*"]
17
+ p.development_dependencies = ["rspec >=1.3.1"]
18
+ end
19
+
20
+ Dir["#File.dirname(__FILE__)}/tasks/*.rake"].sort.each { |ext| load ext }
21
+ spec_files = Rake::FileList["spec/**/*_spec.rb"]
22
+
23
+ desc "Run specs"
24
+ Spec::Rake::SpecTask.new do |t|
25
+ t.spec_files = spec_files
26
+ t.spec_opts = ["-c"]
27
+ end
28
+
29
+ Cucumber::Rake::Task.new(:features) do |t|
30
+ t.cucumber_opts = "features --format progress"
31
+ end
32
+
33
+ task :default => [:spec, :features]
data/Version.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 1
4
+ :patch: 9
@@ -0,0 +1,22 @@
1
+ Feature: Calculate APR
2
+ In order to show sorted APR search results
3
+ As a person navigating OMT
4
+ I want to see correct APRs
5
+
6
+ Scenario Outline: Calculate APR
7
+ Given <loan_amount>, <fees>, <points>, <rate>, <period>
8
+ Then the resultant apr should be <apr>
9
+
10
+ Scenarios: with APR fields
11
+ | loan_amount | fees | points | rate | period | apr |
12
+ | 125000 | 5000 | 0 | 6.5 | 360 | 6.881 |
13
+ | 125000 | 5000 | -1.25 | 6.5 | 360 | 6.763 |
14
+ | 400000 | 1200 | 0 | 5.25 | 180 | 5.296 |
15
+ | 125000 | 811 | 0.375 | 6.125 | 360 | 6.221 |
16
+ | 125000 | 811 | -0.375 | 6.5 | 360 | 6.526 |
17
+ | 100000 | 1000 | 0 | 7.0 | 360 | 7.099 |
18
+ | 100000 | 0 | 0 | 7.0 | 360 | 7.0 |
19
+ | 250000 | 3135 | 2.125 | 6.5 | 360 | 6.822 |
20
+ | 80000 | 800 | 1.2 | 5.125 | 360 | 5.319 |
21
+ | 100000 | 2000 | 0 | 5.9 | 480 | 6.056 |
22
+ | 400000 | 3159 | 0 | 4.375 | 360 | 4.442 |
@@ -0,0 +1,7 @@
1
+ Given /^(.*), (.*), (.*), (.*), (.*)$/ do |loan_amount, fees, points, rate, period|
2
+ @mortgage_util = MortgageCalc::MortgageUtil.new(Integer(loan_amount), Float(rate), Integer(period), Float(fees), Float(points))
3
+ end
4
+
5
+ Then /^the resultant apr should be (.*)$/ do |apr_expected|
6
+ @mortgage_util.apr.should be_within(0.001).of(apr_expected.to_f)
7
+ end
@@ -0,0 +1,5 @@
1
+ $LOAD_PATH << File.join(File.dirname(__FILE__),"..","..","lib")
2
+ require 'mortgage_calc'
3
+ require 'spec/expectations'
4
+ require 'spec/matchers'
5
+ require 'spec/stubs/cucumber'
@@ -0,0 +1,73 @@
1
+ module MortgageCalc
2
+ class MortgageUtil
3
+ attr_accessor :loan_amount, :interest_rate, :period, :fees, :points
4
+
5
+ def initialize(loan_amount, interest_rate, period=360, fees=0, points=0.0)
6
+ self.loan_amount = Float(loan_amount.to_s)
7
+ self.interest_rate = Float(interest_rate.to_s)
8
+ self.period = Integer(period.to_s)
9
+ self.fees = fees
10
+ self.points = Float(points.to_s)
11
+ end
12
+
13
+ def apr
14
+ @apr ||= calculate_apr
15
+ end
16
+
17
+ def monthly_payment
18
+ @monthly_payment ||= calculate_monthly_payment(self.loan_amount, monthly_interest_rate, self.period)
19
+ end
20
+
21
+ def monthly_payment_with_fees
22
+ @monthly_payment_with_fees ||= calculate_monthly_payment(self.loan_amount + total_fees, monthly_interest_rate, self.period)
23
+ end
24
+
25
+ def total_fees(negative_allowed = true)
26
+ #fees may not be negative (borrower is not paid)
27
+ total_fees = calculate_total_fees
28
+ !negative_allowed && total_fees < 0 ? 0 : total_fees
29
+ end
30
+
31
+ private
32
+ def monthly_interest_rate
33
+ self.interest_rate / 100 / 12
34
+ end
35
+
36
+ def calculate_monthly_payment(amount, monthly_rate, period)
37
+ amount * (monthly_rate/(1 - (1 + monthly_rate)**(-period)))
38
+ end
39
+
40
+ def calculate_total_fees
41
+ self.fees + (self.loan_amount * points/100)
42
+ end
43
+
44
+ # solves APR
45
+ # [a (1 + a)^N] / [(1 + a)^N - 1] - P/C = 0
46
+ # where a = APR/1200, N = period, P = monthly payment, C = loan_amount
47
+ # calculate APR uses the Newton-Raphson to find the root (the value for 'a' that makes f(a) = 0)
48
+ def calculate_apr
49
+ payment_ratio = monthly_payment_with_fees / loan_amount
50
+ f = lambda {|k| (k**(self.period + 1) - (k**self.period * (payment_ratio + 1)) + payment_ratio)}
51
+ f_deriv = lambda { |k| ((self.period + 1) * k**self.period) - (self.period * (payment_ratio + 1) * k**(self.period - 1))}
52
+
53
+ root = newton_raphson(f, f_deriv, monthly_interest_rate + 1)
54
+ 100 * 12 * (root - 1).to_f
55
+ end
56
+
57
+ # if 'start' is the monthly_interest_rate, Newton Raphson will find the apr root very quickly
58
+ # k1 = k0 - f(k0)/f'(k0)
59
+ # k_plus_one = k - f(k)/f_deriv(k)
60
+ # We find the k-intercept of the tangent line at point k_plus_one and compare k to k_plus_one.
61
+ # This is repeated until a sufficiently accurate value is reached, which can be specified with the 'precision' parameter
62
+ def newton_raphson(f, f_deriv, start, precision = 5)
63
+ k_plus_one = start
64
+ k = 0.0
65
+
66
+ while ((k - 1) * 10**precision).to_f.floor != ((k_plus_one - 1) * 10**precision).to_f.floor
67
+ k = k_plus_one
68
+ k_plus_one = k - f.call(k) / f_deriv.call(k)
69
+ end
70
+ k_plus_one
71
+ end
72
+ end
73
+ end
@@ -0,0 +1,9 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
2
+ require 'yaml'
3
+
4
+ module MortgageCalc
5
+ version = YAML.load_file(File.dirname(__FILE__) + "/../Version.yml")
6
+ VERSION = "#{version[:major]}.#{version[:minor]}.#{version[:patch]}"
7
+ end
8
+
9
+ require 'mortgage_calc/mortgage_util'
@@ -0,0 +1,33 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{mortgage_calculations}
5
+ s.version = "0.1.9"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Perry Hertler"]
9
+ s.date = %q{2010-11-20}
10
+ s.description = %q{Utilities for Mortgage related calculations (APR and Monthly Payments)}
11
+ s.email = %q{perry@hertler.org}
12
+ s.extra_rdoc_files = ["README.rdoc", "lib/mortgage_calc.rb", "lib/mortgage_calc/mortgage_util.rb"]
13
+ s.files = ["Manifest", "README.rdoc", "Rakefile", "Version.yml", "features/apr.feature", "features/step_definitions/apr_steps.rb", "features/support/env.rb", "lib/mortgage_calc.rb", "lib/mortgage_calc/mortgage_util.rb", "mortgage_calculations.gemspec", "spec/mortgage_calc/mortgage_util_spec.rb", "spec/spec_helper.rb"]
14
+ s.homepage = %q{http://www.pathf.com/blogs/2010/02/mortcalc-gem/}
15
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Mortgage_calculations", "--main", "README.rdoc"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = %q{mortgage_calculations}
18
+ s.rubygems_version = %q{1.3.7}
19
+ s.summary = %q{Utilities for Mortgage related calculations (APR and Monthly Payments)}
20
+
21
+ if s.respond_to? :specification_version then
22
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
23
+ s.specification_version = 3
24
+
25
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
26
+ s.add_development_dependency(%q<rspec>, [">= 1.3.1"])
27
+ else
28
+ s.add_dependency(%q<rspec>, [">= 1.3.1"])
29
+ end
30
+ else
31
+ s.add_dependency(%q<rspec>, [">= 1.3.1"])
32
+ end
33
+ end
@@ -0,0 +1,94 @@
1
+ require File.dirname(__FILE__) + '/../spec_helper'
2
+ module MortgageCalc
3
+ describe MortgageUtil do
4
+ def assert_monthly_apr_payment_matches(loan_amount, rate, period, fee, points)
5
+ mortgage_util = MortgageUtil.new(loan_amount, rate, period, fee, points)
6
+ monthly_payment_with_fees = mortgage_util.monthly_payment_with_fees
7
+ monthly_payment_from_apr = MortgageUtil.new(loan_amount, mortgage_util.apr, period, 0, 0).monthly_payment
8
+ monthly_payment_with_fees.should be_close(monthly_payment_from_apr, 0.01)
9
+ end
10
+
11
+ context "with valid MortgageUtil" do
12
+ before(:all) do
13
+ @mortgage_util = MortgageUtil.new(100000, 6.0, 360, 1200, 1.25)
14
+ @mortgage_util_with_apr_as_rate = MortgageUtil.new(100000, @mortgage_util.apr, 360, 1200, 1.25)
15
+ end
16
+ it "should have proper monthly interest rate" do
17
+ @mortgage_util.send(:monthly_interest_rate).should == 0.005
18
+ end
19
+ it "should have proper monthly payment" do
20
+ @mortgage_util.monthly_payment.should be_close(599.55, 0.001)
21
+ end
22
+ it "should have proper total fees" do
23
+ @mortgage_util.total_fees.should be_close(2450, 0.001)
24
+ end
25
+ it "should have proper APR" do
26
+ @mortgage_util.apr.should be_close(6.22726, 0.00001)
27
+ end
28
+ end
29
+ it "should calculate original monthly payment from APR" do
30
+ assert_monthly_apr_payment_matches(300000, 6.5, 360, 1200, 1.25)
31
+ assert_monthly_apr_payment_matches(300000, 6.5, 360, 0, 0)
32
+ assert_monthly_apr_payment_matches(400000, 1.1, 180, 1200, 1.25)
33
+ assert_monthly_apr_payment_matches(300000, 6.5, 360, 0, 7.25)
34
+ assert_monthly_apr_payment_matches(300000, 6.5, 360, 10000, 7.25)
35
+ end
36
+ end
37
+
38
+ # context "with very small loan amount" do
39
+ # it "should calculate proper apr when loan_amount is very small" do
40
+ # @mortgage_util = MortgageUtil.new(5000, 6.0, 360, 1200, 1.25)
41
+ # puts "@mortgage_util.apr = #{@mortgage_util.apr}"
42
+ # end
43
+ # end
44
+
45
+ # APR calculations from following web site are assumed to be accurate:
46
+ # http://www.debtconsolidationcare.com/calculator/apr.html
47
+ context "test apr calculation" do
48
+ it "should calculate proper apr" do
49
+ @mortgage_util = MortgageUtil.new(125000, 6.5, 360, 5000, 0)
50
+ @mortgage_util.apr.should be_close 6.881, 0.001
51
+ end
52
+ it "should calculate apr < interest_rate properly" do
53
+ @mortgage_util = MortgageUtil.new(125000, 6.5, 360, -5000, 0)
54
+ @mortgage_util.apr.should be_close 6.112, 0.001
55
+ end
56
+ end
57
+
58
+ context "net negative fees" do
59
+ before(:all) do
60
+ @mortgage_util = MortgageUtil.new(100000, 6.0, 360, 1200, -11.25)
61
+ @mortgage_util.total_fees.should eql -10050.0
62
+ end
63
+ it "calculate total fees should return actual total fees is less than 0" do
64
+ @mortgage_util.send(:calculate_total_fees).should eql -10050.0
65
+ end
66
+ it "total fees should return 0 if total fees is less than 0" do
67
+ @mortgage_util.total_fees(false).should eql 0
68
+ end
69
+ it "total fees should return actual fees if negative parameter is true" do
70
+ @mortgage_util.total_fees(true).should eql -10050.0
71
+ end
72
+ it "should not return APR less than interest rate" do
73
+ @mortgage_util.apr.should be_close 5.04043, 0.00001
74
+ end
75
+ end
76
+
77
+ context "initialize convert to best types" do
78
+ before(:all) do
79
+ @mortgage_util = MortgageUtil.new('100000', '6.0', 360, 1200, '-1.25')
80
+ end
81
+ it "should convert rate to float if necessary" do
82
+ @mortgage_util.interest_rate.class.should == Float
83
+ end
84
+ it "should convert points to float if necessary" do
85
+ @mortgage_util.points.class.should == Float
86
+ end
87
+ it "should convert loan_amount to float if necessary" do
88
+ @mortgage_util.loan_amount.class.should == Float
89
+ end
90
+ it "should convert period to integer if necessary" do
91
+ @mortgage_util.period.class.should == Fixnum
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,3 @@
1
+ $LOAD_PATH << File.join(File.dirname(__FILE__),"..","lib")
2
+ require 'spec'
3
+ require 'mortgage_calc'
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: mortgage_calculations
3
+ version: !ruby/object:Gem::Version
4
+ hash: 9
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 9
10
+ version: 0.1.9
11
+ platform: ruby
12
+ authors:
13
+ - Perry Hertler
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2010-11-20 00:00:00 -06:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: rspec
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ hash: 25
30
+ segments:
31
+ - 1
32
+ - 3
33
+ - 1
34
+ version: 1.3.1
35
+ type: :development
36
+ version_requirements: *id001
37
+ description: Utilities for Mortgage related calculations (APR and Monthly Payments)
38
+ email: perry@hertler.org
39
+ executables: []
40
+
41
+ extensions: []
42
+
43
+ extra_rdoc_files:
44
+ - README.rdoc
45
+ - lib/mortgage_calc.rb
46
+ - lib/mortgage_calc/mortgage_util.rb
47
+ files:
48
+ - Manifest
49
+ - README.rdoc
50
+ - Rakefile
51
+ - Version.yml
52
+ - features/apr.feature
53
+ - features/step_definitions/apr_steps.rb
54
+ - features/support/env.rb
55
+ - lib/mortgage_calc.rb
56
+ - lib/mortgage_calc/mortgage_util.rb
57
+ - mortgage_calculations.gemspec
58
+ - spec/mortgage_calc/mortgage_util_spec.rb
59
+ - spec/spec_helper.rb
60
+ has_rdoc: true
61
+ homepage: http://www.pathf.com/blogs/2010/02/mortcalc-gem/
62
+ licenses: []
63
+
64
+ post_install_message:
65
+ rdoc_options:
66
+ - --line-numbers
67
+ - --inline-source
68
+ - --title
69
+ - Mortgage_calculations
70
+ - --main
71
+ - README.rdoc
72
+ require_paths:
73
+ - lib
74
+ required_ruby_version: !ruby/object:Gem::Requirement
75
+ none: false
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ hash: 3
80
+ segments:
81
+ - 0
82
+ version: "0"
83
+ required_rubygems_version: !ruby/object:Gem::Requirement
84
+ none: false
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ hash: 11
89
+ segments:
90
+ - 1
91
+ - 2
92
+ version: "1.2"
93
+ requirements: []
94
+
95
+ rubyforge_project: mortgage_calculations
96
+ rubygems_version: 1.3.7
97
+ signing_key:
98
+ specification_version: 3
99
+ summary: Utilities for Mortgage related calculations (APR and Monthly Payments)
100
+ test_files: []
101
+