rebalance 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,138 @@
1
+ require File.expand_path('../../spec_helper', __FILE__)
2
+
3
+ describe Rebalance::Target do
4
+ before do
5
+ @target = Rebalance::Target.new do
6
+ asset_class 30, 'Some Asset Class'
7
+ asset_class 20, 'Another Asset Class'
8
+ asset_class 50, 'Bonds'
9
+ end
10
+ end
11
+
12
+ it 'allows asset_class to be called' do
13
+ @target.asset_classes.size.must_equal 3
14
+ end
15
+
16
+ it 'remembers the asset class description' do
17
+ @target.asset_classes['Some Asset Class'].must_equal 30
18
+ end
19
+
20
+ describe 'with account information' do
21
+ before do
22
+ @account = Rebalance::Account.new 'Test Account' do
23
+ fund 'ABCDE', 'Some Asset Class', 500, 10.00
24
+ fund 'FGHIJ', 'Some Asset Class', 300, 25.00
25
+ fund 'KLMNO', 'Another Asset Class', 75, 300
26
+ fund 'PQRST', 'Bonds', 35.5, 32.00
27
+ fund 'UVWXY', 'Bonds', 75, 5.50
28
+ end
29
+ end
30
+
31
+ it 'calculates the target value for each asset class' do
32
+ target_values = @target.calculate_target_asset_class_values(@account)
33
+
34
+ expected_target_values = {
35
+ 'Some Asset Class' => 10964.55,
36
+ 'Another Asset Class' => 7309.70,
37
+ 'Bonds' => 18274.25
38
+ }
39
+
40
+ target_values.must_equal expected_target_values
41
+ end
42
+
43
+ it 'calculates the current value for each asset class' do
44
+ current_values = @target.calculate_current_asset_class_values(@account)
45
+
46
+ expected_current_values = {
47
+ 'Some Asset Class' => 12500.00,
48
+ 'Another Asset Class' => 22500.00,
49
+ 'Bonds' => 1548.50
50
+ }
51
+
52
+ current_values.must_equal expected_current_values
53
+ end
54
+ end
55
+
56
+ describe 'with multiple accounts' do
57
+ before do
58
+ @wifes_roth = Rebalance::Account.new "Wife's Roth" do
59
+ fund 'ABCDE', 'Some Asset Class', 500, 10.00 # $5,000
60
+ fund 'FGHIJ', 'Some Asset Class', 300, 25.00 # $7,500
61
+ fund 'KLMNO', 'Another Asset Class', 75, 300 # $22,500
62
+ fund 'PQRST', 'Bonds', 35.5, 32.00 # $1,136
63
+ fund 'UVWXY', 'Bonds', 75, 5.50 # $412.50
64
+ end
65
+
66
+ @my_roth = Rebalance::Account.new 'My Roth' do
67
+ fund 'AAAAA', 'Cash', 150, 1.00 # $150
68
+ fund 'BBBBB', 'Some Asset Class', 10, 23.00 # $230
69
+ fund 'FGHIJ', 'Some Asset Class', 100, 25.00 # $2,500
70
+ end
71
+
72
+ @my_sep_ira = Rebalance::Account.new 'My SEP IRA' do
73
+ fund 'ZZZZZ', 'Bonds', 250, 20.25 # $5,062.50
74
+ end
75
+ end
76
+
77
+ it 'calculates the total value of all accounts' do
78
+ @target.total_value_of_all_accounts(@wifes_roth, @my_roth, @my_sep_ira).must_equal 44491.00
79
+ end
80
+
81
+ it 'calculates the target value for each asset class' do
82
+ target_values = @target.calculate_target_asset_class_values(@wifes_roth, @my_roth, @my_sep_ira)
83
+
84
+ expected_target_values = {
85
+ 'Some Asset Class' => 13347.30,
86
+ 'Another Asset Class' => 8898.20,
87
+ 'Bonds' => 22245.50
88
+ }
89
+
90
+ target_values.must_equal expected_target_values
91
+ end
92
+
93
+ it 'calculates the current value for each asset class' do
94
+ current_values = @target.calculate_current_asset_class_values(@wifes_roth, @my_roth, @my_sep_ira)
95
+
96
+ expected_current_values = {
97
+ 'Some Asset Class' => 15230.00,
98
+ 'Another Asset Class' => 22500.00,
99
+ 'Bonds' => 6611.00,
100
+ 'Cash' => 150.00
101
+ }
102
+
103
+ current_values.must_equal expected_current_values
104
+ end
105
+
106
+ it 'calculates the current percentage for each asset class' do
107
+ current_values = @target.calculate_current_asset_class_percentages(@wifes_roth, @my_roth, @my_sep_ira)
108
+
109
+ expected_current_values = {
110
+ 'Some Asset Class' => 34.2316,
111
+ 'Another Asset Class' => 50.5720,
112
+ 'Bonds' => 14.8592,
113
+ 'Cash' => 0.3371
114
+ }
115
+
116
+ current_values.must_equal expected_current_values
117
+ end
118
+
119
+ it 'calculates the asset class percentages across all accounts' do
120
+ expected_percentages = {
121
+ "Wife's Roth" => {
122
+ "Some Asset Class" => 28.0956,
123
+ "Another Asset Class" => 50.5720,
124
+ "Bonds" => 3.4805
125
+ },
126
+ "My Roth" => {
127
+ "Cash" => 0.3371,
128
+ "Some Asset Class" => 6.1361
129
+ },
130
+ "My SEP IRA" => {
131
+ "Bonds" => 11.3787
132
+ }
133
+ }
134
+
135
+ @target.asset_class_percentages_across_all_accounts(@wifes_roth, @my_roth, @my_sep_ira).must_equal expected_percentages
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,46 @@
1
+ require 'minitest/autorun'
2
+
3
+ require 'rebalance/target'
4
+ require 'rebalance/account'
5
+ require 'rebalance/fund'
6
+ require 'rebalance/rebalancer'
7
+
8
+ require 'vcr'
9
+
10
+ VCR.config do |c|
11
+ c.cassette_library_dir = 'spec/cassettes'
12
+ c.stub_with :webmock
13
+ c.default_cassette_options = { :record => :new_episodes }
14
+ end
15
+
16
+ module MiniTest
17
+ module Assertions
18
+ def assert_rebalanced rebalance
19
+ target = rebalance.target
20
+ accounts = rebalance.accounts
21
+
22
+ target_values = target.calculate_target_asset_class_values(*accounts)
23
+
24
+ # Allow the rebalance to be off by half a percent of the total value of all accounts
25
+ acceptable_delta = target.total_value_of_all_accounts(*accounts) * 0.005
26
+
27
+ rebalance.calculate_rebalanced_asset_class_values.each do |asset_class, rebalanced_value|
28
+ if !target_values[asset_class].nil?
29
+ rebalanced_value.must_be_within_delta target_values[asset_class], acceptable_delta, "Failed for asset class: #{asset_class}"
30
+ end
31
+ end
32
+ end
33
+
34
+ def assert_accounts_have_same_values_after_rebalance rebalance
35
+ rebalance.accounts.each do |account|
36
+ if rebalance.accounts.size > 1
37
+ values = rebalance.rebalanced_values[account.name].values
38
+ else
39
+ values = rebalance.rebalanced_values.values
40
+ end
41
+ rebalanced_total = values.inject{|sum,x| sum + x }
42
+ account.total_value.must_be_within_delta rebalanced_total, 0.10
43
+ end
44
+ end
45
+ end
46
+ end
metadata ADDED
@@ -0,0 +1,116 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rebalance
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Bryce Thornton
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-12-31 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: ruport
16
+ requirement: &70357428827480 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70357428827480
25
+ - !ruby/object:Gem::Dependency
26
+ name: json
27
+ requirement: &70357428826720 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70357428826720
36
+ - !ruby/object:Gem::Dependency
37
+ name: vcr
38
+ requirement: &70357428826040 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70357428826040
47
+ - !ruby/object:Gem::Dependency
48
+ name: webmock
49
+ requirement: &70357428825540 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70357428825540
58
+ description: Rebalances mutual fund accounts to match your target asset allocation
59
+ email:
60
+ - brycethornton@gmail.com
61
+ executables: []
62
+ extensions: []
63
+ extra_rdoc_files: []
64
+ files:
65
+ - .gitignore
66
+ - Gemfile
67
+ - README.md
68
+ - Rakefile
69
+ - lib/rebalance.rb
70
+ - lib/rebalance/account.rb
71
+ - lib/rebalance/fund.rb
72
+ - lib/rebalance/rebalancer.rb
73
+ - lib/rebalance/target.rb
74
+ - lib/rebalance/version.rb
75
+ - rebalance.gemspec
76
+ - spec/cassettes/price_lookup_for_98765.yml
77
+ - spec/cassettes/price_lookup_for_VISVX.yml
78
+ - spec/cassettes/price_lookup_for_VMMXX.yml
79
+ - spec/rebalance/account_spec.rb
80
+ - spec/rebalance/fund_spec.rb
81
+ - spec/rebalance/rebalancer_spec.rb
82
+ - spec/rebalance/target_spec.rb
83
+ - spec/spec_helper.rb
84
+ homepage: ''
85
+ licenses: []
86
+ post_install_message:
87
+ rdoc_options: []
88
+ require_paths:
89
+ - lib
90
+ required_ruby_version: !ruby/object:Gem::Requirement
91
+ none: false
92
+ requirements:
93
+ - - ! '>='
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ version: '0'
102
+ requirements: []
103
+ rubyforge_project: rebalance
104
+ rubygems_version: 1.8.10
105
+ signing_key:
106
+ specification_version: 3
107
+ summary: Target asset allocation rebalancer
108
+ test_files:
109
+ - spec/cassettes/price_lookup_for_98765.yml
110
+ - spec/cassettes/price_lookup_for_VISVX.yml
111
+ - spec/cassettes/price_lookup_for_VMMXX.yml
112
+ - spec/rebalance/account_spec.rb
113
+ - spec/rebalance/fund_spec.rb
114
+ - spec/rebalance/rebalancer_spec.rb
115
+ - spec/rebalance/target_spec.rb
116
+ - spec/spec_helper.rb