finmodeling 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. data/.gitignore +3 -0
  2. data/Gemfile +10 -0
  3. data/README.md +292 -0
  4. data/Rakefile +6 -0
  5. data/TODO.txt +36 -0
  6. data/examples/dump_report.rb +33 -0
  7. data/examples/lists/nasdaq-mid-to-mega-tech-symbols.txt +226 -0
  8. data/examples/show_report.rb +218 -0
  9. data/examples/show_reports.rb +77 -0
  10. data/finmodeling.gemspec +31 -0
  11. data/lib/finmodeling/annual_report_filing.rb +104 -0
  12. data/lib/finmodeling/array_with_stats.rb +22 -0
  13. data/lib/finmodeling/assets_calculation.rb +36 -0
  14. data/lib/finmodeling/assets_item.rb +14 -0
  15. data/lib/finmodeling/assets_item_vectors.rb +638 -0
  16. data/lib/finmodeling/balance_sheet_analyses.rb +33 -0
  17. data/lib/finmodeling/balance_sheet_calculation.rb +68 -0
  18. data/lib/finmodeling/calculation_summary.rb +148 -0
  19. data/lib/finmodeling/can_cache_classifications.rb +36 -0
  20. data/lib/finmodeling/can_cache_summaries.rb +16 -0
  21. data/lib/finmodeling/can_classify_rows.rb +54 -0
  22. data/lib/finmodeling/cash_change_calculation.rb +67 -0
  23. data/lib/finmodeling/cash_change_item.rb +14 -0
  24. data/lib/finmodeling/cash_change_item_vectors.rb +241 -0
  25. data/lib/finmodeling/cash_flow_statement_calculation.rb +85 -0
  26. data/lib/finmodeling/classifiers.rb +11 -0
  27. data/lib/finmodeling/company.rb +102 -0
  28. data/lib/finmodeling/company_filing.rb +64 -0
  29. data/lib/finmodeling/company_filing_calculation.rb +75 -0
  30. data/lib/finmodeling/company_filings.rb +100 -0
  31. data/lib/finmodeling/config.rb +37 -0
  32. data/lib/finmodeling/constant_forecasting_policy.rb +23 -0
  33. data/lib/finmodeling/factory.rb +27 -0
  34. data/lib/finmodeling/float_helpers.rb +17 -0
  35. data/lib/finmodeling/forecasts.rb +48 -0
  36. data/lib/finmodeling/generic_forecasting_policy.rb +19 -0
  37. data/lib/finmodeling/has_string_classifer.rb +96 -0
  38. data/lib/finmodeling/income_statement_analyses.rb +74 -0
  39. data/lib/finmodeling/income_statement_calculation.rb +71 -0
  40. data/lib/finmodeling/income_statement_item.rb +14 -0
  41. data/lib/finmodeling/income_statement_item_vectors.rb +654 -0
  42. data/lib/finmodeling/liabs_and_equity_calculation.rb +36 -0
  43. data/lib/finmodeling/liabs_and_equity_item.rb +14 -0
  44. data/lib/finmodeling/liabs_and_equity_item_vectors.rb +1936 -0
  45. data/lib/finmodeling/net_income_calculation.rb +41 -0
  46. data/lib/finmodeling/paths.rb +5 -0
  47. data/lib/finmodeling/period_array.rb +24 -0
  48. data/lib/finmodeling/quarterly_report_filing.rb +23 -0
  49. data/lib/finmodeling/rate.rb +20 -0
  50. data/lib/finmodeling/ratio.rb +20 -0
  51. data/lib/finmodeling/reformulated_balance_sheet.rb +176 -0
  52. data/lib/finmodeling/reformulated_cash_flow_statement.rb +140 -0
  53. data/lib/finmodeling/reformulated_income_statement.rb +436 -0
  54. data/lib/finmodeling/string_helpers.rb +26 -0
  55. data/lib/finmodeling/version.rb +3 -0
  56. data/lib/finmodeling.rb +70 -0
  57. data/spec/annual_report_filing_spec.rb +68 -0
  58. data/spec/assets_calculation_spec.rb +21 -0
  59. data/spec/assets_item_spec.rb +66 -0
  60. data/spec/balance_sheet_analyses_spec.rb +43 -0
  61. data/spec/balance_sheet_calculation_spec.rb +91 -0
  62. data/spec/calculation_summary_spec.rb +63 -0
  63. data/spec/can_classify_rows_spec.rb +86 -0
  64. data/spec/cash_change_calculation_spec.rb +56 -0
  65. data/spec/cash_change_item_spec.rb +66 -0
  66. data/spec/cash_flow_statement_calculation_spec.rb +108 -0
  67. data/spec/company_filing_calculation_spec.rb +74 -0
  68. data/spec/company_filing_spec.rb +30 -0
  69. data/spec/company_filings_spec.rb +55 -0
  70. data/spec/company_spec.rb +73 -0
  71. data/spec/constant_forecasting_policy_spec.rb +37 -0
  72. data/spec/factory_spec.rb +18 -0
  73. data/spec/forecasts_spec.rb +21 -0
  74. data/spec/generic_forecasting_policy_spec.rb +33 -0
  75. data/spec/income_statement_analyses_spec.rb +63 -0
  76. data/spec/income_statement_calculation_spec.rb +88 -0
  77. data/spec/income_statement_item_spec.rb +86 -0
  78. data/spec/liabs_and_equity_calculation_spec.rb +20 -0
  79. data/spec/liabs_and_equity_item_spec.rb +66 -0
  80. data/spec/mocks/calculation.rb +10 -0
  81. data/spec/mocks/income_statement_analyses.rb +93 -0
  82. data/spec/mocks/sec_query.rb +31 -0
  83. data/spec/net_income_calculation_spec.rb +23 -0
  84. data/spec/period_array.rb +52 -0
  85. data/spec/quarterly_report_filing_spec.rb +69 -0
  86. data/spec/rate_spec.rb +33 -0
  87. data/spec/ratio_spec.rb +33 -0
  88. data/spec/reformulated_balance_sheet_spec.rb +146 -0
  89. data/spec/reformulated_cash_flow_statement_spec.rb +174 -0
  90. data/spec/reformulated_income_statement_spec.rb +293 -0
  91. data/spec/spec_helper.rb +5 -0
  92. data/spec/string_helpers_spec.rb +23 -0
  93. data/tools/create_balance_sheet_training_vectors.rb +65 -0
  94. data/tools/create_cash_change_training_vectors.rb +48 -0
  95. data/tools/create_credit_debit_training_vectors.rb +51 -0
  96. data/tools/create_income_statement_training_vectors.rb +48 -0
  97. metadata +289 -0
@@ -0,0 +1,436 @@
1
+ module FinModeling
2
+ class ReformulatedIncomeStatement
3
+ attr_accessor :period
4
+
5
+ class FakeNetIncomeSummary
6
+ def initialize(ris1, ris2)
7
+ @ris1 = ris1
8
+ @ris2 = ris2
9
+ end
10
+ def filter_by_type(key)
11
+ case key
12
+ when :or
13
+ @cs = FinModeling::CalculationSummary.new
14
+ @cs.title = "Operating Revenues"
15
+ @cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @ris1.operating_revenues.total] ),
16
+ CalculationRow.new(:key => "Second Row", :vals => [-@ris2.operating_revenues.total] ) ]
17
+ return @cs
18
+ when :cogs
19
+ @cs = FinModeling::CalculationSummary.new
20
+ @cs.title = "Cost of Revenues"
21
+ @cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @ris1.cost_of_revenues.total] ),
22
+ CalculationRow.new(:key => "Second Row", :vals => [-@ris2.cost_of_revenues.total] ) ]
23
+ return @cs
24
+ when :oe
25
+ @cs = FinModeling::CalculationSummary.new
26
+ @cs.title = "Operating Expenses"
27
+ @cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @ris1.operating_expenses.total] ),
28
+ CalculationRow.new(:key => "Second Row", :vals => [-@ris2.operating_expenses.total] ) ]
29
+ return @cs
30
+ when :oibt
31
+ @cs = FinModeling::CalculationSummary.new
32
+ @cs.title = "Operating Income from Sales, Before taxes"
33
+ @cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @ris1.operating_income_after_tax.rows[1].vals.first] ),
34
+ CalculationRow.new(:key => "Second Row", :vals => [-@ris2.operating_income_after_tax.rows[1].vals.first] ) ]
35
+ return @cs
36
+ when :fibt
37
+ @cs = FinModeling::CalculationSummary.new
38
+ @cs.title = "Financing Income, Before Taxes"
39
+ @cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @ris1.net_financing_income.rows[0].vals.first] ),
40
+ CalculationRow.new(:key => "Second Row", :vals => [-@ris2.net_financing_income.rows[0].vals.first] ) ]
41
+ return @cs
42
+ when :tax
43
+ @cs = FinModeling::CalculationSummary.new
44
+ @cs.title = "Taxes"
45
+ @cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @ris1.income_from_sales_after_tax.rows[1].vals.first] ),
46
+ CalculationRow.new(:key => "Second Row", :vals => [-@ris2.income_from_sales_after_tax.rows[1].vals.first] ) ]
47
+ return @cs
48
+ when :ooiat
49
+ @cs = FinModeling::CalculationSummary.new
50
+ @cs.title = "Other Operating Income, After Taxes"
51
+ @cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @ris1.operating_income_after_tax.rows[3].vals.first] ),
52
+ CalculationRow.new(:key => "Second Row", :vals => [-@ris2.operating_income_after_tax.rows[3].vals.first] ) ]
53
+ return @cs
54
+ when :fiat
55
+ @cs = FinModeling::CalculationSummary.new
56
+ @cs.title = "Financing Income, After Taxes"
57
+ @cs.rows = [ CalculationRow.new(:key => "First Row", :vals => [ @ris1.net_financing_income.rows[2].vals.first] ),
58
+ CalculationRow.new(:key => "Second Row", :vals => [-@ris2.net_financing_income.rows[2].vals.first] ) ]
59
+ return @cs
60
+ end
61
+ end
62
+ end
63
+
64
+ def initialize(period, net_income_summary, tax_rate=0.35)
65
+ @period = period
66
+ @tax_rate = tax_rate
67
+
68
+ @orev = net_income_summary.filter_by_type(:or )
69
+ @cogs = net_income_summary.filter_by_type(:cogs )
70
+ @oe = net_income_summary.filter_by_type(:oe )
71
+ @oibt = net_income_summary.filter_by_type(:oibt )
72
+ @fibt = net_income_summary.filter_by_type(:fibt )
73
+ @tax = net_income_summary.filter_by_type(:tax )
74
+ @ooiat = net_income_summary.filter_by_type(:ooiat)
75
+ @fiat = net_income_summary.filter_by_type(:fiat )
76
+
77
+ @fibt_tax_effect = (@fibt.total * @tax_rate).round.to_f
78
+ @nfi = @fibt.total + -@fibt_tax_effect + @fiat.total
79
+
80
+ @oibt_tax_effect = (@oibt.total * @tax_rate).round.to_f
81
+
82
+ @gm = @orev.total + @cogs.total
83
+ @oisbt = @gm + @oe.total
84
+
85
+ @oisat = @oisbt + @tax.total + @fibt_tax_effect + @oibt_tax_effect
86
+
87
+ @oi = @oisat + @oibt.total - @oibt_tax_effect + @ooiat.total
88
+
89
+ @ci = @nfi + @oi
90
+ end
91
+
92
+ def -(ris2)
93
+ net_income_summary = FakeNetIncomeSummary.new(self, ris2)
94
+ return ReformulatedIncomeStatement.new(@period, net_income_summary, @tax_rate)
95
+ end
96
+
97
+ def operating_revenues
98
+ @orev
99
+ end
100
+
101
+ def cost_of_revenues
102
+ @cogs
103
+ end
104
+
105
+ def gross_revenue
106
+ cs = FinModeling::CalculationSummary.new
107
+ cs.title = "Gross Revenue"
108
+ cs.rows = [ CalculationRow.new(:key => "Operating Revenues (OR)", :vals => [@orev.total] ),
109
+ CalculationRow.new(:key => "Cost of Goods Sold (COGS)", :vals => [@cogs.total] ) ]
110
+ return cs
111
+ end
112
+
113
+ def operating_expenses
114
+ @oe
115
+ end
116
+
117
+ def income_from_sales_before_tax
118
+ cs = FinModeling::CalculationSummary.new
119
+ cs.title = "Operating Income from sales, before tax (OISBT)"
120
+ cs.rows = [ CalculationRow.new(:key => "Gross Margin (GM)", :vals => [@gm] ),
121
+ CalculationRow.new(:key => "Operating Expense (OE)", :vals => [@oe.total] ) ]
122
+ return cs
123
+ end
124
+
125
+ def income_from_sales_after_tax
126
+ cs = FinModeling::CalculationSummary.new
127
+ cs.title = "Operating Income from sales, after tax (OISAT)"
128
+ cs.rows = [ CalculationRow.new(:key => "Operating income from sales (before tax)", :vals => [@oisbt] ),
129
+ CalculationRow.new(:key => "Reported taxes", :vals => [@tax.total] ),
130
+ CalculationRow.new(:key => "Taxes on net financing income", :vals => [@fibt_tax_effect] ),
131
+ CalculationRow.new(:key => "Taxes on other operating income", :vals => [@oibt_tax_effect] ) ]
132
+ return cs
133
+ end
134
+
135
+ def operating_income_after_tax
136
+ cs = FinModeling::CalculationSummary.new
137
+ cs.title = "Operating income, after tax (OI)"
138
+ cs.rows = [ CalculationRow.new(:key => "Operating income after sales, after tax (OISAT)", :vals => [@oisat] ),
139
+ CalculationRow.new(:key => "Other operating income, before tax (OIBT)", :vals => [@oibt.total] ),
140
+ CalculationRow.new(:key => "Tax on other operating income", :vals => [-@oibt_tax_effect] ),
141
+ CalculationRow.new(:key => "Other operating income, after tax (OOIAT)", :vals => [@ooiat.total] ) ]
142
+ return cs
143
+ end
144
+
145
+ def net_financing_income
146
+ cs = FinModeling::CalculationSummary.new
147
+ cs.title = "Net financing income, after tax (NFI)"
148
+ cs.rows = [ CalculationRow.new(:key => "Financing income, before tax (FIBT)", :vals => [@fibt.total] ),
149
+ CalculationRow.new(:key => "Tax effect (FIBT_TAX_EFFECT)", :vals => [-@fibt_tax_effect] ),
150
+ CalculationRow.new(:key => "Financing income, after tax (FIAT)", :vals => [@fiat.total] ) ]
151
+ return cs
152
+ end
153
+
154
+ def comprehensive_income
155
+ cs = FinModeling::CalculationSummary.new
156
+ cs.title = "Comprehensive income (CI)"
157
+ cs.rows = [ CalculationRow.new(:key => "Operating income, after tax (OI)", :vals => [@oi] ),
158
+ CalculationRow.new(:key => "Net financing income, after tax (NFI)", :vals => [@nfi] ) ]
159
+ return cs
160
+ end
161
+
162
+ def gross_margin
163
+ gross_revenue.total / operating_revenues.total
164
+ end
165
+
166
+ def sales_profit_margin
167
+ income_from_sales_after_tax.total / operating_revenues.total
168
+ end
169
+
170
+ def operating_profit_margin
171
+ operating_income_after_tax.total / operating_revenues.total
172
+ end
173
+
174
+ def fi_over_sales
175
+ net_financing_income.total / operating_revenues.total
176
+ end
177
+
178
+ def ni_over_sales
179
+ comprehensive_income.total / operating_revenues.total
180
+ end
181
+
182
+ def sales_over_noa(reformed_bal_sheet)
183
+ ratio = operating_revenues.total / reformed_bal_sheet.net_operating_assets.total
184
+ Ratio.new(ratio).annualize(from_days=@period.days, to_days=365.0)
185
+ end
186
+
187
+ def fi_over_nfa(reformed_bal_sheet)
188
+ ratio = net_financing_income.total / reformed_bal_sheet.net_financial_assets.total
189
+ Ratio.new(ratio).annualize(from_days=@period.days, to_days=365.0)
190
+ end
191
+
192
+ def revenue_growth(prev)
193
+ rate = (operating_revenues.total - prev.operating_revenues.total) / prev.operating_revenues.total
194
+ return annualize_rate(prev, rate)
195
+ end
196
+
197
+ def core_oi_growth(prev)
198
+ rate = (income_from_sales_after_tax.total - prev.income_from_sales_after_tax.total) / prev.income_from_sales_after_tax.total
199
+ return annualize_rate(prev, rate)
200
+ end
201
+
202
+ def oi_growth(prev)
203
+ rate = (operating_income_after_tax.total - prev.operating_income_after_tax.total) / prev.operating_income_after_tax.total
204
+ return annualize_rate(prev, rate)
205
+ end
206
+
207
+ def re_oi(prev_bal_sheet, expected_rate_of_return=0.10)
208
+ e_ror = deannualize_rate(prev_bal_sheet, expected_rate_of_return)
209
+ return (operating_income_after_tax.total - (e_ror * prev_bal_sheet.net_operating_assets.total))
210
+ end
211
+
212
+ def self.empty_analysis
213
+ analysis = CalculationSummary.new
214
+ analysis.title = ""
215
+ analysis.rows = []
216
+
217
+ analysis.header_row = CalculationHeader.new(:key => "", :vals => ["Unknown..."])
218
+
219
+ analysis.rows << CalculationRow.new(:key => "Revenue ($MM)", :vals => [nil])
220
+ if Config.income_detail_enabled?
221
+ analysis.rows << CalculationRow.new(:key => "COGS ($MM)", :vals => [nil])
222
+ analysis.rows << CalculationRow.new(:key => "GM ($MM)", :vals => [nil])
223
+ analysis.rows << CalculationRow.new(:key => "OE ($MM)", :vals => [nil])
224
+ analysis.rows << CalculationRow.new(:key => "OISBT ($MM)", :vals => [nil])
225
+ end
226
+ analysis.rows << CalculationRow.new(:key => "Core OI ($MM)", :vals => [nil])
227
+ analysis.rows << CalculationRow.new(:key => "OI ($MM)", :vals => [nil])
228
+ analysis.rows << CalculationRow.new(:key => "FI ($MM)", :vals => [nil])
229
+ analysis.rows << CalculationRow.new(:key => "NI ($MM)", :vals => [nil])
230
+ analysis.rows << CalculationRow.new(:key => "Gross Margin", :vals => [nil])
231
+ analysis.rows << CalculationRow.new(:key => "Sales PM", :vals => [nil])
232
+ analysis.rows << CalculationRow.new(:key => "Operating PM", :vals => [nil])
233
+ analysis.rows << CalculationRow.new(:key => "FI / Sales", :vals => [nil])
234
+ analysis.rows << CalculationRow.new(:key => "NI / Sales", :vals => [nil])
235
+ analysis.rows << CalculationRow.new(:key => "Sales / NOA", :vals => [nil])
236
+ analysis.rows << CalculationRow.new(:key => "FI / NFA", :vals => [nil])
237
+ analysis.rows << CalculationRow.new(:key => "Revenue Growth", :vals => [nil])
238
+ analysis.rows << CalculationRow.new(:key => "Core OI Growth", :vals => [nil])
239
+ analysis.rows << CalculationRow.new(:key => "OI Growth", :vals => [nil])
240
+ analysis.rows << CalculationRow.new(:key => "ReOI ($MM)", :vals => [nil])
241
+
242
+ return analysis
243
+ end
244
+
245
+ def analysis(re_bs, prev_re_is, prev_re_bs)
246
+ analysis = CalculationSummary.new
247
+ analysis.title = ""
248
+ analysis.rows = []
249
+
250
+ if re_bs.nil?
251
+ analysis.header_row = CalculationHeader.new(:key => "", :vals => ["Unknown..."])
252
+ else
253
+ analysis.header_row = CalculationHeader.new(:key => "", :vals => [re_bs.period.to_pretty_s])
254
+ end
255
+
256
+ analysis.rows << CalculationRow.new(:key => "Revenue ($MM)", :vals => [operating_revenues.total.to_nearest_million])
257
+ if Config.income_detail_enabled?
258
+ analysis.rows << CalculationRow.new(:key => "COGS ($MM)", :vals => [@cogs.total.to_nearest_million])
259
+ analysis.rows << CalculationRow.new(:key => "GM ($MM)", :vals => [@gm.to_nearest_million])
260
+ analysis.rows << CalculationRow.new(:key => "OE ($MM)", :vals => [@oe.total.to_nearest_million])
261
+ analysis.rows << CalculationRow.new(:key => "OISBT ($MM)", :vals => [income_from_sales_before_tax.total.to_nearest_million])
262
+ end
263
+ analysis.rows << CalculationRow.new(:key => "Core OI ($MM)", :vals => [income_from_sales_after_tax.total.to_nearest_million])
264
+ analysis.rows << CalculationRow.new(:key => "OI ($MM)", :vals => [operating_income_after_tax.total.to_nearest_million])
265
+ analysis.rows << CalculationRow.new(:key => "FI ($MM)", :vals => [net_financing_income.total.to_nearest_million])
266
+ analysis.rows << CalculationRow.new(:key => "NI ($MM)", :vals => [comprehensive_income.total.to_nearest_million])
267
+ analysis.rows << CalculationRow.new(:key => "Gross Margin", :vals => [gross_margin])
268
+ analysis.rows << CalculationRow.new(:key => "Sales PM", :vals => [sales_profit_margin])
269
+ analysis.rows << CalculationRow.new(:key => "Operating PM", :vals => [operating_profit_margin])
270
+ analysis.rows << CalculationRow.new(:key => "FI / Sales", :vals => [fi_over_sales])
271
+ analysis.rows << CalculationRow.new(:key => "NI / Sales", :vals => [ni_over_sales])
272
+
273
+ if !prev_re_bs.nil? && !prev_re_is.nil?
274
+ analysis.rows << CalculationRow.new(:key => "Sales / NOA", :vals => [sales_over_noa(prev_re_bs)])
275
+ analysis.rows << CalculationRow.new(:key => "FI / NFA", :vals => [fi_over_nfa( prev_re_bs)])
276
+ analysis.rows << CalculationRow.new(:key => "Revenue Growth",:vals => [revenue_growth(prev_re_is)])
277
+ analysis.rows << CalculationRow.new(:key => "Core OI Growth",:vals => [core_oi_growth(prev_re_is)])
278
+ analysis.rows << CalculationRow.new(:key => "OI Growth", :vals => [oi_growth( prev_re_is)])
279
+ analysis.rows << CalculationRow.new(:key => "ReOI ($MM)", :vals => [re_oi( prev_re_bs).to_nearest_million])
280
+ else
281
+ analysis.rows << CalculationRow.new(:key => "Sales / NOA", :vals => [nil])
282
+ analysis.rows << CalculationRow.new(:key => "FI / NFA", :vals => [nil])
283
+ analysis.rows << CalculationRow.new(:key => "Revenue Growth",:vals => [nil])
284
+ analysis.rows << CalculationRow.new(:key => "Core OI Growth",:vals => [nil])
285
+ analysis.rows << CalculationRow.new(:key => "OI Growth", :vals => [nil])
286
+ analysis.rows << CalculationRow.new(:key => "ReOI ($MM)", :vals => [nil])
287
+ end
288
+
289
+ return analysis
290
+ end
291
+
292
+ def self.forecast_next(period, policy, last_re_bs, last_re_is)
293
+ operating_revenues = last_re_is.operating_revenues.total * (1.0 + Rate.new(policy.revenue_growth).yearly_to_quarterly)
294
+ income_from_sales_after_tax = operating_revenues * policy.sales_pm
295
+ net_financing_income = last_re_bs.net_financial_assets.total * Ratio.new(policy.fi_over_nfa).yearly_to_quarterly
296
+
297
+ comprehensive_income = income_from_sales_after_tax + net_financing_income
298
+
299
+ ForecastedReformulatedIncomeStatement.new(period, operating_revenues,
300
+ income_from_sales_after_tax,
301
+ net_financing_income, comprehensive_income)
302
+ end
303
+
304
+ private
305
+
306
+ def annualize_rate(prev, rate)
307
+ from_days = case
308
+ when prev.period.is_instant?
309
+ Xbrlware::DateUtil.days_between(prev.period.value, @period.value["end_date"])
310
+ when prev.period.is_duration?
311
+ Xbrlware::DateUtil.days_between(prev.period.value["end_date"], @period.value["end_date"])
312
+ end
313
+ Rate.new(rate).annualize(from_days, to_days=365)
314
+ end
315
+
316
+ def deannualize_rate(prev, rate)
317
+ to_days = case
318
+ when prev.period.is_instant?
319
+ Xbrlware::DateUtil.days_between(prev.period.value, @period.value["end_date"])
320
+ when prev.period.is_duration?
321
+ Xbrlware::DateUtil.days_between(prev.period.value["end_date"], @period.value["end_date"])
322
+ end
323
+ Rate.new(rate).annualize(from_days=365, to_days)
324
+ end
325
+
326
+ end
327
+
328
+ class ForecastedReformulatedIncomeStatement < ReformulatedIncomeStatement
329
+ def initialize(period, operating_revenues, income_from_sales_after_tax, net_financing_income, comprehensive_income)
330
+ @period = period
331
+ @orev = operating_revenues
332
+ @income_from_sales_after_tax = income_from_sales_after_tax
333
+ @net_financing_income = net_financing_income
334
+ @comprehensive_income = comprehensive_income
335
+ end
336
+
337
+ def -(ris2)
338
+ raise RuntimeError.new("not implmeneted")
339
+ end
340
+
341
+ def operating_revenues
342
+ cs = FinModeling::CalculationSummary.new
343
+ cs.title = "Operating Revenues"
344
+ cs.rows = [ CalculationRow.new(:key => "Operating Revenues (OR)", :vals => [@orev] ) ]
345
+ return cs
346
+ end
347
+
348
+ def cost_of_revenues
349
+ nil
350
+ end
351
+
352
+ def gross_revenue
353
+ nil
354
+ end
355
+
356
+ def operating_expenses
357
+ nil
358
+ end
359
+
360
+ def income_from_sales_before_tax
361
+ nil
362
+ end
363
+
364
+ def income_from_sales_after_tax
365
+ cs = FinModeling::CalculationSummary.new
366
+ cs.title = "Operating Income from sales, after tax (OISAT)"
367
+ cs.rows = [ CalculationRow.new(:key => "Operating income from sales (after tax)", :vals => [@income_from_sales_after_tax] ) ]
368
+ return cs
369
+ end
370
+
371
+ def operating_income_after_tax
372
+ income_from_sales_after_tax # this simplified version assumes no non-sales operating income
373
+ end
374
+
375
+ def net_financing_income
376
+ cs = FinModeling::CalculationSummary.new
377
+ cs.title = "Net financing income, after tax (NFI)"
378
+ cs.rows = [ CalculationRow.new(:key => "Net financing income", :vals => [@net_financing_income] ) ]
379
+ return cs
380
+ end
381
+
382
+ def comprehensive_income
383
+ cs = FinModeling::CalculationSummary.new
384
+ cs.title = "Comprehensive Income (CI)"
385
+ cs.rows = [ CalculationRow.new(:key => "Comprehensive income", :vals => [@comprehensive_income] ) ]
386
+ return cs
387
+ end
388
+
389
+ def analysis(re_bs, prev_re_is, prev_re_bs)
390
+ analysis = CalculationSummary.new
391
+ analysis.title = ""
392
+ analysis.rows = []
393
+
394
+ if re_bs.nil?
395
+ analysis.header_row = CalculationHeader.new(:key => "", :vals => ["Unknown..."])
396
+ else
397
+ analysis.header_row = CalculationHeader.new(:key => "", :vals => [re_bs.period.to_pretty_s + "E"])
398
+ end
399
+
400
+ analysis.rows << CalculationRow.new(:key => "Revenue ($MM)", :vals => [operating_revenues.total.to_nearest_million])
401
+ if Config.income_detail_enabled?
402
+ analysis.rows << CalculationRow.new(:key => "COGS ($MM)", :vals => [nil])
403
+ analysis.rows << CalculationRow.new(:key => "GM ($MM)", :vals => [nil])
404
+ analysis.rows << CalculationRow.new(:key => "OE ($MM)", :vals => [nil])
405
+ analysis.rows << CalculationRow.new(:key => "OISBT ($MM)", :vals => [nil])
406
+ end
407
+ analysis.rows << CalculationRow.new(:key => "Core OI ($MM)", :vals => [income_from_sales_after_tax.total.to_nearest_million])
408
+ analysis.rows << CalculationRow.new(:key => "OI ($MM)", :vals => [nil])
409
+ analysis.rows << CalculationRow.new(:key => "FI ($MM)", :vals => [net_financing_income.total.to_nearest_million])
410
+ analysis.rows << CalculationRow.new(:key => "NI ($MM)", :vals => [comprehensive_income.total.to_nearest_million])
411
+ analysis.rows << CalculationRow.new(:key => "Gross Margin", :vals => [nil])
412
+ analysis.rows << CalculationRow.new(:key => "Sales PM", :vals => [sales_profit_margin])
413
+ analysis.rows << CalculationRow.new(:key => "Operating PM", :vals => [nil])
414
+ analysis.rows << CalculationRow.new(:key => "FI / Sales", :vals => [fi_over_sales])
415
+ analysis.rows << CalculationRow.new(:key => "NI / Sales", :vals => [ni_over_sales])
416
+
417
+ if !prev_re_bs.nil? && !prev_re_is.nil?
418
+ analysis.rows << CalculationRow.new(:key => "Sales / NOA", :vals => [sales_over_noa(prev_re_bs)])
419
+ analysis.rows << CalculationRow.new(:key => "FI / NFA", :vals => [fi_over_nfa( prev_re_bs)])
420
+ analysis.rows << CalculationRow.new(:key => "Revenue Growth",:vals => [revenue_growth(prev_re_is)])
421
+ analysis.rows << CalculationRow.new(:key => "Core OI Growth",:vals => [core_oi_growth(prev_re_is)])
422
+ analysis.rows << CalculationRow.new(:key => "OI Growth", :vals => [nil])
423
+ analysis.rows << CalculationRow.new(:key => "ReOI ($MM)", :vals => [re_oi( prev_re_bs).to_nearest_million])
424
+ else
425
+ analysis.rows << CalculationRow.new(:key => "Sales / NOA", :vals => [nil])
426
+ analysis.rows << CalculationRow.new(:key => "FI / NFA", :vals => [nil])
427
+ analysis.rows << CalculationRow.new(:key => "Revenue Growth",:vals => [nil])
428
+ analysis.rows << CalculationRow.new(:key => "Core OI Growth",:vals => [nil])
429
+ analysis.rows << CalculationRow.new(:key => "OI Growth", :vals => [nil])
430
+ analysis.rows << CalculationRow.new(:key => "ReOI ($MM)", :vals => [nil])
431
+ end
432
+
433
+ return analysis
434
+ end
435
+ end
436
+ end
@@ -0,0 +1,26 @@
1
+ class String
2
+ def fixed_width_left_justify(width)
3
+ return self[0..(width-1 )] if self.length == width
4
+ return self[0..(width-1-3)]+"..." if self.length > width
5
+ return self + (" " * (width - self.length))
6
+ end
7
+
8
+ def fixed_width_right_justify(width)
9
+ return self[(-width )..-1] if self.length == width
10
+ return "..."+self[(-width+3)..-1] if self.length > width
11
+ return (" " * (width - self.length)) + self
12
+ end
13
+
14
+ def with_thousands_separators
15
+ self.reverse.scan(/(?:\d*\.)?\d{1,3}-?/).join(',').reverse
16
+ end
17
+
18
+ def cap_decimals(num_decimals)
19
+ r = Regexp.new('(.*\.[0-9]{' + num_decimals.to_s + '})[0-9]*')
20
+ self.gsub(r, '\1')
21
+ end
22
+
23
+ def matches_regexes?(regexes)
24
+ return regexes.inject(false){ |matches, regex| matches or regex =~ self }
25
+ end
26
+ end
@@ -0,0 +1,3 @@
1
+ module FinModeling
2
+ VERSION = "0.1"
3
+ end
@@ -0,0 +1,70 @@
1
+ require 'fileutils'
2
+ require 'digest'
3
+ require 'sec_query'
4
+ require 'edgar'
5
+
6
+ require 'xbrlware-ruby19'
7
+ require 'xbrlware-extras'
8
+
9
+ require 'naive_bayes'
10
+ require 'statsample'
11
+
12
+ require 'finmodeling/float_helpers'
13
+ require 'finmodeling/string_helpers'
14
+ require 'finmodeling/factory'
15
+
16
+ require 'finmodeling/paths'
17
+
18
+ require 'finmodeling/has_string_classifer'
19
+
20
+ require 'finmodeling/period_array'
21
+ require 'finmodeling/rate'
22
+ require 'finmodeling/ratio'
23
+ require 'finmodeling/company'
24
+
25
+ require 'finmodeling/company_filings'
26
+ require 'finmodeling/company_filing'
27
+ require 'finmodeling/annual_report_filing'
28
+ require 'finmodeling/quarterly_report_filing'
29
+
30
+ require 'finmodeling/array_with_stats'
31
+ require 'finmodeling/calculation_summary'
32
+
33
+ require 'finmodeling/can_classify_rows'
34
+ require 'finmodeling/can_cache_classifications'
35
+ require 'finmodeling/can_cache_summaries'
36
+
37
+ require 'finmodeling/assets_item_vectors'
38
+ require 'finmodeling/assets_item'
39
+ require 'finmodeling/liabs_and_equity_item_vectors'
40
+ require 'finmodeling/liabs_and_equity_item'
41
+ require 'finmodeling/income_statement_item_vectors'
42
+ require 'finmodeling/income_statement_item'
43
+ require 'finmodeling/cash_change_item_vectors'
44
+ require 'finmodeling/cash_change_item'
45
+
46
+ require 'finmodeling/company_filing_calculation'
47
+ require 'finmodeling/balance_sheet_calculation'
48
+ require 'finmodeling/assets_calculation'
49
+ require 'finmodeling/liabs_and_equity_calculation'
50
+ require 'finmodeling/income_statement_calculation'
51
+ require 'finmodeling/net_income_calculation'
52
+ require 'finmodeling/cash_flow_statement_calculation'
53
+ require 'finmodeling/cash_change_calculation'
54
+
55
+ require 'finmodeling/reformulated_income_statement'
56
+ require 'finmodeling/reformulated_balance_sheet'
57
+ require 'finmodeling/reformulated_cash_flow_statement'
58
+
59
+ require 'finmodeling/config'
60
+
61
+ require 'finmodeling/classifiers'
62
+ FinModeling::Classifiers.train
63
+
64
+ require 'finmodeling/balance_sheet_analyses'
65
+ require 'finmodeling/income_statement_analyses'
66
+
67
+ require 'finmodeling/generic_forecasting_policy'
68
+ require 'finmodeling/constant_forecasting_policy'
69
+ require 'finmodeling/forecasts'
70
+
@@ -0,0 +1,68 @@
1
+ # annual_report_filing_spec.rb
2
+
3
+ require 'spec_helper'
4
+
5
+ describe FinModeling::AnnualReportFiling do
6
+ before(:all) do
7
+ company = FinModeling::Company.new(FinModeling::Mocks::Entity.new)
8
+ filing_url = company.annual_reports.last.link
9
+ FinModeling::Config::disable_caching
10
+ @filing = FinModeling::AnnualReportFiling.download(filing_url)
11
+ end
12
+
13
+ after(:all) do
14
+ FinModeling::Config::enable_caching
15
+ end
16
+
17
+ subject { @filing }
18
+ its(:balance_sheet) { should be_a FinModeling::BalanceSheetCalculation }
19
+ its(:income_statement) { should be_a FinModeling::IncomeStatementCalculation }
20
+ its(:cash_flow_statement) { should be_a FinModeling::CashFlowStatementCalculation }
21
+
22
+ its(:is_valid?) { should == (@filing.income_statement.is_valid? && @filing.balance_sheet.is_valid? && @filing.cash_flow_statement.is_valid?) }
23
+
24
+ describe "write_constructor" do
25
+ before(:all) do
26
+ file_name = "/tmp/finmodeling-annual-rpt.rb"
27
+ schema_version_item_name = "@schema_version"
28
+ item_name = "@annual_rpt"
29
+ file = File.open(file_name, "w")
30
+ @filing.write_constructor(file, item_name)
31
+ file.close
32
+
33
+ eval(File.read(file_name))
34
+
35
+ @schema_version = eval(schema_version_item_name)
36
+ @loaded_filing = eval(item_name)
37
+ end
38
+
39
+ it "writes itself to a file, and saves a schema version of 1.1" do
40
+ @schema_version.should be == 1.1
41
+ end
42
+
43
+ it "writes itself to a file, and when reloaded, has the same periods" do
44
+ expected_periods = @filing.balance_sheet.periods.map{|x| x.to_pretty_s}.join(',')
45
+ @loaded_filing.balance_sheet.periods.map{|x| x.to_pretty_s}.join(',').should == expected_periods
46
+ end
47
+ it "writes itself to a file, and when reloaded, has the same net operating assets" do
48
+ period = @filing.balance_sheet.periods.last
49
+ expected_noa = @filing.balance_sheet.reformulated(period).net_operating_assets.total
50
+ @loaded_filing.balance_sheet.reformulated(period).net_operating_assets.total.should be_within(1.0).of(expected_noa)
51
+ end
52
+ it "writes itself to a file, and when reloaded, has the same net financing income" do
53
+ period = @filing.income_statement.periods.last
54
+ expected_nfi = @filing.income_statement.reformulated(period).net_financing_income.total
55
+ @loaded_filing.income_statement.reformulated(period).net_financing_income.total.should be_within(1.0).of(expected_nfi)
56
+ end
57
+ it "writes itself to a file, and when reloaded, has the same net change in cash" do
58
+ period = @filing.cash_flow_statement.periods.last
59
+ expected_cash_change = @filing.cash_flow_statement.cash_change_calculation.summary(:period=>period).total
60
+ @loaded_filing.cash_flow_statement.cash_change_calculation.summary(:period=>period).total.should be_within(1.0).of(expected_cash_change)
61
+ end
62
+ it "writes itself to a file, and when reloaded, has the same disclosures" do
63
+ period = @filing.disclosures.first.periods.last
64
+ expected_total = @filing.disclosures.first.summary(:period=>period).total
65
+ @loaded_filing.disclosures.first.summary(:period=>period).total.should == expected_total
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,21 @@
1
+ # assets_calculation_spec.rb
2
+
3
+ require 'spec_helper'
4
+
5
+ describe FinModeling::AssetsCalculation do
6
+ before(:all) do
7
+ google_2011_annual_rpt = "http://www.sec.gov/Archives/edgar/data/1288776/000119312512025336/0001193125-12-025336-index.htm"
8
+ filing = FinModeling::AnnualReportFiling.download google_2011_annual_rpt
9
+ @bal_sheet = filing.balance_sheet
10
+
11
+ @period = @bal_sheet.periods.last
12
+ @a = @bal_sheet.assets_calculation
13
+ end
14
+
15
+ describe "summary" do
16
+ it "only requires a period (knows how debts/credits work and whether to flip the total)" do
17
+ @a.summary(:period=>@period).should be_an_instance_of FinModeling::CalculationSummary
18
+ end
19
+ end
20
+ end
21
+