finmodeling 0.1 → 0.2

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.
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")