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,218 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'finmodeling'
|
|
4
|
+
|
|
5
|
+
class Arguments
|
|
6
|
+
def self.show_usage_and_exit
|
|
7
|
+
puts "usage:"
|
|
8
|
+
puts "\t#{__FILE__} [options] <stock symbol> <10-k|10-q> <0, for most recent|-1 for prevous|-2, etc>"
|
|
9
|
+
puts "\t#{__FILE__} [options] <report URL> <10-k|10-q>"
|
|
10
|
+
puts
|
|
11
|
+
puts "\tOptions:"
|
|
12
|
+
puts "\t\t--no-cache: disables caching"
|
|
13
|
+
puts "\t\t--show-disclosures: prints out all the disclosure calculations in the filing"
|
|
14
|
+
exit
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def self.parse(raw_args)
|
|
18
|
+
parsed_args = { :stock_symbol => nil, :filing_url => nil,
|
|
19
|
+
:report_type => nil, :report_offset => nil,
|
|
20
|
+
:show_disclosures => false }
|
|
21
|
+
|
|
22
|
+
while raw_args.any? && raw_args.first =~ /^--/
|
|
23
|
+
case raw_args.first.downcase
|
|
24
|
+
when '--no-cache'
|
|
25
|
+
FinModeling::Config.disable_caching
|
|
26
|
+
puts "Caching is #{FinModeling::Config.caching_enabled? ? "enabled" : "disabled"}"
|
|
27
|
+
when '--show-disclosures'
|
|
28
|
+
parsed_args[:show_disclosures] = true
|
|
29
|
+
puts "Showing disclosures"
|
|
30
|
+
else
|
|
31
|
+
self.show_usage_and_exit
|
|
32
|
+
end
|
|
33
|
+
raw_args = raw_args[1..-1]
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
if raw_args[0] =~ /http/
|
|
37
|
+
return self.parse_just_a_url(raw_args, parsed_args)
|
|
38
|
+
else
|
|
39
|
+
return self.parse_symbol_etc(raw_args, parsed_args)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
protected
|
|
44
|
+
|
|
45
|
+
def self.parse_just_a_url(raw_args, parsed)
|
|
46
|
+
self.show_usage_and_exit if raw_args.length != 2
|
|
47
|
+
|
|
48
|
+
parsed_args[:filing_url] = raw_args[0]
|
|
49
|
+
parsed_args[:report_type] = case raw_args[1].downcase
|
|
50
|
+
when "10-k"
|
|
51
|
+
:annual_report
|
|
52
|
+
when "10-q"
|
|
53
|
+
:quarterly_report
|
|
54
|
+
else
|
|
55
|
+
self.show_usage_and_exit
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
return parsed_args
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def self.parse_symbol_etc(raw_args, parsed_args)
|
|
62
|
+
self.show_usage_and_exit if raw_args.length != 3
|
|
63
|
+
|
|
64
|
+
parsed_args[:stock_symbol] = raw_args[0]
|
|
65
|
+
parsed_args[:report_type] = case raw_args[1].downcase
|
|
66
|
+
when "10-k"
|
|
67
|
+
:annual_report
|
|
68
|
+
when "10-q"
|
|
69
|
+
:quarterly_report
|
|
70
|
+
else
|
|
71
|
+
self.show_usage_and_exit
|
|
72
|
+
end
|
|
73
|
+
parsed_args[:report_offset] = raw_args[2].to_i
|
|
74
|
+
|
|
75
|
+
return parsed_args
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def get_company_filing_url(stock_symbol, report_type, report_offset)
|
|
80
|
+
company = FinModeling::Company.find(stock_symbol)
|
|
81
|
+
raise RuntimeError.new("couldn't find company") if company.nil?
|
|
82
|
+
puts "company name: #{company.name}"
|
|
83
|
+
|
|
84
|
+
filing_url = case report_type
|
|
85
|
+
when :annual_report
|
|
86
|
+
raise RuntimeError.new("company has no annual reports") if company.annual_reports.length == 0
|
|
87
|
+
company.annual_reports[-1+report_offset].link
|
|
88
|
+
when :quarterly_report
|
|
89
|
+
raise RuntimeError.new("company has no quarterly reports") if company.annual_reports.length == 0
|
|
90
|
+
company.quarterly_reports[-1+report_offset].link
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
return filing_url
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def get_filing(filing_url, report_type)
|
|
97
|
+
puts "url: #{filing_url}\n"
|
|
98
|
+
return FinModeling::AnnualReportFiling.download( filing_url) if report_type == :annual_report
|
|
99
|
+
return FinModeling::QuarterlyReportFiling.download(filing_url) if report_type == :quarterly_report
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def print_balance_sheet(filing, report_type)
|
|
103
|
+
period = filing.balance_sheet.periods.last
|
|
104
|
+
puts "Balance Sheet (#{period.to_pretty_s})"
|
|
105
|
+
|
|
106
|
+
summaries = []
|
|
107
|
+
summaries << filing.balance_sheet.assets_calculation.summary(:period => period)
|
|
108
|
+
summaries << filing.balance_sheet.liabs_and_equity_calculation.summary(:period => period)
|
|
109
|
+
|
|
110
|
+
print_summaries(summaries)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def print_reformulated_balance_sheet(filing, report_type)
|
|
114
|
+
period = filing.balance_sheet.periods.last
|
|
115
|
+
|
|
116
|
+
reformed_balance_sheet = filing.balance_sheet.reformulated(period)
|
|
117
|
+
|
|
118
|
+
summaries = []
|
|
119
|
+
summaries << reformed_balance_sheet.net_operating_assets
|
|
120
|
+
summaries << reformed_balance_sheet.net_financial_assets
|
|
121
|
+
summaries << reformed_balance_sheet.common_shareholders_equity
|
|
122
|
+
|
|
123
|
+
print_summaries(summaries)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def print_income_statement(filing, report_type)
|
|
127
|
+
period = filing.income_statement.net_income_calculation.periods.yearly.last if report_type == :annual_report
|
|
128
|
+
period = filing.income_statement.net_income_calculation.periods.quarterly.last if report_type == :quarterly_report
|
|
129
|
+
puts "Income Statement (#{period.to_pretty_s})"
|
|
130
|
+
|
|
131
|
+
summaries = []
|
|
132
|
+
summaries << filing.income_statement.net_income_calculation.summary(:period => period)
|
|
133
|
+
|
|
134
|
+
print_summaries(summaries)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def print_reformulated_income_statement(filing, report_type)
|
|
138
|
+
period = filing.income_statement.net_income_calculation.periods.yearly.last if report_type == :annual_report
|
|
139
|
+
period = filing.income_statement.net_income_calculation.periods.quarterly.last if report_type == :quarterly_report
|
|
140
|
+
|
|
141
|
+
reformed_inc_stmt = filing.income_statement.reformulated(period)
|
|
142
|
+
|
|
143
|
+
summaries = []
|
|
144
|
+
summaries << reformed_inc_stmt.gross_revenue
|
|
145
|
+
summaries << reformed_inc_stmt.income_from_sales_before_tax
|
|
146
|
+
summaries << reformed_inc_stmt.income_from_sales_after_tax
|
|
147
|
+
summaries << reformed_inc_stmt.operating_income_after_tax
|
|
148
|
+
summaries << reformed_inc_stmt.net_financing_income
|
|
149
|
+
summaries << reformed_inc_stmt.comprehensive_income
|
|
150
|
+
|
|
151
|
+
print_summaries(summaries)
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def print_cash_flow_statement(filing, report_type)
|
|
155
|
+
period = filing.cash_flow_statement.periods.yearly.last if report_type == :annual_report
|
|
156
|
+
period = filing.cash_flow_statement.periods.quarterly.last if report_type == :quarterly_report
|
|
157
|
+
puts "Cash Flow Statement (#{period.to_pretty_s})"
|
|
158
|
+
|
|
159
|
+
summaries = []
|
|
160
|
+
summaries << filing.cash_flow_statement.cash_change_calculation.summary(:period => period)
|
|
161
|
+
|
|
162
|
+
print_summaries(summaries)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def print_reformulated_cash_flow_statement(filing, report_type)
|
|
166
|
+
period = filing.cash_flow_statement.periods.yearly.last if report_type == :annual_report
|
|
167
|
+
period = filing.cash_flow_statement.periods.quarterly.last if report_type == :quarterly_report
|
|
168
|
+
|
|
169
|
+
reformed_cash_flow_stmt = filing.cash_flow_statement.reformulated(period)
|
|
170
|
+
|
|
171
|
+
summaries = []
|
|
172
|
+
summaries << reformed_cash_flow_stmt.cash_from_operations
|
|
173
|
+
summaries << reformed_cash_flow_stmt.cash_investments_in_operations
|
|
174
|
+
summaries << reformed_cash_flow_stmt.payments_to_debtholders
|
|
175
|
+
summaries << reformed_cash_flow_stmt.payments_to_stockholders
|
|
176
|
+
summaries << reformed_cash_flow_stmt.free_cash_flow
|
|
177
|
+
summaries << reformed_cash_flow_stmt.financing_flows
|
|
178
|
+
|
|
179
|
+
print_summaries(summaries)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def print_disclosures(filing, report_type)
|
|
183
|
+
puts "Disclosures"
|
|
184
|
+
|
|
185
|
+
summaries = []
|
|
186
|
+
filing.disclosures.each do |disclosure|
|
|
187
|
+
summaries << disclosure.summary(:period => disclosure.periods.last)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
print_summaries(summaries)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def print_summaries(summaries)
|
|
194
|
+
summaries.each do |summary|
|
|
195
|
+
summary.key_width = 60
|
|
196
|
+
summary.val_width = 18
|
|
197
|
+
summary.print
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
args = Arguments.parse(ARGV)
|
|
204
|
+
if args[:filing_url].nil?
|
|
205
|
+
args[:filing_url] = get_company_filing_url(args[:stock_symbol], args[:report_type], args[:report_offset])
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
filing = get_filing( args[:filing_url], args[:report_type])
|
|
209
|
+
|
|
210
|
+
print_balance_sheet( filing, args[:report_type])
|
|
211
|
+
print_reformulated_balance_sheet( filing, args[:report_type])
|
|
212
|
+
print_income_statement( filing, args[:report_type])
|
|
213
|
+
print_reformulated_income_statement( filing, args[:report_type])
|
|
214
|
+
print_cash_flow_statement( filing, args[:report_type])
|
|
215
|
+
print_reformulated_cash_flow_statement(filing, args[:report_type])
|
|
216
|
+
print_disclosures( filing, args[:report_type]) if args[:show_disclosures]
|
|
217
|
+
|
|
218
|
+
raise RuntimeError.new("filing is not valid") if !filing.is_valid?
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require 'finmodeling'
|
|
4
|
+
|
|
5
|
+
class Arguments
|
|
6
|
+
def self.show_usage_and_exit
|
|
7
|
+
puts "usage:"
|
|
8
|
+
puts "\t#{__FILE__} [options] <stock symbol> <start date, e.g. '2010-01-01'>"
|
|
9
|
+
puts
|
|
10
|
+
puts "\tOptions:"
|
|
11
|
+
puts "\t\t--num-forecasts <num>: how many periods to forecast"
|
|
12
|
+
puts "\t\t--no-cache: disable caching"
|
|
13
|
+
puts "\t\t--balance-detail: show details about the balance sheet calculation"
|
|
14
|
+
puts "\t\t--income-detail: show details about the net income calculation"
|
|
15
|
+
exit
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.parse(args)
|
|
19
|
+
a = { :stock_symbol => nil, :start_date => nil, :num_forecasts => nil }
|
|
20
|
+
|
|
21
|
+
while args.any? && args.first =~ /^--/
|
|
22
|
+
case args.first.downcase
|
|
23
|
+
when '--no-cache'
|
|
24
|
+
FinModeling::Config.disable_caching
|
|
25
|
+
puts "Caching is #{FinModeling::Config.caching_enabled? ? "enabled" : "disabled"}"
|
|
26
|
+
when '--balance-detail'
|
|
27
|
+
FinModeling::Config.enable_balance_detail
|
|
28
|
+
puts "Balance sheet detail is #{FinModeling::Config.balance_detail_enabled? ? "enabled" : "disabled"}"
|
|
29
|
+
when '--income-detail'
|
|
30
|
+
FinModeling::Config.enable_income_detail
|
|
31
|
+
puts "Net income detail is #{FinModeling::Config.income_detail_enabled? ? "enabled" : "disabled"}"
|
|
32
|
+
when '--num-forecasts'
|
|
33
|
+
a[:num_forecasts] = args[1].to_i
|
|
34
|
+
self.show_usage_and_exit unless a[:num_forecasts] >= 1
|
|
35
|
+
puts "Forecasting #{a[:num_forecasts]} periods"
|
|
36
|
+
args = args[1..-1]
|
|
37
|
+
else
|
|
38
|
+
self.show_usage_and_exit
|
|
39
|
+
end
|
|
40
|
+
args = args[1..-1]
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
self.show_usage_and_exit if args.length != 2
|
|
44
|
+
a[:stock_symbol] = args[0]
|
|
45
|
+
a[:start_date] = Time.parse(args[1])
|
|
46
|
+
|
|
47
|
+
return a
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
args = Arguments.parse(ARGV)
|
|
52
|
+
|
|
53
|
+
company = FinModeling::Company.find(args[:stock_symbol])
|
|
54
|
+
raise RuntimeError.new("couldn't find company") if !company
|
|
55
|
+
puts "company name: #{company.name}"
|
|
56
|
+
|
|
57
|
+
filings = FinModeling::CompanyFilings.new(company.filings_since_date(args[:start_date]))
|
|
58
|
+
if filings.empty?
|
|
59
|
+
puts "No filings..."
|
|
60
|
+
exit
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
forecasts = filings.forecasts(filings.choose_forecasting_policy, num_quarters=args[:num_forecasts]) if args[:num_forecasts]
|
|
64
|
+
|
|
65
|
+
bs_analyses = filings.balance_sheet_analyses
|
|
66
|
+
bs_analyses += forecasts.balance_sheet_analyses(filings) if forecasts
|
|
67
|
+
bs_analyses.totals_row_enabled = false
|
|
68
|
+
bs_analyses.print
|
|
69
|
+
filings.balance_sheet_analyses.print_extras if filings.balance_sheet_analyses.respond_to?(:print_extras)
|
|
70
|
+
|
|
71
|
+
is_analyses = filings.income_statement_analyses
|
|
72
|
+
is_analyses += forecasts.income_statement_analyses(filings) if forecasts
|
|
73
|
+
is_analyses.totals_row_enabled = false
|
|
74
|
+
is_analyses.print
|
|
75
|
+
filings.income_statement_analyses.print_extras if filings.income_statement_analyses.respond_to?(:print_extras)
|
|
76
|
+
|
|
77
|
+
filings.cash_flow_statement_analyses.print
|
data/finmodeling.gemspec
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
|
2
|
+
require File.expand_path('../lib/finmodeling/version', __FILE__)
|
|
3
|
+
|
|
4
|
+
Gem::Specification.new do |gem|
|
|
5
|
+
gem.authors = ["Jim Lindstrom"]
|
|
6
|
+
gem.email = ["jim.lindstrom@gmail.com"]
|
|
7
|
+
gem.description = %q{A gem for manipulating XBRL financial filings}
|
|
8
|
+
gem.summary = %q{A gem for manipulating XBRL financial filings}
|
|
9
|
+
gem.homepage = "https://github.com/jimlindstrom/FinModeling"
|
|
10
|
+
|
|
11
|
+
gem.add_dependency("fileutils")
|
|
12
|
+
gem.add_dependency("sec_query")
|
|
13
|
+
gem.add_dependency("edgar")
|
|
14
|
+
|
|
15
|
+
gem.add_dependency("xbrlware-ruby19", "1.1.2.19.2")
|
|
16
|
+
gem.add_dependency("xbrlware-extras", "1.1.2.19.2")
|
|
17
|
+
|
|
18
|
+
gem.add_dependency("sec_query")
|
|
19
|
+
gem.add_dependency("naive_bayes")
|
|
20
|
+
gem.add_dependency("statsample")
|
|
21
|
+
|
|
22
|
+
gem.add_development_dependency("rspec", "2.5")
|
|
23
|
+
gem.add_development_dependency("rake")
|
|
24
|
+
|
|
25
|
+
gem.files = `git ls-files`.split($\)
|
|
26
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
|
27
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
|
28
|
+
gem.name = "finmodeling"
|
|
29
|
+
gem.require_paths = ["lib"]
|
|
30
|
+
gem.version = FinModeling::VERSION
|
|
31
|
+
end
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
module FinModeling
|
|
2
|
+
|
|
3
|
+
class AnnualReportFiling < CompanyFiling
|
|
4
|
+
|
|
5
|
+
CONSTRUCTOR_PATH = File.join(FinModeling::BASE_PATH, "constructors/")
|
|
6
|
+
SCHEMA_VERSION_ITEM = "@schema_version"
|
|
7
|
+
CURRENT_SCHEMA_VERSION = 1.1
|
|
8
|
+
# History:
|
|
9
|
+
# 1.0: initial version
|
|
10
|
+
# 1.1: added CFS to quarterly filings
|
|
11
|
+
# added disclosures
|
|
12
|
+
# renamed fake(.*)report to cached(.*)report
|
|
13
|
+
|
|
14
|
+
def self.download(url)
|
|
15
|
+
uid = url.split("/")[-2..-1].join('-').gsub(/\.[A-zA-z]*$/, '')
|
|
16
|
+
constructor_file = CONSTRUCTOR_PATH + uid + '.rb'
|
|
17
|
+
if File.exists?(constructor_file) && FinModeling::Config.caching_enabled?
|
|
18
|
+
begin
|
|
19
|
+
eval(File.read(constructor_file))
|
|
20
|
+
#puts "info: annual report, cache hit. schema version: #{@schema_version}"
|
|
21
|
+
return @filing if @schema_version == CURRENT_SCHEMA_VERSION
|
|
22
|
+
rescue
|
|
23
|
+
#puts "warn: annual report, cache hit. error eval'ing though."
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
filing = super(url)
|
|
28
|
+
|
|
29
|
+
FileUtils.mkdir_p(CONSTRUCTOR_PATH) if !File.exists?(CONSTRUCTOR_PATH)
|
|
30
|
+
file = File.open(constructor_file, "w")
|
|
31
|
+
filing.write_constructor(file, "@filing")
|
|
32
|
+
file.close
|
|
33
|
+
|
|
34
|
+
return filing
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def balance_sheet
|
|
38
|
+
if @balance_sheet.nil?
|
|
39
|
+
calculations=@taxonomy.callb.calculation
|
|
40
|
+
bal_sheet = calculations.find{ |x| (x.clean_downcased_title =~ /statement.*financial.*position/) or
|
|
41
|
+
(x.clean_downcased_title =~ /statement.*financial.*condition/) or
|
|
42
|
+
(x.clean_downcased_title =~ /balance.*sheet/) }
|
|
43
|
+
if bal_sheet.nil?
|
|
44
|
+
raise RuntimeError.new("Couldn't find balance sheet in: " + calculations.map{ |x| "\"#{x.clean_downcased_title}\"" }.join("; "))
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
@balance_sheet = BalanceSheetCalculation.new(bal_sheet)
|
|
48
|
+
end
|
|
49
|
+
return @balance_sheet
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def income_statement
|
|
53
|
+
if @income_stmt.nil?
|
|
54
|
+
calculations=@taxonomy.callb.calculation
|
|
55
|
+
inc_stmt = calculations.find{ |x| (x.clean_downcased_title =~ /statement.*operations/) or
|
|
56
|
+
(x.clean_downcased_title =~ /statement[s]*.*of.*earnings/) or
|
|
57
|
+
(x.clean_downcased_title =~ /statement[s]*.*of.*income/) or
|
|
58
|
+
(x.clean_downcased_title =~ /statement[s]*.*of.*net.*income/) }
|
|
59
|
+
if inc_stmt.nil?
|
|
60
|
+
raise RuntimeError.new("Couldn't find income statement in: " + calculations.map{ |x| "\"#{x.clean_downcased_title}\"" }.join("; "))
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
@income_stmt = IncomeStatementCalculation.new(inc_stmt)
|
|
64
|
+
end
|
|
65
|
+
return @income_stmt
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def cash_flow_statement
|
|
69
|
+
if @cash_flow_stmt.nil?
|
|
70
|
+
calculations=@taxonomy.callb.calculation
|
|
71
|
+
cash_flow_stmt = calculations.find{ |x| (x.clean_downcased_title =~ /statement.*cash.*flows/) or
|
|
72
|
+
(x.clean_downcased_title =~ /^cash flows$/) }
|
|
73
|
+
if cash_flow_stmt.nil?
|
|
74
|
+
raise RuntimeError.new("Couldn't find cash flow statement in: " + calculations.map{ |x| "\"#{x.clean_downcased_title}\"" }.join("; "))
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
@cash_flow_stmt = CashFlowStatementCalculation.new(cash_flow_stmt)
|
|
78
|
+
end
|
|
79
|
+
return @cash_flow_stmt
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def is_valid?
|
|
83
|
+
return (income_statement.is_valid? and balance_sheet.is_valid? and cash_flow_statement.is_valid?)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def write_constructor(file, item_name)
|
|
87
|
+
balance_sheet.write_constructor( file, bs_name = item_name + "_bs")
|
|
88
|
+
income_statement.write_constructor( file, is_name = item_name + "_is")
|
|
89
|
+
cash_flow_statement.write_constructor(file, cfs_name = item_name + "_cfs")
|
|
90
|
+
|
|
91
|
+
names_of_discs = []
|
|
92
|
+
disclosures.each_with_index do |disclosure, idx|
|
|
93
|
+
name_of_disc = item_name + "_disc#{idx}"
|
|
94
|
+
disclosure.write_constructor(file, name_of_disc)
|
|
95
|
+
names_of_discs << name_of_disc
|
|
96
|
+
end
|
|
97
|
+
names_of_discs_str = "[" + names_of_discs.join(',') + "]"
|
|
98
|
+
|
|
99
|
+
file.puts "#{SCHEMA_VERSION_ITEM} = #{CURRENT_SCHEMA_VERSION}"
|
|
100
|
+
|
|
101
|
+
file.puts "#{item_name} = FinModeling::CachedAnnualFiling.new(#{bs_name}, #{is_name}, #{cfs_name}, #{names_of_discs_str})"
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
module FinModeling
|
|
2
|
+
|
|
3
|
+
class ArrayWithStats < Array
|
|
4
|
+
def mean
|
|
5
|
+
return nil if empty?
|
|
6
|
+
self.inject(:+) / self.length
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def variance
|
|
10
|
+
x_sqrd = self.map{ |x| x*x }
|
|
11
|
+
x_sqrd_mean = (ArrayWithStats.new(x_sqrd).mean)
|
|
12
|
+
x_sqrd_mean - (mean**2)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def linear_regression
|
|
16
|
+
x = Array(0..(self.length-1)).to_scale
|
|
17
|
+
y = self.to_scale
|
|
18
|
+
Statsample::Regression.simple(x,y)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module FinModeling
|
|
2
|
+
|
|
3
|
+
class AssetsCalculation < CompanyFilingCalculation
|
|
4
|
+
include CanCacheClassifications
|
|
5
|
+
include CanCacheSummaries
|
|
6
|
+
include CanClassifyRows
|
|
7
|
+
|
|
8
|
+
BASE_FILENAME = File.join(FinModeling::BASE_PATH, "summaries/ai_")
|
|
9
|
+
|
|
10
|
+
ALL_STATES = [ :oa, :fa ]
|
|
11
|
+
NEXT_STATES = { nil => [ :oa, :fa ],
|
|
12
|
+
:oa => [ :oa, :fa ],
|
|
13
|
+
:fa => [ :oa, :fa ] }
|
|
14
|
+
|
|
15
|
+
def summary(args)
|
|
16
|
+
summary_cache_key = args[:period].to_pretty_s
|
|
17
|
+
thesummary = lookup_cached_summary(summary_cache_key)
|
|
18
|
+
return thesummary if !thesummary.nil?
|
|
19
|
+
|
|
20
|
+
mapping = Xbrlware::ValueMapping.new
|
|
21
|
+
mapping.policy[:credit] = :flip
|
|
22
|
+
|
|
23
|
+
thesummary = super(:period => args[:period], :mapping => mapping)
|
|
24
|
+
if !lookup_cached_classifications(BASE_FILENAME, thesummary.rows)
|
|
25
|
+
lookahead = [4, thesummary.rows.length-1].min
|
|
26
|
+
classify_rows(ALL_STATES, NEXT_STATES, thesummary.rows, FinModeling::AssetsItem, lookahead)
|
|
27
|
+
save_cached_classifications(BASE_FILENAME, thesummary.rows)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
save_cached_summary(summary_cache_key, thesummary)
|
|
31
|
+
|
|
32
|
+
return thesummary
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
module FinModeling
|
|
2
|
+
class AssetsItem < String
|
|
3
|
+
include HasStringClassifier
|
|
4
|
+
|
|
5
|
+
BASE_FILENAME = File.join(FinModeling::BASE_PATH, "classifiers/ai_")
|
|
6
|
+
TYPES = [ :oa, :fa ]
|
|
7
|
+
|
|
8
|
+
has_string_classifier(TYPES, AssetsItem)
|
|
9
|
+
|
|
10
|
+
def self.load_vectors_and_train
|
|
11
|
+
self._load_vectors_and_train(BASE_FILENAME, FinModeling::AssetsItem::TRAINING_VECTORS)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|