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
@@ -1,17 +1,23 @@
1
1
  class Fixnum
2
- def to_nearest_million
3
- return (self/1000000.0).round.to_f
2
+ def to_nearest_million(num_decimals=1)
3
+ return (self/1000000.0*(10.0**num_decimals)).round.to_f/(10.0**num_decimals)
4
4
  end
5
- def to_nearest_thousand
6
- return (self/1000.0).round.to_f
5
+ def to_nearest_thousand(num_decimals=1)
6
+ return (self/1000.0*(10.0**num_decimals)).round.to_f/(10.0**num_decimals)
7
+ end
8
+ def to_nearest_dollar(num_decimals=1)
9
+ return ((self*(10.0**num_decimals)).round/(10.0**num_decimals)).to_f
7
10
  end
8
11
  end
9
12
 
10
13
  class Float
11
- def to_nearest_million
12
- return (self/1000000.0).round.to_f
14
+ def to_nearest_million(num_decimals=1)
15
+ return (self/1000000.0*(10.0**num_decimals)).round.to_f/(10.0**num_decimals)
16
+ end
17
+ def to_nearest_thousand(num_decimals=1)
18
+ return (self/1000.0*(10.0**num_decimals)).round.to_f/(10.0**num_decimals)
13
19
  end
14
- def to_nearest_thousand
15
- return (self/1000.0).round.to_f
20
+ def to_nearest_dollar(num_decimals=1)
21
+ return ((self*(10.0**num_decimals)).round/(10.0**num_decimals)).to_f
16
22
  end
17
23
  end
@@ -0,0 +1,55 @@
1
+ module FinModeling
2
+ class ForecastedReformulatedBalanceSheet < ReformulatedBalanceSheet
3
+ def initialize(period, noa, nfa, cse)
4
+ @period = period
5
+ @noa = noa
6
+ @nfa = nfa
7
+ @cse = cse
8
+
9
+ @minority_interest = FinModeling::CalculationSummary.new
10
+ end
11
+
12
+ def operating_assets
13
+ nil
14
+ end
15
+
16
+ def financial_assets
17
+ nil
18
+ end
19
+
20
+ def operating_liabilities
21
+ nil
22
+ end
23
+
24
+ def financial_liabilities
25
+ nil
26
+ end
27
+
28
+ def net_operating_assets
29
+ cs = FinModeling::CalculationSummary.new
30
+ cs.title = "Net Operational Assets"
31
+ cs.rows = [ CalculationRow.new( :key => "NOA", :vals => [@noa] ) ]
32
+ return cs
33
+ end
34
+
35
+ def net_financial_assets
36
+ cs = FinModeling::CalculationSummary.new
37
+ cs.title = "Net Financial Assets"
38
+ cs.rows = [ CalculationRow.new( :key => "NFA", :vals => [@nfa] ) ]
39
+ return cs
40
+ end
41
+
42
+ def common_shareholders_equity
43
+ cs = FinModeling::CalculationSummary.new
44
+ cs.title = "Common Shareholders' Equity"
45
+ cs.rows = [ CalculationRow.new( :key => "CSE", :vals => [@cse] ) ]
46
+ return cs
47
+ end
48
+
49
+ def analysis(prev)
50
+ analysis = super(prev)
51
+ analysis.header_row.vals[0] += "E" # for estimated
52
+ return analysis
53
+ end
54
+ end
55
+ end
@@ -0,0 +1,110 @@
1
+ module FinModeling
2
+ class ForecastedReformulatedIncomeStatement < ReformulatedIncomeStatement
3
+ def initialize(period, operating_revenues, income_from_sales_after_tax, net_financing_income, comprehensive_income)
4
+ @period = period
5
+ @orev = operating_revenues
6
+ @income_from_sales_after_tax = income_from_sales_after_tax
7
+ @net_financing_income = net_financing_income
8
+ @comprehensive_income = comprehensive_income
9
+ end
10
+
11
+ def -(ris2)
12
+ raise RuntimeError.new("not implmeneted")
13
+ end
14
+
15
+ def operating_revenues
16
+ cs = FinModeling::CalculationSummary.new
17
+ cs.title = "Operating Revenues"
18
+ cs.rows = [ CalculationRow.new(:key => "Operating Revenues (OR)", :vals => [@orev] ) ]
19
+ return cs
20
+ end
21
+
22
+ def cost_of_revenues
23
+ nil
24
+ end
25
+
26
+ def gross_revenue
27
+ nil
28
+ end
29
+
30
+ def operating_expenses
31
+ nil
32
+ end
33
+
34
+ def income_from_sales_before_tax
35
+ nil
36
+ end
37
+
38
+ def income_from_sales_after_tax
39
+ cs = FinModeling::CalculationSummary.new
40
+ cs.title = "Operating Income from sales, after tax (OISAT)"
41
+ cs.rows = [ CalculationRow.new(:key => "Operating income from sales (after tax)", :vals => [@income_from_sales_after_tax] ) ]
42
+ return cs
43
+ end
44
+
45
+ def operating_income_after_tax
46
+ income_from_sales_after_tax # this simplified version assumes no non-sales operating income
47
+ end
48
+
49
+ def net_financing_income
50
+ cs = FinModeling::CalculationSummary.new
51
+ cs.title = "Net financing income, after tax (NFI)"
52
+ cs.rows = [ CalculationRow.new(:key => "Net financing income", :vals => [@net_financing_income] ) ]
53
+ return cs
54
+ end
55
+
56
+ def comprehensive_income
57
+ cs = FinModeling::CalculationSummary.new
58
+ cs.title = "Comprehensive Income (CI)"
59
+ cs.rows = [ CalculationRow.new(:key => "Comprehensive income", :vals => [@comprehensive_income] ) ]
60
+ return cs
61
+ end
62
+
63
+ def analysis(re_bs, prev_re_is, prev_re_bs, expected_cost_of_capital)
64
+ analysis = CalculationSummary.new
65
+ analysis.title = ""
66
+ analysis.rows = []
67
+
68
+ if re_bs.nil?
69
+ analysis.header_row = CalculationHeader.new(:key => "", :vals => ["Unknown..."])
70
+ else
71
+ analysis.header_row = CalculationHeader.new(:key => "", :vals => [re_bs.period.to_pretty_s + "E"])
72
+ end
73
+
74
+ analysis.rows << CalculationRow.new(:key => "Revenue ($MM)", :vals => [operating_revenues.total.to_nearest_million])
75
+ if Config.income_detail_enabled?
76
+ analysis.rows << CalculationRow.new(:key => "COGS ($MM)", :vals => [nil])
77
+ analysis.rows << CalculationRow.new(:key => "GM ($MM)", :vals => [nil])
78
+ analysis.rows << CalculationRow.new(:key => "OE ($MM)", :vals => [nil])
79
+ analysis.rows << CalculationRow.new(:key => "OISBT ($MM)", :vals => [nil])
80
+ end
81
+ analysis.rows << CalculationRow.new(:key => "Core OI ($MM)", :vals => [income_from_sales_after_tax.total.to_nearest_million])
82
+ analysis.rows << CalculationRow.new(:key => "OI ($MM)", :vals => [nil])
83
+ analysis.rows << CalculationRow.new(:key => "FI ($MM)", :vals => [net_financing_income.total.to_nearest_million])
84
+ analysis.rows << CalculationRow.new(:key => "NI ($MM)", :vals => [comprehensive_income.total.to_nearest_million])
85
+ analysis.rows << CalculationRow.new(:key => "Gross Margin", :vals => [nil])
86
+ analysis.rows << CalculationRow.new(:key => "Sales PM", :vals => [sales_profit_margin])
87
+ analysis.rows << CalculationRow.new(:key => "Operating PM", :vals => [nil])
88
+ analysis.rows << CalculationRow.new(:key => "FI / Sales", :vals => [fi_over_sales])
89
+ analysis.rows << CalculationRow.new(:key => "NI / Sales", :vals => [ni_over_sales])
90
+
91
+ if !prev_re_bs.nil? && !prev_re_is.nil?
92
+ analysis.rows << CalculationRow.new(:key => "Sales / NOA", :vals => [sales_over_noa(prev_re_bs)])
93
+ analysis.rows << CalculationRow.new(:key => "FI / NFA", :vals => [fi_over_nfa( prev_re_bs)])
94
+ analysis.rows << CalculationRow.new(:key => "Revenue Growth",:vals => [revenue_growth(prev_re_is)])
95
+ analysis.rows << CalculationRow.new(:key => "Core OI Growth",:vals => [core_oi_growth(prev_re_is)])
96
+ analysis.rows << CalculationRow.new(:key => "OI Growth", :vals => [nil])
97
+ analysis.rows << CalculationRow.new(:key => "ReOI ($MM)", :vals => [re_oi(prev_re_bs, expected_cost_of_capital).to_nearest_million])
98
+ else
99
+ analysis.rows << CalculationRow.new(:key => "Sales / NOA", :vals => [nil])
100
+ analysis.rows << CalculationRow.new(:key => "FI / NFA", :vals => [nil])
101
+ analysis.rows << CalculationRow.new(:key => "Revenue Growth",:vals => [nil])
102
+ analysis.rows << CalculationRow.new(:key => "Core OI Growth",:vals => [nil])
103
+ analysis.rows << CalculationRow.new(:key => "OI Growth", :vals => [nil])
104
+ analysis.rows << CalculationRow.new(:key => "ReOI ($MM)", :vals => [nil])
105
+ end
106
+
107
+ return analysis
108
+ end
109
+ end
110
+ end
@@ -23,16 +23,16 @@ module FinModeling
23
23
  return @balance_sheet_analyses
24
24
  end
25
25
 
26
- def income_statement_analyses(filings)
26
+ def income_statement_analyses(filings, expected_rate_of_return)
27
27
  if !@income_statement_analyses
28
28
  prev_filing = filings.last
29
29
  prev_re_bs = prev_filing.balance_sheet.reformulated(prev_filing.balance_sheet.periods.last)
30
30
  prev_prev_is = (filings.length > 2) ? filings[-2].income_statement : nil
31
- prev_re_is = prev_filing.income_statement.latest_quarterly_reformulated(prev_prev_is)
31
+ prev_re_is = prev_filing.income_statement.latest_quarterly_reformulated(prev_cis=nil, prev_prev_is, prev_prev_cis=nil)
32
32
 
33
33
  @reformulated_income_statements.zip(@reformulated_balance_sheets).each do |re_is, re_bs|
34
- next_analysis = FinModeling::ReformulatedIncomeStatement.empty_analysis if !re_is
35
- next_analysis = re_is.analysis(re_bs, prev_re_is, prev_re_bs) if re_is
34
+ next_analysis = FinModeling::ReformulatedIncomeStatement.empty_analysis if !re_is
35
+ next_analysis = re_is.analysis(re_bs, prev_re_is, prev_re_bs, expected_rate_of_return) if re_is
36
36
 
37
37
  @income_statement_analyses = @income_statement_analyses + next_analysis if @income_statement_analyses
38
38
  @income_statement_analyses = next_analysis if !@income_statement_analyses
File without changes
@@ -1,3 +1,5 @@
1
+ # encoding: utf-8
2
+
1
3
  module FinModeling
2
4
 
3
5
  class IncomeStatementAnalyses < CalculationSummary
@@ -11,41 +13,45 @@ module FinModeling
11
13
  @totals_row_enabled = false
12
14
  end
13
15
 
14
- def print_extras
15
- if operating_pm_row && operating_pm_row.valid_vals.any?
16
- lr = operating_pm_row.valid_vals.linear_regression
17
- puts "\t\toperating pm: "+
16
+ def print_regressions
17
+ if revenue_growth_row && revenue_growth_row.valid_vals.any?
18
+ lr = revenue_growth_row.valid_vals.linear_regression
19
+ puts "\t\trevenue growth: "+
18
20
  "a:#{lr.a.to_s.cap_decimals(4)}, "+
19
21
  "b:#{lr.b.to_s.cap_decimals(4)}, "+
20
- "r:#{lr.r.to_s.cap_decimals(4)}, "+
21
- "var:#{operating_pm_row.valid_vals.variance.to_s.cap_decimals(4)}"
22
+ "r²:#{lr.r2.to_s.cap_decimals(4)}, "+
23
+ "σ²:#{revenue_growth_row.valid_vals.variance.to_s.cap_decimals(4)}, " +
24
+ ( (lr.r2 > 0.6) ? "strong fit" : ( (lr.r2 < 0.2) ? "weak fit [**]" : "avg fit") )
22
25
  end
23
26
 
24
27
  if sales_over_noa_row && sales_over_noa_row.valid_vals.any?
25
28
  lr = sales_over_noa_row.valid_vals.linear_regression
26
- puts "\t\tsales / noa: "+
29
+ puts "\t\tsales / noa: "+
27
30
  "a:#{lr.a.to_s.cap_decimals(4)}, "+
28
31
  "b:#{lr.b.to_s.cap_decimals(4)}, "+
29
- "r:#{lr.r.to_s.cap_decimals(4)}, "+
30
- "var:#{sales_over_noa_row.valid_vals.variance.to_s.cap_decimals(4)}"
32
+ "r²:#{lr.r2.to_s.cap_decimals(4)}, "+
33
+ "σ²:#{sales_over_noa_row.valid_vals.variance.to_s.cap_decimals(4)}, " +
34
+ ( (lr.r2 > 0.6) ? "strong fit" : ( (lr.r2 < 0.2) ? "weak fit [**]" : "avg fit") )
31
35
  end
32
36
 
33
- if revenue_growth_row && revenue_growth_row.valid_vals.any?
34
- lr = revenue_growth_row.valid_vals.linear_regression
35
- puts "\t\trevenue growth: "+
37
+ if operating_pm_row && operating_pm_row.valid_vals.any?
38
+ lr = operating_pm_row.valid_vals.linear_regression
39
+ puts "\t\toperating pm: "+
36
40
  "a:#{lr.a.to_s.cap_decimals(4)}, "+
37
41
  "b:#{lr.b.to_s.cap_decimals(4)}, "+
38
- "r:#{lr.r.to_s.cap_decimals(4)}, "+
39
- "var:#{revenue_growth_row.valid_vals.variance.to_s.cap_decimals(4)}"
42
+ "r²:#{lr.r2.to_s.cap_decimals(4)}, "+
43
+ "σ²:#{operating_pm_row.valid_vals.variance.to_s.cap_decimals(4)}, " +
44
+ ( (lr.r2 > 0.6) ? "strong fit" : ( (lr.r2 < 0.2) ? "weak fit [**]" : "avg fit") )
40
45
  end
41
46
 
42
47
  if fi_over_nfa_row && fi_over_nfa_row.valid_vals.any?
43
48
  lr = fi_over_nfa_row.valid_vals.linear_regression
44
- puts "\t\tfi / nfa: "+
49
+ puts "\t\tfi / nfa: "+
45
50
  "a:#{lr.a.to_s.cap_decimals(4)}, "+
46
51
  "b:#{lr.b.to_s.cap_decimals(4)}, "+
47
- "r:#{lr.r.to_s.cap_decimals(4)}, "+
48
- "var:#{fi_over_nfa_row.valid_vals.variance.to_s.cap_decimals(4)}"
52
+ "r²:#{lr.r2.to_s.cap_decimals(4)}, "+
53
+ "σ²:#{fi_over_nfa_row.valid_vals.variance.to_s.cap_decimals(4)}, " +
54
+ ( (lr.r2 > 0.6) ? "strong fit" : ( (lr.r2 < 0.2) ? "weak fit [**]" : "avg fit") )
49
55
  end
50
56
  end
51
57
 
@@ -1,66 +1,69 @@
1
1
  module FinModeling
2
2
  class IncomeStatementCalculation < CompanyFilingCalculation
3
+ include CanChooseSuccessivePeriods
3
4
 
5
+ NI_GOAL = "net income"
6
+ NI_LABELS = [ /^(|consolidated )net (|income loss|loss income|income|loss|)(| net of tax)(| attributable to parent)/,
7
+ /^profit loss$/, # I have a feeling this is from the misguided attempt to parse CI here. Get rid of it...
8
+ /^allocation.*of.*undistributed.*earnings/ ]
9
+ NI_ANTI_LABELS = [ ]
10
+ NI_IDS = [ /^(|Locator_|loc_)(|us-gaap_)NetIncomeLoss[_0-9a-z]+/,
11
+ /^(|Locator_|loc_)(|us-gaap_)NetIncomeLossAvailableToCommonStockholdersBasic[_0-9a-z]+/,
12
+ /^(|Locator_|loc_)(|us-gaap_)ProfitLoss[_0-9a-z]+/ ]
4
13
  def net_income_calculation
5
- if @ni.nil?
6
- friendly_goal = "net income"
7
- label_regexes = [ /^net (income|loss|loss income)/,
8
- /^profit loss$/,
9
- /^allocation.*of.*undistributed.*earnings/ ]
10
- id_regexes = [ /^(|Locator_|loc_)(|us-gaap_)NetIncomeLoss[_0-9a-z]+/,
11
- /^(|Locator_|loc_)(|us-gaap_)NetIncomeLossAvailableToCommonStockholdersBasic[_0-9a-z]+/,
12
- /^(|Locator_|loc_)(|us-gaap_)ProfitLoss[_0-9a-z]+/ ]
13
- calc = find_and_verify_calculation_arc(friendly_goal, label_regexes, id_regexes)
14
- @ni = NetIncomeCalculation.new(calc)
14
+ begin
15
+ @ni ||= NetIncomeCalculation.new(find_calculation_arc(NI_GOAL, NI_LABELS, NI_ANTI_LABELS, NI_IDS))
16
+ rescue FinModeling::InvalidFilingError => e
17
+ pre_msg = "calculation tree:\n" + self.calculation.sprint_tree
18
+ raise e, pre_msg+e.message, e.backtrace
15
19
  end
16
- return @ni
17
20
  end
18
21
 
19
22
  def is_valid?
20
- has_revenue_item = false
21
- has_tax_item = false
22
- net_income_calculation.leaf_items.each do |leaf|
23
- if !has_revenue_item and leaf.name.downcase.matches_regexes?([/revenue/, /sales/])
24
- has_revenue_item = true
25
- end
26
- if !has_tax_item and leaf.name.downcase.matches_regexes?([/tax/])
27
- has_tax_item = true
23
+ puts "income statement's net income calculation lacks tax item" if !net_income_calculation.has_tax_item?
24
+ puts "income statement's net income calculation lacks sales/revenue item" if !net_income_calculation.has_revenue_item?
25
+ if !net_income_calculation.has_tax_item? || !net_income_calculation.has_revenue_item?
26
+ if net_income_calculation
27
+ puts "summary:"
28
+ net_income_calculation.summary(:period => periods.last).print
28
29
  end
30
+ puts "calculation tree:\n" + self.calculation.sprint_tree(indent_count=0, simplified=true)
29
31
  end
30
-
31
- puts "income statement's net income calculation lacks tax item" if !has_tax_item
32
- puts "income statement's net income calculation lacks sales/revenue item" if !has_revenue_item
33
- return (has_revenue_item and has_tax_item)
32
+ return (net_income_calculation.has_revenue_item? && net_income_calculation.has_tax_item?)
34
33
  end
35
34
 
36
- def reformulated(period)
35
+ def reformulated(period, comprehensive_income_calculation)
37
36
  return ReformulatedIncomeStatement.new(period,
38
- net_income_calculation.summary(:period=>period))
37
+ net_income_calculation.summary(:period=>period),
38
+ comprehensive_income_calculation ? comprehensive_income_calculation.summary(:period=>period) : nil)
39
39
  end
40
40
 
41
- def latest_quarterly_reformulated(prev_income_statement)
42
- if (net_income_calculation.periods.quarterly.any?) &&
43
- (reformulated(net_income_calculation.periods.quarterly.last).operating_revenues.total.abs > 1.0) && # FIXME: make an is_valid here?
44
- (reformulated(net_income_calculation.periods.quarterly.last).cost_of_revenues.total.abs > 1.0) # FIXME: make an is_valid here?
45
- return reformulated(net_income_calculation.periods.quarterly.last)
41
+ def latest_quarterly_reformulated(cur_ci_calc, prev_is, prev_ci_calc)
42
+ if net_income_calculation.periods.quarterly.any?
43
+ period = net_income_calculation.periods.quarterly.last
44
+ lqr = reformulated(period, cur_ci_calc)
46
45
 
47
- elsif !prev_income_statement
48
- return nil
46
+ if (lqr.operating_revenues.total.abs > 1.0) && # FIXME: make an is_valid here?
47
+ (lqr.cost_of_revenues .total.abs > 1.0) # FIXME: make an is_valid here?
48
+ return lqr
49
+ end
50
+ end
49
51
 
50
- elsif net_income_calculation.periods.yearly.any? &&
51
- prev_income_statement.net_income_calculation.periods.threequarterly.any?
52
- is_period = net_income_calculation.periods.yearly.last
53
- re_is = reformulated(is_period)
54
-
55
- period_1q_thru_3q = prev_income_statement.net_income_calculation.periods.threequarterly.last
56
- prev3q = prev_income_statement.reformulated(period_1q_thru_3q)
57
- re_is = re_is - prev3q
58
- return re_is
52
+ return nil if !prev_is
53
+
54
+ cur_period, prev_period = choose_successive_periods(net_income_calculation, prev_is.net_income_calculation)
55
+ if cur_period && prev_period
56
+ new_re_is = reformulated(cur_period, cur_ci_calc) - prev_is.reformulated(prev_period, prev_ci_calc)
57
+ # the above subtraction doesn't know what period you want. So let's patch the result to have
58
+ # a quarterly period with the right end-points
59
+ new_re_is.period = Xbrlware::Context::Period.new({"start_date"=>prev_period.value["end_date"],
60
+ "end_date" =>cur_period.value["end_date"]})
61
+ return new_re_is
59
62
  end
60
-
63
+
61
64
  return nil
62
65
  end
63
-
66
+
64
67
  def write_constructor(file, item_name)
65
68
  item_calc_name = item_name + "_calc"
66
69
  @calculation.write_constructor(file, item_calc_name)
@@ -3,7 +3,7 @@ module FinModeling
3
3
  include HasStringClassifier
4
4
 
5
5
  BASE_FILENAME = File.join(FinModeling::BASE_PATH, "classifiers/isi_")
6
- TYPES = [ :or, :cogs, :oe, :oibt, :fibt, :tax, :ooiat, :fiat ]
6
+ TYPES = [ :or, :cogs, :oe, :oibt, :fibt, :tax, :ooiat, :ooiat_nci, :fiat ]
7
7
 
8
8
  has_string_classifier(TYPES, IncomeStatementItem)
9
9
 
@@ -19,6 +19,17 @@ module FinModeling
19
19
  {:klass=>:cogs, :item_string=>"Fulfillment Expense"},
20
20
  {:klass=>:cogs, :item_string=>"Fulfillment Expense"},
21
21
 
22
+ {:klass=>:or, :item_string=>"Net Sales"},
23
+ {:klass=>:cogs, :item_string=>"Cost of products sold"},
24
+ {:klass=>:oe, :item_string=>"Marketing, selling and administrative"},
25
+ {:klass=>:oe, :item_string=>"Advertising and product promotion"},
26
+ {:klass=>:oe, :item_string=>"Research and development"},
27
+ {:klass=>:oibt, :item_string=>"Provision for restructuring"},
28
+ {:klass=>:oibt, :item_string=>"Litigation expense, net"},
29
+ {:klass=>:oibt, :item_string=>"Equity in net income of affiliates"},
30
+ {:klass=>:oibt, :item_string=>"Other (income)/expense"},
31
+ {:klass=>:tax, :item_string=>"Provision for income taxes"},
32
+
22
33
  {:klass=>:or, :item_string=>"Subscription Revenue"},
23
34
  {:klass=>:or, :item_string=>"Sales Revenue Net"},
24
35
  {:klass=>:cogs, :item_string=>"Cost Of Goods Sold Subscription"},
@@ -40,7 +51,7 @@ module FinModeling
40
51
  {:klass=>:fibt, :item_string=>"Interest Expense"},
41
52
  {:klass=>:fibt, :item_string=>"Other Nonoperating Income Expense"},
42
53
  {:klass=>:tax, :item_string=>"Income Tax Expense Benefit"},
43
- {:klass=>:fiat, :item_string=>"Net Income Loss Attributable To Noncontrolling Interest"},
54
+ {:klass=>:ooiat_nci, :item_string=>"Net Income Loss Attributable To Noncontrolling Interest"},
44
55
 
45
56
  {:klass=>:or, :item_string=>"Sales Revenue Goods Net"},
46
57
  {:klass=>:or, :item_string=>"Sales Revenue Services Net"},
@@ -80,9 +91,9 @@ module FinModeling
80
91
  {:klass=>:oibt, :item_string=>"Restructuring Charges"},
81
92
  {:klass=>:fibt, :item_string=>"Interest Expense"},
82
93
  {:klass=>:fibt, :item_string=>"Nonoperating Income Expense"},
83
- {:klass=>:fibt, :item_string=>"Gain Loss On Sale Of Equity Investments"},
94
+ {:klass=>:oibt, :item_string=>"Gain Loss On Sale Of Equity Investments"},
84
95
  {:klass=>:tax, :item_string=>"Income Tax Expense Benefit"},
85
- {:klass=>:fiat, :item_string=>"Income Loss From Discontinued Operations Net Of Tax Attributable To Reporting Entity"},
96
+ {:klass=>:ooiat, :item_string=>"Income Loss From Discontinued Operations Net Of Tax Attributable To Reporting Entity"},
86
97
 
87
98
  {:klass=>:or, :item_string=>"Sales Revenue Net"},
88
99
  {:klass=>:cogs, :item_string=>"Cost Of Goods And Services Sold"},
@@ -133,7 +144,7 @@ module FinModeling
133
144
  {:klass=>:oibt, :item_string=>"Restructuring Charges"},
134
145
  {:klass=>:fibt, :item_string=>"Nonoperating Income Expense"},
135
146
  {:klass=>:tax, :item_string=>"Income Tax Expense Benefit"},
136
- {:klass=>:fiat, :item_string=>"Income Loss From Discontinued Operations Net Of Tax"},
147
+ {:klass=>:ooiat, :item_string=>"Income Loss From Discontinued Operations Net Of Tax"},
137
148
 
138
149
  {:klass=>:or, :item_string=>"Sales Revenue Goods Net"},
139
150
  {:klass=>:or, :item_string=>"Sales Revenue Services Net"},
@@ -197,7 +208,7 @@ module FinModeling
197
208
  {:klass=>:fibt, :item_string=>"Interest Expense"},
198
209
  {:klass=>:fibt, :item_string=>"Other Nonoperating Income Expense"},
199
210
  {:klass=>:tax, :item_string=>"Income Tax Expense Benefit"},
200
- {:klass=>:fiat, :item_string=>"Income Loss From Equity Method Investments"},
211
+ {:klass=>:ooiat, :item_string=>"Income Loss From Equity Method Investments"},
201
212
 
202
213
  {:klass=>:or, :item_string=>"Licenses Revenue"},
203
214
  {:klass=>:or, :item_string=>"Maintenance Revenue"},
@@ -244,7 +255,7 @@ module FinModeling
244
255
  {:klass=>:fibt, :item_string=>"Gain Loss On Sale Of Business"},
245
256
  {:klass=>:fibt, :item_string=>"Other Nonoperating Income Expense"},
246
257
  {:klass=>:tax, :item_string=>"Income Tax Expense Benefit"},
247
- {:klass=>:fiat, :item_string=>"Net Income Loss Attributable To Noncontrolling Interest"},
258
+ {:klass=>:ooiat_nci, :item_string=>"Net Income Loss Attributable To Noncontrolling Interest"},
248
259
 
249
260
  {:klass=>:or, :item_string=>"Licenses Revenue"},
250
261
  {:klass=>:or, :item_string=>"Maintenance Revenue"},
@@ -297,10 +308,10 @@ module FinModeling
297
308
  {:klass=>:oe, :item_string=>"Amortization Of Intangible Assets"},
298
309
  {:klass=>:fibt, :item_string=>"Investment Income Interest"},
299
310
  {:klass=>:fibt, :item_string=>"Interest Expense"},
300
- {:klass=>:fibt, :item_string=>"Income Loss From Equity Method Investments"},
311
+ {:klass=>:oibt, :item_string=>"Income Loss From Equity Method Investments"},
301
312
  {:klass=>:fibt, :item_string=>"Other Nonoperating Expense"},
302
313
  {:klass=>:tax, :item_string=>"Income Tax Expense Benefit"},
303
- {:klass=>:fiat, :item_string=>"Net Income Loss Attributable To Noncontrolling Interest"},
314
+ {:klass=>:ooiat_nci, :item_string=>"Net Income Loss Attributable To Noncontrolling Interest"},
304
315
 
305
316
  {:klass=>:or, :item_string=>"Revenues"},
306
317
  {:klass=>:cogs, :item_string=>"Cost Of Revenue"},
@@ -356,7 +367,7 @@ module FinModeling
356
367
  {:klass=>:fibt, :item_string=>"Other Nonoperating Income Expense"},
357
368
  {:klass=>:fibt, :item_string=>"Impairment Of Investments"},
358
369
  {:klass=>:tax, :item_string=>"Income Tax Expense Benefit"},
359
- {:klass=>:fiat, :item_string=>"Net Income Loss Attributable To Noncontrolling Interest"},
370
+ {:klass=>:ooiat_nci, :item_string=>"Net Income Loss Attributable To Noncontrolling Interest"},
360
371
 
361
372
  {:klass=>:or, :item_string=>"Sales Revenue Goods Net"},
362
373
  {:klass=>:or, :item_string=>"Sales Revenue Services Net"},
@@ -504,7 +515,7 @@ module FinModeling
504
515
  {:klass=>:oe, :item_string=>"Research And Development In Process"},
505
516
  {:klass=>:oe, :item_string=>"Goodwill And Acquisition Related Intangible Assets Impairment"},
506
517
  {:klass=>:fibt, :item_string=>"Gain Loss On Disposition Of Assets"},
507
- {:klass=>:fibt, :item_string=>"Equity Method Investment Other Than Temporary Impairment"},
518
+ {:klass=>:oibt, :item_string=>"Equity Method Investment Other Than Temporary Impairment"},
508
519
  {:klass=>:fibt, :item_string=>"Interest Income And Other Expense Net"},
509
520
  {:klass=>:tax, :item_string=>"Income Tax Expense Benefit"},
510
521
 
@@ -522,7 +533,7 @@ module FinModeling
522
533
  {:klass=>:fibt, :item_string=>"Interest Expense"},
523
534
  {:klass=>:fibt, :item_string=>"Other Nonoperating Income Expense"},
524
535
  {:klass=>:tax, :item_string=>"Income Tax Expense Benefit"},
525
- {:klass=>:fiat, :item_string=>"Income Loss From Equity Method Investments Net Of Tax"},
536
+ {:klass=>:ooiat, :item_string=>"Income Loss From Equity Method Investments Net Of Tax"},
526
537
 
527
538
  {:klass=>:or, :item_string=>"Sales Revenue Net"},
528
539
  {:klass=>:cogs, :item_string=>"Cost Of Revenue"},
@@ -533,7 +544,7 @@ module FinModeling
533
544
  {:klass=>:fibt, :item_string=>"Interest Expense"},
534
545
  {:klass=>:fibt, :item_string=>"Other Nonoperating Expense"},
535
546
  {:klass=>:tax, :item_string=>"Income Tax Expense Benefit"},
536
- {:klass=>:fiat, :item_string=>"Net Income Loss Attributable To Noncontrolling Interest"},
547
+ {:klass=>:ooiat_nci, :item_string=>"Net Income Loss Attributable To Noncontrolling Interest"},
537
548
 
538
549
  {:klass=>:or, :item_string=>"Sales Revenue Net"},
539
550
  {:klass=>:cogs, :item_string=>"Cost Of Revenue"},
@@ -614,7 +625,7 @@ module FinModeling
614
625
  {:klass=>:oe, :item_string=>"Research And Development Expense Software Excluding Acquired In Process Cost"},
615
626
  {:klass=>:oe, :item_string=>"Selling And Marketing Expense"},
616
627
  {:klass=>:oe, :item_string=>"General And Administrative Expense"},
617
- {:klass=>:fibt, :item_string=>"Income Loss From Equity Method Investments"},
628
+ {:klass=>:oibt, :item_string=>"Income Loss From Equity Method Investments"},
618
629
  {:klass=>:oibt, :item_string=>"Amortization Of Intangible Assets"},
619
630
  {:klass=>:oibt, :item_string=>"Other Restructuring Costs"},
620
631
  {:klass=>:fibt, :item_string=>"Other Nonoperating Income Expense"},
@@ -0,0 +1,4 @@
1
+ module FinModeling
2
+ class InvalidFilingError < StandardError
3
+ end
4
+ end
@@ -6,20 +6,18 @@ module FinModeling
6
6
 
7
7
  BASE_FILENAME = File.join(FinModeling::BASE_PATH, "summaries/liabs_and_equity_")
8
8
 
9
- ALL_STATES = [ :ol, :fl, :cse ]
10
- NEXT_STATES = { nil => [ :ol, :fl, :cse ],
11
- :ol => [ :ol, :fl, :cse ],
12
- :fl => [ :ol, :fl, :cse ],
13
- :cse => [ :fl, :cse ] }
9
+ ALL_STATES = [ :ol, :fl, :cse, :mi ]
10
+ NEXT_STATES = { nil => [ :ol, :fl, :cse, :mi ],
11
+ :ol => [ :ol, :fl, :cse, :mi ], # operating liabilities
12
+ :fl => [ :ol, :fl, :cse, :mi ], # financial liabilities
13
+ :cse => [ :ol, :fl, :cse, :mi ], # common shareholder equity
14
+ :mi => [ :fl, :cse, :mi ] } # minority interest
14
15
 
15
16
  def summary(args)
16
17
  summary_cache_key = args[:period].to_pretty_s
17
18
  summary = lookup_cached_summary(summary_cache_key)
18
19
  return summary if !summary.nil?
19
20
 
20
- mapping = Xbrlware::ValueMapping.new
21
- mapping.policy[:debit] = :flip
22
-
23
21
  summary = super(:period => args[:period], :mapping => mapping) # FIXME: flip_total should == true!
24
22
  if !lookup_cached_classifications(BASE_FILENAME, summary.rows)
25
23
  lookahead = [4, summary.rows.length-1].min
@@ -32,5 +30,17 @@ module FinModeling
32
30
  return summary
33
31
  end
34
32
 
33
+ def mapping
34
+ m = Xbrlware::ValueMapping.new
35
+ m.policy[:debit] = :flip
36
+ m
37
+ end
38
+
39
+ def has_equity_item
40
+ @has_equity_item ||= leaf_items.any? do |leaf|
41
+ leaf.name.downcase.matches_any_regex?([/equity/, /stock/])
42
+ end
43
+ end
44
+
35
45
  end
36
46
  end
@@ -3,7 +3,7 @@ module FinModeling
3
3
  include HasStringClassifier
4
4
 
5
5
  BASE_FILENAME = File.join(FinModeling::BASE_PATH, "classifiers/laei_")
6
- TYPES = [ :ol, :fl, :cse ]
6
+ TYPES = [ :ol, :fl, :cse, :mi ]
7
7
 
8
8
  has_string_classifier(TYPES, LiabsAndEquityItem)
9
9