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.
- data/.gitignore +3 -0
- data/Gemfile +10 -0
- data/README.md +292 -0
- data/Rakefile +6 -0
- data/TODO.txt +36 -0
- data/examples/dump_report.rb +33 -0
- data/examples/lists/nasdaq-mid-to-mega-tech-symbols.txt +226 -0
- data/examples/show_report.rb +218 -0
- data/examples/show_reports.rb +77 -0
- data/finmodeling.gemspec +31 -0
- data/lib/finmodeling/annual_report_filing.rb +104 -0
- data/lib/finmodeling/array_with_stats.rb +22 -0
- data/lib/finmodeling/assets_calculation.rb +36 -0
- data/lib/finmodeling/assets_item.rb +14 -0
- data/lib/finmodeling/assets_item_vectors.rb +638 -0
- data/lib/finmodeling/balance_sheet_analyses.rb +33 -0
- data/lib/finmodeling/balance_sheet_calculation.rb +68 -0
- data/lib/finmodeling/calculation_summary.rb +148 -0
- data/lib/finmodeling/can_cache_classifications.rb +36 -0
- data/lib/finmodeling/can_cache_summaries.rb +16 -0
- data/lib/finmodeling/can_classify_rows.rb +54 -0
- data/lib/finmodeling/cash_change_calculation.rb +67 -0
- data/lib/finmodeling/cash_change_item.rb +14 -0
- data/lib/finmodeling/cash_change_item_vectors.rb +241 -0
- data/lib/finmodeling/cash_flow_statement_calculation.rb +85 -0
- data/lib/finmodeling/classifiers.rb +11 -0
- data/lib/finmodeling/company.rb +102 -0
- data/lib/finmodeling/company_filing.rb +64 -0
- data/lib/finmodeling/company_filing_calculation.rb +75 -0
- data/lib/finmodeling/company_filings.rb +100 -0
- data/lib/finmodeling/config.rb +37 -0
- data/lib/finmodeling/constant_forecasting_policy.rb +23 -0
- data/lib/finmodeling/factory.rb +27 -0
- data/lib/finmodeling/float_helpers.rb +17 -0
- data/lib/finmodeling/forecasts.rb +48 -0
- data/lib/finmodeling/generic_forecasting_policy.rb +19 -0
- data/lib/finmodeling/has_string_classifer.rb +96 -0
- data/lib/finmodeling/income_statement_analyses.rb +74 -0
- data/lib/finmodeling/income_statement_calculation.rb +71 -0
- data/lib/finmodeling/income_statement_item.rb +14 -0
- data/lib/finmodeling/income_statement_item_vectors.rb +654 -0
- data/lib/finmodeling/liabs_and_equity_calculation.rb +36 -0
- data/lib/finmodeling/liabs_and_equity_item.rb +14 -0
- data/lib/finmodeling/liabs_and_equity_item_vectors.rb +1936 -0
- data/lib/finmodeling/net_income_calculation.rb +41 -0
- data/lib/finmodeling/paths.rb +5 -0
- data/lib/finmodeling/period_array.rb +24 -0
- data/lib/finmodeling/quarterly_report_filing.rb +23 -0
- data/lib/finmodeling/rate.rb +20 -0
- data/lib/finmodeling/ratio.rb +20 -0
- data/lib/finmodeling/reformulated_balance_sheet.rb +176 -0
- data/lib/finmodeling/reformulated_cash_flow_statement.rb +140 -0
- data/lib/finmodeling/reformulated_income_statement.rb +436 -0
- data/lib/finmodeling/string_helpers.rb +26 -0
- data/lib/finmodeling/version.rb +3 -0
- data/lib/finmodeling.rb +70 -0
- data/spec/annual_report_filing_spec.rb +68 -0
- data/spec/assets_calculation_spec.rb +21 -0
- data/spec/assets_item_spec.rb +66 -0
- data/spec/balance_sheet_analyses_spec.rb +43 -0
- data/spec/balance_sheet_calculation_spec.rb +91 -0
- data/spec/calculation_summary_spec.rb +63 -0
- data/spec/can_classify_rows_spec.rb +86 -0
- data/spec/cash_change_calculation_spec.rb +56 -0
- data/spec/cash_change_item_spec.rb +66 -0
- data/spec/cash_flow_statement_calculation_spec.rb +108 -0
- data/spec/company_filing_calculation_spec.rb +74 -0
- data/spec/company_filing_spec.rb +30 -0
- data/spec/company_filings_spec.rb +55 -0
- data/spec/company_spec.rb +73 -0
- data/spec/constant_forecasting_policy_spec.rb +37 -0
- data/spec/factory_spec.rb +18 -0
- data/spec/forecasts_spec.rb +21 -0
- data/spec/generic_forecasting_policy_spec.rb +33 -0
- data/spec/income_statement_analyses_spec.rb +63 -0
- data/spec/income_statement_calculation_spec.rb +88 -0
- data/spec/income_statement_item_spec.rb +86 -0
- data/spec/liabs_and_equity_calculation_spec.rb +20 -0
- data/spec/liabs_and_equity_item_spec.rb +66 -0
- data/spec/mocks/calculation.rb +10 -0
- data/spec/mocks/income_statement_analyses.rb +93 -0
- data/spec/mocks/sec_query.rb +31 -0
- data/spec/net_income_calculation_spec.rb +23 -0
- data/spec/period_array.rb +52 -0
- data/spec/quarterly_report_filing_spec.rb +69 -0
- data/spec/rate_spec.rb +33 -0
- data/spec/ratio_spec.rb +33 -0
- data/spec/reformulated_balance_sheet_spec.rb +146 -0
- data/spec/reformulated_cash_flow_statement_spec.rb +174 -0
- data/spec/reformulated_income_statement_spec.rb +293 -0
- data/spec/spec_helper.rb +5 -0
- data/spec/string_helpers_spec.rb +23 -0
- data/tools/create_balance_sheet_training_vectors.rb +65 -0
- data/tools/create_cash_change_training_vectors.rb +48 -0
- data/tools/create_credit_debit_training_vectors.rb +51 -0
- data/tools/create_income_statement_training_vectors.rb +48 -0
- 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,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
|