finmodeling 0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. data/.gitignore +0 -0
  2. data/Gemfile +2 -0
  3. data/README.md +289 -269
  4. data/Rakefile +12 -0
  5. data/TODO.txt +113 -20
  6. data/examples/{dump_report.rb → dump_latest_10k.rb} +1 -1
  7. data/examples/list_disclosures.rb +50 -0
  8. data/examples/lists/nasdaq-mid-to-mega-tech-symbols.txt +0 -0
  9. data/examples/show_report.rb +112 -32
  10. data/examples/show_reports.rb +162 -33
  11. data/finmodeling.gemspec +4 -1
  12. data/lib/finmodeling/annual_report_filing.rb +97 -18
  13. data/lib/finmodeling/array_with_stats.rb +0 -0
  14. data/lib/finmodeling/assets_calculation.rb +12 -3
  15. data/lib/finmodeling/assets_item.rb +0 -0
  16. data/lib/finmodeling/assets_item_vectors.rb +0 -0
  17. data/lib/finmodeling/balance_sheet_analyses.rb +19 -4
  18. data/lib/finmodeling/balance_sheet_calculation.rb +52 -37
  19. data/lib/finmodeling/calculation_summary.rb +119 -14
  20. data/lib/finmodeling/can_cache_classifications.rb +0 -0
  21. data/lib/finmodeling/can_cache_summaries.rb +0 -0
  22. data/lib/finmodeling/can_choose_successive_periods.rb +15 -0
  23. data/lib/finmodeling/can_classify_rows.rb +0 -0
  24. data/lib/finmodeling/capm.rb +80 -0
  25. data/lib/finmodeling/cash_change_calculation.rb +3 -3
  26. data/lib/finmodeling/cash_change_item.rb +0 -0
  27. data/lib/finmodeling/cash_change_item_vectors.rb +0 -0
  28. data/lib/finmodeling/cash_change_summary_from_differences.rb +36 -0
  29. data/lib/finmodeling/cash_flow_statement_analyses.rb +36 -0
  30. data/lib/finmodeling/cash_flow_statement_calculation.rb +28 -52
  31. data/lib/finmodeling/classifiers.rb +2 -0
  32. data/lib/finmodeling/company.rb +0 -0
  33. data/lib/finmodeling/company_filing.rb +30 -7
  34. data/lib/finmodeling/company_filing_calculation.rb +16 -6
  35. data/lib/finmodeling/company_filings.rb +112 -46
  36. data/lib/finmodeling/comprehensive_income_calculation.rb +60 -0
  37. data/lib/finmodeling/comprehensive_income_statement_calculation.rb +74 -0
  38. data/lib/finmodeling/comprehensive_income_statement_item.rb +20 -0
  39. data/lib/finmodeling/comprehensive_income_statement_item_vectors.rb +235 -0
  40. data/lib/finmodeling/config.rb +0 -0
  41. data/lib/finmodeling/debt_cost_of_capital.rb +14 -0
  42. data/lib/finmodeling/equity_change_calculation.rb +43 -0
  43. data/lib/finmodeling/equity_change_item.rb +25 -0
  44. data/lib/finmodeling/equity_change_item_vectors.rb +156 -0
  45. data/lib/finmodeling/factory.rb +0 -0
  46. data/lib/finmodeling/fama_french_cost_of_equity.rb +119 -0
  47. data/lib/finmodeling/float_helpers.rb +14 -8
  48. data/lib/finmodeling/forecasted_reformulated_balance_sheet.rb +55 -0
  49. data/lib/finmodeling/forecasted_reformulated_income_statement.rb +110 -0
  50. data/lib/finmodeling/forecasts.rb +4 -4
  51. data/lib/finmodeling/has_string_classifer.rb +0 -0
  52. data/lib/finmodeling/income_statement_analyses.rb +23 -17
  53. data/lib/finmodeling/income_statement_calculation.rb +46 -43
  54. data/lib/finmodeling/income_statement_item.rb +1 -1
  55. data/lib/finmodeling/income_statement_item_vectors.rb +24 -13
  56. data/lib/finmodeling/invalid_filing_error.rb +4 -0
  57. data/lib/finmodeling/liabs_and_equity_calculation.rb +18 -8
  58. data/lib/finmodeling/liabs_and_equity_item.rb +1 -1
  59. data/lib/finmodeling/liabs_and_equity_item_vectors.rb +24 -24
  60. data/lib/finmodeling/linear_trend_forecasting_policy.rb +23 -0
  61. data/lib/finmodeling/net_income_calculation.rb +23 -10
  62. data/lib/finmodeling/net_income_summary_from_differences.rb +51 -0
  63. data/lib/finmodeling/paths.rb +0 -0
  64. data/lib/finmodeling/period_array.rb +8 -4
  65. data/lib/finmodeling/quarterly_report_filing.rb +9 -4
  66. data/lib/finmodeling/rate.rb +8 -0
  67. data/lib/finmodeling/ratio.rb +0 -0
  68. data/lib/finmodeling/reformulated_balance_sheet.rb +47 -88
  69. data/lib/finmodeling/reformulated_cash_flow_statement.rb +18 -41
  70. data/lib/finmodeling/reformulated_income_statement.rb +44 -206
  71. data/lib/finmodeling/reformulated_shareholder_equity_statement.rb +50 -0
  72. data/lib/finmodeling/reoi_valuation.rb +104 -0
  73. data/lib/finmodeling/shareholder_equity_statement_calculation.rb +34 -0
  74. data/lib/finmodeling/string_helpers.rb +18 -1
  75. data/lib/finmodeling/time_series_estimator.rb +25 -0
  76. data/lib/finmodeling/trailing_avg_forecasting_policy.rb +23 -0
  77. data/lib/finmodeling/version.rb +1 -1
  78. data/lib/finmodeling/weighted_avg_cost_of_capital.rb +35 -0
  79. data/lib/finmodeling/yahoo_finance_helpers.rb +20 -0
  80. data/lib/finmodeling.rb +33 -2
  81. data/spec/annual_report_filing_spec.rb +81 -45
  82. data/spec/assets_calculation_spec.rb +7 -4
  83. data/spec/assets_item_spec.rb +9 -14
  84. data/spec/balance_sheet_analyses_spec.rb +13 -13
  85. data/spec/balance_sheet_calculation_spec.rb +45 -51
  86. data/spec/calculation_summary_spec.rb +113 -21
  87. data/spec/can_classify_rows_spec.rb +0 -0
  88. data/spec/cash_change_calculation_spec.rb +1 -10
  89. data/spec/cash_change_item_spec.rb +10 -18
  90. data/spec/cash_flow_statement_calculation_spec.rb +10 -24
  91. data/spec/company_beta_spec.rb +53 -0
  92. data/spec/company_filing_calculation_spec.rb +39 -49
  93. data/spec/company_filing_spec.rb +0 -0
  94. data/spec/company_filings_spec.rb +75 -25
  95. data/spec/company_spec.rb +37 -47
  96. data/spec/comprehensive_income_statement_calculation_spec.rb +54 -0
  97. data/spec/comprehensive_income_statement_item_spec.rb +56 -0
  98. data/spec/debt_cost_of_capital_spec.rb +19 -0
  99. data/spec/equity_change_calculation_spec.rb +33 -0
  100. data/spec/equity_change_item_spec.rb +58 -0
  101. data/spec/factory_spec.rb +2 -2
  102. data/spec/forecasts_spec.rb +2 -2
  103. data/spec/income_statement_analyses_spec.rb +23 -21
  104. data/spec/income_statement_calculation_spec.rb +17 -49
  105. data/spec/income_statement_item_spec.rb +17 -29
  106. data/spec/liabs_and_equity_calculation_spec.rb +6 -3
  107. data/spec/liabs_and_equity_item_spec.rb +14 -22
  108. data/spec/linear_trend_forecasting_policy_spec.rb +37 -0
  109. data/spec/matchers/custom_matchers.rb +79 -0
  110. data/spec/mocks/calculation.rb +0 -0
  111. data/spec/mocks/income_statement_analyses.rb +0 -0
  112. data/spec/mocks/sec_query.rb +0 -0
  113. data/spec/net_income_calculation_spec.rb +16 -10
  114. data/spec/period_array.rb +0 -0
  115. data/spec/quarterly_report_filing_spec.rb +21 -38
  116. data/spec/rate_spec.rb +0 -0
  117. data/spec/ratio_spec.rb +0 -0
  118. data/spec/reformulated_balance_sheet_spec.rb +56 -33
  119. data/spec/reformulated_cash_flow_statement_spec.rb +18 -10
  120. data/spec/reformulated_income_statement_spec.rb +16 -15
  121. data/spec/reformulated_shareholder_equity_statement_spec.rb +43 -0
  122. data/spec/reoi_valuation_spec.rb +146 -0
  123. data/spec/shareholder_equity_statement_calculation_spec.rb +59 -0
  124. data/spec/spec_helper.rb +4 -1
  125. data/spec/string_helpers_spec.rb +15 -13
  126. data/spec/time_series_estimator_spec.rb +61 -0
  127. data/spec/trailing_avg_forecasting_policy_spec.rb +37 -0
  128. data/spec/weighted_avg_cost_of_capital_spec.rb +32 -0
  129. data/tools/create_equity_change_training_vectors.rb +49 -0
  130. data/tools/time_specs.sh +7 -0
  131. metadata +182 -36
  132. data/lib/finmodeling/constant_forecasting_policy.rb +0 -23
  133. data/lib/finmodeling/generic_forecasting_policy.rb +0 -19
  134. data/spec/constant_forecasting_policy_spec.rb +0 -37
  135. data/spec/generic_forecasting_policy_spec.rb +0 -33
@@ -9,14 +9,14 @@ describe FinModeling::CalculationSummary do
9
9
  @summary.rows = [ FinModeling::CalculationRow.new(:key => "Row", :vals => [nil, 0, nil, -101, 2.4]) ]
10
10
  end
11
11
 
12
- describe "valid_vals" do
12
+ describe ".valid_vals" do # FIXME: ... this should be on the row, not on the summary ?
13
13
  subject { @summary.rows.first.valid_vals }
14
14
  it "should return all non-nil values" do
15
15
  subject.should == @summary.rows[0].vals.select{ |x| !x.nil? }
16
16
  end
17
17
  end
18
18
 
19
- describe "filter_by_type" do
19
+ describe ".filter_by_type" do
20
20
  before(:all) do
21
21
  @summary2 = FinModeling::CalculationSummary.new
22
22
  @summary2.rows = [ ]
@@ -25,11 +25,84 @@ describe FinModeling::CalculationSummary do
25
25
  @summary2.rows << FinModeling::CalculationRow.new(:key => "Row", :type => :oa, :vals => [ 93])
26
26
  @summary2.rows << FinModeling::CalculationRow.new(:key => "Row", :type => :fa, :vals => [ 1])
27
27
  end
28
- it "should return a new FinModeling::CalculationSummary" do
29
- @summary2.filter_by_type(:oa).should be_an_instance_of FinModeling::CalculationSummary
30
- end
28
+ subject { @summary2.filter_by_type(:oa) }
29
+ it { should be_a FinModeling::CalculationSummary }
31
30
  it "should return a summary of only the requested type" do
32
- @summary2.filter_by_type(:oa).rows.map{ |row| row.type }.uniq.should == [:oa]
31
+ subject.rows.map{ |row| row.type }.uniq.should == [:oa]
32
+ end
33
+ end
34
+
35
+ describe ".insert_column_before" do
36
+ before(:each) do
37
+ @summary2 = FinModeling::CalculationSummary.new
38
+ @summary2.rows = [ ]
39
+ @summary2.rows << FinModeling::CalculationRow.new(:key => "Row", :type => :oa, :vals => [ 4])
40
+ @summary2.rows << FinModeling::CalculationRow.new(:key => "Row", :type => :oa, :vals => [ 93])
41
+ end
42
+ context "when given a column index of 0 through (length-1)" do
43
+ before(:each) do
44
+ @summary2.insert_column_before(0)
45
+ end
46
+ subject { @summary2 }
47
+ it "should insert a nil value in every row's vals, at the right column" do
48
+ subject.rows.map{ |row| row.vals }.should == [ [ nil, 4], [ nil, 93 ] ]
49
+ end
50
+ end
51
+ context "when given a column index greater than (length-1)" do
52
+ before(:each) do
53
+ @summary2.insert_column_before(2)
54
+ end
55
+ subject { @summary2 }
56
+ it "should append columns as needed" do
57
+ subject.rows.map{ |row| row.vals }.should == [ [ 4, nil, nil ], [ 93, nil, nil ] ]
58
+ end
59
+ end
60
+ end
61
+
62
+ describe ".auto_scale!" do
63
+ before(:each) do
64
+ @summary2 = FinModeling::CalculationSummary.new
65
+ end
66
+ context "when the minimum abs value is < 1M and >= 1k" do
67
+ before(:each) do
68
+ @summary2.rows = [ FinModeling::CalculationRow.new(:key => "Row", :type => :oa, :vals => [10000, -1000000, 20000]) ]
69
+ @summary2.auto_scale!
70
+ end
71
+ subject { @summary2 }
72
+ it "should scale all values down by 1k" do
73
+ subject.rows.first.vals.should == [10.0, -1000.0, 20.0]
74
+ end
75
+ it "should append ' ($KK)' to all keys" do
76
+ subject.rows.map{ |row| row.key }.all?{ |key| key =~ / \(\$KK\)$/ }.should be_true
77
+ end
78
+ end
79
+ context "when the minimum abs value is >= 1M" do
80
+ before(:each) do
81
+ @summary2.rows = [ FinModeling::CalculationRow.new(:key => "Row", :type => :oa, :vals => [10000000, 1000000, -25000000]) ]
82
+ @summary2.auto_scale!
83
+ end
84
+ subject { @summary2 }
85
+ it "should scale all values down by 1k" do
86
+ subject.rows.first.vals.should == [10.0, 1.0, -25.0]
87
+ end
88
+ it "should append ' ($MM)' to all keys" do
89
+ subject.rows.map{ |row| row.key }.all?{ |key| key =~ / \(\$MM\)$/ }.should be_true
90
+ end
91
+ end
92
+ end
93
+
94
+ describe ".num_value_columns" do
95
+ before(:all) do
96
+ @summary2 = FinModeling::CalculationSummary.new
97
+ @summary2.rows = [ ]
98
+ @summary2.rows << FinModeling::CalculationRow.new(:key => "Row", :type => :oa, :vals => [])
99
+ @summary2.rows << FinModeling::CalculationRow.new(:key => "Row", :type => :fa, :vals => [9])
100
+ @summary2.rows << FinModeling::CalculationRow.new(:key => "Row", :type => :oa, :vals => [3,2,1])
101
+ @summary2.rows << FinModeling::CalculationRow.new(:key => "Row", :type => :fa, :vals => [1])
102
+ end
103
+ subject { @summary2.num_value_columns }
104
+ it "should return the width of the table (the maximum length of any row's vals)" do
105
+ subject.should == @summary2.rows.map{ |row| row.vals.length }.max
33
106
  end
34
107
  end
35
108
 
@@ -38,25 +111,44 @@ describe FinModeling::CalculationSummary do
38
111
  @mccs1 = FinModeling::CalculationSummary.new
39
112
  @mccs1.title = "MCCS 1"
40
113
  @mccs1.rows = [ FinModeling::CalculationRow.new(:key => "Row 1", :vals => [nil, 0, nil, -101, 2.4]) ]
41
-
42
- @mccs2 = FinModeling::CalculationSummary.new
43
- @mccs2.title = "MCCS 2"
44
- @mccs2.rows = [ FinModeling::CalculationRow.new(:key => "Row 1", :vals => [nil, 0, nil, -101, 2.4]) ]
45
114
  end
46
115
 
47
- subject { @mccs1 + @mccs2 }
48
-
49
- it { should be_an_instance_of FinModeling::CalculationSummary }
50
-
51
- its(:title) { should == @mccs1.title }
52
-
53
- it "should set the row labels to the first summary's row labels" do
54
- subject.rows.map{ |row| row.key }.should == @mccs1.rows.map{ |row| row.key }
116
+ context "when the two calculations have different keys (or the same keys in different orders)" do
117
+ before(:all) do
118
+ @mccs2 = FinModeling::CalculationSummary.new
119
+ @mccs2.title = "MCCS 2"
120
+ @mccs2.rows = [ FinModeling::CalculationRow.new(:key => "Row 1", :vals => [nil, 0, nil, -101, 2.4]) ]
121
+ end
122
+ subject { @mccs1 + @mccs2 }
123
+
124
+ it { should be_a FinModeling::CalculationSummary }
125
+ its(:title) { should == @mccs1.title }
126
+ it "should set the row labels to the first summary's row labels" do
127
+ subject.rows.map{ |row| row.key }.should == @mccs1.rows.map{ |row| row.key }
128
+ end
129
+ it "should merge the values of summary into an array of values in the result" do
130
+ 0.upto(subject.rows.length-1).each do |row_idx|
131
+ subject.rows[row_idx].vals.should == ( @mccs1.rows[row_idx].vals + @mccs2.rows[row_idx].vals )
132
+ end
133
+ end
55
134
  end
56
135
 
57
- it "should merge the values of summary into an array of values in the result" do
58
- 0.upto(subject.rows.length-1).each do |row_idx|
59
- subject.rows[row_idx].vals.should == ( @mccs1.rows[row_idx].vals + @mccs2.rows[row_idx].vals )
136
+ context "when the two calculations have different keys (or the same keys in different orders)" do
137
+ before(:all) do
138
+ @mccs3 = FinModeling::CalculationSummary.new
139
+ @mccs3.title = "MCCS 3"
140
+ @mccs3.rows = [ FinModeling::CalculationRow.new(:key => "Row 2", :vals => [1, 0, nil, -101, 2.4]) ]
141
+ @mccs3.rows += [ FinModeling::CalculationRow.new(:key => "Row 1", :vals => [32, 0, nil, nil, 2 ]) ]
142
+ end
143
+ subject { @mccs1 + @mccs3 }
144
+ it "should have one row per unique key" do
145
+ subject.rows.map{ |row| row.key }.sort.should == (@mccs1.rows + @mccs3.rows).map{ |row| row.key }.sort.uniq
146
+ end
147
+ it "should merge the values of summary into an array of values in the result" do
148
+ expected_vals = []
149
+ expected_vals << ([""]*@mccs1.num_value_columns + @mccs3.rows[0].vals)
150
+ expected_vals << (@mccs1.rows[0].vals + @mccs3.rows[1].vals)
151
+ subject.rows.map{ |row| row.vals }.should == expected_vals
60
152
  end
61
153
  end
62
154
  end
File without changes
@@ -17,18 +17,9 @@ describe FinModeling::CashChangeCalculation do
17
17
 
18
18
  @cash_initial = @filing.balance_sheet.assets_calculation.summary(:period => bs_period_initial).rows[0].vals.first
19
19
  @cash_final = @filing.balance_sheet.assets_calculation.summary(:period => bs_period_final ).rows[0].vals.first
20
-
21
- #puts "initial cash: #{@cash_initial}"
22
- #puts "final cash: #{@cash_final}"
23
-
24
- #@filing.cash_flow_statement.cash_change_calculation.summary(:period => @cfs_period_q1_thru_q3).print
25
-
26
- #@cash_changes.leaf_items(:period => @cfs_period_q1_thru_q3).each do |item|
27
- # puts "#{item.name}: #{item.def ? item.def["xbrli:balance"] : "nil"}"
28
- #end
29
20
  end
30
21
 
31
- describe "summary(period)" do
22
+ describe ".summary" do
32
23
  subject{ @cash_changes.summary(:period => @cfs_period_q1_thru_q3) }
33
24
  it { should be_an_instance_of FinModeling::CalculationSummary }
34
25
 
@@ -4,38 +4,30 @@ require 'spec_helper'
4
4
 
5
5
  describe FinModeling::CashChangeItem do
6
6
 
7
- before(:all) do
8
- #FinModeling::CashChangeItem.load_vectors_and_train(FinModeling::CashChangeItem::TRAINING_VECTORS)
9
- end
10
-
11
7
  describe "new" do
12
- it "takes a string and returns a new CashChangeItem" do
13
- cci = FinModeling::CashChangeItem.new("Depreciation and amortization of property and equipment")
14
- cci.should be_an_instance_of FinModeling::CashChangeItem
15
- end
8
+ subject { FinModeling::CashChangeItem.new("Depreciation and amortization of property and equipment") }
9
+ it { should be_a FinModeling::CashChangeItem }
16
10
  end
17
11
 
18
12
  describe "train" do
13
+ let(:item) { FinModeling::CashChangeItem.new("Depreciation and amortization of property and equipment") }
19
14
  it "trains the classifier that this CashChangeItem is of the given type" do
20
- FinModeling::CashChangeItem.new("Depreciation and amortization of property and equipment").train(:c)
15
+ item.train(:c)
21
16
  end
22
17
  end
23
18
 
24
19
  describe "classification_estimates" do
25
- it "returns a hash with the confidence in each CashChangeItem type" do
26
- cci = FinModeling::CashChangeItem.new("Depreciation and amortization of property and equipment")
27
-
28
- FinModeling::CashChangeItem::TYPES.each do |klass|
29
- cci.classification_estimates.keys.include?(klass).should be_true
30
- end
31
- end
20
+ let(:item) { FinModeling::CashChangeItem.new("Depreciation and amortization of property and equipment") }
21
+ subject { item.classification_estimates }
22
+ its(:keys) { should == FinModeling::CashChangeItem::TYPES }
32
23
  end
33
24
 
34
25
  describe "classify" do
26
+ let(:cci) { FinModeling::CashChangeItem.new("Depreciation and amortization of property and equipment") }
27
+ subject { cci.classify }
35
28
  it "returns the CashChangeItem type with the highest probability estimate" do
36
- cci = FinModeling::CashChangeItem.new("Depreciation and amortization of property and equipment")
37
29
  estimates = cci.classification_estimates
38
- estimates[cci.classify].should be_within(0.1).of(estimates.values.max)
30
+ estimates[subject].should be_within(0.1).of(estimates.values.max)
39
31
  end
40
32
  end
41
33
 
@@ -13,9 +13,7 @@ describe FinModeling::CashFlowStatementCalculation do
13
13
  describe "cash_change_calculation" do
14
14
  subject { @cash_flow_stmt.cash_change_calculation }
15
15
  it { should be_an_instance_of FinModeling::CashChangeCalculation }
16
- it "returns the root node of the cash change calculation" do
17
- @cash_flow_stmt.cash_change_calculation.label.downcase.should match /^cash/
18
- end
16
+ its(:label) { should match /^cash/i }
19
17
  end
20
18
 
21
19
  describe "is_valid?" do
@@ -26,13 +24,14 @@ describe FinModeling::CashFlowStatementCalculation do
26
24
  (re_cfs.cash_investments_in_operations.total != 0) &&
27
25
  (re_cfs.payments_to_debtholders.total != 0) &&
28
26
  (re_cfs.payments_to_stockholders.total != 0)
27
+
29
28
  @cash_flow_stmt.is_valid?.should == (flows_are_balanced && none_are_zero)
30
29
  end
31
30
  end
32
31
 
33
32
  describe "reformulated" do
34
33
  subject { @cash_flow_stmt.reformulated(@period) }
35
- it { should be_an_instance_of FinModeling::ReformulatedCashFlowStatement }
34
+ it { should be_a FinModeling::ReformulatedCashFlowStatement }
36
35
  end
37
36
 
38
37
  describe "latest_quarterly_reformulated" do
@@ -50,9 +49,7 @@ describe FinModeling::CashFlowStatementCalculation do
50
49
  context "when given a Q1 report" do
51
50
  subject { @cash_flow_stmt_2011_q1.latest_quarterly_reformulated(nil) }
52
51
  it { should be_an_instance_of FinModeling::ReformulatedCashFlowStatement }
53
- it "should be valid" do
54
- subject.cash_investments_in_operations.total.abs.should be > 1.0
55
- end
52
+ its(:cash_investments_in_operations) { should have_a_plausible_total }
56
53
  end
57
54
 
58
55
  context "when given a Q2 report (and a previous Q1 report)" do
@@ -66,21 +63,17 @@ describe FinModeling::CashFlowStatementCalculation do
66
63
  context "when given a Q3 report (and a previous Q2 report)" do
67
64
  subject { @cash_flow_stmt_2011_q3.latest_quarterly_reformulated(@cash_flow_stmt_2011_q2) }
68
65
  it { should be_an_instance_of FinModeling::ReformulatedCashFlowStatement }
69
- it "should be valid" do
70
- subject.cash_investments_in_operations.total.abs.should be > 1.0
71
- end
66
+ its(:cash_investments_in_operations) { should have_a_plausible_total }
72
67
  end
73
68
 
74
69
  context "when given an annual report (and a previous Q3 report)" do
75
70
  subject { @cash_flow_stmt.latest_quarterly_reformulated(@cash_flow_stmt_2011_q3) }
76
71
  it { should be_an_instance_of FinModeling::ReformulatedCashFlowStatement }
77
- it "should be valid" do
78
- subject.cash_investments_in_operations.total.abs.should be > 1.0
79
- end
72
+ its(:cash_investments_in_operations) { should have_a_plausible_total }
80
73
  end
81
74
  end
82
75
 
83
- describe "write_constructor" do
76
+ context "after write_constructor()ing it to a file and then eval()ing the results" do
84
77
  before(:all) do
85
78
  file_name = "/tmp/finmodeling-cash_flow_stmt.rb"
86
79
  item_name = "@cfs"
@@ -89,19 +82,12 @@ describe FinModeling::CashFlowStatementCalculation do
89
82
  file.close
90
83
 
91
84
  eval(File.read(file_name))
92
-
93
85
  @loaded_cfs = eval(item_name)
94
86
  end
95
87
 
96
- it "writes itself to a file, and when reloaded, has the same periods" do
97
- expected_periods = @cash_flow_stmt.periods.map{|x| x.to_pretty_s}.join(',')
98
- @loaded_cfs.periods.map{|x| x.to_pretty_s}.join(',').should == expected_periods
99
- end
100
- it "writes itself to a file, and when reloaded, has the same change in cash" do
101
- period = @cash_flow_stmt.periods.last
102
- expected_cash_change = @cash_flow_stmt.cash_change_calculation.summary(:period=>period).total
103
- @loaded_cfs.cash_change_calculation.summary(:period=>period).total.should be_within(1.0).of(expected_cash_change)
104
- end
88
+ subject { @loaded_cfs }
89
+ it { should have_the_same_periods_as(@cash_flow_stmt) }
90
+ its(:cash_change_calculation) { should have_the_same_last_total_as(@cash_flow_stmt.cash_change_calculation) }
105
91
  end
106
92
 
107
93
  end
@@ -0,0 +1,53 @@
1
+ require 'spec_helper'
2
+
3
+ describe FinModeling::CAPM::Beta do
4
+ describe "#from_ticker" do
5
+ it "returns the right value" do
6
+ index_ticker = "SPY"
7
+ mock_index_quotes = []
8
+ mock_index_quotes << YahooFinance::HistoricalQuote.new("SPY", ["2013-02-05",153.66,154.7,153.64,154.29,121431900, 54.29])
9
+ mock_index_quotes << YahooFinance::HistoricalQuote.new("SPY", ["2013-02-06",153.66,154.7,153.64,154.29,121431900, 52.29])
10
+ mock_index_quotes << YahooFinance::HistoricalQuote.new("SPY", ["2013-02-07",153.66,154.7,153.64,154.29,121431900, 58.29])
11
+ mock_index_quotes << YahooFinance::HistoricalQuote.new("SPY", ["2013-02-08",153.66,154.7,153.64,154.29,121431900, 57.29])
12
+ mock_index_quotes << YahooFinance::HistoricalQuote.new("SPY", ["2013-02-09",153.66,154.7,153.64,154.29,121431900, 84.29])
13
+ mock_index_quotes << YahooFinance::HistoricalQuote.new("SPY", ["2013-02-10",153.66,154.7,153.64,154.29,121431900, 73.29])
14
+ mock_index_quotes << YahooFinance::HistoricalQuote.new("SPY", ["2013-02-11",153.66,154.7,153.64,154.29,121431900, 64.29])
15
+ mock_index_quotes << YahooFinance::HistoricalQuote.new("SPY", ["2013-02-12",153.66,154.7,153.64,154.29,121431900, 54.29])
16
+ mock_index_quotes << YahooFinance::HistoricalQuote.new("SPY", ["2013-02-13",153.66,154.7,153.64,154.29,121431900, 61.29])
17
+ mock_index_quotes << YahooFinance::HistoricalQuote.new("SPY", ["2013-02-14",153.66,154.7,153.64,154.29,121431900, 44.29])
18
+
19
+ company_ticker = "AAPL"
20
+ mock_company_quotes = []
21
+ mock_company_quotes << YahooFinance::HistoricalQuote.new("AAPL",["2013-02-05",153.66,154.7,153.64,154.29,121431900,154.29])
22
+ mock_company_quotes << YahooFinance::HistoricalQuote.new("AAPL",["2013-02-06",153.66,154.7,153.64,154.29,121431900,152.29])
23
+ mock_company_quotes << YahooFinance::HistoricalQuote.new("AAPL",["2013-02-07",153.66,154.7,153.64,154.29,121431900,158.29])
24
+ mock_company_quotes << YahooFinance::HistoricalQuote.new("AAPL",["2013-02-08",153.66,154.7,153.64,154.29,121431900,157.29])
25
+ mock_company_quotes << YahooFinance::HistoricalQuote.new("AAPL",["2013-02-09",153.66,154.7,153.64,154.29,121431900,184.29])
26
+ mock_company_quotes << YahooFinance::HistoricalQuote.new("AAPL",["2013-02-10",153.66,154.7,153.64,154.29,121431900,173.29])
27
+ mock_company_quotes << YahooFinance::HistoricalQuote.new("AAPL",["2013-02-11",153.66,154.7,153.64,154.29,121431900,164.29])
28
+ mock_company_quotes << YahooFinance::HistoricalQuote.new("AAPL",["2013-02-12",153.66,154.7,153.64,154.29,121431900,154.29])
29
+ mock_company_quotes << YahooFinance::HistoricalQuote.new("AAPL",["2013-02-13",153.66,154.7,153.64,154.29,121431900,161.29])
30
+ mock_company_quotes << YahooFinance::HistoricalQuote.new("AAPL",["2013-02-14",153.66,154.7,153.64,154.29,121431900,144.29])
31
+
32
+ num_days = 10
33
+
34
+ YahooFinance.should_receive(:get_HistoricalQuotes_days).with(index_ticker, num_days).and_return(mock_index_quotes)
35
+ YahooFinance.should_receive(:get_HistoricalQuotes_days).with(company_ticker, num_days).and_return(mock_company_quotes)
36
+
37
+ common_dates = mock_index_quotes.map{ |x| x.date } & mock_company_quotes.map{ |x| x.date }
38
+
39
+ index_quotes = mock_index_quotes .select{ |x| common_dates.include?(x.date) }.sort{ |x,y| x.date <=> y.date }
40
+ company_quotes = mock_company_quotes.select{ |x| common_dates.include?(x.date) }.sort{ |x,y| x.date <=> y.date }
41
+
42
+ index_daily_change = index_quotes.each_cons(2).map { |pair| (pair[1].adjClose-pair[0].adjClose)/pair[0].adjClose }
43
+ company_daily_change = company_quotes.each_cons(2).map{ |pair| (pair[1].adjClose-pair[0].adjClose)/pair[0].adjClose }
44
+
45
+ x = GSL::Vector.alloc(index_daily_change)
46
+ y = GSL::Vector.alloc(company_daily_change)
47
+ intercept, slope = GSL::Fit::linear(x, y)
48
+ expected_beta = slope
49
+
50
+ FinModeling::CAPM::Beta.from_ticker(company_ticker, num_days).should be_within(0.1).of(expected_beta) # FIXME: this is now ignorant of dividends...
51
+ end
52
+ end
53
+ end
@@ -8,67 +8,57 @@ describe FinModeling::CompanyFilingCalculation do
8
8
  @filing = FinModeling::AnnualReportFiling.download google_2011_annual_rpt
9
9
  end
10
10
 
11
- describe "new" do
12
- before(:each) do
13
- @calculation = FinModeling::Mocks::Calculation.new
14
- end
15
- it "takes a taxonomy and a xbrlware calculation and returns a CompanyFilingCalculation" do
16
- FinModeling::CompanyFilingCalculation.new(@calculation).should be_an_instance_of FinModeling::CompanyFilingCalculation
17
- end
18
- end
11
+ let(:calculation) { FinModeling::Mocks::Calculation.new }
12
+ subject { FinModeling::CompanyFilingCalculation.new(calculation) }
19
13
 
20
- describe "label" do
21
- before(:each) do
22
- @calculation = FinModeling::Mocks::Calculation.new
23
- end
24
- it "returns the calculation's label" do
25
- cfc = FinModeling::CompanyFilingCalculation.new(@calculation)
26
- cfc.label.should == @calculation.label
27
- end
28
- end
14
+ it { should be_a FinModeling::CompanyFilingCalculation }
15
+ its(:label) { should == calculation.label }
29
16
 
30
17
  describe "periods" do
31
- it "returns a PeriodArray, which helps filter and choose periods" do
32
- @filing.balance_sheet.periods.should be_an_instance_of FinModeling::PeriodArray
33
- end
34
- it "returns an array of the periods over/at which this calculation can be queried" do
35
- @filing.balance_sheet.periods.map{|x| x.to_s }.sort.should == ["2008-12-31", "2009-12-31", "2010-12-31", "2011-12-31"]
36
- end
18
+ subject { @filing.balance_sheet.periods }
19
+ it { should be_a FinModeling::PeriodArray }
20
+ specify { subject.map{ |x| x.to_s }.sort.should == ["2008-12-31", "2009-12-31", "2010-12-31", "2011-12-31"] }
37
21
  end
38
22
 
39
23
  describe "leaf_items" do
40
- before(:all) do
41
- @assets = @filing.balance_sheet.assets_calculation
42
- @period = @filing.balance_sheet.periods.last
43
- end
44
- it "returns an array of the leaf items in the calculation tree that match the period" do
45
- @assets.leaf_items(:period => @period).length.should == 12
24
+ let(:assets) { @filing.balance_sheet.assets_calculation }
25
+ context "when given a period" do
26
+ subject { assets.leaf_items(:period => @filing.balance_sheet.periods.last) }
27
+ it "should contain Xbrlware::Item's" do
28
+ subject.all?{ |x| x.class == Xbrlware::Item }.should be_true
29
+ end
30
+ it "returns the leaf items that match the period" do
31
+ subject.should have(12).items
32
+ end
46
33
  end
47
- it "returns an array of the leaf items in the calculation tree that match the period" do
48
- @assets.leaf_items(:period => @period).first.should be_an_instance_of Xbrlware::Item
49
- end
50
- it "returns all leaf items, if no period given" do
51
- @assets.leaf_items.length.should == 26
34
+ context "when not given a period" do
35
+ subject { assets.leaf_items }
36
+ it "should contain Xbrlware::Item's" do
37
+ subject.all?{ |x| x.class == Xbrlware::Item }.should be_true
38
+ end
39
+ it "returns all leaf items" do
40
+ subject.should have(26).items
41
+ end
52
42
  end
53
43
  end
54
44
 
55
45
  describe "leaf_items_sum" do
56
- before(:all) do
57
- vepc_2010_annual_rpt = "http://www.sec.gov/Archives/edgar/data/103682/000119312511049905/d10k.htm"
58
- @filing_with_mixed_order = FinModeling::AnnualReportFiling.download vepc_2010_annual_rpt
59
-
60
- # this balance sheet has some items (accumulated depreciation) that
61
- # should be subtracted from total assets, which makes it a better test
62
- # than the google annual report
63
- balance_sheet = @filing_with_mixed_order.balance_sheet
64
- @assets = balance_sheet.assets_calculation
65
- @period = balance_sheet.periods.last
66
- end
67
- it "returns the sum of the calculation tree, in the given period" do
68
- mapping = Xbrlware::ValueMapping.new
69
- mapping.policy[:credit] = :flip
46
+ context "given a balance sheet with items that subtract from the total (like accum. depreciation)" do
47
+ it "returns the sum of the calculation tree, in the given period" do
48
+ pending "can't parse this 10-k. need to find another suitable example"
70
49
 
71
- @assets.leaf_items_sum(:period=>@period, :mapping=>mapping).should be_within(1.0).of(42817000000.0)
50
+ vepc_2010_annual_rpt = "http://www.sec.gov/Archives/edgar/data/103682/000119312511049905/d10k.htm"
51
+ @filing_with_mixed_order = FinModeling::AnnualReportFiling.download vepc_2010_annual_rpt
52
+
53
+ balance_sheet = @filing_with_mixed_order.balance_sheet
54
+ @assets = balance_sheet.assets_calculation
55
+ @period = balance_sheet.periods.last
56
+
57
+ mapping = Xbrlware::ValueMapping.new
58
+ mapping.policy[:credit] = :flip
59
+
60
+ @assets.leaf_items_sum(:period=>@period, :mapping=>mapping).should be_within(1.0).of(42817000000.0)
61
+ end
72
62
  end
73
63
  end
74
64
  end
File without changes
@@ -2,53 +2,103 @@
2
2
 
3
3
  require 'spec_helper'
4
4
 
5
- describe FinModeling::CompanyFiling do
5
+ describe FinModeling::CompanyFilings do
6
6
  before (:all) do
7
7
  @company = FinModeling::Company.find("aapl")
8
8
  @filings = FinModeling::CompanyFilings.new(@company.filings_since_date(Time.parse("2010-10-01")))
9
9
  end
10
10
 
11
- describe "balance_sheet_analyses" do
11
+ describe ".balance_sheet_analyses" do
12
12
  subject { @filings.balance_sheet_analyses }
13
- it { should be_an_instance_of FinModeling::BalanceSheetAnalyses }
13
+ it { should be_a FinModeling::BalanceSheetAnalyses }
14
+ it "should have one column per filing" do
15
+ subject.num_value_columns.should == @filings.length
16
+ end
14
17
  end
15
18
 
16
- describe "cash_flow_statement_analyses" do
19
+ describe ".cash_flow_statement_analyses" do
17
20
  subject { @filings.cash_flow_statement_analyses }
18
- it { should be_an_instance_of FinModeling::CalculationSummary } # FIXME: model this guy the same way...
21
+ it { should be_a FinModeling::CalculationSummary }
22
+ it "should have one column per filing" do
23
+ subject.num_value_columns.should == @filings.length
24
+ end
19
25
  end
20
26
 
21
- describe "income_statement_analyses" do
22
- subject { @filings.income_statement_analyses }
23
- it { should be_an_instance_of FinModeling::IncomeStatementAnalyses }
27
+ describe ".income_statement_analyses" do
28
+ subject { @filings.income_statement_analyses(e_ror=0.10) }
29
+ it { should be_a FinModeling::IncomeStatementAnalyses }
30
+ it "should have one column per filing" do
31
+ subject.num_value_columns.should == @filings.length
32
+ end
24
33
  end
25
34
 
26
- describe "choose_forecasting_policy" do
27
- context "when one or two filings" do
28
- let(:filings) { FinModeling::CompanyFilings.new(@filings[-2..-1]) }
29
- subject { filings.choose_forecasting_policy }
35
+ describe ".re_bs_arr" do
36
+ subject { @filings.re_bs_arr }
37
+ it "should be an array of FinModeling::ReformulatedBalanceSheet" do
38
+ subject.all?{ |re_bs| re_bs.should be_a FinModeling::ReformulatedBalanceSheet }
39
+ end
40
+ it "should have one per filing" do
41
+ subject.length.should == @filings.length
42
+ end
43
+ end
30
44
 
31
- it { should be_an_instance_of FinModeling::GenericForecastingPolicy }
45
+ describe ".re_is_arr" do
46
+ subject { @filings.re_is_arr }
47
+ it "should be an array whose first element is nil" do
48
+ subject.first.should be_nil
49
+ end
50
+ it "should be an array whose remaining elements are FinModeling::ReformulatedIncomeStatement's" do
51
+ subject[1..-1].all?{ |re_is| re_is.should be_a FinModeling::ReformulatedIncomeStatement }
52
+ end
53
+ it "should have one per filing" do
54
+ subject.length.should == @filings.length
32
55
  end
33
- context "when two or more filings" do
34
- let(:filings) { FinModeling::CompanyFilings.new(@filings[-3..-1]) }
35
- subject { filings.choose_forecasting_policy }
36
- it { should be_an_instance_of FinModeling::ConstantForecastingPolicy }
56
+ end
57
+
58
+ describe ".disclosures" do
59
+ context "when a yearly disclosure is requested" do
60
+ subject { @filings.disclosures(/Disclosure Provision For Income Taxes/, :yearly) }
61
+ it { should be_a FinModeling::CalculationSummary }
62
+ it "should have one column per 4 filings" do
63
+ subject.num_value_columns.should be_within(2).of((@filings.length/4).floor)
64
+ end
65
+ end
66
+ context "when a quarterly disclosure is requested" do
67
+ subject { @filings.disclosures(/Disclosure Components Of Total Comprehensive I/, :quarterly) }
68
+ it { should be_a FinModeling::CalculationSummary }
69
+ it "should have one column per filing" do
70
+ #subject.num_value_columns.should be_within(3).of(@filings.length)
71
+ pending "this is returning more than expected. not sure why..."
72
+ end
73
+ end
74
+ context "when no period modifier is given (and the disclosure is yearly)" do
75
+ subject { @filings.disclosures(/Disclosure Components Of Gross And Net Intangible Asset Balances/) }
76
+ it { should be_a FinModeling::CalculationSummary }
77
+ it "should have one column per 4 filings" do
78
+ subject.num_value_columns.should be_within(2).of((@filings.length/4).floor)
79
+ end
80
+ end
81
+ end
37
82
 
38
- let(:isa) { filings.income_statement_analyses }
83
+ describe ".choose_forecasting_policy" do
84
+ context "when one or two filings" do
85
+ let(:filings) { FinModeling::CompanyFilings.new(@filings.last(2)) }
86
+ subject { filings.choose_forecasting_policy(e_ror=0.10) }
39
87
 
40
- its(:revenue_growth) { should be_within(0.01).of(isa.revenue_growth_row.valid_vals.mean) }
41
- its(:sales_pm) { should be_within(0.01).of(isa.operating_pm_row.valid_vals.mean) } # FIXME: name mismatch
42
- its(:sales_over_noa) { should be_within(0.01).of(isa.sales_over_noa_row.valid_vals.mean) }
43
- its(:fi_over_nfa) { should be_within(0.01).of(isa.fi_over_nfa_row.valid_vals.mean) }
88
+ it { should be_a FinModeling::GenericForecastingPolicy }
89
+ end
90
+ context "when three or more filings" do
91
+ let(:filings) { FinModeling::CompanyFilings.new(@filings.last(3)) }
92
+ subject { filings.choose_forecasting_policy(e_ror=0.10) }
93
+ it { should be_a FinModeling::ConstantForecastingPolicy }
44
94
  end
45
95
  end
46
96
 
47
- describe "forecasts" do
48
- let(:policy) { @filings.choose_forecasting_policy }
97
+ describe ".forecasts" do
98
+ let(:policy) { @filings.choose_forecasting_policy(e_ror=0.10) }
49
99
  let(:num_quarters) { 3 }
50
100
  subject { @filings.forecasts(policy, num_quarters) }
51
- it { should be_an_instance_of FinModeling::Forecasts }
101
+ it { should be_a FinModeling::Forecasts }
52
102
  its(:reformulated_income_statements) { should have(num_quarters).items }
53
103
  its(:reformulated_balance_sheets) { should have(num_quarters).items }
54
104
  end