finmodeling 0.1

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 (97) hide show
  1. data/.gitignore +3 -0
  2. data/Gemfile +10 -0
  3. data/README.md +292 -0
  4. data/Rakefile +6 -0
  5. data/TODO.txt +36 -0
  6. data/examples/dump_report.rb +33 -0
  7. data/examples/lists/nasdaq-mid-to-mega-tech-symbols.txt +226 -0
  8. data/examples/show_report.rb +218 -0
  9. data/examples/show_reports.rb +77 -0
  10. data/finmodeling.gemspec +31 -0
  11. data/lib/finmodeling/annual_report_filing.rb +104 -0
  12. data/lib/finmodeling/array_with_stats.rb +22 -0
  13. data/lib/finmodeling/assets_calculation.rb +36 -0
  14. data/lib/finmodeling/assets_item.rb +14 -0
  15. data/lib/finmodeling/assets_item_vectors.rb +638 -0
  16. data/lib/finmodeling/balance_sheet_analyses.rb +33 -0
  17. data/lib/finmodeling/balance_sheet_calculation.rb +68 -0
  18. data/lib/finmodeling/calculation_summary.rb +148 -0
  19. data/lib/finmodeling/can_cache_classifications.rb +36 -0
  20. data/lib/finmodeling/can_cache_summaries.rb +16 -0
  21. data/lib/finmodeling/can_classify_rows.rb +54 -0
  22. data/lib/finmodeling/cash_change_calculation.rb +67 -0
  23. data/lib/finmodeling/cash_change_item.rb +14 -0
  24. data/lib/finmodeling/cash_change_item_vectors.rb +241 -0
  25. data/lib/finmodeling/cash_flow_statement_calculation.rb +85 -0
  26. data/lib/finmodeling/classifiers.rb +11 -0
  27. data/lib/finmodeling/company.rb +102 -0
  28. data/lib/finmodeling/company_filing.rb +64 -0
  29. data/lib/finmodeling/company_filing_calculation.rb +75 -0
  30. data/lib/finmodeling/company_filings.rb +100 -0
  31. data/lib/finmodeling/config.rb +37 -0
  32. data/lib/finmodeling/constant_forecasting_policy.rb +23 -0
  33. data/lib/finmodeling/factory.rb +27 -0
  34. data/lib/finmodeling/float_helpers.rb +17 -0
  35. data/lib/finmodeling/forecasts.rb +48 -0
  36. data/lib/finmodeling/generic_forecasting_policy.rb +19 -0
  37. data/lib/finmodeling/has_string_classifer.rb +96 -0
  38. data/lib/finmodeling/income_statement_analyses.rb +74 -0
  39. data/lib/finmodeling/income_statement_calculation.rb +71 -0
  40. data/lib/finmodeling/income_statement_item.rb +14 -0
  41. data/lib/finmodeling/income_statement_item_vectors.rb +654 -0
  42. data/lib/finmodeling/liabs_and_equity_calculation.rb +36 -0
  43. data/lib/finmodeling/liabs_and_equity_item.rb +14 -0
  44. data/lib/finmodeling/liabs_and_equity_item_vectors.rb +1936 -0
  45. data/lib/finmodeling/net_income_calculation.rb +41 -0
  46. data/lib/finmodeling/paths.rb +5 -0
  47. data/lib/finmodeling/period_array.rb +24 -0
  48. data/lib/finmodeling/quarterly_report_filing.rb +23 -0
  49. data/lib/finmodeling/rate.rb +20 -0
  50. data/lib/finmodeling/ratio.rb +20 -0
  51. data/lib/finmodeling/reformulated_balance_sheet.rb +176 -0
  52. data/lib/finmodeling/reformulated_cash_flow_statement.rb +140 -0
  53. data/lib/finmodeling/reformulated_income_statement.rb +436 -0
  54. data/lib/finmodeling/string_helpers.rb +26 -0
  55. data/lib/finmodeling/version.rb +3 -0
  56. data/lib/finmodeling.rb +70 -0
  57. data/spec/annual_report_filing_spec.rb +68 -0
  58. data/spec/assets_calculation_spec.rb +21 -0
  59. data/spec/assets_item_spec.rb +66 -0
  60. data/spec/balance_sheet_analyses_spec.rb +43 -0
  61. data/spec/balance_sheet_calculation_spec.rb +91 -0
  62. data/spec/calculation_summary_spec.rb +63 -0
  63. data/spec/can_classify_rows_spec.rb +86 -0
  64. data/spec/cash_change_calculation_spec.rb +56 -0
  65. data/spec/cash_change_item_spec.rb +66 -0
  66. data/spec/cash_flow_statement_calculation_spec.rb +108 -0
  67. data/spec/company_filing_calculation_spec.rb +74 -0
  68. data/spec/company_filing_spec.rb +30 -0
  69. data/spec/company_filings_spec.rb +55 -0
  70. data/spec/company_spec.rb +73 -0
  71. data/spec/constant_forecasting_policy_spec.rb +37 -0
  72. data/spec/factory_spec.rb +18 -0
  73. data/spec/forecasts_spec.rb +21 -0
  74. data/spec/generic_forecasting_policy_spec.rb +33 -0
  75. data/spec/income_statement_analyses_spec.rb +63 -0
  76. data/spec/income_statement_calculation_spec.rb +88 -0
  77. data/spec/income_statement_item_spec.rb +86 -0
  78. data/spec/liabs_and_equity_calculation_spec.rb +20 -0
  79. data/spec/liabs_and_equity_item_spec.rb +66 -0
  80. data/spec/mocks/calculation.rb +10 -0
  81. data/spec/mocks/income_statement_analyses.rb +93 -0
  82. data/spec/mocks/sec_query.rb +31 -0
  83. data/spec/net_income_calculation_spec.rb +23 -0
  84. data/spec/period_array.rb +52 -0
  85. data/spec/quarterly_report_filing_spec.rb +69 -0
  86. data/spec/rate_spec.rb +33 -0
  87. data/spec/ratio_spec.rb +33 -0
  88. data/spec/reformulated_balance_sheet_spec.rb +146 -0
  89. data/spec/reformulated_cash_flow_statement_spec.rb +174 -0
  90. data/spec/reformulated_income_statement_spec.rb +293 -0
  91. data/spec/spec_helper.rb +5 -0
  92. data/spec/string_helpers_spec.rb +23 -0
  93. data/tools/create_balance_sheet_training_vectors.rb +65 -0
  94. data/tools/create_cash_change_training_vectors.rb +48 -0
  95. data/tools/create_credit_debit_training_vectors.rb +51 -0
  96. data/tools/create_income_statement_training_vectors.rb +48 -0
  97. metadata +289 -0
@@ -0,0 +1,100 @@
1
+ module FinModeling
2
+ class CompanyFilings < Array
3
+ def balance_sheet_analyses
4
+ if !@balance_sheet_analyses
5
+ re_bs = nil
6
+ self.each do |filing|
7
+ prev_re_bs = re_bs
8
+ re_bs = filing.balance_sheet.reformulated(filing.balance_sheet.periods.last)
9
+ next_analysis = re_bs.analysis(prev_re_bs)
10
+
11
+ @balance_sheet_analyses = @balance_sheet_analyses + next_analysis if @balance_sheet_analyses
12
+ @balance_sheet_analyses = next_analysis if !@balance_sheet_analyses
13
+ end
14
+ @balance_sheet_analyses = BalanceSheetAnalyses.new(@balance_sheet_analyses)
15
+ end
16
+ return @balance_sheet_analyses
17
+ end
18
+
19
+ def income_statement_analyses
20
+ if !@income_statement_analyses
21
+ prev_re_bs, prev_re_is, prev_filing = [nil, nil, nil]
22
+ self.each do |filing|
23
+ re_is = filing.income_statement.latest_quarterly_reformulated(prev_filing ? prev_filing.income_statement : nil)
24
+ re_bs = filing.balance_sheet.reformulated(filing.balance_sheet.periods.last)
25
+
26
+ next_analysis = FinModeling::ReformulatedIncomeStatement.empty_analysis if !re_is
27
+ next_analysis = re_is.analysis(re_bs, prev_re_is, prev_re_bs) if re_is
28
+
29
+ @income_statement_analyses = @income_statement_analyses + next_analysis if @income_statement_analyses
30
+ @income_statement_analyses = next_analysis if !@income_statement_analyses
31
+
32
+ prev_re_bs, prev_re_is, prev_filing = [re_bs, re_is, filing]
33
+ end
34
+ @income_statement_analyses = IncomeStatementAnalyses.new(@income_statement_analyses)
35
+ end
36
+ return @income_statement_analyses
37
+ end
38
+
39
+ def cash_flow_statement_analyses
40
+ if !@cash_flow_statement_analyses
41
+ prev_filing, prev_re_cfs = [nil, nil]
42
+ self.each do |filing|
43
+ re_is = filing.income_statement.latest_quarterly_reformulated(prev_filing ? prev_filing.income_statement : nil)
44
+ re_cfs = filing.cash_flow_statement.latest_quarterly_reformulated(prev_filing ? prev_filing.cash_flow_statement : nil)
45
+
46
+ next_analysis = FinModeling::ReformulatedCashFlowStatement.empty_analysis if !re_cfs
47
+ next_analysis = re_cfs.analysis(re_is) if re_cfs
48
+
49
+ @cash_flow_statement_analyses = @cash_flow_statement_analyses + next_analysis if @cash_flow_statement_analyses
50
+ @cash_flow_statement_analyses = next_analysis if !@cash_flow_statement_analyses
51
+
52
+ prev_filing, prev_re_cfs = [filing, re_cfs]
53
+ end
54
+ @cash_flow_statement_analyses.totals_row_enabled = false
55
+ end
56
+ return @cash_flow_statement_analyses
57
+ end
58
+
59
+ def choose_forecasting_policy
60
+ if length < 3
61
+ return FinModeling::GenericForecastingPolicy.new
62
+ else
63
+ isa = income_statement_analyses
64
+ args = { }
65
+ args[:revenue_growth] = isa.revenue_growth_row.valid_vals.mean
66
+ args[:sales_pm ] = isa.operating_pm_row.valid_vals.mean
67
+ args[:sales_over_noa] = isa.sales_over_noa_row.valid_vals.mean
68
+ args[:fi_over_nfa ] = isa.fi_over_nfa_row.valid_vals.mean
69
+ return FinModeling::ConstantForecastingPolicy.new(args)
70
+ end
71
+ end
72
+
73
+ def forecasts(policy, num_quarters)
74
+ f = Forecasts.new
75
+
76
+ last_re_bs = self.last.balance_sheet.reformulated(self.last.balance_sheet.periods.last)
77
+
78
+ last_last_is = (self.length >= 2) ? self[-2].income_statement : nil
79
+ puts "warning: last_last_is is nil..." if !last_last_is
80
+ last_re_is = self.last.income_statement.latest_quarterly_reformulated(last_last_is)
81
+ raise RuntimeError.new("last_re_is is nil!") if !last_re_is
82
+
83
+ num_quarters.times do |i|
84
+ next_bs_period = last_re_bs.period.plus_n_months(3)
85
+ next_is_period = last_re_is.period.plus_n_months(3)
86
+
87
+ next_re_is = FinModeling::ReformulatedIncomeStatement.forecast_next(next_is_period, policy, last_re_bs, last_re_is)
88
+ next_re_bs = FinModeling::ReformulatedBalanceSheet .forecast_next(next_bs_period, policy, last_re_bs, next_re_is)
89
+
90
+ f.reformulated_income_statements << next_re_is
91
+ f.reformulated_balance_sheets << next_re_bs
92
+
93
+ last_last_re_is, last_re_bs, last_re_is = [last_re_is, next_re_bs, next_re_is]
94
+ end
95
+
96
+ return f
97
+ end
98
+
99
+ end
100
+ end
@@ -0,0 +1,37 @@
1
+ module FinModeling
2
+ class Config
3
+ @@caching_enabled = true
4
+ def self.enable_caching
5
+ @@caching_enabled = true
6
+ end
7
+ def self.disable_caching
8
+ @@caching_enabled = false
9
+ end
10
+ def self.caching_enabled?
11
+ @@caching_enabled
12
+ end
13
+
14
+ @@balance_detail_enabled = false
15
+ def self.enable_balance_detail
16
+ @@balance_detail_enabled = true
17
+ end
18
+ def self.disable_balance_detail
19
+ @@balance_detail_enabled = false
20
+ end
21
+ def self.balance_detail_enabled?
22
+ @@balance_detail_enabled
23
+ end
24
+
25
+ @@income_detail_enabled = false
26
+ def self.enable_income_detail
27
+ @@income_detail_enabled = true
28
+ end
29
+ def self.disable_income_detail
30
+ @@income_detail_enabled = false
31
+ end
32
+ def self.income_detail_enabled?
33
+ @@income_detail_enabled
34
+ end
35
+ end
36
+ end
37
+
@@ -0,0 +1,23 @@
1
+ module FinModeling
2
+ class ConstantForecastingPolicy
3
+ def initialize(args)
4
+ @vals = args
5
+ end
6
+
7
+ def revenue_growth
8
+ @vals[:revenue_growth]
9
+ end
10
+
11
+ def sales_pm
12
+ @vals[:sales_pm]
13
+ end
14
+
15
+ def fi_over_nfa
16
+ @vals[:fi_over_nfa]
17
+ end
18
+
19
+ def sales_over_noa
20
+ @vals[:sales_over_noa]
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,27 @@
1
+ module FinModeling
2
+ class Factory
3
+ def self.IncomeStatementCalculation(args = {})
4
+ entity_details = {}
5
+ title = ""
6
+ role = ""
7
+ href = ""
8
+ arcs = []
9
+ contexts = nil
10
+
11
+ calculation = Xbrlware::Linkbase::CalculationLinkbase::Calculation.new(entity_details, title, role, href, arcs, contexts)
12
+ return FinModeling::IncomeStatementCalculation.new(calculation)
13
+ end
14
+
15
+ def self.BalanceSheetCalculation(args = {})
16
+ entity_details = {}
17
+ title = ""
18
+ role = ""
19
+ href = ""
20
+ arcs = []
21
+ contexts = nil
22
+
23
+ calculation = Xbrlware::Linkbase::CalculationLinkbase::Calculation.new(entity_details, title, role, href, arcs, contexts)
24
+ return FinModeling::BalanceSheetCalculation.new(calculation)
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,17 @@
1
+ class Fixnum
2
+ def to_nearest_million
3
+ return (self/1000000.0).round.to_f
4
+ end
5
+ def to_nearest_thousand
6
+ return (self/1000.0).round.to_f
7
+ end
8
+ end
9
+
10
+ class Float
11
+ def to_nearest_million
12
+ return (self/1000000.0).round.to_f
13
+ end
14
+ def to_nearest_thousand
15
+ return (self/1000.0).round.to_f
16
+ end
17
+ end
@@ -0,0 +1,48 @@
1
+ module FinModeling
2
+ class Forecasts
3
+ attr_accessor :reformulated_income_statements, :reformulated_balance_sheets
4
+
5
+ def initialize
6
+ @reformulated_income_statements = []
7
+ @reformulated_balance_sheets = []
8
+ end
9
+
10
+ def balance_sheet_analyses(filings)
11
+ if !@balance_sheet_analyses
12
+ prev_filing = filings.last
13
+ prev_re_bs = prev_filing.balance_sheet.reformulated(prev_filing.balance_sheet.periods.last)
14
+ @reformulated_balance_sheets.each do |re_bs|
15
+ next_analysis = re_bs.analysis(prev_re_bs)
16
+
17
+ @balance_sheet_analyses = @balance_sheet_analyses + next_analysis if @balance_sheet_analyses
18
+ @balance_sheet_analyses = next_analysis if !@balance_sheet_analyses
19
+ prev_re_bs = re_bs
20
+ end
21
+ @balance_sheet_analyses = BalanceSheetAnalyses.new(@balance_sheet_analyses)
22
+ end
23
+ return @balance_sheet_analyses
24
+ end
25
+
26
+ def income_statement_analyses(filings)
27
+ if !@income_statement_analyses
28
+ prev_filing = filings.last
29
+ prev_re_bs = prev_filing.balance_sheet.reformulated(prev_filing.balance_sheet.periods.last)
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)
32
+
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
36
+
37
+ @income_statement_analyses = @income_statement_analyses + next_analysis if @income_statement_analyses
38
+ @income_statement_analyses = next_analysis if !@income_statement_analyses
39
+
40
+ prev_re_bs, prev_re_is = [re_bs, re_is]
41
+ end
42
+ @income_statement_analyses = IncomeStatementAnalyses.new(@income_statement_analyses)
43
+ end
44
+ return @income_statement_analyses
45
+ end
46
+
47
+ end
48
+ end
@@ -0,0 +1,19 @@
1
+ module FinModeling
2
+ class GenericForecastingPolicy
3
+ def revenue_growth
4
+ 0.04
5
+ end
6
+
7
+ def sales_pm
8
+ 0.20
9
+ end
10
+
11
+ def fi_over_nfa
12
+ 0.01
13
+ end
14
+
15
+ def sales_over_noa
16
+ 2.00
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,96 @@
1
+ module FinModeling
2
+
3
+ module HasStringClassifier
4
+ module ClassMethods
5
+ @klasses ||= []
6
+ @classifiers ||= {}
7
+ @item_klass ||= nil
8
+
9
+ def has_string_classifier(klasses, item_klass)
10
+ @klasses = klasses
11
+ @item_klass = item_klass
12
+ @classifiers = Hash[ *klasses.zip(klasses.map{ |x| NaiveBayes.new(:yes, :no) }).flatten ]
13
+ end
14
+
15
+ def _load_vectors_and_train(base_filename, vectors)
16
+ FileUtils.mkdir_p(File.dirname(base_filename)) if !File.exists?(File.dirname(base_filename))
17
+ success = FinModeling::Config.caching_enabled?
18
+ @klasses.each do |cur_klass|
19
+ filename = base_filename + cur_klass.to_s + ".db"
20
+ success = success && File.exists?(filename)
21
+ if success
22
+ @classifiers[cur_klass] = NaiveBayes.load(filename)
23
+ else
24
+ @classifiers[cur_klass].db_filepath = filename
25
+ end
26
+ end
27
+ return if success
28
+
29
+ vectors.each do |vector|
30
+ begin
31
+ item = @item_klass.new(vector[:item_string])
32
+ item.train(vector[:klass])
33
+ rescue Exception => e
34
+ puts "\"#{vector[:item_string]}\" has a bogus klass: \"#{vector[:klass]}\""
35
+ puts "\t" + e.message
36
+ puts "\t" + e.backtrace.inspect.gsub(/, /, "\n\t ")
37
+ end
38
+ end
39
+
40
+ @klasses.each do |cur_klass|
41
+ @classifiers[cur_klass].save
42
+ end
43
+ end
44
+
45
+ def klasses
46
+ @klasses
47
+ end
48
+
49
+ def classifiers
50
+ @classifiers
51
+ end
52
+ end
53
+
54
+ def self.included(base)
55
+ base.extend(ClassMethods)
56
+ end
57
+
58
+ def train(expected_klass)
59
+ raise TypeError.new("#{expected_klass} is not in #{self.class.klasses}") if !self.class.klasses.include?(expected_klass)
60
+
61
+ self.class.klasses.each do |cur_klass|
62
+ is_expected_klass = (expected_klass == cur_klass) ? :yes : :no
63
+ self.class.classifiers[cur_klass].train(is_expected_klass, *tokenize)
64
+ end
65
+ end
66
+
67
+ def classification_estimates
68
+ tokens = tokenize
69
+
70
+ estimates = {}
71
+ self.class.klasses.each do |cur_klass|
72
+ ret = self.class.classifiers[cur_klass].classify(*tokens)
73
+ result = {:klass=>ret[0], :confidence=>ret[1]}
74
+ estimates[cur_klass] = (result[:klass] == :yes) ? result[:confidence] : -result[:confidence]
75
+ end
76
+
77
+ return estimates
78
+ end
79
+
80
+ def classify
81
+ estimates = classification_estimates
82
+ best_guess_klass = estimates.keys.sort{ |x,y| estimates[x] <=> estimates[y] }.last
83
+ return best_guess_klass
84
+ end
85
+
86
+ def tokenize
87
+ words = ["^"] + self.downcase.split(" ") + ["$"]
88
+
89
+ tokens = [1, 2, 3].collect do |words_per_token|
90
+ words.each_cons(words_per_token).to_a.map{|x| x.join(" ") }
91
+ end
92
+ return tokens.flatten
93
+ end
94
+ end
95
+
96
+ end
@@ -0,0 +1,74 @@
1
+ module FinModeling
2
+
3
+ class IncomeStatementAnalyses < CalculationSummary
4
+ def initialize(calc_summary)
5
+ @title = calc_summary.title
6
+ @rows = calc_summary.rows
7
+ @header_row = calc_summary.header_row
8
+ @key_width = calc_summary.key_width
9
+ @val_width = calc_summary.val_width
10
+ @max_decimals = calc_summary.max_decimals
11
+ @totals_row_enabled = false
12
+ end
13
+
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: "+
18
+ "a:#{lr.a.to_s.cap_decimals(4)}, "+
19
+ "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
+ end
23
+
24
+ if sales_over_noa_row && sales_over_noa_row.valid_vals.any?
25
+ lr = sales_over_noa_row.valid_vals.linear_regression
26
+ puts "\t\tsales / noa: "+
27
+ "a:#{lr.a.to_s.cap_decimals(4)}, "+
28
+ "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)}"
31
+ end
32
+
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: "+
36
+ "a:#{lr.a.to_s.cap_decimals(4)}, "+
37
+ "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)}"
40
+ end
41
+
42
+ if fi_over_nfa_row && fi_over_nfa_row.valid_vals.any?
43
+ lr = fi_over_nfa_row.valid_vals.linear_regression
44
+ puts "\t\tfi / nfa: "+
45
+ "a:#{lr.a.to_s.cap_decimals(4)}, "+
46
+ "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)}"
49
+ end
50
+ end
51
+
52
+ def revenue_growth_row
53
+ find_row_by_key('Revenue Growth')
54
+ end
55
+
56
+ def operating_pm_row
57
+ find_row_by_key('Operating PM')
58
+ end
59
+
60
+ def sales_over_noa_row
61
+ find_row_by_key('Sales / NOA')
62
+ end
63
+
64
+ def fi_over_nfa_row
65
+ find_row_by_key('FI / NFA')
66
+ end
67
+
68
+ def find_row_by_key(key)
69
+ self.rows.find{ |x| x.key == key }
70
+ end
71
+ end
72
+
73
+ end
74
+
@@ -0,0 +1,71 @@
1
+ module FinModeling
2
+ class IncomeStatementCalculation < CompanyFilingCalculation
3
+
4
+ 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)
15
+ end
16
+ return @ni
17
+ end
18
+
19
+ 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
28
+ end
29
+ 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)
34
+ end
35
+
36
+ def reformulated(period)
37
+ return ReformulatedIncomeStatement.new(period,
38
+ net_income_calculation.summary(:period=>period))
39
+ end
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)
46
+
47
+ elsif !prev_income_statement
48
+ return nil
49
+
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
59
+ end
60
+
61
+ return nil
62
+ end
63
+
64
+ def write_constructor(file, item_name)
65
+ item_calc_name = item_name + "_calc"
66
+ @calculation.write_constructor(file, item_calc_name)
67
+ file.puts "#{item_name} = FinModeling::IncomeStatementCalculation.new(#{item_calc_name})"
68
+ end
69
+
70
+ end
71
+ end
@@ -0,0 +1,14 @@
1
+ module FinModeling
2
+ class IncomeStatementItem < String
3
+ include HasStringClassifier
4
+
5
+ BASE_FILENAME = File.join(FinModeling::BASE_PATH, "classifiers/isi_")
6
+ TYPES = [ :or, :cogs, :oe, :oibt, :fibt, :tax, :ooiat, :fiat ]
7
+
8
+ has_string_classifier(TYPES, IncomeStatementItem)
9
+
10
+ def self.load_vectors_and_train
11
+ self._load_vectors_and_train(BASE_FILENAME, FinModeling::IncomeStatementItem::TRAINING_VECTORS)
12
+ end
13
+ end
14
+ end