finmodeling 0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. data/.gitignore +0 -0
  2. data/Gemfile +2 -0
  3. data/README.md +289 -269
  4. data/Rakefile +12 -0
  5. data/TODO.txt +113 -20
  6. data/examples/{dump_report.rb → dump_latest_10k.rb} +1 -1
  7. data/examples/list_disclosures.rb +50 -0
  8. data/examples/lists/nasdaq-mid-to-mega-tech-symbols.txt +0 -0
  9. data/examples/show_report.rb +112 -32
  10. data/examples/show_reports.rb +162 -33
  11. data/finmodeling.gemspec +4 -1
  12. data/lib/finmodeling/annual_report_filing.rb +97 -18
  13. data/lib/finmodeling/array_with_stats.rb +0 -0
  14. data/lib/finmodeling/assets_calculation.rb +12 -3
  15. data/lib/finmodeling/assets_item.rb +0 -0
  16. data/lib/finmodeling/assets_item_vectors.rb +0 -0
  17. data/lib/finmodeling/balance_sheet_analyses.rb +19 -4
  18. data/lib/finmodeling/balance_sheet_calculation.rb +52 -37
  19. data/lib/finmodeling/calculation_summary.rb +119 -14
  20. data/lib/finmodeling/can_cache_classifications.rb +0 -0
  21. data/lib/finmodeling/can_cache_summaries.rb +0 -0
  22. data/lib/finmodeling/can_choose_successive_periods.rb +15 -0
  23. data/lib/finmodeling/can_classify_rows.rb +0 -0
  24. data/lib/finmodeling/capm.rb +80 -0
  25. data/lib/finmodeling/cash_change_calculation.rb +3 -3
  26. data/lib/finmodeling/cash_change_item.rb +0 -0
  27. data/lib/finmodeling/cash_change_item_vectors.rb +0 -0
  28. data/lib/finmodeling/cash_change_summary_from_differences.rb +36 -0
  29. data/lib/finmodeling/cash_flow_statement_analyses.rb +36 -0
  30. data/lib/finmodeling/cash_flow_statement_calculation.rb +28 -52
  31. data/lib/finmodeling/classifiers.rb +2 -0
  32. data/lib/finmodeling/company.rb +0 -0
  33. data/lib/finmodeling/company_filing.rb +30 -7
  34. data/lib/finmodeling/company_filing_calculation.rb +16 -6
  35. data/lib/finmodeling/company_filings.rb +112 -46
  36. data/lib/finmodeling/comprehensive_income_calculation.rb +60 -0
  37. data/lib/finmodeling/comprehensive_income_statement_calculation.rb +74 -0
  38. data/lib/finmodeling/comprehensive_income_statement_item.rb +20 -0
  39. data/lib/finmodeling/comprehensive_income_statement_item_vectors.rb +235 -0
  40. data/lib/finmodeling/config.rb +0 -0
  41. data/lib/finmodeling/debt_cost_of_capital.rb +14 -0
  42. data/lib/finmodeling/equity_change_calculation.rb +43 -0
  43. data/lib/finmodeling/equity_change_item.rb +25 -0
  44. data/lib/finmodeling/equity_change_item_vectors.rb +156 -0
  45. data/lib/finmodeling/factory.rb +0 -0
  46. data/lib/finmodeling/fama_french_cost_of_equity.rb +119 -0
  47. data/lib/finmodeling/float_helpers.rb +14 -8
  48. data/lib/finmodeling/forecasted_reformulated_balance_sheet.rb +55 -0
  49. data/lib/finmodeling/forecasted_reformulated_income_statement.rb +110 -0
  50. data/lib/finmodeling/forecasts.rb +4 -4
  51. data/lib/finmodeling/has_string_classifer.rb +0 -0
  52. data/lib/finmodeling/income_statement_analyses.rb +23 -17
  53. data/lib/finmodeling/income_statement_calculation.rb +46 -43
  54. data/lib/finmodeling/income_statement_item.rb +1 -1
  55. data/lib/finmodeling/income_statement_item_vectors.rb +24 -13
  56. data/lib/finmodeling/invalid_filing_error.rb +4 -0
  57. data/lib/finmodeling/liabs_and_equity_calculation.rb +18 -8
  58. data/lib/finmodeling/liabs_and_equity_item.rb +1 -1
  59. data/lib/finmodeling/liabs_and_equity_item_vectors.rb +24 -24
  60. data/lib/finmodeling/linear_trend_forecasting_policy.rb +23 -0
  61. data/lib/finmodeling/net_income_calculation.rb +23 -10
  62. data/lib/finmodeling/net_income_summary_from_differences.rb +51 -0
  63. data/lib/finmodeling/paths.rb +0 -0
  64. data/lib/finmodeling/period_array.rb +8 -4
  65. data/lib/finmodeling/quarterly_report_filing.rb +9 -4
  66. data/lib/finmodeling/rate.rb +8 -0
  67. data/lib/finmodeling/ratio.rb +0 -0
  68. data/lib/finmodeling/reformulated_balance_sheet.rb +47 -88
  69. data/lib/finmodeling/reformulated_cash_flow_statement.rb +18 -41
  70. data/lib/finmodeling/reformulated_income_statement.rb +44 -206
  71. data/lib/finmodeling/reformulated_shareholder_equity_statement.rb +50 -0
  72. data/lib/finmodeling/reoi_valuation.rb +104 -0
  73. data/lib/finmodeling/shareholder_equity_statement_calculation.rb +34 -0
  74. data/lib/finmodeling/string_helpers.rb +18 -1
  75. data/lib/finmodeling/time_series_estimator.rb +25 -0
  76. data/lib/finmodeling/trailing_avg_forecasting_policy.rb +23 -0
  77. data/lib/finmodeling/version.rb +1 -1
  78. data/lib/finmodeling/weighted_avg_cost_of_capital.rb +35 -0
  79. data/lib/finmodeling/yahoo_finance_helpers.rb +20 -0
  80. data/lib/finmodeling.rb +33 -2
  81. data/spec/annual_report_filing_spec.rb +81 -45
  82. data/spec/assets_calculation_spec.rb +7 -4
  83. data/spec/assets_item_spec.rb +9 -14
  84. data/spec/balance_sheet_analyses_spec.rb +13 -13
  85. data/spec/balance_sheet_calculation_spec.rb +45 -51
  86. data/spec/calculation_summary_spec.rb +113 -21
  87. data/spec/can_classify_rows_spec.rb +0 -0
  88. data/spec/cash_change_calculation_spec.rb +1 -10
  89. data/spec/cash_change_item_spec.rb +10 -18
  90. data/spec/cash_flow_statement_calculation_spec.rb +10 -24
  91. data/spec/company_beta_spec.rb +53 -0
  92. data/spec/company_filing_calculation_spec.rb +39 -49
  93. data/spec/company_filing_spec.rb +0 -0
  94. data/spec/company_filings_spec.rb +75 -25
  95. data/spec/company_spec.rb +37 -47
  96. data/spec/comprehensive_income_statement_calculation_spec.rb +54 -0
  97. data/spec/comprehensive_income_statement_item_spec.rb +56 -0
  98. data/spec/debt_cost_of_capital_spec.rb +19 -0
  99. data/spec/equity_change_calculation_spec.rb +33 -0
  100. data/spec/equity_change_item_spec.rb +58 -0
  101. data/spec/factory_spec.rb +2 -2
  102. data/spec/forecasts_spec.rb +2 -2
  103. data/spec/income_statement_analyses_spec.rb +23 -21
  104. data/spec/income_statement_calculation_spec.rb +17 -49
  105. data/spec/income_statement_item_spec.rb +17 -29
  106. data/spec/liabs_and_equity_calculation_spec.rb +6 -3
  107. data/spec/liabs_and_equity_item_spec.rb +14 -22
  108. data/spec/linear_trend_forecasting_policy_spec.rb +37 -0
  109. data/spec/matchers/custom_matchers.rb +79 -0
  110. data/spec/mocks/calculation.rb +0 -0
  111. data/spec/mocks/income_statement_analyses.rb +0 -0
  112. data/spec/mocks/sec_query.rb +0 -0
  113. data/spec/net_income_calculation_spec.rb +16 -10
  114. data/spec/period_array.rb +0 -0
  115. data/spec/quarterly_report_filing_spec.rb +21 -38
  116. data/spec/rate_spec.rb +0 -0
  117. data/spec/ratio_spec.rb +0 -0
  118. data/spec/reformulated_balance_sheet_spec.rb +56 -33
  119. data/spec/reformulated_cash_flow_statement_spec.rb +18 -10
  120. data/spec/reformulated_income_statement_spec.rb +16 -15
  121. data/spec/reformulated_shareholder_equity_statement_spec.rb +43 -0
  122. data/spec/reoi_valuation_spec.rb +146 -0
  123. data/spec/shareholder_equity_statement_calculation_spec.rb +59 -0
  124. data/spec/spec_helper.rb +4 -1
  125. data/spec/string_helpers_spec.rb +15 -13
  126. data/spec/time_series_estimator_spec.rb +61 -0
  127. data/spec/trailing_avg_forecasting_policy_spec.rb +37 -0
  128. data/spec/weighted_avg_cost_of_capital_spec.rb +32 -0
  129. data/tools/create_equity_change_training_vectors.rb +49 -0
  130. data/tools/time_specs.sh +7 -0
  131. metadata +182 -36
  132. data/lib/finmodeling/constant_forecasting_policy.rb +0 -23
  133. data/lib/finmodeling/generic_forecasting_policy.rb +0 -19
  134. data/spec/constant_forecasting_policy_spec.rb +0 -37
  135. 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
- Statement of Shareholder Equity
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
- - Dig into the disclosures. They've got lots more interesting goodness.
17
- - It'd be interesting to try to correct for cyclicality.
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
- - net income != comprehensive income. pull in items from SSE
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.
@@ -3,7 +3,7 @@
3
3
  require 'finmodeling'
4
4
 
5
5
  if ARGV.length != 1
6
- puts "usage #{__FILE__} <stock symbol>"
6
+ puts "usage #{__FILE__} <stock symbol | URL>"
7
7
  exit
8
8
  end
9
9
 
@@ -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
@@ -42,7 +42,7 @@ class Arguments
42
42
 
43
43
  protected
44
44
 
45
- def self.parse_just_a_url(raw_args, parsed)
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
- 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)
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
- print_summaries(summaries)
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
- 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
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
- reformed_inc_stmt = filing.income_statement.reformulated(period)
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
- print_summaries(summaries)
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
- reformed_cash_flow_stmt = filing.cash_flow_statement.reformulated(period)
250
+ reformed_shareholder_equity_stmt = filing.shareholder_equity_statement.reformulated(period)
170
251
 
171
252
  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
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( 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]
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?
@@ -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(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]
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 args.length != 2
44
- a[:stock_symbol] = args[0]
45
- a[:start_date] = Time.parse(args[1])
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 a
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
- filings.balance_sheet_analyses.print_extras if filings.balance_sheet_analyses.respond_to?(:print_extras)
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
- filings.income_statement_analyses.print_extras if filings.income_statement_analyses.respond_to?(:print_extras)
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.2")
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")