finmodeling 0.1 → 0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. data/.gitignore +0 -0
  2. data/Gemfile +2 -0
  3. data/README.md +289 -269
  4. data/Rakefile +12 -0
  5. data/TODO.txt +113 -20
  6. data/examples/{dump_report.rb → dump_latest_10k.rb} +1 -1
  7. data/examples/list_disclosures.rb +50 -0
  8. data/examples/lists/nasdaq-mid-to-mega-tech-symbols.txt +0 -0
  9. data/examples/show_report.rb +112 -32
  10. data/examples/show_reports.rb +162 -33
  11. data/finmodeling.gemspec +4 -1
  12. data/lib/finmodeling/annual_report_filing.rb +97 -18
  13. data/lib/finmodeling/array_with_stats.rb +0 -0
  14. data/lib/finmodeling/assets_calculation.rb +12 -3
  15. data/lib/finmodeling/assets_item.rb +0 -0
  16. data/lib/finmodeling/assets_item_vectors.rb +0 -0
  17. data/lib/finmodeling/balance_sheet_analyses.rb +19 -4
  18. data/lib/finmodeling/balance_sheet_calculation.rb +52 -37
  19. data/lib/finmodeling/calculation_summary.rb +119 -14
  20. data/lib/finmodeling/can_cache_classifications.rb +0 -0
  21. data/lib/finmodeling/can_cache_summaries.rb +0 -0
  22. data/lib/finmodeling/can_choose_successive_periods.rb +15 -0
  23. data/lib/finmodeling/can_classify_rows.rb +0 -0
  24. data/lib/finmodeling/capm.rb +80 -0
  25. data/lib/finmodeling/cash_change_calculation.rb +3 -3
  26. data/lib/finmodeling/cash_change_item.rb +0 -0
  27. data/lib/finmodeling/cash_change_item_vectors.rb +0 -0
  28. data/lib/finmodeling/cash_change_summary_from_differences.rb +36 -0
  29. data/lib/finmodeling/cash_flow_statement_analyses.rb +36 -0
  30. data/lib/finmodeling/cash_flow_statement_calculation.rb +28 -52
  31. data/lib/finmodeling/classifiers.rb +2 -0
  32. data/lib/finmodeling/company.rb +0 -0
  33. data/lib/finmodeling/company_filing.rb +30 -7
  34. data/lib/finmodeling/company_filing_calculation.rb +16 -6
  35. data/lib/finmodeling/company_filings.rb +112 -46
  36. data/lib/finmodeling/comprehensive_income_calculation.rb +60 -0
  37. data/lib/finmodeling/comprehensive_income_statement_calculation.rb +74 -0
  38. data/lib/finmodeling/comprehensive_income_statement_item.rb +20 -0
  39. data/lib/finmodeling/comprehensive_income_statement_item_vectors.rb +235 -0
  40. data/lib/finmodeling/config.rb +0 -0
  41. data/lib/finmodeling/debt_cost_of_capital.rb +14 -0
  42. data/lib/finmodeling/equity_change_calculation.rb +43 -0
  43. data/lib/finmodeling/equity_change_item.rb +25 -0
  44. data/lib/finmodeling/equity_change_item_vectors.rb +156 -0
  45. data/lib/finmodeling/factory.rb +0 -0
  46. data/lib/finmodeling/fama_french_cost_of_equity.rb +119 -0
  47. data/lib/finmodeling/float_helpers.rb +14 -8
  48. data/lib/finmodeling/forecasted_reformulated_balance_sheet.rb +55 -0
  49. data/lib/finmodeling/forecasted_reformulated_income_statement.rb +110 -0
  50. data/lib/finmodeling/forecasts.rb +4 -4
  51. data/lib/finmodeling/has_string_classifer.rb +0 -0
  52. data/lib/finmodeling/income_statement_analyses.rb +23 -17
  53. data/lib/finmodeling/income_statement_calculation.rb +46 -43
  54. data/lib/finmodeling/income_statement_item.rb +1 -1
  55. data/lib/finmodeling/income_statement_item_vectors.rb +24 -13
  56. data/lib/finmodeling/invalid_filing_error.rb +4 -0
  57. data/lib/finmodeling/liabs_and_equity_calculation.rb +18 -8
  58. data/lib/finmodeling/liabs_and_equity_item.rb +1 -1
  59. data/lib/finmodeling/liabs_and_equity_item_vectors.rb +24 -24
  60. data/lib/finmodeling/linear_trend_forecasting_policy.rb +23 -0
  61. data/lib/finmodeling/net_income_calculation.rb +23 -10
  62. data/lib/finmodeling/net_income_summary_from_differences.rb +51 -0
  63. data/lib/finmodeling/paths.rb +0 -0
  64. data/lib/finmodeling/period_array.rb +8 -4
  65. data/lib/finmodeling/quarterly_report_filing.rb +9 -4
  66. data/lib/finmodeling/rate.rb +8 -0
  67. data/lib/finmodeling/ratio.rb +0 -0
  68. data/lib/finmodeling/reformulated_balance_sheet.rb +47 -88
  69. data/lib/finmodeling/reformulated_cash_flow_statement.rb +18 -41
  70. data/lib/finmodeling/reformulated_income_statement.rb +44 -206
  71. data/lib/finmodeling/reformulated_shareholder_equity_statement.rb +50 -0
  72. data/lib/finmodeling/reoi_valuation.rb +104 -0
  73. data/lib/finmodeling/shareholder_equity_statement_calculation.rb +34 -0
  74. data/lib/finmodeling/string_helpers.rb +18 -1
  75. data/lib/finmodeling/time_series_estimator.rb +25 -0
  76. data/lib/finmodeling/trailing_avg_forecasting_policy.rb +23 -0
  77. data/lib/finmodeling/version.rb +1 -1
  78. data/lib/finmodeling/weighted_avg_cost_of_capital.rb +35 -0
  79. data/lib/finmodeling/yahoo_finance_helpers.rb +20 -0
  80. data/lib/finmodeling.rb +33 -2
  81. data/spec/annual_report_filing_spec.rb +81 -45
  82. data/spec/assets_calculation_spec.rb +7 -4
  83. data/spec/assets_item_spec.rb +9 -14
  84. data/spec/balance_sheet_analyses_spec.rb +13 -13
  85. data/spec/balance_sheet_calculation_spec.rb +45 -51
  86. data/spec/calculation_summary_spec.rb +113 -21
  87. data/spec/can_classify_rows_spec.rb +0 -0
  88. data/spec/cash_change_calculation_spec.rb +1 -10
  89. data/spec/cash_change_item_spec.rb +10 -18
  90. data/spec/cash_flow_statement_calculation_spec.rb +10 -24
  91. data/spec/company_beta_spec.rb +53 -0
  92. data/spec/company_filing_calculation_spec.rb +39 -49
  93. data/spec/company_filing_spec.rb +0 -0
  94. data/spec/company_filings_spec.rb +75 -25
  95. data/spec/company_spec.rb +37 -47
  96. data/spec/comprehensive_income_statement_calculation_spec.rb +54 -0
  97. data/spec/comprehensive_income_statement_item_spec.rb +56 -0
  98. data/spec/debt_cost_of_capital_spec.rb +19 -0
  99. data/spec/equity_change_calculation_spec.rb +33 -0
  100. data/spec/equity_change_item_spec.rb +58 -0
  101. data/spec/factory_spec.rb +2 -2
  102. data/spec/forecasts_spec.rb +2 -2
  103. data/spec/income_statement_analyses_spec.rb +23 -21
  104. data/spec/income_statement_calculation_spec.rb +17 -49
  105. data/spec/income_statement_item_spec.rb +17 -29
  106. data/spec/liabs_and_equity_calculation_spec.rb +6 -3
  107. data/spec/liabs_and_equity_item_spec.rb +14 -22
  108. data/spec/linear_trend_forecasting_policy_spec.rb +37 -0
  109. data/spec/matchers/custom_matchers.rb +79 -0
  110. data/spec/mocks/calculation.rb +0 -0
  111. data/spec/mocks/income_statement_analyses.rb +0 -0
  112. data/spec/mocks/sec_query.rb +0 -0
  113. data/spec/net_income_calculation_spec.rb +16 -10
  114. data/spec/period_array.rb +0 -0
  115. data/spec/quarterly_report_filing_spec.rb +21 -38
  116. data/spec/rate_spec.rb +0 -0
  117. data/spec/ratio_spec.rb +0 -0
  118. data/spec/reformulated_balance_sheet_spec.rb +56 -33
  119. data/spec/reformulated_cash_flow_statement_spec.rb +18 -10
  120. data/spec/reformulated_income_statement_spec.rb +16 -15
  121. data/spec/reformulated_shareholder_equity_statement_spec.rb +43 -0
  122. data/spec/reoi_valuation_spec.rb +146 -0
  123. data/spec/shareholder_equity_statement_calculation_spec.rb +59 -0
  124. data/spec/spec_helper.rb +4 -1
  125. data/spec/string_helpers_spec.rb +15 -13
  126. data/spec/time_series_estimator_spec.rb +61 -0
  127. data/spec/trailing_avg_forecasting_policy_spec.rb +37 -0
  128. data/spec/weighted_avg_cost_of_capital_spec.rb +32 -0
  129. data/tools/create_equity_change_training_vectors.rb +49 -0
  130. data/tools/time_specs.sh +7 -0
  131. metadata +182 -36
  132. data/lib/finmodeling/constant_forecasting_policy.rb +0 -23
  133. data/lib/finmodeling/generic_forecasting_policy.rb +0 -19
  134. data/spec/constant_forecasting_policy_spec.rb +0 -37
  135. data/spec/generic_forecasting_policy_spec.rb +0 -33
@@ -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