finmodeling 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.
Files changed (97) hide show
  1. data/.gitignore +3 -0
  2. data/Gemfile +10 -0
  3. data/README.md +292 -0
  4. data/Rakefile +6 -0
  5. data/TODO.txt +36 -0
  6. data/examples/dump_report.rb +33 -0
  7. data/examples/lists/nasdaq-mid-to-mega-tech-symbols.txt +226 -0
  8. data/examples/show_report.rb +218 -0
  9. data/examples/show_reports.rb +77 -0
  10. data/finmodeling.gemspec +31 -0
  11. data/lib/finmodeling/annual_report_filing.rb +104 -0
  12. data/lib/finmodeling/array_with_stats.rb +22 -0
  13. data/lib/finmodeling/assets_calculation.rb +36 -0
  14. data/lib/finmodeling/assets_item.rb +14 -0
  15. data/lib/finmodeling/assets_item_vectors.rb +638 -0
  16. data/lib/finmodeling/balance_sheet_analyses.rb +33 -0
  17. data/lib/finmodeling/balance_sheet_calculation.rb +68 -0
  18. data/lib/finmodeling/calculation_summary.rb +148 -0
  19. data/lib/finmodeling/can_cache_classifications.rb +36 -0
  20. data/lib/finmodeling/can_cache_summaries.rb +16 -0
  21. data/lib/finmodeling/can_classify_rows.rb +54 -0
  22. data/lib/finmodeling/cash_change_calculation.rb +67 -0
  23. data/lib/finmodeling/cash_change_item.rb +14 -0
  24. data/lib/finmodeling/cash_change_item_vectors.rb +241 -0
  25. data/lib/finmodeling/cash_flow_statement_calculation.rb +85 -0
  26. data/lib/finmodeling/classifiers.rb +11 -0
  27. data/lib/finmodeling/company.rb +102 -0
  28. data/lib/finmodeling/company_filing.rb +64 -0
  29. data/lib/finmodeling/company_filing_calculation.rb +75 -0
  30. data/lib/finmodeling/company_filings.rb +100 -0
  31. data/lib/finmodeling/config.rb +37 -0
  32. data/lib/finmodeling/constant_forecasting_policy.rb +23 -0
  33. data/lib/finmodeling/factory.rb +27 -0
  34. data/lib/finmodeling/float_helpers.rb +17 -0
  35. data/lib/finmodeling/forecasts.rb +48 -0
  36. data/lib/finmodeling/generic_forecasting_policy.rb +19 -0
  37. data/lib/finmodeling/has_string_classifer.rb +96 -0
  38. data/lib/finmodeling/income_statement_analyses.rb +74 -0
  39. data/lib/finmodeling/income_statement_calculation.rb +71 -0
  40. data/lib/finmodeling/income_statement_item.rb +14 -0
  41. data/lib/finmodeling/income_statement_item_vectors.rb +654 -0
  42. data/lib/finmodeling/liabs_and_equity_calculation.rb +36 -0
  43. data/lib/finmodeling/liabs_and_equity_item.rb +14 -0
  44. data/lib/finmodeling/liabs_and_equity_item_vectors.rb +1936 -0
  45. data/lib/finmodeling/net_income_calculation.rb +41 -0
  46. data/lib/finmodeling/paths.rb +5 -0
  47. data/lib/finmodeling/period_array.rb +24 -0
  48. data/lib/finmodeling/quarterly_report_filing.rb +23 -0
  49. data/lib/finmodeling/rate.rb +20 -0
  50. data/lib/finmodeling/ratio.rb +20 -0
  51. data/lib/finmodeling/reformulated_balance_sheet.rb +176 -0
  52. data/lib/finmodeling/reformulated_cash_flow_statement.rb +140 -0
  53. data/lib/finmodeling/reformulated_income_statement.rb +436 -0
  54. data/lib/finmodeling/string_helpers.rb +26 -0
  55. data/lib/finmodeling/version.rb +3 -0
  56. data/lib/finmodeling.rb +70 -0
  57. data/spec/annual_report_filing_spec.rb +68 -0
  58. data/spec/assets_calculation_spec.rb +21 -0
  59. data/spec/assets_item_spec.rb +66 -0
  60. data/spec/balance_sheet_analyses_spec.rb +43 -0
  61. data/spec/balance_sheet_calculation_spec.rb +91 -0
  62. data/spec/calculation_summary_spec.rb +63 -0
  63. data/spec/can_classify_rows_spec.rb +86 -0
  64. data/spec/cash_change_calculation_spec.rb +56 -0
  65. data/spec/cash_change_item_spec.rb +66 -0
  66. data/spec/cash_flow_statement_calculation_spec.rb +108 -0
  67. data/spec/company_filing_calculation_spec.rb +74 -0
  68. data/spec/company_filing_spec.rb +30 -0
  69. data/spec/company_filings_spec.rb +55 -0
  70. data/spec/company_spec.rb +73 -0
  71. data/spec/constant_forecasting_policy_spec.rb +37 -0
  72. data/spec/factory_spec.rb +18 -0
  73. data/spec/forecasts_spec.rb +21 -0
  74. data/spec/generic_forecasting_policy_spec.rb +33 -0
  75. data/spec/income_statement_analyses_spec.rb +63 -0
  76. data/spec/income_statement_calculation_spec.rb +88 -0
  77. data/spec/income_statement_item_spec.rb +86 -0
  78. data/spec/liabs_and_equity_calculation_spec.rb +20 -0
  79. data/spec/liabs_and_equity_item_spec.rb +66 -0
  80. data/spec/mocks/calculation.rb +10 -0
  81. data/spec/mocks/income_statement_analyses.rb +93 -0
  82. data/spec/mocks/sec_query.rb +31 -0
  83. data/spec/net_income_calculation_spec.rb +23 -0
  84. data/spec/period_array.rb +52 -0
  85. data/spec/quarterly_report_filing_spec.rb +69 -0
  86. data/spec/rate_spec.rb +33 -0
  87. data/spec/ratio_spec.rb +33 -0
  88. data/spec/reformulated_balance_sheet_spec.rb +146 -0
  89. data/spec/reformulated_cash_flow_statement_spec.rb +174 -0
  90. data/spec/reformulated_income_statement_spec.rb +293 -0
  91. data/spec/spec_helper.rb +5 -0
  92. data/spec/string_helpers_spec.rb +23 -0
  93. data/tools/create_balance_sheet_training_vectors.rb +65 -0
  94. data/tools/create_cash_change_training_vectors.rb +48 -0
  95. data/tools/create_credit_debit_training_vectors.rb +51 -0
  96. data/tools/create_income_statement_training_vectors.rb +48 -0
  97. metadata +289 -0
@@ -0,0 +1,66 @@
1
+ # period_array_spec.rb
2
+
3
+ require 'spec_helper'
4
+
5
+ describe FinModeling::AssetsItem do
6
+
7
+ before(:all) do
8
+ #FinModeling::AssetsItem.load_vectors_and_train(FinModeling::AssetsItem::TRAINING_VECTORS)
9
+ end
10
+
11
+ describe "new" do
12
+ it "takes a string and returns a new AssetsItem" do
13
+ ai = FinModeling::AssetsItem.new("Property Plant And Equipment Net")
14
+ ai.should be_an_instance_of FinModeling::AssetsItem
15
+ end
16
+ end
17
+
18
+ describe "train" do
19
+ it "trains the classifier that this AssetsItem is of the given type" do
20
+ FinModeling::AssetsItem.new("Property Plant and Equipment Net").train(:oa)
21
+ end
22
+ end
23
+
24
+ describe "classification_estimates" do
25
+ it "returns a hash with the confidence in each AssetsItem type" do
26
+ ai = FinModeling::AssetsItem.new("Property Plant And Equipment Net")
27
+
28
+ FinModeling::AssetsItem::TYPES.each do |klass|
29
+ ai.classification_estimates.keys.include?(klass).should be_true
30
+ end
31
+ end
32
+ end
33
+
34
+ describe "classify" do
35
+ it "returns the AssetsItem type with the highest probability estimate" do
36
+ ai = FinModeling::AssetsItem.new("Property Plant And Equipment Net")
37
+ estimates = ai.classification_estimates
38
+ estimates[ai.classify].should be_within(0.1).of(estimates.values.max)
39
+ end
40
+ end
41
+
42
+ describe "load_vectors_and_train" do
43
+ # the before(:all) clause calls load_vectors_and_train already
44
+ # we can just focus, here, on its effects
45
+
46
+ it "classifies >95% correctly" do
47
+ num_items = 0
48
+ errors = []
49
+ FinModeling::AssetsItem::TRAINING_VECTORS.each do |vector|
50
+ num_items += 1
51
+ ai = FinModeling::AssetsItem.new(vector[:item_string])
52
+ if ai.classify != vector[:klass]
53
+ errors.push({ :ai=>ai.to_s, :expected=>vector[:klass], :got=>ai.classify })
54
+ end
55
+ end
56
+
57
+ pct_errors = errors.length.to_f / num_items
58
+ if pct_errors > 0.05
59
+ puts "errors: " + errors.inspect
60
+ end
61
+ pct_errors.should be < 0.05
62
+
63
+ end
64
+ end
65
+
66
+ end
@@ -0,0 +1,43 @@
1
+ # balance_sheets_analyses_spec.rb
2
+
3
+ require 'spec_helper'
4
+
5
+ describe FinModeling::BalanceSheetAnalyses do
6
+ before(:all) do
7
+ @summary = FinModeling::CalculationSummary.new
8
+ @summary.title = "Title 123"
9
+ @summary.rows = [ ]
10
+ @summary.rows << FinModeling::CalculationRow.new(:key => "NOA Growth", :type => :oa, :vals => [ 4])
11
+ @summary.rows << FinModeling::CalculationRow.new(:key => "Row", :type => :fa, :vals => [109])
12
+ @summary.rows << FinModeling::CalculationRow.new(:key => "Row", :type => :oa, :vals => [ 93])
13
+ @summary.rows << FinModeling::CalculationRow.new(:key => "Row", :type => :fa, :vals => [ 1])
14
+ end
15
+
16
+ describe ".new" do
17
+ subject { FinModeling::BalanceSheetAnalyses.new(@summary) }
18
+
19
+ it { should be_a_kind_of FinModeling::CalculationSummary }
20
+ its(:title) { should == @summary.title }
21
+ its(:rows) { should == @summary.rows }
22
+ its(:header_row) { should == @summary.header_row }
23
+ its(:rows) { should == @summary.rows }
24
+ its(:num_value_columns) { should == @summary.num_value_columns }
25
+ its(:key_width) { should == @summary.key_width }
26
+ its(:val_width) { should == @summary.val_width }
27
+ its(:max_decimals) { should == @summary.max_decimals }
28
+ its(:totals_row_enabled) { should be_false }
29
+ end
30
+
31
+ describe ".print_extras" do
32
+ subject { FinModeling::BalanceSheetAnalyses.new(@summary) }
33
+
34
+ it { should respond_to(:print_extras) }
35
+ end
36
+
37
+ describe ".noa_growth_row" do
38
+ subject { FinModeling::BalanceSheetAnalyses.new(@summary).noa_growth_row }
39
+
40
+ it { should be_a FinModeling::CalculationRow }
41
+ its(:key) { should == "NOA Growth" }
42
+ end
43
+ end
@@ -0,0 +1,91 @@
1
+ # balance_sheet_calculation_spec.rb
2
+
3
+ require 'spec_helper'
4
+
5
+ describe FinModeling::BalanceSheetCalculation do
6
+ before(:all) do
7
+ google_2011_annual_rpt = "http://www.sec.gov/Archives/edgar/data/1288776/000119312512025336/0001193125-12-025336-index.htm"
8
+ filing = FinModeling::AnnualReportFiling.download google_2011_annual_rpt
9
+ @balance_sheet = filing.balance_sheet
10
+ @period = @balance_sheet.periods.last
11
+ end
12
+
13
+ describe "assets_calculation" do
14
+ it "returns an AssetsCalculation" do
15
+ @balance_sheet.assets_calculation.should be_an_instance_of FinModeling::AssetsCalculation
16
+ end
17
+ it "returns the root node of the assets calculation" do
18
+ @balance_sheet.assets_calculation.label.downcase.should match /asset/
19
+ end
20
+ it "sums to the same value as do the liabilities and equity" do
21
+ left_sum = @balance_sheet.assets_calculation.leaf_items_sum(:period=>@period)
22
+ right_sum = @balance_sheet.liabs_and_equity_calculation.leaf_items_sum(:period=>@period)
23
+ left_sum.should be_within(1.0).of(right_sum)
24
+ end
25
+ end
26
+
27
+ describe "liabs_and_equity_calculation" do
28
+ it "returns a LiabsAndEquityCalculation" do
29
+ @balance_sheet.liabs_and_equity_calculation.should be_an_instance_of FinModeling::LiabsAndEquityCalculation
30
+ end
31
+ it "returns the root node of the liability & shareholders' equity calculation" do
32
+ @balance_sheet.liabs_and_equity_calculation.label.downcase.should match /liab.*equity/
33
+ end
34
+ end
35
+
36
+ describe "is_valid?" do
37
+ it "returns false if none of the asset leaf nodes contains the term 'cash'" do
38
+ #ea_2011_annual_rpt = "http://www.sec.gov/Archives/edgar/data/712515/000119312511149262/0001193125-11-149262-index.htm"
39
+ #filing = FinModeling::AnnualReportFiling.download ea_2011_annual_rpt
40
+ #filing.balance_sheet.is_valid?.should be_false
41
+ pending
42
+ end
43
+ it "returns false if none of the liability/equity net income leaf nodes contains the term 'equity'" do
44
+ #ea_2011_annual_rpt = "http://www.sec.gov/Archives/edgar/data/712515/000119312511149262/0001193125-11-149262-index.htm"
45
+ #filing = FinModeling::AnnualReportFiling.download ea_2011_annual_rpt
46
+ #filing.balance_sheet.is_valid?.should be_false
47
+ pending
48
+ end
49
+ it "returns false if the assets total does not match the liabilities and equity total" do
50
+ #ea_2011_annual_rpt = "http://www.sec.gov/Archives/edgar/data/712515/000119312511149262/0001193125-11-149262-index.htm"
51
+ #filing = FinModeling::AnnualReportFiling.download ea_2011_annual_rpt
52
+ #filing.balance_sheet.is_valid?.should be_false
53
+ pending
54
+ end
55
+ it "returns true otherwise" do
56
+ @balance_sheet.is_valid?.should be_true
57
+ end
58
+ end
59
+
60
+ describe "reformulated" do
61
+ it "takes a period and returns a ReformulatedBalanceSheet" do
62
+ @balance_sheet.reformulated(@period).should be_an_instance_of FinModeling::ReformulatedBalanceSheet
63
+ end
64
+ end
65
+
66
+ describe "write_constructor" do
67
+ before(:all) do
68
+ file_name = "/tmp/finmodeling-bal-sheet.rb"
69
+ item_name = "@bal_sheet"
70
+ file = File.open(file_name, "w")
71
+ @balance_sheet.write_constructor(file, item_name)
72
+ file.close
73
+
74
+ eval(File.read(file_name))
75
+
76
+ @loaded_bs = eval(item_name)
77
+ end
78
+
79
+ it "writes itself to a file, and when reloaded, has the same periods" do
80
+ expected_periods = @balance_sheet.periods.map{|x| x.to_pretty_s}.join(',')
81
+ @loaded_bs.periods.map{|x| x.to_pretty_s}.join(',').should == expected_periods
82
+ end
83
+ it "writes itself to a file, and when reloaded, has the same net operating assets" do
84
+ period = @balance_sheet.periods.last
85
+ expected_noa = @balance_sheet.reformulated(period).net_operating_assets.total
86
+ @loaded_bs.reformulated(period).net_operating_assets.total.should be_within(1.0).of(expected_noa)
87
+ end
88
+ end
89
+
90
+ end
91
+
@@ -0,0 +1,63 @@
1
+ # multi_column_calculation_summary_spec.rb
2
+
3
+ require 'spec_helper'
4
+
5
+ describe FinModeling::CalculationSummary do
6
+ before(:all) do
7
+ @summary = FinModeling::CalculationSummary.new
8
+ @summary.title = "CS 1"
9
+ @summary.rows = [ FinModeling::CalculationRow.new(:key => "Row", :vals => [nil, 0, nil, -101, 2.4]) ]
10
+ end
11
+
12
+ describe "valid_vals" do
13
+ subject { @summary.rows.first.valid_vals }
14
+ it "should return all non-nil values" do
15
+ subject.should == @summary.rows[0].vals.select{ |x| !x.nil? }
16
+ end
17
+ end
18
+
19
+ describe "filter_by_type" do
20
+ before(:all) do
21
+ @summary2 = FinModeling::CalculationSummary.new
22
+ @summary2.rows = [ ]
23
+ @summary2.rows << FinModeling::CalculationRow.new(:key => "Row", :type => :oa, :vals => [ 4])
24
+ @summary2.rows << FinModeling::CalculationRow.new(:key => "Row", :type => :fa, :vals => [109])
25
+ @summary2.rows << FinModeling::CalculationRow.new(:key => "Row", :type => :oa, :vals => [ 93])
26
+ @summary2.rows << FinModeling::CalculationRow.new(:key => "Row", :type => :fa, :vals => [ 1])
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
31
+ 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]
33
+ end
34
+ end
35
+
36
+ describe "+" do
37
+ before(:all) do
38
+ @mccs1 = FinModeling::CalculationSummary.new
39
+ @mccs1.title = "MCCS 1"
40
+ @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
+ end
46
+
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 }
55
+ end
56
+
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 )
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,86 @@
1
+ # assets_calculation_spec.rb
2
+
3
+ require 'spec_helper'
4
+
5
+ describe FinModeling::CanClassifyRows do
6
+ before(:all) do
7
+ class AgeItem < String
8
+ def classification_estimates
9
+ case
10
+ when self =~ /^1/ then {:teens=>1.0, :twenties=>0.0, :thirties=>0.0, :fourties=>0.0}
11
+ when self =~ /^2/ then {:teens=>0.0, :twenties=>1.0, :thirties=>0.0, :fourties=>0.0}
12
+ when self =~ /^3/ then {:teens=>0.0, :twenties=>0.0, :thirties=>1.0, :fourties=>0.0}
13
+ when self =~ /^4/ then {:teens=>0.0, :twenties=>0.0, :thirties=>0.0, :fourties=>1.0}
14
+ else {:teens=>0.0, :twenties=>0.0, :thirties=>0.0, :fourties=>0.0}
15
+ end
16
+ end
17
+ end
18
+
19
+ class AgeList
20
+ attr_accessor :calculation
21
+
22
+ include FinModeling::CanClassifyRows
23
+
24
+ ALL_STATES = [ :teens, :twenties, :thirties, :fourties ]
25
+ NEXT_STATES = { nil => [ :teens, :twenties, :thirties, :fourties ],
26
+ :teens => [ :teens, :twenties, :thirties, :fourties ],
27
+ :twenties => [ :twenties, :thirties, :fourties ],
28
+ :thirties => [ :thirties, :fourties ],
29
+ :fourties => [ :fourties ] }
30
+
31
+ def classify(args)
32
+ lookahead = [args[:max_lookahead], calculation.rows.length-1].min
33
+ classify_rows(ALL_STATES, NEXT_STATES, calculation.rows, AgeItem, lookahead)
34
+ end
35
+ end
36
+ end
37
+
38
+ describe "classify_rows" do
39
+ context "with 1 consecutive error" do
40
+ before(:all) do
41
+ @age_list = AgeList.new
42
+ @age_list.calculation = FinModeling::CalculationSummary.new
43
+ ages = [21, 41, 30, 35]
44
+ @age_list.calculation.rows = ages.collect { |age| FinModeling::CalculationRow.new(:key => age.to_s, :vals => 0) }
45
+ end
46
+ context "with lookahead of 0" do
47
+ it "should fail to correct errors" do
48
+ expected_rows = [:twenties, :fourties, :fourties, :fourties]
49
+ @age_list.classify(:max_lookahead=>0)
50
+ @age_list.calculation.rows.map{ |row| row.type }.should == expected_rows
51
+ end
52
+ end
53
+ context "with lookahead of 1" do
54
+ it "should correct one error" do
55
+ expected_rows = [:twenties, :twenties, :thirties, :thirties]
56
+ @age_list.classify(:max_lookahead=>1)
57
+ @age_list.calculation.rows.map{ |row| row.type }.should == expected_rows
58
+ end
59
+ end
60
+ end
61
+ context "with 2 consecutive errors" do
62
+ before(:all) do
63
+ @age_list = AgeList.new
64
+ @age_list.calculation = FinModeling::CalculationSummary.new
65
+ ages = [21, 41, 40, 25, 30, 35, 38, 40]
66
+ @age_list.calculation.rows = ages.collect { |age| FinModeling::CalculationRow.new(:key => age.to_s, :vals => 0) }
67
+ end
68
+ context "with lookahead of 2" do
69
+ it "should fail to correct errors" do
70
+ expected_rows = [:twenties, :fourties, :fourties, :fourties, :fourties, :fourties, :fourties, :fourties]
71
+ @age_list.classify(:max_lookahead=>2)
72
+ @age_list.calculation.rows.map{ |row| row.type }.should == expected_rows
73
+ end
74
+ end
75
+ context "with lookahead of 3" do
76
+ it "should correct one error" do
77
+ expected_rows = [:twenties, :twenties, :twenties, :twenties, :thirties, :thirties, :thirties, :fourties]
78
+ @age_list.classify(:max_lookahead=>3)
79
+ @age_list.calculation.rows.map{ |row| row.type }.should == expected_rows
80
+ end
81
+ end
82
+ end
83
+
84
+ end
85
+ end
86
+
@@ -0,0 +1,56 @@
1
+ # cash_change_calculation_spec.rb
2
+
3
+ require 'spec_helper'
4
+
5
+ describe FinModeling::CashChangeCalculation do
6
+ before(:all) do
7
+ goog_2011_q3_report = "http://www.sec.gov/Archives/edgar/data/1288776/000119312511282235/0001193125-11-282235-index.htm"
8
+ FinModeling::Config::disable_caching
9
+ @filing = FinModeling::AnnualReportFiling.download(goog_2011_q3_report)
10
+ FinModeling::Config::enable_caching
11
+ @cfs_period_q1_thru_q3 = @filing.cash_flow_statement.periods.threequarterly.last
12
+
13
+ @cash_changes = @filing.cash_flow_statement.cash_change_calculation
14
+
15
+ bs_period_initial = @filing.balance_sheet.periods[-2]
16
+ bs_period_final = @filing.balance_sheet.periods[-1]
17
+
18
+ @cash_initial = @filing.balance_sheet.assets_calculation.summary(:period => bs_period_initial).rows[0].vals.first
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
+ end
30
+
31
+ describe "summary(period)" do
32
+ subject{ @cash_changes.summary(:period => @cfs_period_q1_thru_q3) }
33
+ it { should be_an_instance_of FinModeling::CalculationSummary }
34
+
35
+ it "should have values with the right sign" do
36
+ expected = [7033, 1011, 337, 1437, -61, 526, 3, -247, 268,
37
+ -146, 72, 255, 70, 83, -2487, -43693, 33107,
38
+ -358, 694, -395, -1350, -20, 61, 0, 8780, -8054, 74]
39
+
40
+ actual = subject.rows.map{|row| (row.vals.first/1000.0/1000.0).round}
41
+
42
+ if actual != expected
43
+ num_errors = actual.zip(expected).map{ |x,y| x==y ? 0 : 1 }.inject(:+)
44
+ puts "# errors: #{num_errors}"
45
+ end
46
+
47
+ actual.should == expected
48
+ end
49
+
50
+ describe ".total" do
51
+ subject{ @cash_changes.summary(:period => @cfs_period_q1_thru_q3).total }
52
+ it { should be_within(1.0).of(@cash_final - @cash_initial) }
53
+ end
54
+ end
55
+ end
56
+
@@ -0,0 +1,66 @@
1
+ # period_array_spec.rb
2
+
3
+ require 'spec_helper'
4
+
5
+ describe FinModeling::CashChangeItem do
6
+
7
+ before(:all) do
8
+ #FinModeling::CashChangeItem.load_vectors_and_train(FinModeling::CashChangeItem::TRAINING_VECTORS)
9
+ end
10
+
11
+ 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
16
+ end
17
+
18
+ describe "train" do
19
+ 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)
21
+ end
22
+ end
23
+
24
+ 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
32
+ end
33
+
34
+ describe "classify" do
35
+ it "returns the CashChangeItem type with the highest probability estimate" do
36
+ cci = FinModeling::CashChangeItem.new("Depreciation and amortization of property and equipment")
37
+ estimates = cci.classification_estimates
38
+ estimates[cci.classify].should be_within(0.1).of(estimates.values.max)
39
+ end
40
+ end
41
+
42
+ describe "load_vectors_and_train" do
43
+ # the before(:all) clause calls load_vectors_and_train already
44
+ # we can just focus, here, on its effects
45
+
46
+ it "classifies >92% correctly" do # FIXME: add more vectors to tighten this up
47
+ num_items = 0
48
+ errors = []
49
+ FinModeling::CashChangeItem::TRAINING_VECTORS.each do |vector|
50
+ num_items += 1
51
+ cci = FinModeling::CashChangeItem.new(vector[:item_string])
52
+ if cci.classify != vector[:klass]
53
+ errors.push({ :cci=>cci.to_s, :expected=>vector[:klass], :got=>cci.classify })
54
+ end
55
+ end
56
+
57
+ pct_errors = errors.length.to_f / num_items
58
+ if pct_errors > 0.08
59
+ puts "errors: " + errors.inspect
60
+ end
61
+ pct_errors.should be < 0.08
62
+
63
+ end
64
+ end
65
+
66
+ end
@@ -0,0 +1,108 @@
1
+ # balance_sheet_calculation_spec.rb
2
+
3
+ require 'spec_helper'
4
+
5
+ describe FinModeling::CashFlowStatementCalculation do
6
+ before(:all) do
7
+ google_2011_annual_rpt = "http://www.sec.gov/Archives/edgar/data/1288776/000119312512025336/0001193125-12-025336-index.htm"
8
+ filing = FinModeling::AnnualReportFiling.download(google_2011_annual_rpt)
9
+ @cash_flow_stmt = filing.cash_flow_statement
10
+ @period = @cash_flow_stmt.periods.last
11
+ end
12
+
13
+ describe "cash_change_calculation" do
14
+ subject { @cash_flow_stmt.cash_change_calculation }
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
19
+ end
20
+
21
+ describe "is_valid?" do
22
+ it "returns true if free cash flow matches financing flows and none are zero" do
23
+ re_cfs = @cash_flow_stmt.reformulated(@period)
24
+ flows_are_balanced = (re_cfs.free_cash_flow.total == (-1*re_cfs.financing_flows.total))
25
+ none_are_zero = (re_cfs.cash_from_operations.total != 0) &&
26
+ (re_cfs.cash_investments_in_operations.total != 0) &&
27
+ (re_cfs.payments_to_debtholders.total != 0) &&
28
+ (re_cfs.payments_to_stockholders.total != 0)
29
+ @cash_flow_stmt.is_valid?.should == (flows_are_balanced && none_are_zero)
30
+ end
31
+ end
32
+
33
+ describe "reformulated" do
34
+ subject { @cash_flow_stmt.reformulated(@period) }
35
+ it { should be_an_instance_of FinModeling::ReformulatedCashFlowStatement }
36
+ end
37
+
38
+ describe "latest_quarterly_reformulated" do
39
+ before(:all) do
40
+ google_2011_q1_rpt = "http://www.sec.gov/Archives/edgar/data/1288776/000119312511134428/0001193125-11-134428-index.htm"
41
+ @cash_flow_stmt_2011_q1 = FinModeling::AnnualReportFiling.download(google_2011_q1_rpt).cash_flow_statement
42
+
43
+ google_2011_q2_rpt = "http://www.sec.gov/Archives/edgar/data/1288776/000119312511199078/0001193125-11-199078-index.htm"
44
+ @cash_flow_stmt_2011_q2 = FinModeling::AnnualReportFiling.download(google_2011_q2_rpt).cash_flow_statement
45
+
46
+ google_2011_q3_rpt = "http://www.sec.gov/Archives/edgar/data/1288776/000119312511282235/0001193125-11-282235-index.htm"
47
+ @cash_flow_stmt_2011_q3 = FinModeling::AnnualReportFiling.download(google_2011_q3_rpt).cash_flow_statement
48
+ end
49
+
50
+ context "when given a Q1 report" do
51
+ subject { @cash_flow_stmt_2011_q1.latest_quarterly_reformulated(nil) }
52
+ 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
56
+ end
57
+
58
+ context "when given a Q2 report (and a previous Q1 report)" do
59
+ subject { @cash_flow_stmt_2011_q2.latest_quarterly_reformulated(@cash_flow_stmt_2011_q1) }
60
+ it { should be_an_instance_of FinModeling::ReformulatedCashFlowStatement }
61
+ it "should be valid" do
62
+ subject.cash_investments_in_operations.total.abs.should be > 1.0
63
+ end
64
+ end
65
+
66
+ context "when given a Q3 report (and a previous Q2 report)" do
67
+ subject { @cash_flow_stmt_2011_q3.latest_quarterly_reformulated(@cash_flow_stmt_2011_q2) }
68
+ 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
72
+ end
73
+
74
+ context "when given an annual report (and a previous Q3 report)" do
75
+ subject { @cash_flow_stmt.latest_quarterly_reformulated(@cash_flow_stmt_2011_q3) }
76
+ 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
80
+ end
81
+ end
82
+
83
+ describe "write_constructor" do
84
+ before(:all) do
85
+ file_name = "/tmp/finmodeling-cash_flow_stmt.rb"
86
+ item_name = "@cfs"
87
+ file = File.open(file_name, "w")
88
+ @cash_flow_stmt.write_constructor(file, item_name)
89
+ file.close
90
+
91
+ eval(File.read(file_name))
92
+
93
+ @loaded_cfs = eval(item_name)
94
+ end
95
+
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
105
+ end
106
+
107
+ end
108
+
@@ -0,0 +1,74 @@
1
+ # company_filing_calculation_spec.rb
2
+
3
+ require 'spec_helper'
4
+
5
+ describe FinModeling::CompanyFilingCalculation do
6
+ before(:all) do
7
+ google_2011_annual_rpt = "http://www.sec.gov/Archives/edgar/data/1288776/000119312512025336/0001193125-12-025336-index.htm"
8
+ @filing = FinModeling::AnnualReportFiling.download google_2011_annual_rpt
9
+ end
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
19
+
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
29
+
30
+ 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
37
+ end
38
+
39
+ 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
46
+ 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
52
+ end
53
+ end
54
+
55
+ 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
70
+
71
+ @assets.leaf_items_sum(:period=>@period, :mapping=>mapping).should be_within(1.0).of(42817000000.0)
72
+ end
73
+ end
74
+ end