finmodeling 0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+