mortgage_calculations 0.1.9

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