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,93 @@
1
+ @item = FinModeling::MultiColumnCalculationSummary.new
2
+ @item.title = ""
3
+ @item.num_value_columns = 9
4
+ @item.key_width = 18
5
+ @item.val_width = 12
6
+ @item.max_decimals = 4
7
+ @item.totals_row_enabled = false
8
+ args = { }
9
+ args[:key] = ""
10
+ args[:vals] = ["Unknown...", "2010-03-31", "2010-06-30", "2010-09-30", "2010-12-31", "2011-03-31", "2011-06-30", "2011-09-30", "2011-12-31"]
11
+ @item_header_row = FinModeling::MultiColumnCalculationSummaryHeaderRow.new(args)
12
+ @item.header_row = @item_header_row
13
+ args = { }
14
+ args[:key] = "Revenue (000's)"
15
+ args[:type] = ""
16
+ args[:vals] = [0, 1596960.0, 1601379.0, 1601203.0, 1525109.0, 1214357.0, 1229024.0, 1216665.0, 1324153.0]
17
+ @item_row0 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
18
+ args = { }
19
+ args[:key] = "Core OI (000's)"
20
+ args[:type] = ""
21
+ args[:vals] = [0, 171660.0, 117788.0, 173458.0, 230022.0, 146258.0, 133437.0, 126070.0, 178193.0]
22
+ @item_row1 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
23
+ args = { }
24
+ args[:key] = "OI (000's)"
25
+ args[:type] = ""
26
+ args[:vals] = [0, 168792.0, 111254.0, 169715.0, 205495.0, 139384.0, 133283.0, 127839.0, 167579.0]
27
+ @item_row2 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
28
+ args = { }
29
+ args[:key] = "FI (000's)"
30
+ args[:type] = ""
31
+ args[:vals] = [0, 141399.0, 102067.0, 226416.0, 106525.0, 83608.0, 103689.0, 165452.0, 127993.0]
32
+ @item_row3 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
33
+ args = { }
34
+ args[:key] = "NI (000's)"
35
+ args[:type] = ""
36
+ args[:vals] = [0, 310191.0, 213321.0, 396131.0, 312020.0, 222992.0, 236972.0, 293291.0, 295572.0]
37
+ @item_row4 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
38
+ args = { }
39
+ args[:key] = "Gross Margin"
40
+ args[:type] = ""
41
+ args[:vals] = [0, 0.5576708245666767, 0.5736661964469373, 0.5748484108510913, 0.6343297429888618, 0.689167188890911, 0.6980083383237431, 0.7047042530195247, 0.7018796166304045]
42
+ @item_row5 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
43
+ args = { }
44
+ args[:key] = "Sales PM"
45
+ args[:type] = ""
46
+ args[:vals] = [0, 0.10749148381925659, 0.07355385577055774, 0.10832951849328286, 0.15082361326305202, 0.12044085882487604, 0.10857147622829172, 0.10361968988998616, 0.13457104277224763]
47
+ @item_row6 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
48
+ args = { }
49
+ args[:key] = "Operating PM"
50
+ args[:type] = ""
51
+ args[:vals] = [0, 0.10569569682396554, 0.06947374731403372, 0.10599208844849779, 0.13474099228317452, 0.1147804558297107, 0.10844613286640456, 0.10507337681284495, 0.12655546602243095]
52
+ @item_row7 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
53
+ args = { }
54
+ args[:key] = "FI / Sales"
55
+ args[:type] = ""
56
+ args[:vals] = [0, 0.0885427311892596, 0.06373706661571059, 0.14140377578608085, 0.06984766334734108, 0.06884923461552081, 0.08436702619314188, 0.13598804929869768, 0.09666043123415496]
57
+ @item_row8 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
58
+ args = { }
59
+ args[:key] = "NI / Sales"
60
+ args[:type] = ""
61
+ args[:vals] = [0, 0.19423842801322513, 0.1332108139297443, 0.24739586423457863, 0.2045886556305156, 0.1836296904452315, 0.19281315905954644, 0.24106142611154263, 0.22321589725658592]
62
+ @item_row9 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
63
+ args = { }
64
+ args[:key] = "Sales / NOA"
65
+ args[:type] = ""
66
+ args[:vals] = [0, 0, 0.31589471931674, 0.3027171516963245, 0.2812087090658527, 0.22230803971150923, 0.21556306903197936, 0.2058721704664517, 0.22777909563225565]
67
+ @item_row10 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
68
+ args = { }
69
+ args[:key] = "FI / NFA"
70
+ args[:type] = ""
71
+ args[:vals] = [0, 0, 0.01388443269851155, 0.03307811869055056, 0.01601019419285102, 0.011782960277534123, 0.014556844204896556, 0.0241571697495845, 0.019256398729732317]
72
+ @item_row11 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
73
+ args = { }
74
+ args[:key] = "Revenue Growth"
75
+ args[:type] = ""
76
+ args[:vals] = [0, 0, 0.01114526118072745, -0.00043596613617347124, -0.17565789513442365, -0.6030968787992619, 0.04933275376563051, -0.03930454685490847, 0.39916742999669985]
77
+ @item_row12 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
78
+ args = { }
79
+ args[:key] = "Core OI Growth"
80
+ args[:type] = ""
81
+ args[:vals] = [0, 0, -0.7792359004477285, 3.644010853978843, 2.0641568409990847, -0.8406048886193428, -0.30787317437309647, -0.20172411964684556, 2.9464409948934227]
82
+ @item_row13 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
83
+ args = { }
84
+ args[:key] = "OI Growth"
85
+ args[:type] = ""
86
+ args[:vals] = [0, 0, -0.8121268899268889, 4.341206465523186, 1.136062554145436, -0.7928481118131626, -0.16434540325239466, -0.1524844979682214, 1.926767066125802]
87
+ @item_row14 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
88
+ args = { }
89
+ args[:key] = "ReOI (000's)"
90
+ args[:type] = ""
91
+ args[:vals] = [0, 0, -10648.0, 41106.0, 73629.0, 9489.0, -3819.0, -15854.0, 26232.0]
92
+ @item_row15 = FinModeling::MultiColumnCalculationSummaryRow.new(args)
93
+ @item.rows = [@item_row0,@item_row1,@item_row2,@item_row3,@item_row4,@item_row5,@item_row6,@item_row7,@item_row8,@item_row9,@item_row10,@item_row11,@item_row12,@item_row13,@item_row14,@item_row15]
@@ -0,0 +1,31 @@
1
+ module FinModeling
2
+ module Mocks
3
+ class Filing_10K
4
+ attr_accessor :term, :date, :link
5
+ def initialize
6
+ @term="10-K"
7
+ @date="1994-01-26T00:00:00-05:00"
8
+ @link="http://www.sec.gov/Archives/edgar/data/320193/000119312512023398/0001193125-12-023398-index.htm"
9
+ end
10
+ end
11
+
12
+ class Filing_10Q
13
+ attr_accessor :term, :date, :link
14
+ def initialize
15
+ @term="10-Q"
16
+ @date="1995-01-26T00:00:00-05:00"
17
+ @link="http://www.sec.gov/Archives/edgar/data/1288776/000119312511282235/0001193125-11-282235-index.htm"
18
+ end
19
+ end
20
+
21
+ class Entity
22
+ attr_accessor :name, :filings
23
+ def initialize
24
+ @name = "Apple Inc"
25
+ @filings = []
26
+ @filings.push Mocks::Filing_10K.new
27
+ @filings.push Mocks::Filing_10Q.new
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,23 @@
1
+ # income_statement_calculation_spec.rb
2
+
3
+ require 'spec_helper'
4
+
5
+ describe FinModeling::NetIncomeCalculation 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
+ @inc_stmt = filing.income_statement
10
+ @period = @inc_stmt.periods.last
11
+ @ni = @inc_stmt.net_income_calculation
12
+ end
13
+
14
+ describe "summary" do
15
+ it "only requires a period (knows how debts/credits work and whether to flip the total)" do
16
+ @ni.summary(:period=>@period).should be_an_instance_of FinModeling::CalculationSummary
17
+ end
18
+ it "tags each row with an Income Statement Type" do
19
+ FinModeling::IncomeStatementItem::TYPES.include?(@ni.summary(:period=>@period).rows.first.type).should be_true
20
+ end
21
+ end
22
+ end
23
+
@@ -0,0 +1,52 @@
1
+ # period_array_spec.rb
2
+
3
+ require 'spec_helper'
4
+
5
+ describe FinModeling::PeriodArray do
6
+ before(:all) do
7
+ @t_now = Date.parse('2012-01-01')
8
+ @t_3mo_ago = Date.parse('2011-09-01')
9
+ @t_6mo_ago = Date.parse('2011-06-01')
10
+ @t_0mo_ago = Date.parse('2011-03-01')
11
+ @t_1yr_ago = Date.parse('2011-01-01')
12
+
13
+ @arr = FinModeling::PeriodArray.new
14
+ @arr.push Xbrlware::Context::Period.new({"start_date"=>@t_1yr_ago, "end_date"=>@t_now}) # 1 yr
15
+ @arr.push Xbrlware::Context::Period.new({"start_date"=>@t_1yr_ago, "end_date"=>@t_3mo_ago}) # 9 mo
16
+ @arr.push Xbrlware::Context::Period.new({"start_date"=>@t_1yr_ago, "end_date"=>@t_6mo_ago}) # 6 mo
17
+ @arr.push Xbrlware::Context::Period.new({"start_date"=>@t_3mo_ago, "end_date"=>@t_now}) # 3 mo
18
+ end
19
+
20
+ describe "yearly" do
21
+ subject { @arr.yearly }
22
+ it { should be_an_instance_of FinModeling::PeriodArray }
23
+ it "returns only annual periods" do
24
+ subject.first.to_pretty_s.should == @arr[0].to_pretty_s
25
+ end
26
+ end
27
+
28
+ describe "threequarterly" do
29
+ subject { @arr.threequarterly }
30
+ it { should be_an_instance_of FinModeling::PeriodArray }
31
+ it "returns only three-quarter periods" do
32
+ subject.first.to_pretty_s.should == @arr[1].to_pretty_s
33
+ end
34
+ end
35
+
36
+ describe "halfyearly" do
37
+ subject { @arr.halfyearly }
38
+ it { should be_an_instance_of FinModeling::PeriodArray }
39
+ it "returns only two-quarter periods" do
40
+ subject.first.to_pretty_s.should == @arr[2].to_pretty_s
41
+ end
42
+ end
43
+
44
+ describe "quarterly" do
45
+ subject { @arr.quarterly }
46
+ it { should be_an_instance_of FinModeling::PeriodArray }
47
+ it "returns only quarterly periods" do
48
+ subject.first.to_pretty_s.should == @arr[3].to_pretty_s
49
+ end
50
+ end
51
+
52
+ end
@@ -0,0 +1,69 @@
1
+ # quarterly_report_filing_spec.rb
2
+
3
+ require 'spec_helper'
4
+
5
+ describe FinModeling::QuarterlyReportFiling do
6
+ before(:all) do
7
+ company = FinModeling::Company.new(FinModeling::Mocks::Entity.new)
8
+ filing_url = company.quarterly_reports.last.link
9
+ FinModeling::Config::disable_caching
10
+ @filing = FinModeling::QuarterlyReportFiling.download(filing_url)
11
+ end
12
+
13
+ after(:all) do
14
+ FinModeling::Config::enable_caching
15
+ end
16
+
17
+ subject { @filing }
18
+ its(:balance_sheet) { should be_a FinModeling::BalanceSheetCalculation }
19
+ its(:income_statement) { should be_a FinModeling::IncomeStatementCalculation }
20
+ its(:cash_flow_statement) { should be_a FinModeling::CashFlowStatementCalculation }
21
+
22
+ its(:is_valid?) { should == (@filing.income_statement.is_valid? && @filing.balance_sheet.is_valid? && @filing.cash_flow_statement.is_valid?) }
23
+
24
+ describe "write_constructor" do
25
+ before(:all) do
26
+ file_name = "/tmp/finmodeling-quarterly-rpt.rb"
27
+ schema_version_item_name = "@schema_version"
28
+ item_name = "@quarterly_rpt"
29
+ file = File.open(file_name, "w")
30
+ @filing.write_constructor(file, item_name)
31
+ file.close
32
+
33
+ eval(File.read(file_name))
34
+
35
+ @schema_version = eval(schema_version_item_name)
36
+ @loaded_filing = eval(item_name)
37
+ end
38
+
39
+ it "writes itself to a file, and saves a schema version of 1.1" do
40
+ @schema_version.should be == 1.1
41
+ end
42
+
43
+ it "writes itself to a file, and when reloaded, has the same periods" do
44
+ expected_periods = @filing.balance_sheet.periods.map{|x| x.to_pretty_s}.join(',')
45
+ @loaded_filing.balance_sheet.periods.map{|x| x.to_pretty_s}.join(',').should == expected_periods
46
+ end
47
+ it "writes itself to a file, and when reloaded, has the same net operating assets" do
48
+ period = @filing.balance_sheet.periods.last
49
+ expected_noa = @filing.balance_sheet.reformulated(period).net_operating_assets.total
50
+ @loaded_filing.balance_sheet.reformulated(period).net_operating_assets.total.should be_within(1.0).of(expected_noa)
51
+ end
52
+ it "writes itself to a file, and when reloaded, has the same net financing income" do
53
+ period = @filing.income_statement.periods.last
54
+ expected_nfi = @filing.income_statement.reformulated(period).net_financing_income.total
55
+ @loaded_filing.income_statement.reformulated(period).net_financing_income.total.should be_within(1.0).of(expected_nfi)
56
+ end
57
+ it "writes itself to a file, and when reloaded, has the same net change in cash" do
58
+ period = @filing.cash_flow_statement.periods.last
59
+ expected_cash_change = @filing.cash_flow_statement.cash_change_calculation.summary(:period=>period).total
60
+ @loaded_filing.cash_flow_statement.cash_change_calculation.summary(:period=>period).total.should be_within(1.0).of(expected_cash_change)
61
+ end
62
+ it "writes itself to a file, and when reloaded, has the same disclosures" do
63
+ period = @filing.disclosures.first.periods.last
64
+ expected_total = @filing.disclosures.first.summary(:period=>period).total
65
+ @loaded_filing.disclosures.first.summary(:period=>period).total.should == expected_total
66
+ end
67
+
68
+ end
69
+ end
data/spec/rate_spec.rb ADDED
@@ -0,0 +1,33 @@
1
+ # rate_spec.rb
2
+
3
+ require 'spec_helper'
4
+
5
+ describe FinModeling::Rate do
6
+ describe ".annualize" do
7
+ let(:val) { 0.2 }
8
+ context "when annualizing from a quarter to a year" do
9
+ subject { FinModeling::Rate.new(val).annualize(from_days=365.0/4.0, to_days=365.0) }
10
+ it { should be_a_kind_of Float }
11
+ it { should be_within(0.0001).of( (val+1.0)**(4.00) - 1.0 ) }
12
+ end
13
+ context "when annualizing from a year to a quarter" do
14
+ subject { FinModeling::Rate.new(val).annualize(from_days=365.0, to_days=365.0/4.0) }
15
+ it { should be_a_kind_of Float }
16
+ it { should be_within(0.0001).of( (val+1.0)**(0.25) - 1.0 ) }
17
+ end
18
+ end
19
+
20
+ describe ".yearly_to_quarterly" do
21
+ let(:val) { 0.2 }
22
+ subject { FinModeling::Rate.new(val).yearly_to_quarterly }
23
+ it { should be_a_kind_of Float }
24
+ it { should be_within(0.0001).of( FinModeling::Rate.new(val).annualize(from_days=365.0, to_days=365.0/4.0) ) }
25
+ end
26
+
27
+ describe ".quarterly_to_yearly" do
28
+ let(:val) { 0.2 }
29
+ subject { FinModeling::Rate.new(val).quarterly_to_yearly }
30
+ it { should be_a_kind_of Float }
31
+ it { should be_within(0.0001).of( FinModeling::Rate.new(val).annualize(from_days=365.0/4.0, to_days=365.0) ) }
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ # rate_spec.rb
2
+
3
+ require 'spec_helper'
4
+
5
+ describe FinModeling::Ratio do
6
+ describe ".annualize" do
7
+ let(:val) { 0.2 }
8
+ context "when annualizing from a quarter to a year" do
9
+ subject { FinModeling::Ratio.new(val).annualize(from_days=365.0/4.0, to_days=365.0) }
10
+ it { should be_a_kind_of Float }
11
+ it { should be_within(0.0001).of(val * 4.0) }
12
+ end
13
+ context "when annualizing from a year to a quarter" do
14
+ subject { FinModeling::Ratio.new(val).annualize(from_days=365.0, to_days=365.0/4.0) }
15
+ it { should be_a_kind_of Float }
16
+ it { should be_within(0.0001).of(val / 4.0) }
17
+ end
18
+ end
19
+
20
+ describe ".yearly_to_quarterly" do
21
+ let(:val) { 0.2 }
22
+ subject { FinModeling::Ratio.new(val).yearly_to_quarterly }
23
+ it { should be_a_kind_of Float }
24
+ it { should be_within(0.0001).of( FinModeling::Ratio.new(val).annualize(from_days=365.0, to_days=365.0/4.0) ) }
25
+ end
26
+
27
+ describe ".quarterly_to_yearly" do
28
+ let(:val) { 0.2 }
29
+ subject { FinModeling::Ratio.new(val).quarterly_to_yearly }
30
+ it { should be_a_kind_of Float }
31
+ it { should be_within(0.0001).of( FinModeling::Ratio.new(val).annualize(from_days=365.0/4.0, to_days=365.0) ) }
32
+ end
33
+ end
@@ -0,0 +1,146 @@
1
+ # reformulated_income_statement_spec.rb
2
+
3
+ require 'spec_helper'
4
+
5
+ describe FinModeling::ReformulatedBalanceSheet do
6
+ before(:all) do
7
+ google_2010_annual_rpt = "http://www.sec.gov/Archives/edgar/data/1288776/000119312510030774/0001193125-10-030774-index.htm"
8
+ filing = FinModeling::AnnualReportFiling.download google_2010_annual_rpt
9
+ @bal_sheet= filing.balance_sheet
10
+
11
+ @period = @bal_sheet.periods.last
12
+ @prev_reformed_bal_sheet = @bal_sheet.reformulated(@period)
13
+
14
+ google_2011_annual_rpt = "http://www.sec.gov/Archives/edgar/data/1288776/000119312512025336/0001193125-12-025336-index.htm"
15
+ filing = FinModeling::AnnualReportFiling.download google_2011_annual_rpt
16
+ @bal_sheet = filing.balance_sheet
17
+ @period = @bal_sheet.periods.last
18
+ @reformed_bal_sheet = @bal_sheet.reformulated(@period)
19
+
20
+ @years_between_sheets = 2.0
21
+ end
22
+
23
+ describe "new" do
24
+ it "takes an assets calculation and a liabs_and_equity calculation and a period and returns a CalculationSummary" do
25
+ rbs = FinModeling::ReformulatedBalanceSheet.new(@period, @bal_sheet.assets_calculation.summary(:period=>@period), @bal_sheet.liabs_and_equity_calculation.summary(:period=>@period))
26
+ rbs.should be_an_instance_of FinModeling::ReformulatedBalanceSheet
27
+ end
28
+ end
29
+
30
+ describe "operating_assets" do
31
+ subject { @reformed_bal_sheet.operating_assets }
32
+ it { should be_an_instance_of FinModeling::CalculationSummary }
33
+ its(:total) { should be_within(0.1).of(26943000000.0) }
34
+ end
35
+
36
+ describe "financial_assets" do
37
+ subject { @reformed_bal_sheet.financial_assets }
38
+ it { should be_an_instance_of FinModeling::CalculationSummary }
39
+ its(:total) { should be_within(0.1).of(45631000000.0) }
40
+ end
41
+
42
+ describe "operating_liabilities" do
43
+ subject { @reformed_bal_sheet.operating_liabilities }
44
+ it { should be_an_instance_of FinModeling::CalculationSummary }
45
+ its(:total) { should be_within(0.1).of(6041000000.0) }
46
+ end
47
+
48
+ describe "financial_liabilities" do
49
+ subject { @reformed_bal_sheet.financial_liabilities }
50
+ it { should be_an_instance_of FinModeling::CalculationSummary }
51
+ its(:total) { should be_within(0.1).of(8388000000.0) }
52
+ end
53
+
54
+ describe "net_operating_assets" do
55
+ subject { @reformed_bal_sheet.net_operating_assets }
56
+ it { should be_an_instance_of FinModeling::CalculationSummary }
57
+ its(:total) { should be_within(0.1).of(20902000000.0) }
58
+ end
59
+
60
+ describe "net_financial_assets" do
61
+ subject { @reformed_bal_sheet.net_financial_assets }
62
+ it { should be_an_instance_of FinModeling::CalculationSummary }
63
+ its(:total) { should be_within(0.1).of(37243000000.0) }
64
+ end
65
+
66
+ describe "common_shareholders_equity" do
67
+ subject { @reformed_bal_sheet.common_shareholders_equity }
68
+ it { should be_an_instance_of FinModeling::CalculationSummary }
69
+ its(:total) { should be_within(0.1).of(58145000000.0) }
70
+ end
71
+
72
+ describe "composition_ratio" do
73
+ subject { @reformed_bal_sheet.composition_ratio }
74
+ it { should be_within(0.1).of(@reformed_bal_sheet.net_operating_assets.total / @reformed_bal_sheet.net_financial_assets.total) }
75
+ end
76
+
77
+ describe "noa_growth" do
78
+ subject { @reformed_bal_sheet.noa_growth(@prev_reformed_bal_sheet) }
79
+ let(:noa0) { @prev_reformed_bal_sheet.net_operating_assets.total }
80
+ let(:noa1) { @reformed_bal_sheet.net_operating_assets.total }
81
+ let(:expected_growth) { FinModeling::Rate.new((noa1-noa0) / noa0).annualize(from=365.0*@years_between_sheets, to=365.0) }
82
+ it { should be_within(0.001).of(expected_growth) }
83
+ end
84
+
85
+ describe "cse_growth" do
86
+ subject { @reformed_bal_sheet.cse_growth(@prev_reformed_bal_sheet) }
87
+ let(:cse0) { @prev_reformed_bal_sheet.common_shareholders_equity.total }
88
+ let(:cse1) { @reformed_bal_sheet.common_shareholders_equity.total }
89
+ let(:expected_growth) { FinModeling::Rate.new((cse1-cse0) / cse0).annualize(from=365.0*@years_between_sheets, to=365.0) }
90
+ it { should be_within(0.001).of(expected_growth) }
91
+ end
92
+
93
+ describe "analysis" do
94
+ subject {@reformed_bal_sheet.analysis(@prev_reformed_bal_sheet) }
95
+ it { should be_an_instance_of FinModeling::CalculationSummary }
96
+ it "contains the expected rows" do
97
+ expected_keys = ["NOA ($MM)", "NFA ($MM)", "CSE ($MM)",
98
+ "Composition Ratio", "NOA Growth", "CSE Growth" ]
99
+
100
+ subject.rows.map{ |row| row.key }.should == expected_keys
101
+ end
102
+ end
103
+
104
+ describe "#forecast_next" do
105
+ before (:all) do
106
+ @company = FinModeling::Company.find("aapl")
107
+ @filings = FinModeling::CompanyFilings.new(@company.filings_since_date(Time.parse("2010-10-01")))
108
+ @policy = FinModeling::GenericForecastingPolicy.new
109
+
110
+ prev_bs_period = @filings.last.balance_sheet.periods.last
111
+ next_bs_period_value = prev_bs_period.value.next_month.next_month.next_month
112
+ @next_bs_period = Xbrlware::Context::Period.new(next_bs_period_value)
113
+
114
+ next_is_period_value = {"start_date" => prev_bs_period.value,
115
+ "end_date" => prev_bs_period.value.next_month.next_month.next_month }
116
+ @next_is_period = Xbrlware::Context::Period.new(next_is_period_value)
117
+ end
118
+
119
+ let(:last_re_is) { @filings.last.income_statement.latest_quarterly_reformulated(nil) }
120
+ let(:last_re_bs) { @filings.last.balance_sheet.reformulated(@filings.last.balance_sheet.periods.last) }
121
+ let(:next_re_is) { FinModeling::ReformulatedIncomeStatement.forecast_next(@next_is_period, @policy, last_re_bs, last_re_is) }
122
+
123
+ subject { FinModeling::ReformulatedBalanceSheet.forecast_next(@next_bs_period, @policy, last_re_bs, next_re_is) }
124
+
125
+ it { should be_a_kind_of FinModeling::ReformulatedBalanceSheet }
126
+ it "should have the given period" do
127
+ subject.period.to_pretty_s == @next_bs_period.to_pretty_s
128
+ end
129
+ it "should set NOA to the same period's operating revenue over the policy's asset turnover" do
130
+ expected_val = next_re_is.operating_revenues.total / FinModeling::Ratio.new(@policy.sales_over_noa).yearly_to_quarterly
131
+ subject.net_operating_assets.total.should == expected_val
132
+ end
133
+ it "should set CSE to last year's CSE plus this year's net income" do
134
+ expected_val = last_re_bs.common_shareholders_equity.total + next_re_is.comprehensive_income.total
135
+ subject.common_shareholders_equity.total.should == expected_val
136
+ end
137
+ it "should set NFA to the gap between CSE and NOA" do
138
+ expected_val = subject.common_shareholders_equity.total - subject.net_operating_assets.total
139
+ subject.net_financial_assets.total.should == expected_val
140
+ end
141
+ it "should have an analysis (with the same rows)" do
142
+ subject.analysis(last_re_bs)
143
+ end
144
+ end
145
+ end
146
+
@@ -0,0 +1,174 @@
1
+ # reformulated_income_statement_spec.rb
2
+
3
+ require 'spec_helper'
4
+
5
+ describe FinModeling::ReformulatedCashFlowStatement do
6
+ before(:all) do
7
+ google_2009_annual_rpt = "http://www.sec.gov/Archives/edgar/data/1288776/000119312510030774/0001193125-10-030774-index.htm"
8
+ filing_2009 = FinModeling::AnnualReportFiling.download google_2009_annual_rpt
9
+ cash_flow_stmt_2009 = filing_2009.cash_flow_statement
10
+ period_2009 = cash_flow_stmt_2009.periods.last
11
+ @reformed_cash_flow_stmt_2009 = cash_flow_stmt_2009.reformulated(period_2009)
12
+
13
+ google_2011_annual_rpt = "http://www.sec.gov/Archives/edgar/data/1288776/000119312512025336/0001193125-12-025336-index.htm"
14
+ filing = FinModeling::AnnualReportFiling.download google_2011_annual_rpt
15
+
16
+ @inc_stmt = filing.income_statement
17
+ @is_period = @inc_stmt.periods.last
18
+ @reformed_inc_stmt = @inc_stmt.reformulated(@is_period)
19
+
20
+ @cash_flow_stmt = filing.cash_flow_statement
21
+ @period = @cash_flow_stmt.periods.last
22
+ @reformed_cash_flow_stmt = @cash_flow_stmt.reformulated(@period)
23
+ end
24
+
25
+ describe "new" do
26
+ it "takes a cash change calculation and a period and returns a CalculationSummary" do
27
+ rcfs = FinModeling::ReformulatedCashFlowStatement.new(@period, @cash_flow_stmt.cash_change_calculation.summary(:period=>@period))
28
+ rcfs.should be_an_instance_of FinModeling::ReformulatedCashFlowStatement
29
+ end
30
+ end
31
+
32
+ subject { @reformed_cash_flow_stmt }
33
+
34
+ describe "cash_from_operations" do
35
+ subject { @reformed_cash_flow_stmt.cash_from_operations }
36
+ it { should be_an_instance_of FinModeling::CalculationSummary }
37
+ it "totals up the values of rows with type :c" do
38
+ sum = @cash_flow_stmt.cash_change_calculation
39
+ .summary(:period=>@period)
40
+ .rows.select{ |row| row.type == :c }
41
+ .map{ |row| row.vals.first }
42
+ .inject(:+)
43
+ subject.total.should == sum
44
+ end
45
+ end
46
+
47
+ describe "cash_investments_in_operations" do
48
+ subject { @reformed_cash_flow_stmt.cash_investments_in_operations }
49
+ it { should be_an_instance_of FinModeling::CalculationSummary }
50
+ it "totals up the values of rows with type :i" do
51
+ sum = @cash_flow_stmt.cash_change_calculation
52
+ .summary(:period=>@period)
53
+ .rows.select{ |row| row.type == :i }
54
+ .map{ |row| row.vals.first }
55
+ .inject(:+)
56
+ subject.total.should == sum
57
+ end
58
+ end
59
+
60
+ describe "payments_to_debtholders" do
61
+ subject { @reformed_cash_flow_stmt.payments_to_debtholders }
62
+ it { should be_an_instance_of FinModeling::CalculationSummary }
63
+ it "totals up the values of rows with type :d, minus the total change in cash" do
64
+ sum = @cash_flow_stmt.cash_change_calculation
65
+ .summary(:period=>@period)
66
+ .rows.select{ |row| row.type == :d }
67
+ .map{ |row| row.vals.first }
68
+ .inject(:+)
69
+ sum = sum - @cash_flow_stmt.cash_change_calculation
70
+ .summary(:period=>@period)
71
+ .total
72
+ subject.total.should == sum
73
+ end
74
+ end
75
+
76
+ describe "payments_to_stockholders" do
77
+ subject { @reformed_cash_flow_stmt.payments_to_stockholders }
78
+ it { should be_an_instance_of FinModeling::CalculationSummary }
79
+ it "totals up the values of rows with type :f" do
80
+ sum = @cash_flow_stmt.cash_change_calculation
81
+ .summary(:period=>@period)
82
+ .rows.select{ |row| row.type == :f }
83
+ .map{ |row| row.vals.first }
84
+ .inject(:+)
85
+ subject.total.should == sum
86
+ end
87
+ end
88
+
89
+ describe "free_cash_flow" do
90
+ subject { @reformed_cash_flow_stmt.free_cash_flow }
91
+ it { should be_an_instance_of FinModeling::CalculationSummary }
92
+ it "totals up cash from operations and cash investments in operations" do
93
+ sum = @reformed_cash_flow_stmt.cash_from_operations.total
94
+ sum = sum + @reformed_cash_flow_stmt.cash_investments_in_operations.total
95
+ subject.total.should == sum
96
+ end
97
+ end
98
+
99
+ describe "ni_over_c" do
100
+ subject { @reformed_cash_flow_stmt.ni_over_c(@reformed_inc_stmt) }
101
+ it { should be_an_instance_of Float }
102
+ it { should be_within(0.1).of(@reformed_inc_stmt.comprehensive_income.total / @reformed_cash_flow_stmt.cash_from_operations.total) }
103
+ end
104
+
105
+ describe "financing_flows" do
106
+ subject { @reformed_cash_flow_stmt.financing_flows }
107
+ it { should be_an_instance_of FinModeling::CalculationSummary }
108
+ it "totals up payments to both debtholders and stockholders" do
109
+ sum = @reformed_cash_flow_stmt.payments_to_debtholders.total
110
+ sum = sum + @reformed_cash_flow_stmt.payments_to_stockholders.total
111
+ subject.total.should == sum
112
+ end
113
+ end
114
+
115
+ describe "analysis" do
116
+ subject { @reformed_cash_flow_stmt.analysis(@reformed_inc_stmt) }
117
+
118
+ it { should be_an_instance_of FinModeling::CalculationSummary }
119
+ it "contains the expected rows" do
120
+ expected_keys = [ "C ($MM)", "I ($MM)", "d ($MM)", "F ($MM)", "FCF ($MM)", "NI / C" ]
121
+ subject.rows.map{ |row| row.key }.should == expected_keys
122
+ end
123
+ end
124
+
125
+ describe "-" do
126
+ before(:all) do
127
+ google_2011_q3_rpt = "http://www.sec.gov/Archives/edgar/data/1288776/000119312511282235/0001193125-11-282235-index.htm"
128
+ @filing_2011_q3 = FinModeling::AnnualReportFiling.download google_2011_q3_rpt
129
+
130
+ @cash_flow_stmt_2011_q3 = @filing_2011_q3.cash_flow_statement
131
+ cfs_period_2011_q3 = @cash_flow_stmt_2011_q3.periods.threequarterly.last
132
+ @reformed_cash_flow_stmt_2011_q3 = @cash_flow_stmt_2011_q3.reformulated(cfs_period_2011_q3)
133
+
134
+ @diff = @reformed_cash_flow_stmt - @reformed_cash_flow_stmt_2011_q3
135
+ end
136
+ subject { @diff }
137
+
138
+ it { should be_an_instance_of FinModeling::ReformulatedCashFlowStatement }
139
+ its(:period) { should_not be_nil } # FIXME
140
+
141
+ it "returns the difference between the two re_cfs's for each calculation" do
142
+ methods = [ :cash_from_operations, :cash_investments_in_operations,
143
+ :payments_to_debtholders, :payments_to_stockholders,
144
+ :free_cash_flow, :financing_flows ]
145
+ methods.each do |method|
146
+ expected_val = @reformed_cash_flow_stmt.send(method).total - @reformed_cash_flow_stmt_2011_q3.send(method).total
147
+ @diff.send(method).total.should be_within(1.0).of(expected_val)
148
+ end
149
+ end
150
+
151
+ it "returns values that are close to 1/4th of the annual value" do
152
+ methods = [ :cash_from_operations, :cash_investments_in_operations,
153
+ #:payments_to_debtholders, :payments_to_stockholders,
154
+ :free_cash_flow, :financing_flows ]
155
+
156
+ methods.each do |method|
157
+ orig = @reformed_cash_flow_stmt.send(method).total
158
+ max = (orig > 0) ? (0.35 * orig) : (0.15 * orig)
159
+ min = (orig > 0) ? (0.15 * orig) : (0.35 * orig)
160
+ actual = @diff.send(method).total
161
+ if (actual < min) || (actual > max)
162
+ err = "#{method} returns #{actual.to_nearest_thousand.to_s.with_thousands_separators}, "
163
+ err += "which is outside bounds: [#{min.to_nearest_thousand.to_s.with_thousands_separators}, "
164
+ err += "#{max.to_nearest_thousand.to_s.with_thousands_separators}]"
165
+ puts err
166
+ end
167
+ @diff.send(method).total.should be > min
168
+ @diff.send(method).total.should be < max
169
+ end
170
+ end
171
+ end
172
+
173
+ end
174
+