doughnut 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Rakefile +1 -0
- data/doughnut.gemspec +1 -0
- data/lib/doughnut.rb +8 -3
- data/lib/doughnut/monte_carlo/population_factory.rb +31 -0
- data/lib/doughnut/monte_carlo/population_tester.rb +0 -0
- data/lib/doughnut/monte_carlo/predicted_return.rb +19 -0
- data/lib/doughnut/{expenses.rb → retirement_calculator/expenses.rb} +9 -9
- data/lib/doughnut/{income.rb → retirement_calculator/income.rb} +2 -10
- data/lib/doughnut/{retirement_calculator.rb → retirement_calculator/retirement_calculator.rb} +5 -1
- data/lib/doughnut/tasks/retirement.rake +14 -0
- data/lib/doughnut/version.rb +1 -1
- data/spec/monte_carlo/population_factory_spec.rb +121 -0
- data/spec/monte_carlo/population_tester.rb +14 -0
- data/spec/monte_carlo/predicted_return_spec.rb +78 -0
- data/spec/{expenses_spec.rb → retirement_calculator/expenses_spec.rb} +0 -0
- data/spec/{income_spec.rb → retirement_calculator/income_spec.rb} +0 -0
- data/spec/retirement_calculator/retirement_calculator_spec.rb +142 -0
- metadata +35 -11
- data/spec/retirement_calculator_spec.rb +0 -75
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e0dcdc95522dc60f4014b3d243542e9312b09521
|
4
|
+
data.tar.gz: 5b25a23ebadd05e66344b8d1ed110d62ba15a87b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 23b34df6870d38f36362481b8543b6773700c2dd1710dcef05f73d29c8db10944d0474db4efc2356a6f3004cbd997736084b80735a807c63d25261467c289116
|
7
|
+
data.tar.gz: d80e2c1cff77910bca4634d8d5e5979fdfb2fdd73b3bcb75fe24f4272f02fa4b2eaf9186357e83a09038eca7e455ce573d088fb6e1bf81f0decaf650b8d70535
|
data/Rakefile
CHANGED
data/doughnut.gemspec
CHANGED
data/lib/doughnut.rb
CHANGED
@@ -1,6 +1,11 @@
|
|
1
|
-
require "doughnut/
|
2
|
-
require "doughnut/
|
3
|
-
require "doughnut/
|
1
|
+
require "doughnut/monte_carlo/population_factory"
|
2
|
+
require "doughnut/monte_carlo/population_tester"
|
3
|
+
require "doughnut/monte_carlo/predicted_return"
|
4
|
+
|
5
|
+
require "doughnut/retirement_calculator/expenses"
|
6
|
+
require "doughnut/retirement_calculator/income"
|
7
|
+
require "doughnut/retirement_calculator/retirement_calculator"
|
8
|
+
|
4
9
|
require "doughnut/version"
|
5
10
|
|
6
11
|
module Doughnut
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Doughnut
|
2
|
+
|
3
|
+
class PopulationFactory
|
4
|
+
|
5
|
+
def initialize(list_of_assets)
|
6
|
+
@num_genomes = 10
|
7
|
+
@list_of_assets = list_of_assets
|
8
|
+
end
|
9
|
+
|
10
|
+
def build_random_population
|
11
|
+
Array.new(@num_genomes, random_genome)
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def random_genome
|
17
|
+
output = []
|
18
|
+
@list_of_assets.each_with_index do |asset, indx|
|
19
|
+
output << asset.merge!({fraction: gene_fractions[indx]})
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def gene_fractions
|
24
|
+
fracs = Array.new(@list_of_assets.length, rand)
|
25
|
+
s = fracs.sum
|
26
|
+
fracs.map { |x| x/s }
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
File without changes
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require "rubystats"
|
2
|
+
|
3
|
+
module Doughnut
|
4
|
+
|
5
|
+
class PredictedReturn
|
6
|
+
|
7
|
+
def initialize(avg_return = 0, standard_deviation = 0)
|
8
|
+
@avg_return = avg_return
|
9
|
+
@standard_deviation = standard_deviation
|
10
|
+
end
|
11
|
+
|
12
|
+
def return(num_data_points = 1)
|
13
|
+
return @avg_return if @standard_deviation == 0
|
14
|
+
Rubystats::NormalDistribution.new(@avg_return, @standard_deviation).rng
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
@@ -9,14 +9,6 @@ module Doughnut
|
|
9
9
|
@inflation_rate = 0.0322
|
10
10
|
end
|
11
11
|
|
12
|
-
def future_expenses(death_date, discount_rate)
|
13
|
-
output = []
|
14
|
-
(Date.today..death_date).each do |mydate|
|
15
|
-
output << present_value(mydate, discount_rate) if is_last_day(mydate)
|
16
|
-
end
|
17
|
-
output
|
18
|
-
end
|
19
|
-
|
20
12
|
def total_expenses(death_date, discount_rate)
|
21
13
|
output = 0
|
22
14
|
future_expenses(death_date, discount_rate).each do |h|
|
@@ -25,9 +17,17 @@ module Doughnut
|
|
25
17
|
output
|
26
18
|
end
|
27
19
|
|
20
|
+
def future_expenses(death_date, discount_rate)
|
21
|
+
output = []
|
22
|
+
(Date.today..death_date).each do |mydate|
|
23
|
+
output << present_value(mydate, discount_rate) if is_last_day_of_month(mydate)
|
24
|
+
end
|
25
|
+
output
|
26
|
+
end
|
27
|
+
|
28
28
|
private
|
29
29
|
|
30
|
-
def
|
30
|
+
def is_last_day_of_month(mydate)
|
31
31
|
mydate.month != mydate.next_day.month
|
32
32
|
end
|
33
33
|
|
@@ -12,22 +12,14 @@ module Doughnut
|
|
12
12
|
def future_income(death_date, discount_rate)
|
13
13
|
output = []
|
14
14
|
(Date.today..death_date).each do |mydate|
|
15
|
-
output << present_value(mydate, discount_rate) if
|
16
|
-
end
|
17
|
-
output
|
18
|
-
end
|
19
|
-
|
20
|
-
def total_income(death_date, discount_rate)
|
21
|
-
output = 0
|
22
|
-
future_expenses(death_date, discount_rate).each do |h|
|
23
|
-
output += h[:income]
|
15
|
+
output << present_value(mydate, discount_rate) if is_last_day_of_month(mydate)
|
24
16
|
end
|
25
17
|
output
|
26
18
|
end
|
27
19
|
|
28
20
|
private
|
29
21
|
|
30
|
-
def
|
22
|
+
def is_last_day_of_month(mydate)
|
31
23
|
mydate.month != mydate.next_day.month
|
32
24
|
end
|
33
25
|
|
data/lib/doughnut/{retirement_calculator.rb → retirement_calculator/retirement_calculator.rb}
RENAMED
@@ -3,15 +3,19 @@ module Doughnut
|
|
3
3
|
class RetirementCalculator
|
4
4
|
|
5
5
|
attr_accessor :death_date, :portfolio_return
|
6
|
+
attr_accessor :current_net_worth
|
6
7
|
|
7
|
-
def initialize
|
8
|
+
def initialize(net_worth = 207000)
|
8
9
|
@death_date = Date.new(2067,7,19)
|
10
|
+
@current_net_worth = net_worth
|
9
11
|
@portfolio_return = 0.1
|
10
12
|
end
|
11
13
|
|
12
14
|
def retirement_date(total_expenses, monthly_incomes)
|
13
15
|
return Date.today if total_expenses == 0
|
16
|
+
return Date.today if total_expenses < @current_net_worth
|
14
17
|
return @death_date if monthly_incomes.length == 0
|
18
|
+
monthly_incomes.insert( 0, {date: Date.today, income: @current_net_worth} )
|
15
19
|
running_income = 0
|
16
20
|
monthly_incomes.each do |h|
|
17
21
|
running_income += h[:income]
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require "doughnut/retirement_calculator/expenses"
|
2
|
+
require "doughnut/retirement_calculator/income"
|
3
|
+
require "doughnut/retirement_calculator/retirement_calculator"
|
4
|
+
|
5
|
+
task :calculate_retirement do
|
6
|
+
e = Expenses.new
|
7
|
+
i = Income.new
|
8
|
+
r = RetirementCalculator.new
|
9
|
+
death_date = r.death_date
|
10
|
+
discount_rate = r.portfolio_return
|
11
|
+
e_tot = e.total_expenses(death_date, discount_rate)
|
12
|
+
i_tot = i.future_income(death_date, discount_rate)
|
13
|
+
puts "#{r.retirement_date(e_tot, i_tot)}"
|
14
|
+
end
|
data/lib/doughnut/version.rb
CHANGED
@@ -0,0 +1,121 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module Doughnut
|
4
|
+
|
5
|
+
describe PopulationFactory do
|
6
|
+
|
7
|
+
describe "building a population of random genomes" do
|
8
|
+
|
9
|
+
context "one input" do
|
10
|
+
|
11
|
+
before(:each) do
|
12
|
+
@population = PopulationFactory.new( [{name: "X", avg_return: 1, standard_deviation: 5}] ).build_random_population
|
13
|
+
end
|
14
|
+
|
15
|
+
it "returns ten genomes" do
|
16
|
+
expect(@population.length).to eq 10
|
17
|
+
end
|
18
|
+
|
19
|
+
it "each genome has a length equal to one" do
|
20
|
+
@population.each do |genome|
|
21
|
+
expect(genome.length).to eq 1
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
it "each gene in a given genome has a name" do
|
26
|
+
@population.each do |genome|
|
27
|
+
genome.each do |gene|
|
28
|
+
expect(gene[:name]).not_to be nil
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
it "each gene in a given genome has an average return" do
|
34
|
+
@population.each do |genome|
|
35
|
+
genome.each do |gene|
|
36
|
+
expect(gene[:avg_return]).to eq 1
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
it "each gene in a given genome has a standard deviation" do
|
42
|
+
@population.each do |genome|
|
43
|
+
genome.each do |gene|
|
44
|
+
expect(gene[:standard_deviation]).to eq 5
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
it "each gene in a given genome has a fraction of one" do
|
50
|
+
@population.each do |genome|
|
51
|
+
genome.each do |gene|
|
52
|
+
expect(gene[:fraction]).to eq 1
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
context "multiple inputs" do
|
60
|
+
|
61
|
+
before(:each) do
|
62
|
+
@population = PopulationFactory.new( [{name: "X", avg_return: 1, standard_deviation: 5},{name: "Y", avg_return: 3, standard_deviation: 7}] ).build_random_population
|
63
|
+
end
|
64
|
+
|
65
|
+
it "returns ten genomes" do
|
66
|
+
expect(@population.length).to eq 10
|
67
|
+
end
|
68
|
+
|
69
|
+
it "each genome has a length greater than one" do
|
70
|
+
@population.each do |genome|
|
71
|
+
expect(genome.length).to eq 2
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
it "each gene in a given genome has a name" do
|
76
|
+
@population.each do |genome|
|
77
|
+
genome.each do |gene|
|
78
|
+
expect(gene[:name]).not_to be nil
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
it "each gene in a given genome has an average return" do
|
84
|
+
@population.each do |genome|
|
85
|
+
genome.each do |gene|
|
86
|
+
expect(gene[:avg_return] == 1 || gene[:avg_return] == 3).to be true
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
it "each gene in a given genome has a standard deviation" do
|
92
|
+
@population.each do |genome|
|
93
|
+
genome.each do |gene|
|
94
|
+
expect(gene[:standard_deviation] == 5 || gene[:standard_deviation] == 7).to be true
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
it "each fraction is NOT equal to one" do
|
100
|
+
@population.each do |genome|
|
101
|
+
genome.each do |gene|
|
102
|
+
expect(gene[:fraction]).not_to eq 1
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
it "the sum of all fractions equals one" do
|
108
|
+
@population.each do |genome|
|
109
|
+
sum = 0
|
110
|
+
genome.each { |gene| sum += gene[:fraction] }
|
111
|
+
expect(sum).to eq 1
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
require "descriptive_statistics"
|
3
|
+
|
4
|
+
module Doughnut
|
5
|
+
|
6
|
+
describe PredictedReturn do
|
7
|
+
|
8
|
+
describe "#return" do
|
9
|
+
|
10
|
+
context "boundary conditions" do
|
11
|
+
|
12
|
+
it "gives zero predicted return if there are no inputs" do
|
13
|
+
expect(PredictedReturn.new.return).to eq 0
|
14
|
+
end
|
15
|
+
|
16
|
+
it "gives zero predicted return if both average return and standard deviation are zero" do
|
17
|
+
expect(PredictedReturn.new(0,0).return).to eq 0
|
18
|
+
end
|
19
|
+
|
20
|
+
it "gives the average return if standard deviation is zero" do
|
21
|
+
expect(PredictedReturn.new(0.5,0).return).to eq 0.5
|
22
|
+
end
|
23
|
+
|
24
|
+
it "gives a range of returns if the standard deviation is zero" do
|
25
|
+
expect(PredictedReturn.new(0,0.0001).return).to be > -1
|
26
|
+
expect(PredictedReturn.new(0,0.0001).return).to be < 1
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
context "Gaussian outputs" do
|
32
|
+
|
33
|
+
before(:each) do
|
34
|
+
@simulation = PredictedReturn.new(0.4, 0.1)
|
35
|
+
@thin_sim = PredictedReturn.new(0.8, 0.00000000001)
|
36
|
+
end
|
37
|
+
|
38
|
+
it "the data points should be unique" do
|
39
|
+
output = []
|
40
|
+
50.times { output << @simulation.return }
|
41
|
+
expect(output.uniq.length).to eq 50
|
42
|
+
end
|
43
|
+
|
44
|
+
it "the data points should have the given mean" do
|
45
|
+
output = []
|
46
|
+
50.times { output << @simulation.return }
|
47
|
+
expect(output.mean).to be > 0.25
|
48
|
+
expect(output.mean).to be < 0.45
|
49
|
+
end
|
50
|
+
|
51
|
+
it "the data points should have the given standard deviation" do
|
52
|
+
output = []
|
53
|
+
50.times { output << @simulation.return }
|
54
|
+
expect(output.standard_deviation).to be > 0.0
|
55
|
+
expect(output.standard_deviation).to be < 0.2
|
56
|
+
end
|
57
|
+
|
58
|
+
it "creates data points near the average return if the standard deviation is small" do
|
59
|
+
output = []
|
60
|
+
50.times { output << @thin_sim.return }
|
61
|
+
expect(output.mean).to be > 0.7999
|
62
|
+
expect(output.mean).to be < 0.8001
|
63
|
+
end
|
64
|
+
|
65
|
+
it "creates closely clustered data points if the standard deviation is small" do
|
66
|
+
output = []
|
67
|
+
50.times { output << @thin_sim.return }
|
68
|
+
expect(output.standard_deviation).to be > 0.0000
|
69
|
+
expect(output.standard_deviation).to be < 0.0002
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
File without changes
|
File without changes
|
@@ -0,0 +1,142 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module Doughnut
|
4
|
+
|
5
|
+
describe RetirementCalculator do
|
6
|
+
|
7
|
+
describe "parameters" do
|
8
|
+
|
9
|
+
before(:each) do
|
10
|
+
@retire = RetirementCalculator.new
|
11
|
+
end
|
12
|
+
|
13
|
+
context "defaults" do
|
14
|
+
|
15
|
+
it "returns a death date of 80 years old" do
|
16
|
+
expect(@retire.death_date).to eq Date.new(2067,7,19)
|
17
|
+
end
|
18
|
+
|
19
|
+
it "returns average portfolio return of 10%" do
|
20
|
+
expect(@retire.portfolio_return).to eq 0.1
|
21
|
+
end
|
22
|
+
|
23
|
+
it "returns a current net worth of 207000" do
|
24
|
+
expect(@retire.current_net_worth).to eq 207000
|
25
|
+
end
|
26
|
+
|
27
|
+
it "updates the death date" do
|
28
|
+
@retire.death_date = Date.new(2001,3,7)
|
29
|
+
expect(@retire.death_date).to eq Date.new(2001,3,7)
|
30
|
+
end
|
31
|
+
|
32
|
+
it "updates the average portfolio return" do
|
33
|
+
@retire.portfolio_return = 0.99
|
34
|
+
expect(@retire.portfolio_return).to eq 0.99
|
35
|
+
end
|
36
|
+
|
37
|
+
it "updates the current net worth" do
|
38
|
+
@retire.current_net_worth = 100
|
39
|
+
expect(@retire.current_net_worth).to eq 100
|
40
|
+
end
|
41
|
+
|
42
|
+
it "sets the net worth on initialization" do
|
43
|
+
worth = RetirementCalculator.new(200).current_net_worth
|
44
|
+
expect(worth).to eq 200
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
|
51
|
+
describe "retirement date with no initial net worth" do
|
52
|
+
|
53
|
+
before(:each) do
|
54
|
+
@retire = RetirementCalculator.new(0)
|
55
|
+
end
|
56
|
+
|
57
|
+
it "returns today's date if there are no monthly expenses or incomes" do
|
58
|
+
expect(@retire.retirement_date(0,[])).to eq Date.today
|
59
|
+
end
|
60
|
+
|
61
|
+
it "returns today's date if there are no monthly expenses but positive incomes" do
|
62
|
+
expect(@retire.retirement_date(0, [{date: Date.new(2012,1,2), income: 100}])).to eq Date.today
|
63
|
+
end
|
64
|
+
|
65
|
+
it "returns the death date if there is no monthly income" do
|
66
|
+
expect(@retire.retirement_date(1,[])).to eq Date.new(2067,7,19)
|
67
|
+
end
|
68
|
+
|
69
|
+
it "returns the date of earned income if PV(income) > PV(expenses)" do
|
70
|
+
params = [{:date => Date.new(2040,1,2), :income => 2000}]
|
71
|
+
expect(@retire.retirement_date(1000, params)).to eq Date.new(2040,1,2)
|
72
|
+
end
|
73
|
+
|
74
|
+
it "returns the date of earned income if PV(income) = PV(expenses)" do
|
75
|
+
params = [{:date => Date.new(2041,2,8), :income => 1000}]
|
76
|
+
expect(@retire.retirement_date(1000, params)).to eq Date.new(2041,2,8)
|
77
|
+
end
|
78
|
+
|
79
|
+
it "returns the date of earned income if PV(income) > PV(expenses) for multiple incomes" do
|
80
|
+
params = [{:date => Date.new(2040,1,2), :income => 999}, {:date => Date.new(2049,7,8), :income => 1000}]
|
81
|
+
expect(@retire.retirement_date(1000, params)).to eq Date.new(2049,7,8)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "returns the date of earned income if PV(income) = PV(expenses) for multiple incomes" do
|
85
|
+
params = [{:date => Date.new(2040,1,2), :income => 999}, {:date => Date.new(2049,7,8), :income => 1}]
|
86
|
+
expect(@retire.retirement_date(1000, params)).to eq Date.new(2049,7,8)
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "retirement date with non-zero initial net worth" do
|
92
|
+
|
93
|
+
before(:each) do
|
94
|
+
@retire = RetirementCalculator.new(1000)
|
95
|
+
end
|
96
|
+
|
97
|
+
it "returns today's date if there are no monthly expenses or incomes" do
|
98
|
+
expect(@retire.retirement_date(0,[])).to eq Date.today
|
99
|
+
end
|
100
|
+
|
101
|
+
it "returns today's date if there are no monthly expenses but positive incomes" do
|
102
|
+
expect(@retire.retirement_date(0, [{date: Date.new(2011,1,2), income: 100}])).to eq Date.today
|
103
|
+
end
|
104
|
+
|
105
|
+
it "returns today's if there is no monthly income, but net worth exceeds expenses" do
|
106
|
+
expect(@retire.retirement_date(800,[])).to eq Date.today
|
107
|
+
end
|
108
|
+
|
109
|
+
it "returns the death date if there is no monthly income and net worth is less than expenses" do
|
110
|
+
expect(@retire.retirement_date(2000,[])).to eq Date.new(2067,7,19)
|
111
|
+
end
|
112
|
+
|
113
|
+
it "returns the date of earned income if current_net_worth + PV(income) > PV(expenses) (test 1)" do
|
114
|
+
params = [{:date => Date.new(2040,1,2), :income => 2000}]
|
115
|
+
expect(@retire.retirement_date(1100, params)).to eq Date.new(2040,1,2)
|
116
|
+
end
|
117
|
+
|
118
|
+
it "returns the date of earned income if current_net_worth + PV(income) > PV(expenses) (test 2)" do
|
119
|
+
params = [{:date => Date.new(2032,1,5), :income => 980}]
|
120
|
+
expect(@retire.retirement_date(1500, params)).to eq Date.new(2032,1,5)
|
121
|
+
end
|
122
|
+
|
123
|
+
it "returns the date of earned income if current_net_worth + PV(income) = PV(expenses)" do
|
124
|
+
params = [{:date => Date.new(2021,8,8), :income => 1000}]
|
125
|
+
expect(@retire.retirement_date(2000, params)).to eq Date.new(2021,8,8)
|
126
|
+
end
|
127
|
+
|
128
|
+
it "returns the date of earned income if current_net_worth + PV(income) > PV(expenses) for multiple incomes" do
|
129
|
+
params = [{:date => Date.new(2040,1,2), :income => 999}, {:date => Date.new(2049,7,8), :income => 1000}]
|
130
|
+
expect(@retire.retirement_date(2500, params)).to eq Date.new(2049,7,8)
|
131
|
+
end
|
132
|
+
|
133
|
+
it "returns the date of earned income if current_net_worth + PV(income) = PV(expenses) for multiple incomes" do
|
134
|
+
params = [{:date => Date.new(2040,1,2), :income => 999}, {:date => Date.new(2049,7,8), :income => 1}]
|
135
|
+
expect(@retire.retirement_date(2000, params)).to eq Date.new(2049,7,8)
|
136
|
+
end
|
137
|
+
|
138
|
+
end
|
139
|
+
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: doughnut
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Cyrus Vandrevala
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2015-
|
11
|
+
date: 2015-06-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -66,6 +66,20 @@ dependencies:
|
|
66
66
|
- - ">="
|
67
67
|
- !ruby/object:Gem::Version
|
68
68
|
version: '0'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: rubystats
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: '0'
|
76
|
+
type: :runtime
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - ">="
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '0'
|
69
83
|
description: ''
|
70
84
|
email:
|
71
85
|
- cyrus.vandrevala@gmail.com
|
@@ -80,13 +94,20 @@ files:
|
|
80
94
|
- Rakefile
|
81
95
|
- doughnut.gemspec
|
82
96
|
- lib/doughnut.rb
|
83
|
-
- lib/doughnut/
|
84
|
-
- lib/doughnut/
|
85
|
-
- lib/doughnut/
|
97
|
+
- lib/doughnut/monte_carlo/population_factory.rb
|
98
|
+
- lib/doughnut/monte_carlo/population_tester.rb
|
99
|
+
- lib/doughnut/monte_carlo/predicted_return.rb
|
100
|
+
- lib/doughnut/retirement_calculator/expenses.rb
|
101
|
+
- lib/doughnut/retirement_calculator/income.rb
|
102
|
+
- lib/doughnut/retirement_calculator/retirement_calculator.rb
|
103
|
+
- lib/doughnut/tasks/retirement.rake
|
86
104
|
- lib/doughnut/version.rb
|
87
|
-
- spec/
|
88
|
-
- spec/
|
89
|
-
- spec/
|
105
|
+
- spec/monte_carlo/population_factory_spec.rb
|
106
|
+
- spec/monte_carlo/population_tester.rb
|
107
|
+
- spec/monte_carlo/predicted_return_spec.rb
|
108
|
+
- spec/retirement_calculator/expenses_spec.rb
|
109
|
+
- spec/retirement_calculator/income_spec.rb
|
110
|
+
- spec/retirement_calculator/retirement_calculator_spec.rb
|
90
111
|
- spec/spec_helper.rb
|
91
112
|
homepage: ''
|
92
113
|
licenses:
|
@@ -113,7 +134,10 @@ signing_key:
|
|
113
134
|
specification_version: 4
|
114
135
|
summary: Generate statistics for a personal portfolio.
|
115
136
|
test_files:
|
116
|
-
- spec/
|
117
|
-
- spec/
|
118
|
-
- spec/
|
137
|
+
- spec/monte_carlo/population_factory_spec.rb
|
138
|
+
- spec/monte_carlo/population_tester.rb
|
139
|
+
- spec/monte_carlo/predicted_return_spec.rb
|
140
|
+
- spec/retirement_calculator/expenses_spec.rb
|
141
|
+
- spec/retirement_calculator/income_spec.rb
|
142
|
+
- spec/retirement_calculator/retirement_calculator_spec.rb
|
119
143
|
- spec/spec_helper.rb
|
@@ -1,75 +0,0 @@
|
|
1
|
-
require "spec_helper"
|
2
|
-
|
3
|
-
module Doughnut
|
4
|
-
|
5
|
-
describe RetirementCalculator do
|
6
|
-
|
7
|
-
before(:each) do
|
8
|
-
@retire = RetirementCalculator.new
|
9
|
-
end
|
10
|
-
|
11
|
-
describe "parameters" do
|
12
|
-
|
13
|
-
context "defaults" do
|
14
|
-
|
15
|
-
it "returns a death date of 80 years old" do
|
16
|
-
expect(@retire.death_date).to eq Date.new(2067,7,19)
|
17
|
-
end
|
18
|
-
|
19
|
-
it "returns average portfolio return of 10%" do
|
20
|
-
expect(@retire.portfolio_return).to eq 0.1
|
21
|
-
end
|
22
|
-
|
23
|
-
it "updates the death date" do
|
24
|
-
@retire.death_date = Date.new(2001,3,7)
|
25
|
-
expect(@retire.death_date).to eq Date.new(2001,3,7)
|
26
|
-
end
|
27
|
-
|
28
|
-
it "updates the average portfolio return" do
|
29
|
-
@retire.portfolio_return = 0.99
|
30
|
-
expect(@retire.portfolio_return).to eq 0.99
|
31
|
-
end
|
32
|
-
|
33
|
-
end
|
34
|
-
|
35
|
-
end
|
36
|
-
|
37
|
-
describe "retirement date" do
|
38
|
-
|
39
|
-
it "returns today's date if there are no monthly expenses or incomes" do
|
40
|
-
expect(@retire.retirement_date(0,[])).to eq Date.today
|
41
|
-
end
|
42
|
-
|
43
|
-
it "returns today's date if there are no monthly expenses but positive incomes" do
|
44
|
-
expect(@retire.retirement_date(0, [{date: Date.new(2012,1,2), income: 100}])).to eq Date.today
|
45
|
-
end
|
46
|
-
|
47
|
-
it "returns the death date if there is no monthly income" do
|
48
|
-
expect(@retire.retirement_date(1,[])).to eq Date.new(2067,7,19)
|
49
|
-
end
|
50
|
-
|
51
|
-
it "returns the date of earned income if PV(income) > PV(expenses)" do
|
52
|
-
params = [{:date => Date.new(2040,1,2), :income => 2000}]
|
53
|
-
expect(@retire.retirement_date(1000, params)).to eq Date.new(2040,1,2)
|
54
|
-
end
|
55
|
-
|
56
|
-
it "returns the date of earned income if PV(income) = PV(expenses)" do
|
57
|
-
params = [{:date => Date.new(2041,2,8), :income => 1000}]
|
58
|
-
expect(@retire.retirement_date(1000, params)).to eq Date.new(2041,2,8)
|
59
|
-
end
|
60
|
-
|
61
|
-
it "returns the date of earned income if PV(income) > PV(expenses) for multiple incomes" do
|
62
|
-
params = [{:date => Date.new(2040,1,2), :income => 999}, {:date => Date.new(2049,7,8), :income => 1000}]
|
63
|
-
expect(@retire.retirement_date(1000, params)).to eq Date.new(2049,7,8)
|
64
|
-
end
|
65
|
-
|
66
|
-
it "returns the date of earned income if PV(income) = PV(expenses) for multiple incomes" do
|
67
|
-
params = [{:date => Date.new(2040,1,2), :income => 999}, {:date => Date.new(2049,7,8), :income => 1}]
|
68
|
-
expect(@retire.retirement_date(1000, params)).to eq Date.new(2049,7,8)
|
69
|
-
end
|
70
|
-
|
71
|
-
end
|
72
|
-
|
73
|
-
end
|
74
|
-
|
75
|
-
end
|