finmodeling 0.1 → 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +0 -0
- data/Gemfile +2 -0
- data/README.md +289 -269
- data/Rakefile +12 -0
- data/TODO.txt +113 -20
- data/examples/{dump_report.rb → dump_latest_10k.rb} +1 -1
- data/examples/list_disclosures.rb +50 -0
- data/examples/lists/nasdaq-mid-to-mega-tech-symbols.txt +0 -0
- data/examples/show_report.rb +112 -32
- data/examples/show_reports.rb +162 -33
- data/finmodeling.gemspec +4 -1
- data/lib/finmodeling/annual_report_filing.rb +97 -18
- data/lib/finmodeling/array_with_stats.rb +0 -0
- data/lib/finmodeling/assets_calculation.rb +12 -3
- data/lib/finmodeling/assets_item.rb +0 -0
- data/lib/finmodeling/assets_item_vectors.rb +0 -0
- data/lib/finmodeling/balance_sheet_analyses.rb +19 -4
- data/lib/finmodeling/balance_sheet_calculation.rb +52 -37
- data/lib/finmodeling/calculation_summary.rb +119 -14
- data/lib/finmodeling/can_cache_classifications.rb +0 -0
- data/lib/finmodeling/can_cache_summaries.rb +0 -0
- data/lib/finmodeling/can_choose_successive_periods.rb +15 -0
- data/lib/finmodeling/can_classify_rows.rb +0 -0
- data/lib/finmodeling/capm.rb +80 -0
- data/lib/finmodeling/cash_change_calculation.rb +3 -3
- data/lib/finmodeling/cash_change_item.rb +0 -0
- data/lib/finmodeling/cash_change_item_vectors.rb +0 -0
- data/lib/finmodeling/cash_change_summary_from_differences.rb +36 -0
- data/lib/finmodeling/cash_flow_statement_analyses.rb +36 -0
- data/lib/finmodeling/cash_flow_statement_calculation.rb +28 -52
- data/lib/finmodeling/classifiers.rb +2 -0
- data/lib/finmodeling/company.rb +0 -0
- data/lib/finmodeling/company_filing.rb +30 -7
- data/lib/finmodeling/company_filing_calculation.rb +16 -6
- data/lib/finmodeling/company_filings.rb +112 -46
- data/lib/finmodeling/comprehensive_income_calculation.rb +60 -0
- data/lib/finmodeling/comprehensive_income_statement_calculation.rb +74 -0
- data/lib/finmodeling/comprehensive_income_statement_item.rb +20 -0
- data/lib/finmodeling/comprehensive_income_statement_item_vectors.rb +235 -0
- data/lib/finmodeling/config.rb +0 -0
- data/lib/finmodeling/debt_cost_of_capital.rb +14 -0
- data/lib/finmodeling/equity_change_calculation.rb +43 -0
- data/lib/finmodeling/equity_change_item.rb +25 -0
- data/lib/finmodeling/equity_change_item_vectors.rb +156 -0
- data/lib/finmodeling/factory.rb +0 -0
- data/lib/finmodeling/fama_french_cost_of_equity.rb +119 -0
- data/lib/finmodeling/float_helpers.rb +14 -8
- data/lib/finmodeling/forecasted_reformulated_balance_sheet.rb +55 -0
- data/lib/finmodeling/forecasted_reformulated_income_statement.rb +110 -0
- data/lib/finmodeling/forecasts.rb +4 -4
- data/lib/finmodeling/has_string_classifer.rb +0 -0
- data/lib/finmodeling/income_statement_analyses.rb +23 -17
- data/lib/finmodeling/income_statement_calculation.rb +46 -43
- data/lib/finmodeling/income_statement_item.rb +1 -1
- data/lib/finmodeling/income_statement_item_vectors.rb +24 -13
- data/lib/finmodeling/invalid_filing_error.rb +4 -0
- data/lib/finmodeling/liabs_and_equity_calculation.rb +18 -8
- data/lib/finmodeling/liabs_and_equity_item.rb +1 -1
- data/lib/finmodeling/liabs_and_equity_item_vectors.rb +24 -24
- data/lib/finmodeling/linear_trend_forecasting_policy.rb +23 -0
- data/lib/finmodeling/net_income_calculation.rb +23 -10
- data/lib/finmodeling/net_income_summary_from_differences.rb +51 -0
- data/lib/finmodeling/paths.rb +0 -0
- data/lib/finmodeling/period_array.rb +8 -4
- data/lib/finmodeling/quarterly_report_filing.rb +9 -4
- data/lib/finmodeling/rate.rb +8 -0
- data/lib/finmodeling/ratio.rb +0 -0
- data/lib/finmodeling/reformulated_balance_sheet.rb +47 -88
- data/lib/finmodeling/reformulated_cash_flow_statement.rb +18 -41
- data/lib/finmodeling/reformulated_income_statement.rb +44 -206
- data/lib/finmodeling/reformulated_shareholder_equity_statement.rb +50 -0
- data/lib/finmodeling/reoi_valuation.rb +104 -0
- data/lib/finmodeling/shareholder_equity_statement_calculation.rb +34 -0
- data/lib/finmodeling/string_helpers.rb +18 -1
- data/lib/finmodeling/time_series_estimator.rb +25 -0
- data/lib/finmodeling/trailing_avg_forecasting_policy.rb +23 -0
- data/lib/finmodeling/version.rb +1 -1
- data/lib/finmodeling/weighted_avg_cost_of_capital.rb +35 -0
- data/lib/finmodeling/yahoo_finance_helpers.rb +20 -0
- data/lib/finmodeling.rb +33 -2
- data/spec/annual_report_filing_spec.rb +81 -45
- data/spec/assets_calculation_spec.rb +7 -4
- data/spec/assets_item_spec.rb +9 -14
- data/spec/balance_sheet_analyses_spec.rb +13 -13
- data/spec/balance_sheet_calculation_spec.rb +45 -51
- data/spec/calculation_summary_spec.rb +113 -21
- data/spec/can_classify_rows_spec.rb +0 -0
- data/spec/cash_change_calculation_spec.rb +1 -10
- data/spec/cash_change_item_spec.rb +10 -18
- data/spec/cash_flow_statement_calculation_spec.rb +10 -24
- data/spec/company_beta_spec.rb +53 -0
- data/spec/company_filing_calculation_spec.rb +39 -49
- data/spec/company_filing_spec.rb +0 -0
- data/spec/company_filings_spec.rb +75 -25
- data/spec/company_spec.rb +37 -47
- data/spec/comprehensive_income_statement_calculation_spec.rb +54 -0
- data/spec/comprehensive_income_statement_item_spec.rb +56 -0
- data/spec/debt_cost_of_capital_spec.rb +19 -0
- data/spec/equity_change_calculation_spec.rb +33 -0
- data/spec/equity_change_item_spec.rb +58 -0
- data/spec/factory_spec.rb +2 -2
- data/spec/forecasts_spec.rb +2 -2
- data/spec/income_statement_analyses_spec.rb +23 -21
- data/spec/income_statement_calculation_spec.rb +17 -49
- data/spec/income_statement_item_spec.rb +17 -29
- data/spec/liabs_and_equity_calculation_spec.rb +6 -3
- data/spec/liabs_and_equity_item_spec.rb +14 -22
- data/spec/linear_trend_forecasting_policy_spec.rb +37 -0
- data/spec/matchers/custom_matchers.rb +79 -0
- data/spec/mocks/calculation.rb +0 -0
- data/spec/mocks/income_statement_analyses.rb +0 -0
- data/spec/mocks/sec_query.rb +0 -0
- data/spec/net_income_calculation_spec.rb +16 -10
- data/spec/period_array.rb +0 -0
- data/spec/quarterly_report_filing_spec.rb +21 -38
- data/spec/rate_spec.rb +0 -0
- data/spec/ratio_spec.rb +0 -0
- data/spec/reformulated_balance_sheet_spec.rb +56 -33
- data/spec/reformulated_cash_flow_statement_spec.rb +18 -10
- data/spec/reformulated_income_statement_spec.rb +16 -15
- data/spec/reformulated_shareholder_equity_statement_spec.rb +43 -0
- data/spec/reoi_valuation_spec.rb +146 -0
- data/spec/shareholder_equity_statement_calculation_spec.rb +59 -0
- data/spec/spec_helper.rb +4 -1
- data/spec/string_helpers_spec.rb +15 -13
- data/spec/time_series_estimator_spec.rb +61 -0
- data/spec/trailing_avg_forecasting_policy_spec.rb +37 -0
- data/spec/weighted_avg_cost_of_capital_spec.rb +32 -0
- data/tools/create_equity_change_training_vectors.rb +49 -0
- data/tools/time_specs.sh +7 -0
- metadata +182 -36
- data/lib/finmodeling/constant_forecasting_policy.rb +0 -23
- data/lib/finmodeling/generic_forecasting_policy.rb +0 -19
- data/spec/constant_forecasting_policy_spec.rb +0 -37
- data/spec/generic_forecasting_policy_spec.rb +0 -33
data/Rakefile
CHANGED
@@ -1,6 +1,18 @@
|
|
1
1
|
#!/usr/bin/env rake
|
2
2
|
require "bundler/gem_tasks"
|
3
3
|
|
4
|
+
desc "run the specs"
|
4
5
|
task :test do
|
6
|
+
sh "if git status | grep 'modified:' | grep annual_report >/dev/null; then echo \"\n\nYou should get rid of ~/.finmodeling\"; fi"
|
5
7
|
sh "rspec -c -fd -I. -Ispec spec/*spec.rb"
|
6
8
|
end
|
9
|
+
|
10
|
+
desc "purges anything cached"
|
11
|
+
task :purge_cache do
|
12
|
+
sh "rm -rf ~/.finmodeling"
|
13
|
+
end
|
14
|
+
|
15
|
+
desc "purges anything cached, except the raw XBRL filing downloads."
|
16
|
+
task :purge_cache_except_filings do
|
17
|
+
sh "rm -rf ~/.finmodeling/classifiers/ ~/.finmodeling/constructors/ ~/.finmodeling/summaries/"
|
18
|
+
end
|
data/TODO.txt
CHANGED
@@ -1,36 +1,129 @@
|
|
1
|
-
|
2
|
-
- Parse this one to get at comprehensive income
|
3
|
-
|
4
|
-
Forecasts
|
5
|
-
- Choosing of policies should probably be bumped to a higher-level package.
|
6
|
-
- Need more sophisticated forecasting policies
|
7
|
-
- Need to have more sophisticated policies fall back on simpler ones (or none) when there's
|
8
|
-
too much chaos
|
9
|
-
|
10
|
-
Bugs
|
11
|
-
- Not all disclosures *begin* with disclosure. See CRM's most recent 10-K, for instance.
|
12
|
-
- The "valid vals" thing should hides gaps in data, which silently&crappily affect the regressions.
|
13
|
-
- show_reports doesn't check filing validity
|
1
|
+
XBRL items can include Footnotes. Should probably print these out.
|
14
2
|
|
15
3
|
Income Statement
|
16
|
-
-
|
17
|
-
|
4
|
+
- In IncomeStatement::latest_quarterly_refactored, it's checking whether the statement has any
|
5
|
+
quarterly periods, and if so, using the last one. There's a problem with that, some statements
|
6
|
+
do have quarterly periods, but don't break all lines down to the quarterly level. In this example:
|
7
|
+
./examples/show_reports.rb --income-detail CHRW 2011-09-01
|
8
|
+
The 2012-12-31 column is missing a $283M income (FIBT) because that item is only given in annual
|
9
|
+
periods (not the 2012-10-01..2012-12-31 quarterly period). See it in this dump:
|
10
|
+
./examples/dump_latest_10k.rb http://www.sec.gov/Archives/edgar/data/1043277/000104327713000004/0001043277-13-000004-index.htm
|
11
|
+
Need to modify that method to check whether there are quarterly PERIODS and that all leaf items
|
12
|
+
have that quarterly period.
|
13
|
+
- Improve the readability of the error checking. One option would be to move to
|
14
|
+
a system like ActiveRecord's error-handling, in which a class just accumulates its errors
|
15
|
+
nicely, and lets you print them out whenever it's convenient, and in a well-formatted way,
|
16
|
+
without causing an immediate exception.
|
18
17
|
- The classifier that had been classifying unknown credits/debits is disabled. Replace it?
|
19
|
-
|
18
|
+
(I don't even remember what this refers to...)
|
19
|
+
- net income != comprehensive income. pull in items from SSE? and/or parse the comprehensive
|
20
|
+
income statement.
|
20
21
|
- certain things are being misclassified as FIBT instead of OOIBT. (hedging,
|
21
22
|
currency exchg, etc)
|
22
|
-
- naming consistency (e.g., rename OIBT as OOIBT?)
|
23
|
-
- consistency between operating/financing vs. operational/financial?
|
24
23
|
- need to better understand minority interest, noncontrollable blah-blah,
|
25
|
-
etc.
|
24
|
+
etc. (not a big deal for tech companies, but important for conglomerates, mediaco's, etc)
|
25
|
+
- naming consistency (e.g., rename OIBT as OOIBT?)
|
26
|
+
- naming consistency between operating/financing vs. operational/financial?
|
27
|
+
- Instead of having find_calculation_arc use a discrete set of exact regexes, what about using
|
28
|
+
a classifier (and minimum confidence threshold), like the row classifier works?
|
29
|
+
- How to tell if a stock gave off a dividend? Are we tracking those somewhere?
|
30
|
+
|
31
|
+
Forecasts
|
32
|
+
- Refactor it. The logic is too spread around.
|
33
|
+
- Detect historicals that are too noisy to forecast from:
|
34
|
+
- R^2 of regressions?
|
35
|
+
- Certain ratios remaining constant-ish (e.g, composition ratios)
|
36
|
+
- Detect historicals with poor earnings quality:
|
37
|
+
- Noisy NI/C
|
38
|
+
- Detect forecasts that look suspect:
|
39
|
+
- Derived values in the forecasts (e.g., composition ratio) remain
|
40
|
+
with a standard deviation or two from their trend lines.
|
41
|
+
- Allow assumptions to be tweaked by user:
|
42
|
+
- How is continuing value calculated? (What long-term g?)
|
43
|
+
|
44
|
+
Comprehensive Income Statement
|
45
|
+
- Not very many examples, especially of less common things like FIAT. Find more.
|
46
|
+
- The pension and stock-expense OCIs are getting labeled as operating OCI, which is geting
|
47
|
+
added back in as other operating income after tax. Really, though, they should be treated
|
48
|
+
as a core operating expense, since they're about paying the core employees.
|
26
49
|
|
27
50
|
Balance Sheet
|
28
51
|
- Dig into the disclosures. They've got lots more interesting goodness.
|
52
|
+
- Need to reclassify: credits to shareholders' equity for stock compensation expense
|
53
|
+
- Minority interest?
|
54
|
+
- Need to reclassify: dividends payable
|
29
55
|
- The classifier that had been classifying unknown credits/debits is disabled. Replace it?
|
56
|
+
- "Redeemable Noncontrolling Interest Equity Carrying Amount"??
|
57
|
+
|
58
|
+
Try to figure out why the change in equity is not equal to comprehensive income, in cases where we know it.
|
59
|
+
- Some 10k's (and even some 10q's?) have Statements of SE
|
60
|
+
- When they don't:
|
61
|
+
- Some 10q's have AOCI broken out on BS. We can look for deltas
|
62
|
+
- Some 10q's have disclosures that list components of OCI or CI
|
63
|
+
- The remaining residual is probably stock-based compensation expense
|
64
|
+
- minority interests makes it screwey. also, preferred dividends?
|
30
65
|
|
31
66
|
Cash Flow Statement
|
32
|
-
- Dig into the disclosures. They've got lots more interesting goodness.
|
33
67
|
- not taking into account: net cash interest, tax on net interest, or non-cash
|
34
68
|
transactions. All 3 of these items are listed in supplementary disclosures.
|
35
69
|
- the classifier's success rate isn't that high.
|
36
70
|
- "I" is defined inversely.
|
71
|
+
|
72
|
+
Statement of Shareholder Equity
|
73
|
+
- Not taking into account share-based compensation, dividends payable, etc.
|
74
|
+
|
75
|
+
Cost of Capital
|
76
|
+
- Debt cost of capital - try to estimate before tax cost of capital by modeling
|
77
|
+
credit spread as a function of interest coverage. Is there a way to estimate
|
78
|
+
marginal (not effective) tax rate?
|
79
|
+
|
80
|
+
XBRL includes a "weight" parameter. Is that useable in place of the "mapping" idea used here?
|
81
|
+
|
82
|
+
########################################################################################################################
|
83
|
+
########################################################################################################################
|
84
|
+
########################################################################################################################
|
85
|
+
|
86
|
+
require 'finmodeling'
|
87
|
+
|
88
|
+
report_paths=["/Users/jimlindstrom/.finmodeling/filings/000110465910063219", "/Users/jimlindstrom/.finmodeling/filings/000119312509153165", "/Users/jimlindstrom/.finmodeling/filings/000119312509214859", "/Users/jimlindstrom/.finmodeling/filings/000119312510012085", "/Users/jimlindstrom/.finmodeling/filings/000119312510030774", "/Users/jimlindstrom/.finmodeling/filings/000119312510088957", "/Users/jimlindstrom/.finmodeling/filings/000119312510162840", "/Users/jimlindstrom/.finmodeling/filings/000119312510238044", "/Users/jimlindstrom/.finmodeling/filings/000119312511010144", "/Users/jimlindstrom/.finmodeling/filings/000119312511104388", "/Users/jimlindstrom/.finmodeling/filings/000119312511134428", "/Users/jimlindstrom/.finmodeling/filings/000119312511192493", "/Users/jimlindstrom/.finmodeling/filings/000119312511199078", "/Users/jimlindstrom/.finmodeling/filings/000119312511282113", "/Users/jimlindstrom/.finmodeling/filings/000119312511282235", "/Users/jimlindstrom/.finmodeling/filings/000119312512023398", "/Users/jimlindstrom/.finmodeling/filings/000119312512025336", "/Users/jimlindstrom/.finmodeling/filings/000119312512182321", "/Users/jimlindstrom/.finmodeling/filings/000119312512314552", "/Users/jimlindstrom/.finmodeling/filings/000119312512444068", "/Users/jimlindstrom/.finmodeling/filings/000119312513022339"]
|
89
|
+
|
90
|
+
report_paths.each do |report_path|
|
91
|
+
|
92
|
+
puts report_path
|
93
|
+
|
94
|
+
filing=FinModeling::AnnualReportFiling.new(report_path)
|
95
|
+
bs=filing.balance_sheet
|
96
|
+
le=bs.liabs_and_equity_calculation
|
97
|
+
le.periods.each do |period|
|
98
|
+
puts le.calculation.leaf_items(period).select{ |x| x.footnotes }.inspect
|
99
|
+
end
|
100
|
+
a=bs.assets_calculation
|
101
|
+
a.periods.each do |period|
|
102
|
+
puts a.calculation.leaf_items(period).select{ |x| x.footnotes }.inspect
|
103
|
+
end
|
104
|
+
|
105
|
+
puts
|
106
|
+
|
107
|
+
end
|
108
|
+
# #<Xbrlware::Item:0x007fcd631b7b18
|
109
|
+
# @ins=#<Xbrlware::Instance:0x007fcd63b8cdf0>,
|
110
|
+
# @name="CommonStockValue",
|
111
|
+
# @context=Id [eol_PE2035----0910-Q0009_STD_0_20091226_0],
|
112
|
+
# Entity { Identifier { schema [http://www.sec.gov/CIK], value [] } },
|
113
|
+
# period [2009-12-26],
|
114
|
+
# @precision=nil,
|
115
|
+
# @decimals="-6",
|
116
|
+
# @footnotes={"en-US"=>["See Note 2, \"Retrospective Adoption of New Accounting Principles\" of this Form 10-Q."]},
|
117
|
+
# @value="8962000000.0",
|
118
|
+
# @unit=#<Xbrlware::Unit:0x007fcd66db28e8 @id="iso4217_USD", @measure=["iso4217:USD"], @ns="http://www.xbrl.org/2003/instance", @nsp="">,
|
119
|
+
# @ns="http://xbrl.us/us-gaap/2009-01-31",
|
120
|
+
# @nsp="us-gaap",
|
121
|
+
# @def={"xbrli:balance"=>"credit", "name"=>"CommonStockValue", "nillable"=>"true", "xbrli:periodType"=>"instant", "id"=>"us-gaap_CommonStockValue", "type"=>"xbrli:monetaryItemType", "substitutionGroup"=>"xbrli:item"}>
|
122
|
+
|
123
|
+
|
124
|
+
# several TODOs:
|
125
|
+
# 1. modify xbrlware-extras to write these to the constructors
|
126
|
+
# item = Xbrlware::Item.new(instance, name, context, value, unit, precision, decimals, footnotes)
|
127
|
+
# 2. modify CalculationSummary to handle printing footnoes
|
128
|
+
# 3. modify the various calculations to extract footnoes and stuff them into the summaries.
|
129
|
+
# --- And do it TDD-style.
|
@@ -0,0 +1,50 @@
|
|
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__} <stock symbol> <start date, e.g. '2010-01-01'>"
|
9
|
+
exit
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.parse(args)
|
13
|
+
a = { :stock_symbol => nil, :start_date => nil }
|
14
|
+
|
15
|
+
self.show_usage_and_exit if args.length != 2
|
16
|
+
a[:stock_symbol] = args[0]
|
17
|
+
a[:start_date] = Time.parse(args[1])
|
18
|
+
|
19
|
+
return a
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
args = Arguments.parse(ARGV)
|
24
|
+
|
25
|
+
company = FinModeling::Company.find(args[:stock_symbol])
|
26
|
+
raise RuntimeError.new("couldn't find company") if !company
|
27
|
+
puts "company name: #{company.name}"
|
28
|
+
|
29
|
+
filings = FinModeling::CompanyFilings.new(company.filings_since_date(args[:start_date]))
|
30
|
+
if filings.empty?
|
31
|
+
puts "No filings..."
|
32
|
+
exit
|
33
|
+
end
|
34
|
+
|
35
|
+
disclosure_periods = {}
|
36
|
+
|
37
|
+
filings.each do |filing|
|
38
|
+
|
39
|
+
filing.disclosures.each do |disclosure|
|
40
|
+
disclosure_label = disclosure.summary(:period => disclosure.periods.last).title.gsub(/ \(.*/,'')
|
41
|
+
|
42
|
+
disclosure_periods[disclosure_label] ||= []
|
43
|
+
disclosure_periods[disclosure_label] += disclosure.periods
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
disclosure_periods.keys.sort.each do |disclosure_label|
|
48
|
+
puts disclosure_label.to_s + ": " + disclosure_periods[disclosure_label].map{ |x| x.to_pretty_s }.sort.uniq.join(', ')
|
49
|
+
end
|
50
|
+
|
File without changes
|
data/examples/show_report.rb
CHANGED
@@ -42,7 +42,7 @@ class Arguments
|
|
42
42
|
|
43
43
|
protected
|
44
44
|
|
45
|
-
def self.parse_just_a_url(raw_args,
|
45
|
+
def self.parse_just_a_url(raw_args, parsed_args)
|
46
46
|
self.show_usage_and_exit if raw_args.length != 2
|
47
47
|
|
48
48
|
parsed_args[:filing_url] = raw_args[0]
|
@@ -124,21 +124,63 @@ def print_reformulated_balance_sheet(filing, report_type)
|
|
124
124
|
end
|
125
125
|
|
126
126
|
def print_income_statement(filing, report_type)
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
127
|
+
if filing.has_an_income_statement?
|
128
|
+
period = filing.income_statement.net_income_calculation.periods.yearly.last if report_type == :annual_report
|
129
|
+
period = filing.income_statement.net_income_calculation.periods.quarterly.last if report_type == :quarterly_report
|
130
|
+
puts "Income Statement (#{period.to_pretty_s})"
|
131
|
+
|
132
|
+
summaries = []
|
133
|
+
summaries << filing.income_statement.net_income_calculation.summary(:period => period)
|
134
|
+
|
135
|
+
print_summaries(summaries)
|
136
|
+
else
|
137
|
+
puts "Filing has no income statement."
|
138
|
+
puts
|
139
|
+
end
|
140
|
+
end
|
133
141
|
|
134
|
-
|
142
|
+
def print_comprehensive_income_statement(filing, report_type)
|
143
|
+
if filing.has_a_comprehensive_income_statement?
|
144
|
+
period = filing.comprehensive_income_statement.comprehensive_income_calculation.periods.yearly.last if report_type == :annual_report
|
145
|
+
period = filing.comprehensive_income_statement.comprehensive_income_calculation.periods.quarterly.last if report_type == :quarterly_report
|
146
|
+
puts "Comprehensive Income Statement (#{period.to_pretty_s})"
|
147
|
+
|
148
|
+
summaries = []
|
149
|
+
summaries << filing.comprehensive_income_statement.comprehensive_income_calculation.summary(:period => period)
|
150
|
+
#summaries << filing.comprehensive_income_statement.summary(:period => period) # when debugging, try printing the whole statement like this.
|
151
|
+
|
152
|
+
print_summaries(summaries)
|
153
|
+
else
|
154
|
+
puts "Filing has no comprehensive income statement."
|
155
|
+
puts
|
156
|
+
end
|
135
157
|
end
|
136
158
|
|
137
159
|
def print_reformulated_income_statement(filing, report_type)
|
138
|
-
|
139
|
-
|
160
|
+
reformed_inc_stmt = nil
|
161
|
+
comprehensive_inc_calc = nil
|
162
|
+
|
163
|
+
if filing.has_a_comprehensive_income_statement?
|
164
|
+
comprehensive_inc_calc = filing.comprehensive_income_statement.comprehensive_income_calculation
|
165
|
+
end
|
140
166
|
|
141
|
-
|
167
|
+
if filing.has_an_income_statement?
|
168
|
+
period = filing.income_statement.net_income_calculation.periods.yearly.last if report_type == :annual_report
|
169
|
+
period = filing.income_statement.net_income_calculation.periods.quarterly.last if report_type == :quarterly_report
|
170
|
+
|
171
|
+
reformed_inc_stmt = filing.income_statement.reformulated(period, comprehensive_inc_calc)
|
172
|
+
|
173
|
+
elsif filing.has_a_comprehensive_income_statement? &&
|
174
|
+
filing.comprehensive_income_statement
|
175
|
+
.comprehensive_income_calculation
|
176
|
+
.has_revenue_item?
|
177
|
+
period = filing.comprehensive_income_statement.comprehensive_income_calculation.periods.yearly.last if report_type == :annual_report
|
178
|
+
period = filing.comprehensive_income_statement.comprehensive_income_calculation.periods.quarterly.last if report_type == :quarterly_report
|
179
|
+
|
180
|
+
reformed_inc_stmt = filing.comprehensive_income_statement.reformulated(period, comprehensive_inc_calc)
|
181
|
+
else
|
182
|
+
raise RuntimeError.new("Can't create reformulated income statement")
|
183
|
+
end
|
142
184
|
|
143
185
|
summaries = []
|
144
186
|
summaries << reformed_inc_stmt.gross_revenue
|
@@ -154,27 +196,62 @@ end
|
|
154
196
|
def print_cash_flow_statement(filing, report_type)
|
155
197
|
period = filing.cash_flow_statement.periods.yearly.last if report_type == :annual_report
|
156
198
|
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
199
|
|
162
|
-
|
200
|
+
if period
|
201
|
+
puts "cash flow statement (#{period.to_pretty_s})"
|
202
|
+
|
203
|
+
summaries = []
|
204
|
+
summaries << filing.cash_flow_statement.cash_change_calculation.summary(:period => period)
|
205
|
+
|
206
|
+
print_summaries(summaries)
|
207
|
+
else
|
208
|
+
puts "WARNING: cash flow statement period is nil!"
|
209
|
+
end
|
163
210
|
end
|
164
211
|
|
165
212
|
def print_reformulated_cash_flow_statement(filing, report_type)
|
166
213
|
period = filing.cash_flow_statement.periods.yearly.last if report_type == :annual_report
|
167
214
|
period = filing.cash_flow_statement.periods.quarterly.last if report_type == :quarterly_report
|
215
|
+
|
216
|
+
if period
|
217
|
+
reformed_cash_flow_stmt = filing.cash_flow_statement.reformulated(period)
|
218
|
+
|
219
|
+
summaries = []
|
220
|
+
summaries << reformed_cash_flow_stmt.cash_from_operations
|
221
|
+
summaries << reformed_cash_flow_stmt.cash_investments_in_operations
|
222
|
+
summaries << reformed_cash_flow_stmt.payments_to_debtholders
|
223
|
+
summaries << reformed_cash_flow_stmt.payments_to_stockholders
|
224
|
+
summaries << reformed_cash_flow_stmt.free_cash_flow
|
225
|
+
summaries << reformed_cash_flow_stmt.financing_flows
|
226
|
+
|
227
|
+
print_summaries(summaries)
|
228
|
+
else
|
229
|
+
puts "WARNING: reformulated cash flow statement period is nil!"
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def print_shareholder_equity_statement(filing, report_type)
|
234
|
+
return if !filing.has_a_shareholder_equity_statement?
|
235
|
+
period = filing.shareholder_equity_statement.periods.yearly.last if report_type == :annual_report
|
236
|
+
period = filing.shareholder_equity_statement.periods.quarterly.last if report_type == :quarterly_report
|
237
|
+
puts "shareholder equity statement (#{period.to_pretty_s})"
|
238
|
+
|
239
|
+
summaries = []
|
240
|
+
summaries << filing.shareholder_equity_statement.equity_change_calculation.summary(:period => period)
|
241
|
+
|
242
|
+
print_summaries(summaries)
|
243
|
+
end
|
244
|
+
|
245
|
+
def print_reformulated_shareholder_equity_statement(filing, report_type)
|
246
|
+
return if !filing.has_a_shareholder_equity_statement?
|
247
|
+
period = filing.shareholder_equity_statement.periods.yearly.last if report_type == :annual_report
|
248
|
+
period = filing.shareholder_equity_statement.periods.quarterly.last if report_type == :quarterly_report
|
168
249
|
|
169
|
-
|
250
|
+
reformed_shareholder_equity_stmt = filing.shareholder_equity_statement.reformulated(period)
|
170
251
|
|
171
252
|
summaries = []
|
172
|
-
summaries <<
|
173
|
-
summaries <<
|
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
|
253
|
+
summaries << reformed_shareholder_equity_stmt.transactions_with_shareholders
|
254
|
+
summaries << reformed_shareholder_equity_stmt.comprehensive_income
|
178
255
|
|
179
256
|
print_summaries(summaries)
|
180
257
|
end
|
@@ -205,14 +282,17 @@ if args[:filing_url].nil?
|
|
205
282
|
args[:filing_url] = get_company_filing_url(args[:stock_symbol], args[:report_type], args[:report_offset])
|
206
283
|
end
|
207
284
|
|
208
|
-
filing = get_filing(
|
209
|
-
|
210
|
-
print_balance_sheet(
|
211
|
-
print_reformulated_balance_sheet(
|
212
|
-
print_income_statement(
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
285
|
+
filing = get_filing(args[:filing_url], args[:report_type])
|
286
|
+
|
287
|
+
print_balance_sheet( filing, args[:report_type])
|
288
|
+
print_reformulated_balance_sheet( filing, args[:report_type])
|
289
|
+
print_income_statement( filing, args[:report_type])
|
290
|
+
print_comprehensive_income_statement( filing, args[:report_type])
|
291
|
+
print_reformulated_income_statement( filing, args[:report_type])
|
292
|
+
print_cash_flow_statement( filing, args[:report_type])
|
293
|
+
print_reformulated_cash_flow_statement( filing, args[:report_type])
|
294
|
+
print_shareholder_equity_statement( filing, args[:report_type])
|
295
|
+
print_reformulated_shareholder_equity_statement(filing, args[:report_type])
|
296
|
+
print_disclosures( filing, args[:report_type]) if args[:show_disclosures]
|
217
297
|
|
218
298
|
raise RuntimeError.new("filing is not valid") if !filing.is_valid?
|
data/examples/show_reports.rb
CHANGED
@@ -9,42 +9,108 @@ class Arguments
|
|
9
9
|
puts
|
10
10
|
puts "\tOptions:"
|
11
11
|
puts "\t\t--num-forecasts <num>: how many periods to forecast"
|
12
|
+
puts "\t\t --forecast-policy <trailing_avg|linear_trend> (default: linear_trend)"
|
13
|
+
puts "\t\t --do-valuation: value the company's equity. (requires >= 2 forecasts)"
|
14
|
+
puts "\t\t --marginal-tax-rate <num>: default is 0.36 (36%)"
|
15
|
+
puts "\t\t --before-tax-cost-of-debt <num>: default is 0.05 (5%)"
|
12
16
|
puts "\t\t--no-cache: disable caching"
|
13
17
|
puts "\t\t--balance-detail: show details about the balance sheet calculation"
|
14
18
|
puts "\t\t--income-detail: show details about the net income calculation"
|
19
|
+
puts "\t\t--show-disclosure <part of title>: show a particular disclosure over time"
|
20
|
+
puts "\t\t--show-regressions: show the regressions of calculations that are used to do forecasts"
|
15
21
|
exit
|
16
22
|
end
|
17
|
-
|
18
|
-
def self.parse(
|
19
|
-
|
20
|
-
|
21
|
-
while
|
22
|
-
|
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]
|
23
|
+
|
24
|
+
def self.parse(raw_args)
|
25
|
+
parsed_args = self.default_options
|
26
|
+
|
27
|
+
while raw_args.any? && raw_args.first =~ /^--/
|
28
|
+
self.parse_next_option(raw_args, parsed_args)
|
41
29
|
end
|
42
30
|
|
43
|
-
self.show_usage_and_exit if
|
44
|
-
|
45
|
-
|
31
|
+
self.show_usage_and_exit if raw_args.length != 2
|
32
|
+
parsed_args[:stock_symbol] = raw_args[0]
|
33
|
+
parsed_args[:start_date ] = Time.parse(raw_args[1])
|
46
34
|
|
47
|
-
return
|
35
|
+
return parsed_args
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
def self.default_options
|
41
|
+
{ :stock_symbol => nil,
|
42
|
+
:start_date => nil,
|
43
|
+
:num_forecasts => nil,
|
44
|
+
:do_valuation => false,
|
45
|
+
:forecast_policy => :linear_trend,
|
46
|
+
:marginal_tax_rate => 0.36,
|
47
|
+
:before_tax_cost_of_debt => 0.05,
|
48
|
+
:show_regressions => false,
|
49
|
+
:disclosures => [ ] }
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
def self.parse_next_option(raw_args, parsed_args)
|
54
|
+
case raw_args.first.downcase
|
55
|
+
when '--no-cache'
|
56
|
+
FinModeling::Config.disable_caching
|
57
|
+
puts "Caching is #{FinModeling::Config.caching_enabled? ? "enabled" : "disabled"}"
|
58
|
+
|
59
|
+
when '--balance-detail'
|
60
|
+
FinModeling::Config.enable_balance_detail
|
61
|
+
puts "Balance sheet detail is #{FinModeling::Config.balance_detail_enabled? ? "enabled" : "disabled"}"
|
62
|
+
|
63
|
+
when '--income-detail'
|
64
|
+
FinModeling::Config.enable_income_detail
|
65
|
+
puts "Net income detail is #{FinModeling::Config.income_detail_enabled? ? "enabled" : "disabled"}"
|
66
|
+
|
67
|
+
when '--num-forecasts'
|
68
|
+
self.show_usage_and_exit if raw_args.length < 2
|
69
|
+
parsed_args[:num_forecasts] = raw_args[1].to_i
|
70
|
+
self.show_usage_and_exit unless parsed_args[:num_forecasts] >= 1
|
71
|
+
puts "Forecasting #{parsed_args[:num_forecasts]} periods"
|
72
|
+
raw_args.shift
|
73
|
+
|
74
|
+
when '--do-valuation'
|
75
|
+
parsed_args[:do_valuation] = true
|
76
|
+
puts "Doing valuation"
|
77
|
+
|
78
|
+
when '--forecast-policy'
|
79
|
+
self.show_usage_and_exit if raw_args.length < 2
|
80
|
+
parsed_args[:forecast_policy] = raw_args[1].to_sym
|
81
|
+
self.show_usage_and_exit unless [:trailing_avg, :linear_trend].include?(parsed_args[:forecast_policy])
|
82
|
+
puts "Forecast policy: #{parsed_args[:forecast_policy]}"
|
83
|
+
raw_args.shift
|
84
|
+
|
85
|
+
when '--before-tax-cost-of-debt'
|
86
|
+
self.show_usage_and_exit if raw_args.length < 2
|
87
|
+
parsed_args[:before_tax_cost_of_debt] = raw_args[1].to_f
|
88
|
+
self.show_usage_and_exit unless parsed_args[:before_tax_cost_of_debt] >= 0.00 && parsed_args[:before_tax_cost_of_debt] <= 1.00
|
89
|
+
puts "before tax cost of debt: #{parsed_args[:before_tax_cost_of_debt]}"
|
90
|
+
raw_args.shift
|
91
|
+
|
92
|
+
when '--marginal-tax-rate'
|
93
|
+
self.show_usage_and_exit if raw_args.length < 2
|
94
|
+
parsed_args[:marginal_tax_rate] = raw_args[1].to_f
|
95
|
+
self.show_usage_and_exit unless parsed_args[:marginal_tax_rate] >= 0.00 && parsed_args[:marginal_tax_rate] <= 1.00
|
96
|
+
puts "marginal tax rate: #{parsed_args[:marginal_tax_rate]}"
|
97
|
+
raw_args.shift
|
98
|
+
|
99
|
+
when '--show-regressions'
|
100
|
+
parsed_args[:show_regressions] = true
|
101
|
+
puts "Showing regressions"
|
102
|
+
|
103
|
+
when '--show-disclosure'
|
104
|
+
self.show_usage_and_exit if raw_args.length < 2
|
105
|
+
parsed_args[:disclosures] << raw_args[1]
|
106
|
+
puts "Showing disclosure: #{parsed_args[:disclosures].last}"
|
107
|
+
raw_args.shift
|
108
|
+
|
109
|
+
else
|
110
|
+
self.show_usage_and_exit
|
111
|
+
|
112
|
+
end
|
113
|
+
raw_args.shift
|
48
114
|
end
|
49
115
|
end
|
50
116
|
|
@@ -59,19 +125,82 @@ if filings.empty?
|
|
59
125
|
puts "No filings..."
|
60
126
|
exit
|
61
127
|
end
|
128
|
+
filings.each do |filing|
|
129
|
+
begin
|
130
|
+
if !filing.is_valid?
|
131
|
+
raise RuntimeError.new("filing is not valid. type: #{filing.class}. period: #{filing.balance_sheet.periods.last}.")
|
132
|
+
end
|
133
|
+
rescue FinModeling::InvalidFilingError => e
|
134
|
+
pre_msg = "\n\nFiling is not valid. type: #{filing.class}. period: #{filing.balance_sheet.periods.last}.\n"
|
135
|
+
raise e, pre_msg+e.message, e.backtrace
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
|
140
|
+
fl = filings.re_bs_arr.last.financial_liabilities.total
|
141
|
+
dcoc = FinModeling::DebtCostOfCapital.calculate(:before_tax_cost => FinModeling::Rate.new(args[:before_tax_cost_of_debt]),
|
142
|
+
:marginal_tax_rate => FinModeling::Rate.new(args[:marginal_tax_rate]))
|
143
|
+
ecoc = FinModeling::CAPM::EquityCostOfCapital.from_ticker(args[:stock_symbol])
|
144
|
+
#ecoc = FinModeling::FamaFrench::EquityCostOfCapital.from_ticker(args[:stock_symbol])
|
145
|
+
if (ecoc.value < 0.05) || (ecoc.value > 0.30)
|
146
|
+
puts "WARNING: cost of equity capital is highly suspect..."
|
147
|
+
end
|
148
|
+
wacc = FinModeling::WeightedAvgCostOfCapital.new(equity_market_val = YahooFinance::get_market_cap(args[:stock_symbol].dup),
|
149
|
+
debt_market_val = fl,
|
150
|
+
cost_of_equity = ecoc,
|
151
|
+
after_tax_cost_of_debt = dcoc)
|
62
152
|
|
63
|
-
forecasts = filings.forecasts(filings.choose_forecasting_policy, num_quarters=args[:num_forecasts]) if args[:num_forecasts]
|
153
|
+
forecasts = filings.forecasts(filings.choose_forecasting_policy(wacc.rate.value, args[:forecast_policy]), num_quarters=args[:num_forecasts]) if args[:num_forecasts]
|
64
154
|
|
65
155
|
bs_analyses = filings.balance_sheet_analyses
|
66
156
|
bs_analyses += forecasts.balance_sheet_analyses(filings) if forecasts
|
67
157
|
bs_analyses.totals_row_enabled = false
|
68
158
|
bs_analyses.print
|
69
|
-
|
159
|
+
if args[:show_regressions] && filings.balance_sheet_analyses.respond_to?(:print_regressions)
|
160
|
+
filings.balance_sheet_analyses.print_regressions
|
161
|
+
end
|
70
162
|
|
71
|
-
is_analyses = filings.income_statement_analyses
|
72
|
-
is_analyses += forecasts.income_statement_analyses(filings) if forecasts
|
163
|
+
is_analyses = filings.income_statement_analyses(wacc.rate.value)
|
164
|
+
is_analyses += forecasts.income_statement_analyses(filings, wacc.rate.value) if forecasts
|
73
165
|
is_analyses.totals_row_enabled = false
|
74
166
|
is_analyses.print
|
75
|
-
|
167
|
+
if args[:show_regressions] && filings.income_statement_analyses.respond_to?(:print_regressions)
|
168
|
+
filings.income_statement_analyses.print_regressions
|
169
|
+
end
|
76
170
|
|
77
171
|
filings.cash_flow_statement_analyses.print
|
172
|
+
if args[:show_regressions] && filings.cash_flow_statement_analyses.respond_to?(:print_regressions)
|
173
|
+
filings.cash_flow_statement_analyses.print_regressions
|
174
|
+
end
|
175
|
+
|
176
|
+
args[:disclosures].each do |disclosure_title|
|
177
|
+
title_regex = Regexp.new(disclosure_title, Regexp::IGNORECASE)
|
178
|
+
disclosures = filings.disclosures(title_regex, :quarterly)
|
179
|
+
disclosures ||= filings.disclosures(title_regex, :yearly )
|
180
|
+
disclosures ||= filings.disclosures(title_regex )
|
181
|
+
if disclosures
|
182
|
+
disclosures.auto_scale!
|
183
|
+
if (disclosures.header_row.vals - bs_analyses.header_row.vals).length == 0
|
184
|
+
0.upto(bs_analyses.header_row.vals.length-1) do |idx|
|
185
|
+
if bs_analyses.header_row.vals[idx] != disclosures.header_row.vals[idx]
|
186
|
+
disclosures.insert_column_before(idx, "")
|
187
|
+
end
|
188
|
+
end
|
189
|
+
end
|
190
|
+
disclosures.print
|
191
|
+
else
|
192
|
+
puts "Couldn't find disclosures called: #{title_regex}"
|
193
|
+
end
|
194
|
+
end
|
195
|
+
|
196
|
+
if args[:do_valuation]
|
197
|
+
if args[:num_forecasts] && args[:num_forecasts]>=2
|
198
|
+
wacc.summary.print
|
199
|
+
|
200
|
+
num_shares = YahooFinance::get_num_shares(args[:stock_symbol].dup)
|
201
|
+
valuation = FinModeling::ReOIValuation.new(filings, forecasts, wacc.rate, num_shares)
|
202
|
+
valuation.summary.print
|
203
|
+
else
|
204
|
+
puts "Oops. Can't do valuation without >= 2 forecasts"
|
205
|
+
end
|
206
|
+
end
|
data/finmodeling.gemspec
CHANGED
@@ -13,11 +13,14 @@ Gem::Specification.new do |gem|
|
|
13
13
|
gem.add_dependency("edgar")
|
14
14
|
|
15
15
|
gem.add_dependency("xbrlware-ruby19", "1.1.2.19.2")
|
16
|
-
gem.add_dependency("xbrlware-extras", "1.1.2.19.
|
16
|
+
gem.add_dependency("xbrlware-extras", "1.1.2.19.3")
|
17
|
+
gem.add_dependency("nasdaq_query")
|
17
18
|
|
18
19
|
gem.add_dependency("sec_query")
|
19
20
|
gem.add_dependency("naive_bayes")
|
20
21
|
gem.add_dependency("statsample")
|
22
|
+
gem.add_dependency("yahoofinance")
|
23
|
+
gem.add_dependency("gsl")
|
21
24
|
|
22
25
|
gem.add_development_dependency("rspec", "2.5")
|
23
26
|
gem.add_development_dependency("rake")
|