finmodeling 0.1 → 0.2

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