doughnut 0.0.2 → 0.0.3
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.
- checksums.yaml +4 -4
- data/lib/doughnut.rb +3 -1
- data/lib/doughnut/expenses.rb +45 -0
- data/lib/doughnut/income.rb +45 -0
- data/lib/doughnut/retirement_calculator.rb +24 -0
- data/lib/doughnut/version.rb +1 -1
- data/spec/expenses_spec.rb +164 -0
- data/spec/income_spec.rb +148 -0
- data/spec/retirement_calculator_spec.rb +75 -0
- metadata +11 -5
- data/lib/doughnut/capm.rb +0 -6
- data/spec/capm_spec.rb +0 -30
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7bc1546c0df232365b38159c778448b343458f94
|
4
|
+
data.tar.gz: 19f2d1ef046b25fd11e9835f059ad87c6e5f06a1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 35a7b3618af9f8358f79fbb2e5a400fb447a94a07521b2a15124818d548538e333c685a0a98694b17506b5b3cd52a380d4b87be89ddc83c02e629cbf8e4e3786
|
7
|
+
data.tar.gz: 3b10b380cda929553691e32e602928107cc2a51f97916b2da4bd863175b46f65d3861b3491d0d8ff62236abe435296182cd09d22be9234e79d4e8d938ceeeb5e
|
data/lib/doughnut.rb
CHANGED
@@ -0,0 +1,45 @@
|
|
1
|
+
module Doughnut
|
2
|
+
|
3
|
+
class Expenses
|
4
|
+
|
5
|
+
attr_accessor :monthly_expenses, :inflation_rate
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@monthly_expenses = 2500
|
9
|
+
@inflation_rate = 0.0322
|
10
|
+
end
|
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
|
+
def total_expenses(death_date, discount_rate)
|
21
|
+
output = 0
|
22
|
+
future_expenses(death_date, discount_rate).each do |h|
|
23
|
+
output += h[:expense]
|
24
|
+
end
|
25
|
+
output
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def is_last_day(mydate)
|
31
|
+
mydate.month != mydate.next_day.month
|
32
|
+
end
|
33
|
+
|
34
|
+
def present_value(mydate, discount_rate)
|
35
|
+
{ date: mydate, expense: @monthly_expenses*discount_factor(mydate, discount_rate) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def discount_factor(mydate, discount_rate)
|
39
|
+
t = (mydate - Date.today)/30
|
40
|
+
((1 + @inflation_rate/12)/(1 + discount_rate/12))**t
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Doughnut
|
2
|
+
|
3
|
+
class Income
|
4
|
+
|
5
|
+
attr_accessor :monthly_income, :income_growth_rate
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@monthly_income = 3700
|
9
|
+
@income_growth_rate = 0.01
|
10
|
+
end
|
11
|
+
|
12
|
+
def future_income(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
|
+
def total_income(death_date, discount_rate)
|
21
|
+
output = 0
|
22
|
+
future_expenses(death_date, discount_rate).each do |h|
|
23
|
+
output += h[:income]
|
24
|
+
end
|
25
|
+
output
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def is_last_day(mydate)
|
31
|
+
mydate.month != mydate.next_day.month
|
32
|
+
end
|
33
|
+
|
34
|
+
def present_value(mydate, discount_rate)
|
35
|
+
{ date: mydate, income: @monthly_income*discount_factor(mydate, discount_rate) }
|
36
|
+
end
|
37
|
+
|
38
|
+
def discount_factor(mydate, discount_rate)
|
39
|
+
t = (mydate - Date.today)/30
|
40
|
+
((1 + @income_growth_rate/12)/(1 + discount_rate/12))**t
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Doughnut
|
2
|
+
|
3
|
+
class RetirementCalculator
|
4
|
+
|
5
|
+
attr_accessor :death_date, :portfolio_return
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@death_date = Date.new(2067,7,19)
|
9
|
+
@portfolio_return = 0.1
|
10
|
+
end
|
11
|
+
|
12
|
+
def retirement_date(total_expenses, monthly_incomes)
|
13
|
+
return Date.today if total_expenses == 0
|
14
|
+
return @death_date if monthly_incomes.length == 0
|
15
|
+
running_income = 0
|
16
|
+
monthly_incomes.each do |h|
|
17
|
+
running_income += h[:income]
|
18
|
+
return h[:date] if running_income >= total_expenses
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
data/lib/doughnut/version.rb
CHANGED
@@ -0,0 +1,164 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module Doughnut
|
4
|
+
|
5
|
+
describe Expenses do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
@expenses = Expenses.new
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "parameters" do
|
12
|
+
|
13
|
+
it "returns average monthly expenses of $2500" do
|
14
|
+
expect(@expenses.monthly_expenses).to eq 2500
|
15
|
+
end
|
16
|
+
|
17
|
+
it "updates the average monthly expenses" do
|
18
|
+
@expenses.monthly_expenses = 52.55
|
19
|
+
expect(@expenses.monthly_expenses).to eq 52.55
|
20
|
+
end
|
21
|
+
|
22
|
+
it "returns an inflation rate of 3.22%" do
|
23
|
+
expect(@expenses.inflation_rate).to eq 0.0322
|
24
|
+
end
|
25
|
+
|
26
|
+
it "updates the average inflation rate" do
|
27
|
+
@expenses.inflation_rate = 0.112
|
28
|
+
expect(@expenses.inflation_rate).to eq 0.112
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "predicted expenses" do
|
34
|
+
|
35
|
+
context "no inflation" do
|
36
|
+
|
37
|
+
before(:each) do
|
38
|
+
@expenses.inflation_rate = 0
|
39
|
+
end
|
40
|
+
|
41
|
+
it "returns no expenses if the death date has passed" do
|
42
|
+
allow(Date).to receive(:today) { Date.new(2070,1,1) }
|
43
|
+
expect(@expenses.future_expenses(Date.new(2065,1,1), 0.1)).to eq []
|
44
|
+
end
|
45
|
+
|
46
|
+
it "returns one date if there is only one month to the death date" do
|
47
|
+
allow(Date).to receive(:today) { Date.new(2067,6,19) }
|
48
|
+
future_expenses = @expenses.future_expenses(Date.new(2067,7,19), 0.1)
|
49
|
+
expect(future_expenses.first[:date]).to eq Date.new(2067,6,30)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "returns one expense if there is only one month to the death date" do
|
53
|
+
allow(Date).to receive(:today) { Date.new(2067,6,19) }
|
54
|
+
future_expenses = @expenses.future_expenses(Date.new(2067,7,19), 0.1)
|
55
|
+
lower_bound = 0.99696173*2500
|
56
|
+
upper_bound = 0.99696174*2500
|
57
|
+
expect(future_expenses.first[:expense]).to be > lower_bound
|
58
|
+
expect(future_expenses.first[:expense]).to be < upper_bound
|
59
|
+
end
|
60
|
+
|
61
|
+
it "returns two dates if there are two months to the death date" do
|
62
|
+
allow(Date).to receive(:today) { Date.new(2067,5,22) }
|
63
|
+
future_expenses = @expenses.future_expenses(Date.new(2067,7,19), 0.1)
|
64
|
+
expect(future_expenses.first[:date]).to eq Date.new(2067,5,31)
|
65
|
+
expect(future_expenses.last[:date]).to eq Date.new(2067,6,30)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "returns two expenses if there are two months to the death date" do
|
69
|
+
allow(Date).to receive(:today) { Date.new(2067,5,22) }
|
70
|
+
future_expenses = @expenses.future_expenses(Date.new(2067,7,19), 0.1)
|
71
|
+
lower_bound_one = 0.997513455*2500
|
72
|
+
upper_bound_one = 0.997513456*2500
|
73
|
+
lower_bound_two = 0.989269542*2500
|
74
|
+
upper_bound_two = 0.989269543*2500
|
75
|
+
expect(future_expenses.first[:expense]).to be > lower_bound_one
|
76
|
+
expect(future_expenses.first[:expense]).to be < upper_bound_one
|
77
|
+
expect(future_expenses.last[:expense]).to be > lower_bound_two
|
78
|
+
expect(future_expenses.last[:expense]).to be < upper_bound_two
|
79
|
+
end
|
80
|
+
|
81
|
+
it "returns many expenses if there are many months up to the death date" do
|
82
|
+
allow(Date).to receive(:today) { Date.new(2060,1,2) }
|
83
|
+
expect(@expenses.future_expenses(Date.new(2067,7,19), 0.1).length).to eq 90
|
84
|
+
end
|
85
|
+
|
86
|
+
end
|
87
|
+
|
88
|
+
context "non-zero inflation" do
|
89
|
+
|
90
|
+
before(:each) do
|
91
|
+
@expenses.inflation_rate = 0.01
|
92
|
+
end
|
93
|
+
|
94
|
+
it "returns no expenses if the death date has passed" do
|
95
|
+
allow(Date).to receive(:today) { Date.new(2088,1,1) }
|
96
|
+
expect(@expenses.future_expenses(Date.new(2067,7,19), 0.1)).to eq []
|
97
|
+
end
|
98
|
+
|
99
|
+
it "returns one expense if there is only one month to the death date" do
|
100
|
+
allow(Date).to receive(:today) { Date.new(2067,6,2) }
|
101
|
+
lower_bound = 0.99305612*2500
|
102
|
+
upper_bound = 0.99305613*2500
|
103
|
+
expect(@expenses.future_expenses(Date.new(2067,7,19), 0.1).first[:expense]).to be > lower_bound
|
104
|
+
expect(@expenses.future_expenses(Date.new(2067,7,19), 0.1).first[:expense]).to be < upper_bound
|
105
|
+
end
|
106
|
+
|
107
|
+
it "returns one date if there is only one month to the death date" do
|
108
|
+
allow(Date).to receive(:today) { Date.new(2067,6,2) }
|
109
|
+
expect(@expenses.future_expenses(Date.new(2067,7,19), 0.1).first[:date]).to eq Date.new(2067,6,30)
|
110
|
+
end
|
111
|
+
|
112
|
+
it "returns two dates if there are two months to the death date" do
|
113
|
+
allow(Date).to receive(:today) { Date.new(2067,5,25) }
|
114
|
+
expect(@expenses.future_expenses(Date.new(2067,7,19), 0.1).first[:date]).to eq Date.new(2067,5,31)
|
115
|
+
expect(@expenses.future_expenses(Date.new(2067,7,19), 0.1).last[:date]).to eq Date.new(2067,6,30)
|
116
|
+
end
|
117
|
+
|
118
|
+
it "returns two expenses if there are two months to the death date" do
|
119
|
+
allow(Date).to receive(:today) { Date.new(2067,5,25) }
|
120
|
+
lower_bound_one = 0.99850795*2500
|
121
|
+
upper_bound_one = 0.99850796*2500
|
122
|
+
lower_bound_two = 0.99108103*2500
|
123
|
+
upper_bound_two = 0.99108104*2500
|
124
|
+
expect(@expenses.future_expenses(Date.new(2067,7,19), 0.1).first[:expense]).to be > lower_bound_one
|
125
|
+
expect(@expenses.future_expenses(Date.new(2067,7,19), 0.1).first[:expense]).to be < upper_bound_one
|
126
|
+
expect(@expenses.future_expenses(Date.new(2067,7,19), 0.1).last[:expense]).to be > lower_bound_two
|
127
|
+
expect(@expenses.future_expenses(Date.new(2067,7,19), 0.1).last[:expense]).to be < upper_bound_two
|
128
|
+
end
|
129
|
+
|
130
|
+
it "returns many expenses if there are many months up to the death date" do
|
131
|
+
allow(Date).to receive(:today) { Date.new(2060,1,2) }
|
132
|
+
expect(@expenses.future_expenses(Date.new(2067,7,19), 0.1).length).to eq 90
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
describe "total expenses" do
|
138
|
+
|
139
|
+
it "calculates the total for one expense" do
|
140
|
+
@expenses.inflation_rate = 0
|
141
|
+
allow(Date).to receive(:today) { Date.new(2067,6,19) }
|
142
|
+
lower_bound = 0.99696173*2500
|
143
|
+
upper_bound = 0.99696174*2500
|
144
|
+
expect(@expenses.total_expenses(Date.new(2067,7,19), 0.1)).to be > lower_bound
|
145
|
+
expect(@expenses.total_expenses(Date.new(2067,7,19), 0.1)).to be < upper_bound
|
146
|
+
end
|
147
|
+
|
148
|
+
it "calculates the total for two expenses" do
|
149
|
+
@expenses.inflation_rate = 0
|
150
|
+
allow(Date).to receive(:today) { Date.new(2067,5,22) }
|
151
|
+
lower_bound = 0.997513455*2500 + 0.989269542*2500
|
152
|
+
upper_bound = 0.997513456*2500 + 0.989269543*2500
|
153
|
+
expect(@expenses.total_expenses(Date.new(2067,7,19), 0.1)).to be > lower_bound
|
154
|
+
expect(@expenses.total_expenses(Date.new(2067,7,19), 0.1)).to be < upper_bound
|
155
|
+
end
|
156
|
+
|
157
|
+
end
|
158
|
+
|
159
|
+
end
|
160
|
+
|
161
|
+
|
162
|
+
end
|
163
|
+
|
164
|
+
end
|
data/spec/income_spec.rb
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
module Doughnut
|
4
|
+
|
5
|
+
describe Income do
|
6
|
+
|
7
|
+
before(:each) do
|
8
|
+
@income = Income.new
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "parameters" do
|
12
|
+
|
13
|
+
it "returns average monthly income of $3700" do
|
14
|
+
expect(@income.monthly_income).to eq 3700
|
15
|
+
end
|
16
|
+
|
17
|
+
it "updates the average monthly income" do
|
18
|
+
@income.monthly_income = 200
|
19
|
+
expect(@income.monthly_income).to eq 200
|
20
|
+
end
|
21
|
+
|
22
|
+
it "returns an income growth rate of 1%" do
|
23
|
+
expect(@income.income_growth_rate).to eq 0.01
|
24
|
+
end
|
25
|
+
|
26
|
+
it "updates the income growth rate" do
|
27
|
+
@income.income_growth_rate = 0.37
|
28
|
+
expect(@income.income_growth_rate).to eq 0.37
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
describe "predicted income" do
|
34
|
+
|
35
|
+
context "no income growth" do
|
36
|
+
|
37
|
+
before(:each) do
|
38
|
+
@income.income_growth_rate = 0
|
39
|
+
end
|
40
|
+
|
41
|
+
it "returns no income if the death date has passed" do
|
42
|
+
allow(Date).to receive(:today) { Date.new(2075,1,1) }
|
43
|
+
expect(@income.future_income(Date.new(2065,1,1), 0.1)).to eq []
|
44
|
+
end
|
45
|
+
|
46
|
+
it "returns one date if there is only one month to the death date" do
|
47
|
+
allow(Date).to receive(:today) { Date.new(2067,6,19) }
|
48
|
+
future_income = @income.future_income(Date.new(2067,7,19), 0.1)
|
49
|
+
expect(future_income.first[:date]).to eq Date.new(2067,6,30)
|
50
|
+
end
|
51
|
+
|
52
|
+
it "returns one income if there is only one month to the death date" do
|
53
|
+
allow(Date).to receive(:today) { Date.new(2067,6,19) }
|
54
|
+
future_income = @income.future_income(Date.new(2067,7,19), 0.1)
|
55
|
+
lower_bound = 0.99696173*3700
|
56
|
+
upper_bound = 0.99696174*3700
|
57
|
+
expect(future_income.first[:income]).to be > lower_bound
|
58
|
+
expect(future_income.first[:income]).to be < upper_bound
|
59
|
+
end
|
60
|
+
|
61
|
+
it "returns two dates if there are two months to the death date" do
|
62
|
+
allow(Date).to receive(:today) { Date.new(2067,5,22) }
|
63
|
+
future_income = @income.future_income(Date.new(2067,7,19), 0.1)
|
64
|
+
expect(future_income.first[:date]).to eq Date.new(2067,5,31)
|
65
|
+
expect(future_income.last[:date]).to eq Date.new(2067,6,30)
|
66
|
+
end
|
67
|
+
|
68
|
+
it "returns two incomes if there are two months to the death date" do
|
69
|
+
allow(Date).to receive(:today) { Date.new(2067,5,22) }
|
70
|
+
future_income = @income.future_income(Date.new(2067,7,19), 0.1)
|
71
|
+
lower_bound_one = 0.997513455*3700
|
72
|
+
upper_bound_one = 0.997513456*3700
|
73
|
+
lower_bound_two = 0.989269542*3700
|
74
|
+
upper_bound_two = 0.989269543*3700
|
75
|
+
expect(future_income.first[:income]).to be > lower_bound_one
|
76
|
+
expect(future_income.first[:income]).to be < upper_bound_one
|
77
|
+
expect(future_income.last[:income]).to be > lower_bound_two
|
78
|
+
expect(future_income.last[:income]).to be < upper_bound_two
|
79
|
+
end
|
80
|
+
|
81
|
+
it "returns many incomes if there are many months up to the death date" do
|
82
|
+
allow(Date).to receive(:today) { Date.new(2060,1,2) }
|
83
|
+
future_income = @income.future_income(Date.new(2067,7,19), 0.1)
|
84
|
+
expect(future_income.length).to eq 90
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
|
89
|
+
context "non-zero income growth" do
|
90
|
+
|
91
|
+
before(:each) do
|
92
|
+
@income.income_growth_rate = 0.03
|
93
|
+
end
|
94
|
+
|
95
|
+
it "returns no incomes if the death date has passed" do
|
96
|
+
allow(Date).to receive(:today) { Date.new(2088,1,1) }
|
97
|
+
future_income = @income.future_income(Date.new(2067,7,19), 0.1)
|
98
|
+
expect(future_income).to eq []
|
99
|
+
end
|
100
|
+
|
101
|
+
it "returns one date if there is only one month to the death date" do
|
102
|
+
allow(Date).to receive(:today) { Date.new(2067,6,3) }
|
103
|
+
future_income = @income.future_income(Date.new(2067,7,19), 0.1)
|
104
|
+
expect(future_income.first[:date]).to eq Date.new(2067,6,30)
|
105
|
+
end
|
106
|
+
|
107
|
+
it "returns one income if there is only one month to the death date" do
|
108
|
+
allow(Date).to receive(:today) { Date.new(2067,6,3) }
|
109
|
+
future_income = @income.future_income(Date.new(2067,7,19), 0.1)
|
110
|
+
lower_bound = 0.99479187*3700
|
111
|
+
upper_bound = 0.99479188*3700
|
112
|
+
expect(future_income.first[:income]).to be > lower_bound
|
113
|
+
expect(future_income.first[:income]).to be < upper_bound
|
114
|
+
end
|
115
|
+
|
116
|
+
it "returns two dates if there are two months to the death date" do
|
117
|
+
allow(Date).to receive(:today) { Date.new(2067,5,25) }
|
118
|
+
future_income = @income.future_income(Date.new(2067,7,19), 0.1)
|
119
|
+
expect(future_income.first[:date]).to eq Date.new(2067,5,31)
|
120
|
+
expect(future_income.last[:date]).to eq Date.new(2067,6,30)
|
121
|
+
end
|
122
|
+
|
123
|
+
it "returns two incomes if there are two months to the death date" do
|
124
|
+
allow(Date).to receive(:today) { Date.new(2067,5,25) }
|
125
|
+
future_income = @income.future_income(Date.new(2067,7,19), 0.1)
|
126
|
+
lower_bound_one = 0.99884028*3700
|
127
|
+
upper_bound_one = 0.99884029*3700
|
128
|
+
lower_bound_two = 0.99306187*3700
|
129
|
+
upper_bound_two = 0.99306188*3700
|
130
|
+
expect(future_income.first[:income]).to be > lower_bound_one
|
131
|
+
expect(future_income.first[:income]).to be < upper_bound_one
|
132
|
+
expect(future_income.last[:income]).to be > lower_bound_two
|
133
|
+
expect(future_income.last[:income]).to be < upper_bound_two
|
134
|
+
end
|
135
|
+
|
136
|
+
it "returns many incomes if there are many months up to the death date" do
|
137
|
+
allow(Date).to receive(:today) { Date.new(2060,1,2) }
|
138
|
+
future_income = @income.future_income(Date.new(2067,7,19), 0.1)
|
139
|
+
expect(future_income.length).to eq 90
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
@@ -0,0 +1,75 @@
|
|
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
|
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.3
|
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-05-
|
11
|
+
date: 2015-05-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -80,9 +80,13 @@ files:
|
|
80
80
|
- Rakefile
|
81
81
|
- doughnut.gemspec
|
82
82
|
- lib/doughnut.rb
|
83
|
-
- lib/doughnut/
|
83
|
+
- lib/doughnut/expenses.rb
|
84
|
+
- lib/doughnut/income.rb
|
85
|
+
- lib/doughnut/retirement_calculator.rb
|
84
86
|
- lib/doughnut/version.rb
|
85
|
-
- spec/
|
87
|
+
- spec/expenses_spec.rb
|
88
|
+
- spec/income_spec.rb
|
89
|
+
- spec/retirement_calculator_spec.rb
|
86
90
|
- spec/spec_helper.rb
|
87
91
|
homepage: ''
|
88
92
|
licenses:
|
@@ -109,5 +113,7 @@ signing_key:
|
|
109
113
|
specification_version: 4
|
110
114
|
summary: Generate statistics for a personal portfolio.
|
111
115
|
test_files:
|
112
|
-
- spec/
|
116
|
+
- spec/expenses_spec.rb
|
117
|
+
- spec/income_spec.rb
|
118
|
+
- spec/retirement_calculator_spec.rb
|
113
119
|
- spec/spec_helper.rb
|
data/lib/doughnut/capm.rb
DELETED
data/spec/capm_spec.rb
DELETED
@@ -1,30 +0,0 @@
|
|
1
|
-
require "spec_helper"
|
2
|
-
|
3
|
-
module Doughnut
|
4
|
-
|
5
|
-
describe CAPM do
|
6
|
-
|
7
|
-
describe "risk free rate" do
|
8
|
-
it "retrieves the risk free interest rate from Yahoo Finance"
|
9
|
-
it "uses a default rate if Yahoo cannot be accessed"
|
10
|
-
end
|
11
|
-
|
12
|
-
describe "broad market rate" do
|
13
|
-
it "estimates a broad market rate based on historical data"
|
14
|
-
end
|
15
|
-
|
16
|
-
describe "beta" do
|
17
|
-
it "calculates beta for two correlated assets"
|
18
|
-
it "calculates beta for two uncorrelated assets"
|
19
|
-
it "calculates beta for two random assets"
|
20
|
-
end
|
21
|
-
|
22
|
-
describe "beta" do
|
23
|
-
it "calculates beta for two correlated assets"
|
24
|
-
it "calculates beta for two uncorrelated assets"
|
25
|
-
it "calculates beta for two random assets"
|
26
|
-
end
|
27
|
-
|
28
|
-
end
|
29
|
-
|
30
|
-
end
|