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
@@ -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