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,33 @@
|
|
|
1
|
+
module FinModeling
|
|
2
|
+
|
|
3
|
+
class BalanceSheetAnalyses < 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 # FIXME: rename
|
|
15
|
+
lr = noa_growth_row.valid_vals.linear_regression
|
|
16
|
+
puts "\t\tNOA growth: "+
|
|
17
|
+
"a:#{lr.a.to_s.cap_decimals(4)}, "+
|
|
18
|
+
"b:#{lr.b.to_s.cap_decimals(4)}, "+
|
|
19
|
+
"r:#{lr.r.to_s.cap_decimals(4)}, "+
|
|
20
|
+
"var:#{noa_growth_row.valid_vals.variance.to_s.cap_decimals(4)}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def noa_growth_row
|
|
24
|
+
find_row_by_key('NOA Growth')
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def find_row_by_key(key) # FIXME: move this to CalculationSummary
|
|
28
|
+
self.rows.find{ |x| x.key == key }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
end
|
|
33
|
+
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
module FinModeling
|
|
2
|
+
class BalanceSheetCalculation < CompanyFilingCalculation
|
|
3
|
+
|
|
4
|
+
def assets_calculation
|
|
5
|
+
if @assets.nil?
|
|
6
|
+
friendly_goal = "assets"
|
|
7
|
+
label_regexes = [ /(^total *|^consolidated *|^)assets$/,
|
|
8
|
+
/^assets total$/ ]
|
|
9
|
+
id_regexes = [ /^(|Locator_|loc_)(|us-gaap_)Assets[_a-z0-9]+/ ]
|
|
10
|
+
|
|
11
|
+
calc = find_and_verify_calculation_arc(friendly_goal, label_regexes, id_regexes)
|
|
12
|
+
@assets = AssetsCalculation.new(calc)
|
|
13
|
+
end
|
|
14
|
+
return @assets
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def liabs_and_equity_calculation
|
|
18
|
+
if @liabs_and_equity.nil?
|
|
19
|
+
friendly_goal = "liabilities and equity"
|
|
20
|
+
label_regexes = [ /(^total *|^)liabilities.*and.*equity/ ]
|
|
21
|
+
id_regexes = [ /.*/ ] # no checking...
|
|
22
|
+
|
|
23
|
+
calc = find_and_verify_calculation_arc(friendly_goal, label_regexes, id_regexes)
|
|
24
|
+
@liabs_and_equity = LiabsAndEquityCalculation.new(calc)
|
|
25
|
+
end
|
|
26
|
+
return @liabs_and_equity
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def is_valid?
|
|
30
|
+
has_cash_item = false
|
|
31
|
+
assets_calculation.leaf_items.each do |leaf|
|
|
32
|
+
if !has_cash_item and leaf.name.downcase.matches_regexes?([/cash/])
|
|
33
|
+
has_cash_item = true
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
has_equity_item = false
|
|
38
|
+
liabs_and_equity_calculation.leaf_items.each do |leaf|
|
|
39
|
+
if !has_equity_item and leaf.name.downcase.matches_regexes?([/equity/, /stock/])
|
|
40
|
+
has_equity_item = true
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
left = assets_calculation.leaf_items_sum(:period => periods.last)
|
|
45
|
+
right = liabs_and_equity_calculation.leaf_items_sum(:period => periods.last)
|
|
46
|
+
allowed_error = 1.0
|
|
47
|
+
is_balanced = (left - right) < allowed_error
|
|
48
|
+
|
|
49
|
+
puts "balance sheet's assets calculation lacks cash item" if !has_cash_item
|
|
50
|
+
puts "balance sheet's liabilities and equity calculation lacks equity item" if !has_equity_item
|
|
51
|
+
puts "balance sheet's isn't balanced (#{left}, #{right})" if !is_balanced
|
|
52
|
+
return (has_cash_item and has_equity_item and is_balanced)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def reformulated(period)
|
|
56
|
+
return ReformulatedBalanceSheet.new(period,
|
|
57
|
+
assets_calculation.summary(:period=>period),
|
|
58
|
+
liabs_and_equity_calculation.summary(:period=>period))
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def write_constructor(file, item_name)
|
|
62
|
+
item_calc_name = item_name + "_calc"
|
|
63
|
+
@calculation.write_constructor(file, item_calc_name)
|
|
64
|
+
file.puts "#{item_name} = FinModeling::BalanceSheetCalculation.new(#{item_calc_name})"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
end
|
|
68
|
+
end
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
module FinModeling
|
|
2
|
+
|
|
3
|
+
class CalculationRow
|
|
4
|
+
attr_accessor :key, :type, :vals
|
|
5
|
+
|
|
6
|
+
def initialize(args = {})
|
|
7
|
+
@key = args[:key] || ""
|
|
8
|
+
@type = args[:type]
|
|
9
|
+
@vals = args[:vals] || []
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def valid_vals
|
|
13
|
+
ArrayWithStats.new(@vals.select{ |val| !val.nil? })
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def print(key_width=18, max_decimals=4, val_width=12)
|
|
17
|
+
justified_key = @key.fixed_width_left_justify(key_width)
|
|
18
|
+
|
|
19
|
+
justified_vals = ""
|
|
20
|
+
@vals.each do |val|
|
|
21
|
+
val_with_commas = val.to_s.with_thousands_separators
|
|
22
|
+
justified_vals += " " + val_with_commas.cap_decimals(max_decimals).fixed_width_right_justify(val_width)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
puts "\t" + justified_key + justified_vals
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def write_constructor(file, item_name)
|
|
29
|
+
file.puts "args = { }"
|
|
30
|
+
file.puts "args[:key] = \"#{@key}\""
|
|
31
|
+
file.puts "args[:type] = \"#{@type}\""
|
|
32
|
+
file.puts "args[:vals] = [#{@vals.join(', ')}]"
|
|
33
|
+
file.puts "#{item_name} = FinModeling::CalculationRow.new(args)"
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
class CalculationHeader < CalculationRow
|
|
38
|
+
def print(key_width=18, max_decimals=4, val_width=12)
|
|
39
|
+
justified_key = @key.fixed_width_left_justify(key_width)
|
|
40
|
+
|
|
41
|
+
justified_vals = ""
|
|
42
|
+
@vals.each do |val|
|
|
43
|
+
justified_vals += " " + val.fixed_width_right_justify(val_width)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
puts "\t" + justified_key + justified_vals
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def write_constructor(file, item_name)
|
|
50
|
+
file.puts "args = { }"
|
|
51
|
+
file.puts "args[:key] = \"#{@key}\""
|
|
52
|
+
file.puts "args[:vals] = [#{@vals.map{ |val| "\"#{val}\"" }.join(', ')}]"
|
|
53
|
+
file.puts "#{item_name} = FinModeling::CalculationHeader.new(args)"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
class CalculationSummary
|
|
58
|
+
attr_accessor :title, :header_row, :rows
|
|
59
|
+
attr_accessor :key_width, :val_width, :max_decimals, :totals_row_enabled
|
|
60
|
+
|
|
61
|
+
def initialize
|
|
62
|
+
@key_width = 18
|
|
63
|
+
@val_width = 12
|
|
64
|
+
@max_decimals = 4
|
|
65
|
+
@totals_row_enabled = true
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def num_value_columns
|
|
69
|
+
@rows.map{ |row| row.vals.length }.max
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def total(col_idx=0)
|
|
73
|
+
@rows.map{ |row| row.vals[col_idx] }.inject(:+) || 0.0
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def totals
|
|
77
|
+
return [] if num_value_columns.nil?
|
|
78
|
+
0.upto(num_value_columns-1).map { |col_idx| total(col_idx) }
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def print
|
|
82
|
+
puts @title
|
|
83
|
+
|
|
84
|
+
rows = []
|
|
85
|
+
rows << @header_row if @header_row
|
|
86
|
+
rows += @rows
|
|
87
|
+
rows << CalculationRow.new(:key => "Total", :vals => totals) if @totals_row_enabled
|
|
88
|
+
rows.each { |row| row.print(@key_width, @max_decimals, @val_width) }
|
|
89
|
+
|
|
90
|
+
puts
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def filter_by_type(type)
|
|
94
|
+
cs = CalculationSummary.new
|
|
95
|
+
cs.title = @title
|
|
96
|
+
cs.rows = @rows.select{ |x| x.type == type }
|
|
97
|
+
return cs
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def write_constructor(file, item_name)
|
|
101
|
+
file.puts "#{item_name} = FinModeling::CalculationSummary.new"
|
|
102
|
+
file.puts "#{item_name}.title = \"#{@title}\""
|
|
103
|
+
file.puts "#{item_name}.key_width = #{@key_width}"
|
|
104
|
+
file.puts "#{item_name}.val_width = #{@val_width}"
|
|
105
|
+
file.puts "#{item_name}.max_decimals = #{@max_decimals}"
|
|
106
|
+
file.puts "#{item_name}.totals_row_enabled = #{@totals_row_enabled}"
|
|
107
|
+
|
|
108
|
+
if @header_row
|
|
109
|
+
header_row_item_name = item_name + "_header_row"
|
|
110
|
+
@header_row.write_constructor(file, header_row_item_name)
|
|
111
|
+
file.puts "#{item_name}.header_row = #{header_row_item_name}"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
row_item_names = []
|
|
115
|
+
@rows.each_with_index do |row, index|
|
|
116
|
+
row_item_name = item_name + "_row#{index}"
|
|
117
|
+
row.write_constructor(file, row_item_name)
|
|
118
|
+
row_item_names << row_item_name
|
|
119
|
+
end
|
|
120
|
+
file.puts "#{item_name}.rows = [#{row_item_names.join(',')}]"
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def +(mccs)
|
|
124
|
+
raise RuntimeError.new("can't add a CalculationSummary to a #{mccs.class}") if !mccs.is_a?(CalculationSummary)
|
|
125
|
+
raise RuntimeError.new("can't add CalculationSummaries with different numbers of rows") if @rows.length != mccs.rows.length
|
|
126
|
+
|
|
127
|
+
multics = CalculationSummary.new
|
|
128
|
+
multics.title = @title
|
|
129
|
+
|
|
130
|
+
if @header_row
|
|
131
|
+
multics.header_row = CalculationHeader.new(
|
|
132
|
+
:key => @header_row.key.dup,
|
|
133
|
+
:vals => @header_row.vals.dup + mccs.header_row.vals.dup)
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
multics.rows = []
|
|
137
|
+
0.upto(@rows.length-1).each do |idx|
|
|
138
|
+
multics.rows << CalculationRow.new(
|
|
139
|
+
:key => @rows[idx].key.dup,
|
|
140
|
+
:vals => @rows[idx].vals.dup + mccs.rows[idx].vals.dup)
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
return multics
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module FinModeling
|
|
2
|
+
|
|
3
|
+
module CanCacheClassifications
|
|
4
|
+
protected
|
|
5
|
+
|
|
6
|
+
def lookup_cached_classifications(base_filename, rows)
|
|
7
|
+
filename = rows_to_filename(base_filename, rows)
|
|
8
|
+
return false if !File.exists?(filename) || !Config.caching_enabled?
|
|
9
|
+
|
|
10
|
+
f = File.open(filename, "r")
|
|
11
|
+
rows.each do |row|
|
|
12
|
+
row.type = f.gets.chomp.to_sym
|
|
13
|
+
end
|
|
14
|
+
f.close
|
|
15
|
+
return true
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def save_cached_classifications(base_filename, rows)
|
|
19
|
+
filename = rows_to_filename(base_filename, rows)
|
|
20
|
+
FileUtils.mkdir_p(File.dirname(filename)) if !File.exists?(File.dirname(filename))
|
|
21
|
+
f = File.open(filename, "w")
|
|
22
|
+
rows.each do |row|
|
|
23
|
+
f.puts row.type.to_s
|
|
24
|
+
end
|
|
25
|
+
f.close
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def rows_to_filename(base_filename, rows)
|
|
31
|
+
unique_str = Digest::SHA1.hexdigest(rows.map{ |row| row.key }.join)
|
|
32
|
+
filename = base_filename + unique_str + ".txt"
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module FinModeling
|
|
2
|
+
|
|
3
|
+
module CanCacheSummaries
|
|
4
|
+
protected
|
|
5
|
+
|
|
6
|
+
def lookup_cached_summary(key)
|
|
7
|
+
@summary_cache = { } if @summary_cache.nil?
|
|
8
|
+
return @summary_cache[key]
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def save_cached_summary(key, summary)
|
|
12
|
+
@summary_cache[key] = summary
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
module FinModeling
|
|
2
|
+
|
|
3
|
+
module CanClassifyRows # simple viterbi classifier, with N-element lookahead
|
|
4
|
+
protected
|
|
5
|
+
|
|
6
|
+
def classify_rows(all_states, allowed_next_states, rows, row_item_type, lookahead)
|
|
7
|
+
estimate_of_item_being_in_state = rows.map { |row| row_item_type.new(row.key).classification_estimates }
|
|
8
|
+
|
|
9
|
+
prev_state = nil
|
|
10
|
+
rows.each_with_index do |row, item_index|
|
|
11
|
+
cur_lookahead = [lookahead, rows.length-item_index-1].min
|
|
12
|
+
row.type = classify_row(all_states, allowed_next_states,
|
|
13
|
+
estimate_of_item_being_in_state,
|
|
14
|
+
item_index, prev_state, cur_lookahead)[:state]
|
|
15
|
+
raise RuntimeError.new("couldn't classify....") if row.type.nil?
|
|
16
|
+
|
|
17
|
+
prev_state = row.type
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def classify_row(all_states, allowed_next_states, estimate_of_item_being_in_state, item_index, prev_state, lookahead)
|
|
24
|
+
best_overall = { :estimate => -100000000.0, :state => nil }
|
|
25
|
+
best_allowed = { :estimate => -100000000.0, :state => nil }
|
|
26
|
+
|
|
27
|
+
all_states.each do |state|
|
|
28
|
+
cur = { :estimate => estimate_of_item_being_in_state[item_index][state], :state => state }
|
|
29
|
+
raise RuntimeError.new("estimate is nil: #{estimate_of_item_being_in_state[item_index]} for state #{state}") if !cur[:estimate]
|
|
30
|
+
|
|
31
|
+
if lookahead > 0
|
|
32
|
+
future_error = classify_row(all_states, allowed_next_states,
|
|
33
|
+
estimate_of_item_being_in_state,
|
|
34
|
+
item_index+1, state, lookahead-1)[:error]
|
|
35
|
+
cur[:estimate] -= future_error
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
if cur[:estimate] > best_overall[:estimate]
|
|
39
|
+
best_overall = cur
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
is_allowed = allowed_next_states[prev_state] && allowed_next_states[prev_state].include?(state)
|
|
43
|
+
if is_allowed && (cur[:estimate] > best_allowed[:estimate])
|
|
44
|
+
best_allowed = cur
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
return { :state => best_allowed[:state],
|
|
49
|
+
:error => best_overall[:estimate] -
|
|
50
|
+
best_allowed[:estimate] }
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module FinModeling
|
|
2
|
+
|
|
3
|
+
class CashChangeCalculation < CompanyFilingCalculation
|
|
4
|
+
include CanCacheClassifications
|
|
5
|
+
include CanCacheSummaries
|
|
6
|
+
include CanClassifyRows
|
|
7
|
+
|
|
8
|
+
BASE_FILENAME = File.join(FinModeling::BASE_PATH, "summaries/cash_")
|
|
9
|
+
|
|
10
|
+
ALL_STATES = [ :c, :i, :d, :f ]
|
|
11
|
+
NEXT_STATES = { nil => [ :c ],
|
|
12
|
+
:c => [ :c, :i, :d, :f ],
|
|
13
|
+
:i => [ :i, :d, :f ],
|
|
14
|
+
:d => [ :i, :d, :f ],
|
|
15
|
+
:f => [ :d, :f ] }
|
|
16
|
+
|
|
17
|
+
def summary(args)
|
|
18
|
+
summary_cache_key = args[:period].to_pretty_s
|
|
19
|
+
summary = lookup_cached_summary(summary_cache_key)
|
|
20
|
+
return summary if !summary.nil? and false # FIXME: get rid of "and false"
|
|
21
|
+
|
|
22
|
+
mapping = Xbrlware::ValueMapping.new
|
|
23
|
+
mapping.policy[:unknown] = :flip
|
|
24
|
+
mapping.policy[:credit] = :flip
|
|
25
|
+
mapping.policy[:debit] = :no_action
|
|
26
|
+
mapping.policy[:netincome] = :no_action
|
|
27
|
+
mapping.policy[:taxes] = :no_action
|
|
28
|
+
mapping.policy[:proceedsfromdebt] = :no_action
|
|
29
|
+
|
|
30
|
+
find_and_tag_special_items(args)
|
|
31
|
+
|
|
32
|
+
summary = super(:period => args[:period], :mapping => mapping)
|
|
33
|
+
if !lookup_cached_classifications(BASE_FILENAME, summary.rows) or true # FIXME: get rid of "or true"
|
|
34
|
+
lookahead = [4, summary.rows.length-1].min
|
|
35
|
+
classify_rows(ALL_STATES, NEXT_STATES, summary.rows, FinModeling::CashChangeItem, lookahead)
|
|
36
|
+
save_cached_classifications(BASE_FILENAME, summary.rows)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
save_cached_summary(summary_cache_key, summary)
|
|
40
|
+
|
|
41
|
+
return summary
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def find_and_tag_special_items(args)
|
|
47
|
+
leaf_items(:period => args[:period]).each do |item|
|
|
48
|
+
if item.name.matches_regexes?([ /NetIncomeLoss/,
|
|
49
|
+
/ProfitLoss/ ])
|
|
50
|
+
item.def = {} if !item.def
|
|
51
|
+
item.def["xbrli:balance"] = "netincome"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
if item.name =~ /IncreaseDecreaseInIncomeTaxes/
|
|
55
|
+
item.def = {} if !item.def
|
|
56
|
+
item.def["xbrli:balance"] = "taxes"
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
if item.name =~ /ProceedsFromDebtNetOfIssuanceCosts/
|
|
60
|
+
item.def = {} if !item.def
|
|
61
|
+
item.def["xbrli:balance"] = "proceedsfromdebt"
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module FinModeling
|
|
2
|
+
class CashChangeItem < String
|
|
3
|
+
include HasStringClassifier
|
|
4
|
+
|
|
5
|
+
BASE_FILENAME = File.join(FinModeling::BASE_PATH, "classifiers/cci_")
|
|
6
|
+
TYPES = [ :c, :i, :d, :f ]
|
|
7
|
+
|
|
8
|
+
has_string_classifier(TYPES, CashChangeItem)
|
|
9
|
+
|
|
10
|
+
def self.load_vectors_and_train
|
|
11
|
+
self._load_vectors_and_train(BASE_FILENAME, FinModeling::CashChangeItem::TRAINING_VECTORS)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|