finmodeling 0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. data/.gitignore +0 -0
  2. data/Gemfile +2 -0
  3. data/README.md +289 -269
  4. data/Rakefile +12 -0
  5. data/TODO.txt +113 -20
  6. data/examples/{dump_report.rb → dump_latest_10k.rb} +1 -1
  7. data/examples/list_disclosures.rb +50 -0
  8. data/examples/lists/nasdaq-mid-to-mega-tech-symbols.txt +0 -0
  9. data/examples/show_report.rb +112 -32
  10. data/examples/show_reports.rb +162 -33
  11. data/finmodeling.gemspec +4 -1
  12. data/lib/finmodeling/annual_report_filing.rb +97 -18
  13. data/lib/finmodeling/array_with_stats.rb +0 -0
  14. data/lib/finmodeling/assets_calculation.rb +12 -3
  15. data/lib/finmodeling/assets_item.rb +0 -0
  16. data/lib/finmodeling/assets_item_vectors.rb +0 -0
  17. data/lib/finmodeling/balance_sheet_analyses.rb +19 -4
  18. data/lib/finmodeling/balance_sheet_calculation.rb +52 -37
  19. data/lib/finmodeling/calculation_summary.rb +119 -14
  20. data/lib/finmodeling/can_cache_classifications.rb +0 -0
  21. data/lib/finmodeling/can_cache_summaries.rb +0 -0
  22. data/lib/finmodeling/can_choose_successive_periods.rb +15 -0
  23. data/lib/finmodeling/can_classify_rows.rb +0 -0
  24. data/lib/finmodeling/capm.rb +80 -0
  25. data/lib/finmodeling/cash_change_calculation.rb +3 -3
  26. data/lib/finmodeling/cash_change_item.rb +0 -0
  27. data/lib/finmodeling/cash_change_item_vectors.rb +0 -0
  28. data/lib/finmodeling/cash_change_summary_from_differences.rb +36 -0
  29. data/lib/finmodeling/cash_flow_statement_analyses.rb +36 -0
  30. data/lib/finmodeling/cash_flow_statement_calculation.rb +28 -52
  31. data/lib/finmodeling/classifiers.rb +2 -0
  32. data/lib/finmodeling/company.rb +0 -0
  33. data/lib/finmodeling/company_filing.rb +30 -7
  34. data/lib/finmodeling/company_filing_calculation.rb +16 -6
  35. data/lib/finmodeling/company_filings.rb +112 -46
  36. data/lib/finmodeling/comprehensive_income_calculation.rb +60 -0
  37. data/lib/finmodeling/comprehensive_income_statement_calculation.rb +74 -0
  38. data/lib/finmodeling/comprehensive_income_statement_item.rb +20 -0
  39. data/lib/finmodeling/comprehensive_income_statement_item_vectors.rb +235 -0
  40. data/lib/finmodeling/config.rb +0 -0
  41. data/lib/finmodeling/debt_cost_of_capital.rb +14 -0
  42. data/lib/finmodeling/equity_change_calculation.rb +43 -0
  43. data/lib/finmodeling/equity_change_item.rb +25 -0
  44. data/lib/finmodeling/equity_change_item_vectors.rb +156 -0
  45. data/lib/finmodeling/factory.rb +0 -0
  46. data/lib/finmodeling/fama_french_cost_of_equity.rb +119 -0
  47. data/lib/finmodeling/float_helpers.rb +14 -8
  48. data/lib/finmodeling/forecasted_reformulated_balance_sheet.rb +55 -0
  49. data/lib/finmodeling/forecasted_reformulated_income_statement.rb +110 -0
  50. data/lib/finmodeling/forecasts.rb +4 -4
  51. data/lib/finmodeling/has_string_classifer.rb +0 -0
  52. data/lib/finmodeling/income_statement_analyses.rb +23 -17
  53. data/lib/finmodeling/income_statement_calculation.rb +46 -43
  54. data/lib/finmodeling/income_statement_item.rb +1 -1
  55. data/lib/finmodeling/income_statement_item_vectors.rb +24 -13
  56. data/lib/finmodeling/invalid_filing_error.rb +4 -0
  57. data/lib/finmodeling/liabs_and_equity_calculation.rb +18 -8
  58. data/lib/finmodeling/liabs_and_equity_item.rb +1 -1
  59. data/lib/finmodeling/liabs_and_equity_item_vectors.rb +24 -24
  60. data/lib/finmodeling/linear_trend_forecasting_policy.rb +23 -0
  61. data/lib/finmodeling/net_income_calculation.rb +23 -10
  62. data/lib/finmodeling/net_income_summary_from_differences.rb +51 -0
  63. data/lib/finmodeling/paths.rb +0 -0
  64. data/lib/finmodeling/period_array.rb +8 -4
  65. data/lib/finmodeling/quarterly_report_filing.rb +9 -4
  66. data/lib/finmodeling/rate.rb +8 -0
  67. data/lib/finmodeling/ratio.rb +0 -0
  68. data/lib/finmodeling/reformulated_balance_sheet.rb +47 -88
  69. data/lib/finmodeling/reformulated_cash_flow_statement.rb +18 -41
  70. data/lib/finmodeling/reformulated_income_statement.rb +44 -206
  71. data/lib/finmodeling/reformulated_shareholder_equity_statement.rb +50 -0
  72. data/lib/finmodeling/reoi_valuation.rb +104 -0
  73. data/lib/finmodeling/shareholder_equity_statement_calculation.rb +34 -0
  74. data/lib/finmodeling/string_helpers.rb +18 -1
  75. data/lib/finmodeling/time_series_estimator.rb +25 -0
  76. data/lib/finmodeling/trailing_avg_forecasting_policy.rb +23 -0
  77. data/lib/finmodeling/version.rb +1 -1
  78. data/lib/finmodeling/weighted_avg_cost_of_capital.rb +35 -0
  79. data/lib/finmodeling/yahoo_finance_helpers.rb +20 -0
  80. data/lib/finmodeling.rb +33 -2
  81. data/spec/annual_report_filing_spec.rb +81 -45
  82. data/spec/assets_calculation_spec.rb +7 -4
  83. data/spec/assets_item_spec.rb +9 -14
  84. data/spec/balance_sheet_analyses_spec.rb +13 -13
  85. data/spec/balance_sheet_calculation_spec.rb +45 -51
  86. data/spec/calculation_summary_spec.rb +113 -21
  87. data/spec/can_classify_rows_spec.rb +0 -0
  88. data/spec/cash_change_calculation_spec.rb +1 -10
  89. data/spec/cash_change_item_spec.rb +10 -18
  90. data/spec/cash_flow_statement_calculation_spec.rb +10 -24
  91. data/spec/company_beta_spec.rb +53 -0
  92. data/spec/company_filing_calculation_spec.rb +39 -49
  93. data/spec/company_filing_spec.rb +0 -0
  94. data/spec/company_filings_spec.rb +75 -25
  95. data/spec/company_spec.rb +37 -47
  96. data/spec/comprehensive_income_statement_calculation_spec.rb +54 -0
  97. data/spec/comprehensive_income_statement_item_spec.rb +56 -0
  98. data/spec/debt_cost_of_capital_spec.rb +19 -0
  99. data/spec/equity_change_calculation_spec.rb +33 -0
  100. data/spec/equity_change_item_spec.rb +58 -0
  101. data/spec/factory_spec.rb +2 -2
  102. data/spec/forecasts_spec.rb +2 -2
  103. data/spec/income_statement_analyses_spec.rb +23 -21
  104. data/spec/income_statement_calculation_spec.rb +17 -49
  105. data/spec/income_statement_item_spec.rb +17 -29
  106. data/spec/liabs_and_equity_calculation_spec.rb +6 -3
  107. data/spec/liabs_and_equity_item_spec.rb +14 -22
  108. data/spec/linear_trend_forecasting_policy_spec.rb +37 -0
  109. data/spec/matchers/custom_matchers.rb +79 -0
  110. data/spec/mocks/calculation.rb +0 -0
  111. data/spec/mocks/income_statement_analyses.rb +0 -0
  112. data/spec/mocks/sec_query.rb +0 -0
  113. data/spec/net_income_calculation_spec.rb +16 -10
  114. data/spec/period_array.rb +0 -0
  115. data/spec/quarterly_report_filing_spec.rb +21 -38
  116. data/spec/rate_spec.rb +0 -0
  117. data/spec/ratio_spec.rb +0 -0
  118. data/spec/reformulated_balance_sheet_spec.rb +56 -33
  119. data/spec/reformulated_cash_flow_statement_spec.rb +18 -10
  120. data/spec/reformulated_income_statement_spec.rb +16 -15
  121. data/spec/reformulated_shareholder_equity_statement_spec.rb +43 -0
  122. data/spec/reoi_valuation_spec.rb +146 -0
  123. data/spec/shareholder_equity_statement_calculation_spec.rb +59 -0
  124. data/spec/spec_helper.rb +4 -1
  125. data/spec/string_helpers_spec.rb +15 -13
  126. data/spec/time_series_estimator_spec.rb +61 -0
  127. data/spec/trailing_avg_forecasting_policy_spec.rb +37 -0
  128. data/spec/weighted_avg_cost_of_capital_spec.rb +32 -0
  129. data/tools/create_equity_change_training_vectors.rb +49 -0
  130. data/tools/time_specs.sh +7 -0
  131. metadata +182 -36
  132. data/lib/finmodeling/constant_forecasting_policy.rb +0 -23
  133. data/lib/finmodeling/generic_forecasting_policy.rb +0 -19
  134. data/spec/constant_forecasting_policy_spec.rb +0 -37
  135. data/spec/generic_forecasting_policy_spec.rb +0 -33
@@ -2,41 +2,6 @@ module FinModeling
2
2
  class ReformulatedCashFlowStatement
3
3
  attr_accessor :period
4
4
 
5
- class FakeCashChangeSummary
6
- def initialize(re_cfs1, re_cfs2)
7
- @re_cfs1 = re_cfs1
8
- @re_cfs2 = re_cfs2
9
- end
10
- def filter_by_type(key)
11
- case key
12
- when :c
13
- @cs = FinModeling::CalculationSummary.new
14
- @cs.title = "Cash from Operations"
15
- @cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @re_cfs1.cash_from_operations.total] ),
16
- CalculationRow.new(:key => "Second Row", :vals => [-@re_cfs2.cash_from_operations.total] ) ]
17
- return @cs
18
- when :i
19
- @cs = FinModeling::CalculationSummary.new
20
- @cs.title = "Cash Investments in Operations"
21
- @cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @re_cfs1.cash_investments_in_operations.total] ),
22
- CalculationRow.new(:key => "Second Row", :vals => [-@re_cfs2.cash_investments_in_operations.total] ) ]
23
- return @cs
24
- when :d
25
- @cs = FinModeling::CalculationSummary.new
26
- @cs.title = "Payments to Debtholders"
27
- @cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @re_cfs1.payments_to_debtholders.total] ),
28
- CalculationRow.new(:key => "Second Row", :vals => [-@re_cfs2.payments_to_debtholders.total] ) ]
29
- return @cs
30
- when :f
31
- @cs = FinModeling::CalculationSummary.new
32
- @cs.title = "Payments to Stockholders"
33
- @cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @re_cfs1.payments_to_stockholders.total] ),
34
- CalculationRow.new(:key => "Second Row", :vals => [-@re_cfs2.payments_to_stockholders.total] ) ]
35
- return @cs
36
- end
37
- end
38
- end
39
-
40
5
  def initialize(period, cash_change_summary)
41
6
  @period = period
42
7
 
@@ -50,7 +15,7 @@ module FinModeling
50
15
  @d.title = "Payments to debtholders"
51
16
  @f.title = "Payments to stockholders"
52
17
 
53
- if cash_change_summary.class != FakeCashChangeSummary
18
+ if !(cash_change_summary.is_a? CashChangeSummaryFromDifferences)
54
19
  @d.rows << CalculationRow.new(:key => "Investment in Cash and Equivalents",
55
20
  :type => :d,
56
21
  :vals => [-cash_change_summary.total])
@@ -58,7 +23,7 @@ module FinModeling
58
23
  end
59
24
 
60
25
  def -(re_cfs2)
61
- summary = FakeCashChangeSummary.new(self, re_cfs2)
26
+ summary = CashChangeSummaryFromDifferences.new(self, re_cfs2)
62
27
  return ReformulatedCashFlowStatement.new(@period, summary)
63
28
  end
64
29
 
@@ -105,10 +70,10 @@ module FinModeling
105
70
  analysis.header_row = CalculationHeader.new(:key => "", :vals => ["Unknown..."])
106
71
 
107
72
  analysis.rows = []
108
- analysis.rows << CalculationRow.new(:key => "C ($MM)", :vals => [nil])
109
- analysis.rows << CalculationRow.new(:key => "I ($MM)", :vals => [nil])
110
- analysis.rows << CalculationRow.new(:key => "d ($MM)", :vals => [nil])
111
- analysis.rows << CalculationRow.new(:key => "F ($MM)", :vals => [nil])
73
+ analysis.rows << CalculationRow.new(:key => "C ($MM)", :vals => [nil])
74
+ analysis.rows << CalculationRow.new(:key => "I ($MM)", :vals => [nil])
75
+ analysis.rows << CalculationRow.new(:key => "d ($MM)", :vals => [nil])
76
+ analysis.rows << CalculationRow.new(:key => "F ($MM)", :vals => [nil])
112
77
  analysis.rows << CalculationRow.new(:key => "FCF ($MM)", :vals => [nil])
113
78
  analysis.rows << CalculationRow.new(:key => "NI / C", :vals => [nil])
114
79
 
@@ -136,5 +101,17 @@ module FinModeling
136
101
  return analysis
137
102
  end
138
103
 
104
+ ALLOWED_IMBALANCE = 1.0
105
+ def flows_are_balanced?
106
+ (free_cash_flow.total - financing_flows.total) < ALLOWED_IMBALANCE
107
+ end
108
+
109
+ def flows_are_plausible?
110
+ return [ payments_to_debtholders,
111
+ payments_to_stockholders,
112
+ cash_from_operations,
113
+ cash_investments_in_operations ].all?{ |x| x.total.abs > 1.0 }
114
+ end
115
+
139
116
  end
140
117
  end
@@ -1,86 +1,45 @@
1
1
  module FinModeling
2
2
  class ReformulatedIncomeStatement
3
3
  attr_accessor :period
4
+ attr_accessor :operating_revenues, :cost_of_revenues, :operating_expenses
4
5
 
5
- class FakeNetIncomeSummary
6
- def initialize(ris1, ris2)
7
- @ris1 = ris1
8
- @ris2 = ris2
9
- end
10
- def filter_by_type(key)
11
- case key
12
- when :or
13
- @cs = FinModeling::CalculationSummary.new
14
- @cs.title = "Operating Revenues"
15
- @cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @ris1.operating_revenues.total] ),
16
- CalculationRow.new(:key => "Second Row", :vals => [-@ris2.operating_revenues.total] ) ]
17
- return @cs
18
- when :cogs
19
- @cs = FinModeling::CalculationSummary.new
20
- @cs.title = "Cost of Revenues"
21
- @cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @ris1.cost_of_revenues.total] ),
22
- CalculationRow.new(:key => "Second Row", :vals => [-@ris2.cost_of_revenues.total] ) ]
23
- return @cs
24
- when :oe
25
- @cs = FinModeling::CalculationSummary.new
26
- @cs.title = "Operating Expenses"
27
- @cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @ris1.operating_expenses.total] ),
28
- CalculationRow.new(:key => "Second Row", :vals => [-@ris2.operating_expenses.total] ) ]
29
- return @cs
30
- when :oibt
31
- @cs = FinModeling::CalculationSummary.new
32
- @cs.title = "Operating Income from Sales, Before taxes"
33
- @cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @ris1.operating_income_after_tax.rows[1].vals.first] ),
34
- CalculationRow.new(:key => "Second Row", :vals => [-@ris2.operating_income_after_tax.rows[1].vals.first] ) ]
35
- return @cs
36
- when :fibt
37
- @cs = FinModeling::CalculationSummary.new
38
- @cs.title = "Financing Income, Before Taxes"
39
- @cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @ris1.net_financing_income.rows[0].vals.first] ),
40
- CalculationRow.new(:key => "Second Row", :vals => [-@ris2.net_financing_income.rows[0].vals.first] ) ]
41
- return @cs
42
- when :tax
43
- @cs = FinModeling::CalculationSummary.new
44
- @cs.title = "Taxes"
45
- @cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @ris1.income_from_sales_after_tax.rows[1].vals.first] ),
46
- CalculationRow.new(:key => "Second Row", :vals => [-@ris2.income_from_sales_after_tax.rows[1].vals.first] ) ]
47
- return @cs
48
- when :ooiat
49
- @cs = FinModeling::CalculationSummary.new
50
- @cs.title = "Other Operating Income, After Taxes"
51
- @cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @ris1.operating_income_after_tax.rows[3].vals.first] ),
52
- CalculationRow.new(:key => "Second Row", :vals => [-@ris2.operating_income_after_tax.rows[3].vals.first] ) ]
53
- return @cs
54
- when :fiat
55
- @cs = FinModeling::CalculationSummary.new
56
- @cs.title = "Financing Income, After Taxes"
57
- @cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @ris1.net_financing_income.rows[2].vals.first] ),
58
- CalculationRow.new(:key => "Second Row", :vals => [-@ris2.net_financing_income.rows[2].vals.first] ) ]
59
- return @cs
60
- end
61
- end
62
- end
63
-
64
- def initialize(period, net_income_summary, tax_rate=0.35)
6
+ def initialize(period, net_income_summary, comprehensive_income_summary, tax_rate=0.35) # FIXME: clarify naming. This is effective tax rate? marginal? statutory?
65
7
  @period = period
66
8
  @tax_rate = tax_rate
67
-
68
- @orev = net_income_summary.filter_by_type(:or )
69
- @cogs = net_income_summary.filter_by_type(:cogs )
70
- @oe = net_income_summary.filter_by_type(:oe )
71
- @oibt = net_income_summary.filter_by_type(:oibt )
72
- @fibt = net_income_summary.filter_by_type(:fibt )
73
- @tax = net_income_summary.filter_by_type(:tax )
74
- @ooiat = net_income_summary.filter_by_type(:ooiat)
75
- @fiat = net_income_summary.filter_by_type(:fiat )
9
+
10
+ @operating_revenues = net_income_summary.filter_by_type(:or )
11
+ @cost_of_revenues = net_income_summary.filter_by_type(:cogs)
12
+ @operating_expenses = net_income_summary.filter_by_type(:oe )
13
+ @oibt = net_income_summary.filter_by_type(:oibt )
14
+ @fibt = net_income_summary.filter_by_type(:fibt )
15
+ @tax = net_income_summary.filter_by_type(:tax )
16
+ @ooiat = net_income_summary.filter_by_type(:ooiat )
17
+ @ooiat_nci = net_income_summary.filter_by_type(:ooiat_nci)
18
+ @fiat = net_income_summary.filter_by_type(:fiat )
19
+
20
+ # If there is a CI statement:
21
+ # Strip O-NCI-OCI out of the NI calculation (***)
22
+ # Double-check that the NI portion of the CI calculation matches (NI minus O-NCI-OCI)
23
+ # Add OOCI + 1/2 of the UNKOCI in as an OOIAT
24
+ # Add FOCI + 1/2 of the UNKOCI in as a FIAT
25
+ if comprehensive_income_summary
26
+ @ooiat.rows += comprehensive_income_summary.filter_by_type(:ooci).rows
27
+ @ooiat.rows += comprehensive_income_summary.filter_by_type(:ooci_nci).rows
28
+ @fiat.rows += comprehensive_income_summary.filter_by_type(:foci).rows
29
+ comprehensive_income_summary.filter_by_type(:unkoci).rows.each do |row|
30
+ row.vals = row.vals.map{ |val| val / 2.0 }
31
+ @ooiat.rows << row
32
+ @fiat.rows << row
33
+ end
34
+ end
76
35
 
77
36
  @fibt_tax_effect = (@fibt.total * @tax_rate).round.to_f
78
37
  @nfi = @fibt.total + -@fibt_tax_effect + @fiat.total
79
38
 
80
39
  @oibt_tax_effect = (@oibt.total * @tax_rate).round.to_f
81
40
 
82
- @gm = @orev.total + @cogs.total
83
- @oisbt = @gm + @oe.total
41
+ @gm = @operating_revenues.total + @cost_of_revenues.total
42
+ @oisbt = @gm + @operating_expenses.total
84
43
 
85
44
  @oisat = @oisbt + @tax.total + @fibt_tax_effect + @oibt_tax_effect
86
45
 
@@ -90,35 +49,23 @@ module FinModeling
90
49
  end
91
50
 
92
51
  def -(ris2)
93
- net_income_summary = FakeNetIncomeSummary.new(self, ris2)
94
- return ReformulatedIncomeStatement.new(@period, net_income_summary, @tax_rate)
95
- end
96
-
97
- def operating_revenues
98
- @orev
99
- end
100
-
101
- def cost_of_revenues
102
- @cogs
52
+ net_income_summary = NetIncomeSummaryFromDifferences.new(self, ris2)
53
+ return ReformulatedIncomeStatement.new(@period, net_income_summary, nil, @tax_rate)
103
54
  end
104
55
 
105
56
  def gross_revenue
106
57
  cs = FinModeling::CalculationSummary.new
107
58
  cs.title = "Gross Revenue"
108
- cs.rows = [ CalculationRow.new(:key => "Operating Revenues (OR)", :vals => [@orev.total] ),
109
- CalculationRow.new(:key => "Cost of Goods Sold (COGS)", :vals => [@cogs.total] ) ]
59
+ cs.rows = [ CalculationRow.new(:key => "Operating Revenues (OR)", :vals => [@operating_revenues.total] ),
60
+ CalculationRow.new(:key => "Cost of Goods Sold (COGS)", :vals => [@cost_of_revenues.total] ) ]
110
61
  return cs
111
62
  end
112
63
 
113
- def operating_expenses
114
- @oe
115
- end
116
-
117
64
  def income_from_sales_before_tax
118
65
  cs = FinModeling::CalculationSummary.new
119
66
  cs.title = "Operating Income from sales, before tax (OISBT)"
120
67
  cs.rows = [ CalculationRow.new(:key => "Gross Margin (GM)", :vals => [@gm] ),
121
- CalculationRow.new(:key => "Operating Expense (OE)", :vals => [@oe.total] ) ]
68
+ CalculationRow.new(:key => "Operating Expense (OE)", :vals => [@operating_expenses.total] ) ]
122
69
  return cs
123
70
  end
124
71
 
@@ -204,7 +151,7 @@ module FinModeling
204
151
  return annualize_rate(prev, rate)
205
152
  end
206
153
 
207
- def re_oi(prev_bal_sheet, expected_rate_of_return=0.10)
154
+ def re_oi(prev_bal_sheet, expected_rate_of_return)
208
155
  e_ror = deannualize_rate(prev_bal_sheet, expected_rate_of_return)
209
156
  return (operating_income_after_tax.total - (e_ror * prev_bal_sheet.net_operating_assets.total))
210
157
  end
@@ -242,7 +189,7 @@ module FinModeling
242
189
  return analysis
243
190
  end
244
191
 
245
- def analysis(re_bs, prev_re_is, prev_re_bs)
192
+ def analysis(re_bs, prev_re_is, prev_re_bs, expected_rate_of_return)
246
193
  analysis = CalculationSummary.new
247
194
  analysis.title = ""
248
195
  analysis.rows = []
@@ -253,11 +200,11 @@ module FinModeling
253
200
  analysis.header_row = CalculationHeader.new(:key => "", :vals => [re_bs.period.to_pretty_s])
254
201
  end
255
202
 
256
- analysis.rows << CalculationRow.new(:key => "Revenue ($MM)", :vals => [operating_revenues.total.to_nearest_million])
203
+ analysis.rows << CalculationRow.new(:key => "Revenue ($MM)", :vals => [@operating_revenues.total.to_nearest_million])
257
204
  if Config.income_detail_enabled?
258
- analysis.rows << CalculationRow.new(:key => "COGS ($MM)", :vals => [@cogs.total.to_nearest_million])
205
+ analysis.rows << CalculationRow.new(:key => "COGS ($MM)", :vals => [@cost_of_revenues.total.to_nearest_million])
259
206
  analysis.rows << CalculationRow.new(:key => "GM ($MM)", :vals => [@gm.to_nearest_million])
260
- analysis.rows << CalculationRow.new(:key => "OE ($MM)", :vals => [@oe.total.to_nearest_million])
207
+ analysis.rows << CalculationRow.new(:key => "OE ($MM)", :vals => [@operating_expenses.total.to_nearest_million])
261
208
  analysis.rows << CalculationRow.new(:key => "OISBT ($MM)", :vals => [income_from_sales_before_tax.total.to_nearest_million])
262
209
  end
263
210
  analysis.rows << CalculationRow.new(:key => "Core OI ($MM)", :vals => [income_from_sales_after_tax.total.to_nearest_million])
@@ -276,7 +223,7 @@ module FinModeling
276
223
  analysis.rows << CalculationRow.new(:key => "Revenue Growth",:vals => [revenue_growth(prev_re_is)])
277
224
  analysis.rows << CalculationRow.new(:key => "Core OI Growth",:vals => [core_oi_growth(prev_re_is)])
278
225
  analysis.rows << CalculationRow.new(:key => "OI Growth", :vals => [oi_growth( prev_re_is)])
279
- analysis.rows << CalculationRow.new(:key => "ReOI ($MM)", :vals => [re_oi( prev_re_bs).to_nearest_million])
226
+ analysis.rows << CalculationRow.new(:key => "ReOI ($MM)", :vals => [re_oi(prev_re_bs, expected_rate_of_return).to_nearest_million])
280
227
  else
281
228
  analysis.rows << CalculationRow.new(:key => "Sales / NOA", :vals => [nil])
282
229
  analysis.rows << CalculationRow.new(:key => "FI / NFA", :vals => [nil])
@@ -290,10 +237,9 @@ module FinModeling
290
237
  end
291
238
 
292
239
  def self.forecast_next(period, policy, last_re_bs, last_re_is)
293
- operating_revenues = last_re_is.operating_revenues.total * (1.0 + Rate.new(policy.revenue_growth).yearly_to_quarterly)
294
- income_from_sales_after_tax = operating_revenues * policy.sales_pm
295
- net_financing_income = last_re_bs.net_financial_assets.total * Ratio.new(policy.fi_over_nfa).yearly_to_quarterly
296
-
240
+ operating_revenues = policy.revenue_on(period.value["end_date"])
241
+ income_from_sales_after_tax = operating_revenues * policy.sales_pm_on(period.value["end_date"])
242
+ net_financing_income = last_re_bs.net_financial_assets.total * Ratio.new(policy.fi_over_nfa_on(period.value["end_date"])).yearly_to_quarterly
297
243
  comprehensive_income = income_from_sales_after_tax + net_financing_income
298
244
 
299
245
  ForecastedReformulatedIncomeStatement.new(period, operating_revenues,
@@ -325,112 +271,4 @@ module FinModeling
325
271
 
326
272
  end
327
273
 
328
- class ForecastedReformulatedIncomeStatement < ReformulatedIncomeStatement
329
- def initialize(period, operating_revenues, income_from_sales_after_tax, net_financing_income, comprehensive_income)
330
- @period = period
331
- @orev = operating_revenues
332
- @income_from_sales_after_tax = income_from_sales_after_tax
333
- @net_financing_income = net_financing_income
334
- @comprehensive_income = comprehensive_income
335
- end
336
-
337
- def -(ris2)
338
- raise RuntimeError.new("not implmeneted")
339
- end
340
-
341
- def operating_revenues
342
- cs = FinModeling::CalculationSummary.new
343
- cs.title = "Operating Revenues"
344
- cs.rows = [ CalculationRow.new(:key => "Operating Revenues (OR)", :vals => [@orev] ) ]
345
- return cs
346
- end
347
-
348
- def cost_of_revenues
349
- nil
350
- end
351
-
352
- def gross_revenue
353
- nil
354
- end
355
-
356
- def operating_expenses
357
- nil
358
- end
359
-
360
- def income_from_sales_before_tax
361
- nil
362
- end
363
-
364
- def income_from_sales_after_tax
365
- cs = FinModeling::CalculationSummary.new
366
- cs.title = "Operating Income from sales, after tax (OISAT)"
367
- cs.rows = [ CalculationRow.new(:key => "Operating income from sales (after tax)", :vals => [@income_from_sales_after_tax] ) ]
368
- return cs
369
- end
370
-
371
- def operating_income_after_tax
372
- income_from_sales_after_tax # this simplified version assumes no non-sales operating income
373
- end
374
-
375
- def net_financing_income
376
- cs = FinModeling::CalculationSummary.new
377
- cs.title = "Net financing income, after tax (NFI)"
378
- cs.rows = [ CalculationRow.new(:key => "Net financing income", :vals => [@net_financing_income] ) ]
379
- return cs
380
- end
381
-
382
- def comprehensive_income
383
- cs = FinModeling::CalculationSummary.new
384
- cs.title = "Comprehensive Income (CI)"
385
- cs.rows = [ CalculationRow.new(:key => "Comprehensive income", :vals => [@comprehensive_income] ) ]
386
- return cs
387
- end
388
-
389
- def analysis(re_bs, prev_re_is, prev_re_bs)
390
- analysis = CalculationSummary.new
391
- analysis.title = ""
392
- analysis.rows = []
393
-
394
- if re_bs.nil?
395
- analysis.header_row = CalculationHeader.new(:key => "", :vals => ["Unknown..."])
396
- else
397
- analysis.header_row = CalculationHeader.new(:key => "", :vals => [re_bs.period.to_pretty_s + "E"])
398
- end
399
-
400
- analysis.rows << CalculationRow.new(:key => "Revenue ($MM)", :vals => [operating_revenues.total.to_nearest_million])
401
- if Config.income_detail_enabled?
402
- analysis.rows << CalculationRow.new(:key => "COGS ($MM)", :vals => [nil])
403
- analysis.rows << CalculationRow.new(:key => "GM ($MM)", :vals => [nil])
404
- analysis.rows << CalculationRow.new(:key => "OE ($MM)", :vals => [nil])
405
- analysis.rows << CalculationRow.new(:key => "OISBT ($MM)", :vals => [nil])
406
- end
407
- analysis.rows << CalculationRow.new(:key => "Core OI ($MM)", :vals => [income_from_sales_after_tax.total.to_nearest_million])
408
- analysis.rows << CalculationRow.new(:key => "OI ($MM)", :vals => [nil])
409
- analysis.rows << CalculationRow.new(:key => "FI ($MM)", :vals => [net_financing_income.total.to_nearest_million])
410
- analysis.rows << CalculationRow.new(:key => "NI ($MM)", :vals => [comprehensive_income.total.to_nearest_million])
411
- analysis.rows << CalculationRow.new(:key => "Gross Margin", :vals => [nil])
412
- analysis.rows << CalculationRow.new(:key => "Sales PM", :vals => [sales_profit_margin])
413
- analysis.rows << CalculationRow.new(:key => "Operating PM", :vals => [nil])
414
- analysis.rows << CalculationRow.new(:key => "FI / Sales", :vals => [fi_over_sales])
415
- analysis.rows << CalculationRow.new(:key => "NI / Sales", :vals => [ni_over_sales])
416
-
417
- if !prev_re_bs.nil? && !prev_re_is.nil?
418
- analysis.rows << CalculationRow.new(:key => "Sales / NOA", :vals => [sales_over_noa(prev_re_bs)])
419
- analysis.rows << CalculationRow.new(:key => "FI / NFA", :vals => [fi_over_nfa( prev_re_bs)])
420
- analysis.rows << CalculationRow.new(:key => "Revenue Growth",:vals => [revenue_growth(prev_re_is)])
421
- analysis.rows << CalculationRow.new(:key => "Core OI Growth",:vals => [core_oi_growth(prev_re_is)])
422
- analysis.rows << CalculationRow.new(:key => "OI Growth", :vals => [nil])
423
- analysis.rows << CalculationRow.new(:key => "ReOI ($MM)", :vals => [re_oi( prev_re_bs).to_nearest_million])
424
- else
425
- analysis.rows << CalculationRow.new(:key => "Sales / NOA", :vals => [nil])
426
- analysis.rows << CalculationRow.new(:key => "FI / NFA", :vals => [nil])
427
- analysis.rows << CalculationRow.new(:key => "Revenue Growth",:vals => [nil])
428
- analysis.rows << CalculationRow.new(:key => "Core OI Growth",:vals => [nil])
429
- analysis.rows << CalculationRow.new(:key => "OI Growth", :vals => [nil])
430
- analysis.rows << CalculationRow.new(:key => "ReOI ($MM)", :vals => [nil])
431
- end
432
-
433
- return analysis
434
- end
435
- end
436
274
  end
@@ -0,0 +1,50 @@
1
+ module FinModeling
2
+ class ReformulatedShareholderEquityStatement
3
+ attr_accessor :period
4
+
5
+ def initialize(period, equity_change_summary)
6
+ @period = period
7
+
8
+ @share_issue = equity_change_summary.filter_by_type(:share_issue )
9
+ @minority_int = equity_change_summary.filter_by_type(:minority_int )
10
+ @share_repurch = equity_change_summary.filter_by_type(:share_repurch)
11
+ @common_div = equity_change_summary.filter_by_type(:common_div )
12
+ @net_income = equity_change_summary.filter_by_type(:net_income )
13
+ @oci = equity_change_summary.filter_by_type(:oci )
14
+ @preferred_div = equity_change_summary.filter_by_type(:preferred_div)
15
+ end
16
+
17
+ def transactions_with_shareholders
18
+ cs = FinModeling::CalculationSummary.new
19
+ cs.title = "Transactions with Shareholders"
20
+ cs.rows = [ CalculationRow.new(:key => "Share Issues", :vals => [@share_issue .total] ),
21
+ CalculationRow.new(:key => "Minority Interest", :vals => [@minority_int .total] ),
22
+ CalculationRow.new(:key => "Share Repurchases", :vals => [@share_repurch.total] ),
23
+ CalculationRow.new(:key => "Common Dividends", :vals => [@common_div .total] ) ]
24
+ return cs
25
+ end
26
+
27
+ def comprehensive_income
28
+ cs = FinModeling::CalculationSummary.new
29
+ cs.title = "Comprehensive Income"
30
+ cs.rows = [ CalculationRow.new(:key => "Net Income", :vals => [@net_income .total] ),
31
+ CalculationRow.new(:key => "Other Comprehensive Income", :vals => [@oci .total] ),
32
+ CalculationRow.new(:key => "Preferred Dividends", :vals => [@preferred_div.total] ) ]
33
+ return cs
34
+ end
35
+
36
+ def analysis
37
+ analysis = CalculationSummary.new
38
+
39
+ analysis.title = ""
40
+ analysis.header_row = CalculationHeader.new(:key => "", :vals => [@period.value["end_date"].to_s])
41
+
42
+ analysis.rows = []
43
+ analysis.rows << CalculationRow.new(:key => "Tx w Shareholders ($MM)", :vals => [transactions_with_shareholders.total.to_nearest_million])
44
+ analysis.rows << CalculationRow.new(:key => "CI ($MM)", :vals => [comprehensive_income.total.to_nearest_million])
45
+
46
+ return analysis
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,104 @@
1
+ module FinModeling
2
+ class ReOIValuation
3
+ def initialize(filings, forecasts, cost_of_capital, num_shares)
4
+ @filings, @forecasts, @cost_of_capital, @num_shares = [filings, forecasts, cost_of_capital, num_shares]
5
+ @discount_rate = FinModeling::DiscountRate.new(@cost_of_capital.value + 1.0)
6
+ end
7
+
8
+ def summary
9
+ s = CalculationSummary.new
10
+ s.title = "ReOI Valuation"
11
+ s.totals_row_enabled = false
12
+
13
+ s.header_row = CalculationHeader.new(:key => "", :vals => periods.map{ |x| x.to_pretty_s + ((x.value > Date.today) ? "E" : "") })
14
+
15
+ s.rows = [ ]
16
+
17
+ s.rows << CalculationRow.new(:key => "ReOI ($MM)", :vals => reoi_vals.map{ |x| x ? x.to_nearest_million : nil })
18
+ s.rows << CalculationRow.new(:key => "PV(ReOI) ($MM)", :vals => pv_reoi_vals.map{ |x| x ? x.to_nearest_million : nil })
19
+ s.rows << CalculationRow.new(:key => "CV ($MM)", :vals => cv_vals.map{ |x| x ? x.to_nearest_million : nil })
20
+ s.rows << CalculationRow.new(:key => "PV(CV) ($MM)", :vals => pv_cv_vals.map{ |x| x ? x.to_nearest_million : nil })
21
+ s.rows << CalculationRow.new(:key => "Book Value of Common Equity ($MM)", :vals => bv_cse_vals.map{ |x| x ? x.to_nearest_million : nil })
22
+ s.rows << CalculationRow.new(:key => "Enterprise Value ($MM)", :vals => ev_vals.map{ |x| x ? x.to_nearest_million : nil })
23
+ s.rows << CalculationRow.new(:key => "NFA ($MM)", :vals => bv_nfa_vals.map{ |x| x ? x.to_nearest_million : nil })
24
+ s.rows << CalculationRow.new(:key => "Value of Common Equity ($MM)", :vals => cse_vals.map{ |x| x ? x.to_nearest_million : nil })
25
+ s.rows << CalculationRow.new(:key => "# Shares (MM)", :vals => num_shares_vals.map{ |x| x ? x.to_nearest_million : nil })
26
+ s.rows << CalculationRow.new(:key => "Value / Share ($)", :vals => val_per_shares_vals.map{ |x| x ? x.to_nearest_dollar(num_decimals=2) : nil })
27
+
28
+ return s
29
+ end
30
+
31
+ def periods
32
+ @periods ||= [ @filings.re_bs_arr.last.period ] +
33
+ @forecasts.reformulated_balance_sheets.map{ |x| x.period }
34
+ end
35
+
36
+ private
37
+
38
+ def reoi_vals
39
+ prev_re_bses = [@filings.re_bs_arr.last] + @forecasts.reformulated_balance_sheets[0..-2]
40
+ re_ises = @forecasts.reformulated_income_statements
41
+ re_ois = [nil] + re_ises.zip(prev_re_bses).map{ |pair| pair[0].re_oi(pair[1], @cost_of_capital.value) }
42
+ end
43
+
44
+ def pv_reoi_vals
45
+ reoi_vals[0..-2].each_with_index.map do |reoi, idx|
46
+ days_from_now = periods[idx].value - Date.today
47
+ d = @discount_rate.annualize(from_days=365, to_days=days_from_now)
48
+ reoi ? (reoi / d) : nil
49
+ end + [nil]
50
+ end
51
+
52
+ def cv_vals
53
+ vals = [nil]*periods.length
54
+ d = @cost_of_capital.annualize(from_days=365, @forecasts.reformulated_income_statements.last.period.days)
55
+ vals[-2] = reoi_vals.last / d
56
+ vals
57
+ end
58
+
59
+ def pv_cv_vals
60
+ cv_vals[0..-2].each_with_index.map do |cv, idx|
61
+ days_from_now = periods[idx].value - Date.today
62
+ d = @discount_rate.annualize(from_days=365, to_days=days_from_now)
63
+ cv ? (cv / d) : nil
64
+ end + [nil]
65
+ end
66
+
67
+ def bv_cse_vals
68
+ vals = [nil]*periods.length
69
+ vals[0] = @filings.re_bs_arr.last.common_shareholders_equity.total
70
+ vals
71
+ end
72
+
73
+ def ev_vals
74
+ vals = [nil]*periods.length
75
+ vals[0] = (pv_reoi_vals[1..-2] + pv_cv_vals[-2..-2] + bv_cse_vals[0..0]).inject(:+)
76
+ vals
77
+ end
78
+
79
+ def bv_nfa_vals
80
+ vals = [nil]*periods.length
81
+ vals[0] = @filings.re_bs_arr.last.net_financial_assets.total
82
+ vals
83
+ end
84
+
85
+ def cse_vals
86
+ vals = [nil]*periods.length
87
+ vals[0] = ev_vals[0] + bv_nfa_vals[0]
88
+ vals
89
+ end
90
+
91
+ def num_shares_vals
92
+ vals = [nil]*periods.length
93
+ vals[0] = @num_shares
94
+ vals
95
+ end
96
+
97
+ def val_per_shares_vals
98
+ vals = [nil]*periods.length
99
+ vals[0] = cse_vals[0] / @num_shares
100
+ vals
101
+ end
102
+
103
+ end
104
+ end
@@ -0,0 +1,34 @@
1
+ module FinModeling
2
+ class ShareholderEquityStatementCalculation < CompanyFilingCalculation
3
+ #include CanChooseSuccessivePeriods
4
+
5
+ EC_GOAL = "change in shareholder equity"
6
+ EC_LABELS = [ /^stockholders equity period increase decrease$/ ]
7
+ EC_ANTI_LABELS = [ ]
8
+ EC_IDS = [ /^(|Locator_|loc_)(|us-gaap_)StockholdersEquityPeriodIncreaseDecrease[_a-z0-9]+/ ]
9
+ def equity_change_calculation
10
+ begin
11
+ @ec ||= EquityChangeCalculation.new(find_calculation_arc(EC_GOAL, EC_LABELS, EC_ANTI_LABELS, EC_IDS))
12
+ rescue FinModeling::InvalidFilingError => e
13
+ pre_msg = "calculation tree:\n" + self.calculation.sprint_tree
14
+ raise e, pre_msg+e.message, e.backtrace
15
+ end
16
+ end
17
+
18
+ def is_valid?
19
+ return true
20
+ end
21
+
22
+ def reformulated(period)
23
+ return ReformulatedShareholderEquityStatement.new(period,
24
+ equity_change_calculation.summary(:period=>period))
25
+ end
26
+
27
+ def write_constructor(file, item_name)
28
+ item_calc_name = item_name + "_calc"
29
+ @calculation.write_constructor(file, item_calc_name)
30
+ file.puts "#{item_name} = FinModeling::ShareholderEquityStatementCalculation.new(#{item_calc_name})"
31
+ end
32
+
33
+ end
34
+ end
@@ -20,7 +20,24 @@ class String
20
20
  self.gsub(r, '\1')
21
21
  end
22
22
 
23
- def matches_regexes?(regexes)
23
+ def matches_any_regex?(regexes)
24
24
  return regexes.inject(false){ |matches, regex| matches or regex =~ self }
25
25
  end
26
+
27
+ def split_into_lines_shorter_than(max_line_width)
28
+ lines = []
29
+ cur_line = []
30
+
31
+ split(' ').each do |word|
32
+ if (cur_line + [word]).join(' ').length > max_line_width
33
+ lines << cur_line.join(' ')
34
+ cur_line = []
35
+ end
36
+
37
+ cur_line << word
38
+ end
39
+
40
+ lines << cur_line.join(' ') if !cur_line.empty?
41
+ lines
42
+ end
26
43
  end
@@ -0,0 +1,25 @@
1
+ module FinModeling
2
+ class TimeSeriesEstimator
3
+ attr_reader :a, :b
4
+
5
+ def initialize(a, b)
6
+ @a, @b = a, b
7
+ end
8
+
9
+ def estimate_on(date)
10
+ x = (date - Date.today)
11
+ a + (b*x)
12
+ end
13
+
14
+ def self.from_time_series(dates, ys)
15
+ xs = dates.map{ |date| date - Date.today }
16
+
17
+ simple_regression = Statsample::Regression.simple(xs.to_scale, ys.to_scale)
18
+ TimeSeriesEstimator.new(simple_regression.a, simple_regression.b)
19
+ end
20
+
21
+ def self.from_const(y)
22
+ TimeSeriesEstimator.new(a=y, b=0.0)
23
+ end
24
+ end
25
+ end