finmodeling 0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+