rebalance 0.0.1

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.
@@ -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