finmodeling 0.1 → 0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (135) hide show
  1. data/.gitignore +0 -0
  2. data/Gemfile +2 -0
  3. data/README.md +289 -269
  4. data/Rakefile +12 -0
  5. data/TODO.txt +113 -20
  6. data/examples/{dump_report.rb → dump_latest_10k.rb} +1 -1
  7. data/examples/list_disclosures.rb +50 -0
  8. data/examples/lists/nasdaq-mid-to-mega-tech-symbols.txt +0 -0
  9. data/examples/show_report.rb +112 -32
  10. data/examples/show_reports.rb +162 -33
  11. data/finmodeling.gemspec +4 -1
  12. data/lib/finmodeling/annual_report_filing.rb +97 -18
  13. data/lib/finmodeling/array_with_stats.rb +0 -0
  14. data/lib/finmodeling/assets_calculation.rb +12 -3
  15. data/lib/finmodeling/assets_item.rb +0 -0
  16. data/lib/finmodeling/assets_item_vectors.rb +0 -0
  17. data/lib/finmodeling/balance_sheet_analyses.rb +19 -4
  18. data/lib/finmodeling/balance_sheet_calculation.rb +52 -37
  19. data/lib/finmodeling/calculation_summary.rb +119 -14
  20. data/lib/finmodeling/can_cache_classifications.rb +0 -0
  21. data/lib/finmodeling/can_cache_summaries.rb +0 -0
  22. data/lib/finmodeling/can_choose_successive_periods.rb +15 -0
  23. data/lib/finmodeling/can_classify_rows.rb +0 -0
  24. data/lib/finmodeling/capm.rb +80 -0
  25. data/lib/finmodeling/cash_change_calculation.rb +3 -3
  26. data/lib/finmodeling/cash_change_item.rb +0 -0
  27. data/lib/finmodeling/cash_change_item_vectors.rb +0 -0
  28. data/lib/finmodeling/cash_change_summary_from_differences.rb +36 -0
  29. data/lib/finmodeling/cash_flow_statement_analyses.rb +36 -0
  30. data/lib/finmodeling/cash_flow_statement_calculation.rb +28 -52
  31. data/lib/finmodeling/classifiers.rb +2 -0
  32. data/lib/finmodeling/company.rb +0 -0
  33. data/lib/finmodeling/company_filing.rb +30 -7
  34. data/lib/finmodeling/company_filing_calculation.rb +16 -6
  35. data/lib/finmodeling/company_filings.rb +112 -46
  36. data/lib/finmodeling/comprehensive_income_calculation.rb +60 -0
  37. data/lib/finmodeling/comprehensive_income_statement_calculation.rb +74 -0
  38. data/lib/finmodeling/comprehensive_income_statement_item.rb +20 -0
  39. data/lib/finmodeling/comprehensive_income_statement_item_vectors.rb +235 -0
  40. data/lib/finmodeling/config.rb +0 -0
  41. data/lib/finmodeling/debt_cost_of_capital.rb +14 -0
  42. data/lib/finmodeling/equity_change_calculation.rb +43 -0
  43. data/lib/finmodeling/equity_change_item.rb +25 -0
  44. data/lib/finmodeling/equity_change_item_vectors.rb +156 -0
  45. data/lib/finmodeling/factory.rb +0 -0
  46. data/lib/finmodeling/fama_french_cost_of_equity.rb +119 -0
  47. data/lib/finmodeling/float_helpers.rb +14 -8
  48. data/lib/finmodeling/forecasted_reformulated_balance_sheet.rb +55 -0
  49. data/lib/finmodeling/forecasted_reformulated_income_statement.rb +110 -0
  50. data/lib/finmodeling/forecasts.rb +4 -4
  51. data/lib/finmodeling/has_string_classifer.rb +0 -0
  52. data/lib/finmodeling/income_statement_analyses.rb +23 -17
  53. data/lib/finmodeling/income_statement_calculation.rb +46 -43
  54. data/lib/finmodeling/income_statement_item.rb +1 -1
  55. data/lib/finmodeling/income_statement_item_vectors.rb +24 -13
  56. data/lib/finmodeling/invalid_filing_error.rb +4 -0
  57. data/lib/finmodeling/liabs_and_equity_calculation.rb +18 -8
  58. data/lib/finmodeling/liabs_and_equity_item.rb +1 -1
  59. data/lib/finmodeling/liabs_and_equity_item_vectors.rb +24 -24
  60. data/lib/finmodeling/linear_trend_forecasting_policy.rb +23 -0
  61. data/lib/finmodeling/net_income_calculation.rb +23 -10
  62. data/lib/finmodeling/net_income_summary_from_differences.rb +51 -0
  63. data/lib/finmodeling/paths.rb +0 -0
  64. data/lib/finmodeling/period_array.rb +8 -4
  65. data/lib/finmodeling/quarterly_report_filing.rb +9 -4
  66. data/lib/finmodeling/rate.rb +8 -0
  67. data/lib/finmodeling/ratio.rb +0 -0
  68. data/lib/finmodeling/reformulated_balance_sheet.rb +47 -88
  69. data/lib/finmodeling/reformulated_cash_flow_statement.rb +18 -41
  70. data/lib/finmodeling/reformulated_income_statement.rb +44 -206
  71. data/lib/finmodeling/reformulated_shareholder_equity_statement.rb +50 -0
  72. data/lib/finmodeling/reoi_valuation.rb +104 -0
  73. data/lib/finmodeling/shareholder_equity_statement_calculation.rb +34 -0
  74. data/lib/finmodeling/string_helpers.rb +18 -1
  75. data/lib/finmodeling/time_series_estimator.rb +25 -0
  76. data/lib/finmodeling/trailing_avg_forecasting_policy.rb +23 -0
  77. data/lib/finmodeling/version.rb +1 -1
  78. data/lib/finmodeling/weighted_avg_cost_of_capital.rb +35 -0
  79. data/lib/finmodeling/yahoo_finance_helpers.rb +20 -0
  80. data/lib/finmodeling.rb +33 -2
  81. data/spec/annual_report_filing_spec.rb +81 -45
  82. data/spec/assets_calculation_spec.rb +7 -4
  83. data/spec/assets_item_spec.rb +9 -14
  84. data/spec/balance_sheet_analyses_spec.rb +13 -13
  85. data/spec/balance_sheet_calculation_spec.rb +45 -51
  86. data/spec/calculation_summary_spec.rb +113 -21
  87. data/spec/can_classify_rows_spec.rb +0 -0
  88. data/spec/cash_change_calculation_spec.rb +1 -10
  89. data/spec/cash_change_item_spec.rb +10 -18
  90. data/spec/cash_flow_statement_calculation_spec.rb +10 -24
  91. data/spec/company_beta_spec.rb +53 -0
  92. data/spec/company_filing_calculation_spec.rb +39 -49
  93. data/spec/company_filing_spec.rb +0 -0
  94. data/spec/company_filings_spec.rb +75 -25
  95. data/spec/company_spec.rb +37 -47
  96. data/spec/comprehensive_income_statement_calculation_spec.rb +54 -0
  97. data/spec/comprehensive_income_statement_item_spec.rb +56 -0
  98. data/spec/debt_cost_of_capital_spec.rb +19 -0
  99. data/spec/equity_change_calculation_spec.rb +33 -0
  100. data/spec/equity_change_item_spec.rb +58 -0
  101. data/spec/factory_spec.rb +2 -2
  102. data/spec/forecasts_spec.rb +2 -2
  103. data/spec/income_statement_analyses_spec.rb +23 -21
  104. data/spec/income_statement_calculation_spec.rb +17 -49
  105. data/spec/income_statement_item_spec.rb +17 -29
  106. data/spec/liabs_and_equity_calculation_spec.rb +6 -3
  107. data/spec/liabs_and_equity_item_spec.rb +14 -22
  108. data/spec/linear_trend_forecasting_policy_spec.rb +37 -0
  109. data/spec/matchers/custom_matchers.rb +79 -0
  110. data/spec/mocks/calculation.rb +0 -0
  111. data/spec/mocks/income_statement_analyses.rb +0 -0
  112. data/spec/mocks/sec_query.rb +0 -0
  113. data/spec/net_income_calculation_spec.rb +16 -10
  114. data/spec/period_array.rb +0 -0
  115. data/spec/quarterly_report_filing_spec.rb +21 -38
  116. data/spec/rate_spec.rb +0 -0
  117. data/spec/ratio_spec.rb +0 -0
  118. data/spec/reformulated_balance_sheet_spec.rb +56 -33
  119. data/spec/reformulated_cash_flow_statement_spec.rb +18 -10
  120. data/spec/reformulated_income_statement_spec.rb +16 -15
  121. data/spec/reformulated_shareholder_equity_statement_spec.rb +43 -0
  122. data/spec/reoi_valuation_spec.rb +146 -0
  123. data/spec/shareholder_equity_statement_calculation_spec.rb +59 -0
  124. data/spec/spec_helper.rb +4 -1
  125. data/spec/string_helpers_spec.rb +15 -13
  126. data/spec/time_series_estimator_spec.rb +61 -0
  127. data/spec/trailing_avg_forecasting_policy_spec.rb +37 -0
  128. data/spec/weighted_avg_cost_of_capital_spec.rb +32 -0
  129. data/tools/create_equity_change_training_vectors.rb +49 -0
  130. data/tools/time_specs.sh +7 -0
  131. metadata +182 -36
  132. data/lib/finmodeling/constant_forecasting_policy.rb +0 -23
  133. data/lib/finmodeling/generic_forecasting_policy.rb +0 -19
  134. data/spec/constant_forecasting_policy_spec.rb +0 -37
  135. data/spec/generic_forecasting_policy_spec.rb +0 -33
@@ -14,7 +14,7 @@ describe FinModeling::ReformulatedIncomeStatement do
14
14
 
15
15
  @inc_stmt_2009 = @filing_2009.income_statement
16
16
  is_period_2009 = @inc_stmt_2009.periods.last
17
- @reformed_inc_stmt_2009 = @inc_stmt_2009.reformulated(is_period_2009)
17
+ @reformed_inc_stmt_2009 = @inc_stmt_2009.reformulated(is_period_2009, ci_calc=nil)
18
18
 
19
19
  @bal_sheet_2009 = @filing_2009.balance_sheet
20
20
  bs_period_2009 = @bal_sheet_2009.periods.last
@@ -22,7 +22,7 @@ describe FinModeling::ReformulatedIncomeStatement do
22
22
 
23
23
  @inc_stmt_2011 = @filing_2011.income_statement
24
24
  is_period_2011 = @inc_stmt_2011.periods.last
25
- @reformed_inc_stmt_2011 = @inc_stmt_2011.reformulated(is_period_2011)
25
+ @reformed_inc_stmt_2011 = @inc_stmt_2011.reformulated(is_period_2011, ci_calc=nil)
26
26
 
27
27
  @bal_sheet_2011 = @filing_2011.balance_sheet
28
28
  bs_period_2011 = @bal_sheet_2011.periods.last
@@ -171,7 +171,7 @@ describe FinModeling::ReformulatedIncomeStatement do
171
171
  end
172
172
 
173
173
  describe "analysis" do
174
- subject {@reformed_inc_stmt_2011.analysis(@reformed_bal_sheet_2011, @reformed_inc_stmt_2009, @reformed_bal_sheet_2009) }
174
+ subject {@reformed_inc_stmt_2011.analysis(@reformed_bal_sheet_2011, @reformed_inc_stmt_2009, @reformed_bal_sheet_2009, e_ror=0.10) }
175
175
 
176
176
  it { should be_an_instance_of FinModeling::CalculationSummary }
177
177
  it "contains the expected rows" do
@@ -191,7 +191,7 @@ describe FinModeling::ReformulatedIncomeStatement do
191
191
 
192
192
  @inc_stmt_2011_q3 = @filing_2011_q3.income_statement
193
193
  is_period_2011_q3 = @inc_stmt_2011_q3.periods.threequarterly.last
194
- @reformed_inc_stmt_2011_q3 = @inc_stmt_2011_q3.reformulated(is_period_2011_q3)
194
+ @reformed_inc_stmt_2011_q3 = @inc_stmt_2011_q3.reformulated(is_period_2011_q3, ci_calc=nil)
195
195
 
196
196
  @diff = @reformed_inc_stmt_2011 - @reformed_inc_stmt_2011_q3
197
197
  end
@@ -248,7 +248,8 @@ describe FinModeling::ReformulatedIncomeStatement do
248
248
  before (:all) do
249
249
  @company = FinModeling::Company.find("aapl")
250
250
  @filings = FinModeling::CompanyFilings.new(@company.filings_since_date(Time.parse("2010-10-01")))
251
- @policy = FinModeling::GenericForecastingPolicy.new
251
+ @last_operating_revenues = @filings.last.income_statement.latest_quarterly_reformulated(nil, nil, nil).operating_revenues.total
252
+ @policy = FinModeling::GenericForecastingPolicy.new(:operating_revenues => @last_operating_revenues)
252
253
 
253
254
  prev_bs_period = @filings.last.balance_sheet.periods.last
254
255
  next_bs_period_value = prev_bs_period.value.next_month.next_month.next_month
@@ -260,7 +261,7 @@ describe FinModeling::ReformulatedIncomeStatement do
260
261
  end
261
262
 
262
263
  let(:last_re_bs) { @filings.last.balance_sheet.reformulated(@filings.last.balance_sheet.periods.last) }
263
- let(:last_re_is) { @filings.last.income_statement.latest_quarterly_reformulated(nil) }
264
+ let(:last_re_is) { @filings.last.income_statement.latest_quarterly_reformulated(nil, nil, nil) }
264
265
  let(:next_re_is) { FinModeling::ReformulatedIncomeStatement.forecast_next(@next_is_period, @policy, last_re_bs, last_re_is) }
265
266
  let(:next_re_bs) { FinModeling::ReformulatedBalanceSheet.forecast_next(@next_bs_period, @policy, last_re_bs, next_re_is) }
266
267
 
@@ -270,24 +271,24 @@ describe FinModeling::ReformulatedIncomeStatement do
270
271
  it "should have the given period" do
271
272
  subject.period.to_pretty_s == @next_is_period.to_pretty_s
272
273
  end
273
- it "should set operating_revenue to last year's revenue times the revenue growth" do
274
- expected_val = last_re_is.operating_revenues.total * (1.0 + FinModeling::Rate.new(@policy.revenue_growth).annualize(from=365, to=365/4.0))
275
- subject.operating_revenues.total.should == expected_val
274
+ it "should set operating_revenue to last year's revenue" do
275
+ expected_val = last_re_is.operating_revenues.total
276
+ subject.operating_revenues.total.should be_within(0.1).of(expected_val)
276
277
  end
277
278
  it "should set OISAT to operating revenue times sales PM" do
278
- expected_val = subject.operating_revenues.total * @policy.sales_pm
279
- subject.income_from_sales_after_tax.total.should == expected_val
279
+ expected_val = subject.operating_revenues.total * @policy.sales_pm_on(@next_is_period.value["end_date"])
280
+ subject.income_from_sales_after_tax.total.should be_within(0.1).of(expected_val)
280
281
  end
281
282
  it "should set NFI to fi_over_nfa times last year's NFA" do
282
- expected_val = last_re_bs.net_financial_assets.total * (@policy.fi_over_nfa/4)
283
- subject.net_financing_income.total.should == expected_val
283
+ expected_val = last_re_bs.net_financial_assets.total * (@policy.fi_over_nfa_on(@next_is_period.value["end_date"])/4) # FIXME use Rate.annualize
284
+ subject.net_financing_income.total.should be_within(0.1).of(expected_val)
284
285
  end
285
286
  it "should set comprehensive income to OISAT plus NFI" do
286
287
  expected_val = subject.income_from_sales_after_tax.total + subject.net_financing_income.total
287
- subject.comprehensive_income.total.should == expected_val
288
+ subject.comprehensive_income.total.should be_within(0.1).of(expected_val)
288
289
  end
289
290
  it "should have an empty analysis (with the same rows)" do
290
- subject.analysis(next_re_bs, last_re_is, last_re_bs)
291
+ subject.analysis(next_re_bs, last_re_is, last_re_bs, e_ror=0.10)
291
292
  end
292
293
  end
293
294
  end
@@ -0,0 +1,43 @@
1
+ # reformulated_income_statement_spec.rb
2
+
3
+ require 'spec_helper'
4
+
5
+ describe FinModeling::ReformulatedShareholderEquityStatement do
6
+ before(:all) do
7
+ deere_2011_annual_rpt = "http://www.sec.gov/Archives/edgar/data/315189/000110465910063219/0001104659-10-063219-index.htm"
8
+ filing = FinModeling::AnnualReportFiling.download deere_2011_annual_rpt
9
+ stmt = filing.shareholder_equity_statement
10
+ period = stmt.periods.last
11
+
12
+ @equity_chg = stmt.equity_change_calculation.summary(:period => period)
13
+ @re_ses = stmt.reformulated(period)
14
+ end
15
+
16
+ describe "transactions_with_shareholders" do
17
+ subject { @re_ses.transactions_with_shareholders }
18
+ it { should be_a FinModeling::CalculationSummary }
19
+ its(:total) { should be_within(0.1).of( @equity_chg.filter_by_type(:share_issue ).total +
20
+ @equity_chg.filter_by_type(:minority_int ).total +
21
+ @equity_chg.filter_by_type(:share_repurch).total +
22
+ @equity_chg.filter_by_type(:common_div ).total) }
23
+ end
24
+
25
+ describe "comprehensive_income" do
26
+ subject { @re_ses.comprehensive_income }
27
+ it { should be_a FinModeling::CalculationSummary }
28
+ its(:total) { should be_within(0.1).of( @equity_chg.filter_by_type(:net_income ).total +
29
+ @equity_chg.filter_by_type(:oci ).total +
30
+ @equity_chg.filter_by_type(:preferred_div).total) }
31
+ end
32
+
33
+ describe "analysis" do
34
+ subject { @re_ses.analysis }
35
+
36
+ it { should be_a FinModeling::CalculationSummary }
37
+ it "contains the expected rows" do
38
+ expected_keys = [ "Tx w Shareholders ($MM)", "CI ($MM)" ]
39
+ subject.rows.map{ |row| row.key }.should == expected_keys
40
+ end
41
+ end
42
+
43
+ end
@@ -0,0 +1,146 @@
1
+ require 'spec_helper'
2
+
3
+ describe FinModeling::ReOIValuation do
4
+ before (:all) do
5
+ @company = FinModeling::Company.find("aapl")
6
+ @filings = FinModeling::CompanyFilings.new(@company.filings_since_date(Time.parse("2012-10-01")))
7
+ @cost_of_capital = FinModeling::Rate.new(0.086) # 8.6% (made up)
8
+ @forecasts = @filings.forecasts(@filings.choose_forecasting_policy(@cost_of_capital.value), num_forecast_periods=4)
9
+ @num_shares = 934882640
10
+ end
11
+
12
+ describe ".new" do
13
+ subject { FinModeling::ReOIValuation.new(@filings, @forecasts, @cost_of_capital, @num_shares) }
14
+
15
+ it { should be_a FinModeling::ReOIValuation }
16
+ end
17
+
18
+ describe ".summary" do
19
+ let(:valuation) { FinModeling::ReOIValuation.new(@filings, @forecasts, @cost_of_capital, @num_shares) }
20
+ subject { valuation.summary }
21
+
22
+ it { should be_a FinModeling::CalculationSummary }
23
+ its(:title) { should == "ReOI Valuation" }
24
+ its(:totals_row_enabled) { should be_false }
25
+ it "should have the right row keys" do
26
+ expected_keys = []
27
+ expected_keys << "ReOI ($MM)"
28
+ expected_keys << "PV(ReOI) ($MM)"
29
+ expected_keys << "CV ($MM)"
30
+ expected_keys << "PV(CV) ($MM)"
31
+ expected_keys << "Book Value of Common Equity ($MM)"
32
+ expected_keys << "Enterprise Value ($MM)"
33
+ expected_keys << "NFA ($MM)"
34
+ expected_keys << "Value of Common Equity ($MM)"
35
+ expected_keys << "# Shares (MM)"
36
+ expected_keys << "Value / Share ($)"
37
+ subject.rows.map{ |x| x.key }.should == expected_keys
38
+ end
39
+
40
+ it "should show today, plus the forecasted periods" do
41
+ period_strings = []
42
+ period_strings << @filings.re_bs_arr.last.period.to_pretty_s
43
+ period_strings += @forecasts.reformulated_balance_sheets.map{ |x| x.period.to_pretty_s + "E" }
44
+ subject.header_row.vals.should == period_strings
45
+ end
46
+
47
+ it "should show all forecasted ReOIs" do
48
+ prev_re_bses = [@filings.re_bs_arr.last] + @forecasts.reformulated_balance_sheets[0..-2]
49
+ re_ises = @forecasts.reformulated_income_statements
50
+ re_ois = re_ises.zip(prev_re_bses).map{ |pair| pair[0].re_oi(pair[1], @cost_of_capital.value).to_nearest_million }
51
+
52
+ reoi_row = subject.rows.find{ |x| x.key == "ReOI ($MM)" }
53
+ reoi_row.vals[1..-1].should == re_ois
54
+ end
55
+
56
+ it "should the present value of the first N-1 ReOI forecasts" do
57
+ reoi_row = subject.rows.find{ |x| x.key == "ReOI ($MM)" }
58
+ pv_reoi_row = subject.rows.find{ |x| x.key == "PV(ReOI) ($MM)" }
59
+
60
+ 1.upto(reoi_row.vals.length-2) do |col_idx|
61
+ days_from_now = valuation.periods[col_idx].value - Date.today
62
+ discount_rate = FinModeling::Rate.new(@cost_of_capital.value + 1.0)
63
+ d = discount_rate.annualize(from_days=365, to_days=days_from_now)
64
+ expected_pv_reoi = reoi_row.vals[col_idx] / d
65
+ pv_reoi_row.vals[col_idx].should be_within(100.0).of(expected_pv_reoi) # rounding error bc the test is working off of nearest-million values
66
+ end
67
+ end
68
+
69
+ it "should the continuing value, based on the last period's ReOI" do
70
+ reoi_row = subject.rows.find{ |x| x.key == "ReOI ($MM)" }
71
+ cv_row = subject.rows.find{ |x| x.key == "CV ($MM)" }
72
+
73
+ expected_cv = reoi_row.vals.last / @cost_of_capital.value # FIXME: this is an assumption of zero-growth CV
74
+ cv_row.vals[-2].should be_within(10.0).of(expected_cv)
75
+ end
76
+
77
+ it "should the present value of the continuing value" do
78
+ cv_row = subject.rows.find{ |x| x.key == "CV ($MM)" }
79
+ pv_cv_row = subject.rows.find{ |x| x.key == "PV(CV) ($MM)" }
80
+
81
+ 1.upto(cv_row.vals.length-2) do |col_idx|
82
+ if cv_row.vals[col_idx]
83
+ days_from_now = valuation.periods[col_idx].value - Date.today
84
+ discount_rate = FinModeling::Rate.new(@cost_of_capital.value + 1.0)
85
+ d = discount_rate.annualize(from_days=365, to_days=days_from_now)
86
+ expected_pv_cv = cv_row.vals[col_idx] / d
87
+ pv_cv_row.vals[col_idx].should be_within(2.0).of(expected_pv_cv)
88
+ end
89
+ end
90
+ end
91
+
92
+ it "should show the current book value of equity" do
93
+ bv_cse_row = subject.rows.find{ |x| x.key == "Book Value of Common Equity ($MM)" }
94
+
95
+ bv_cse_row.vals[0].should be_within(1.0).of(@filings.re_bs_arr.last.common_shareholders_equity.total.to_nearest_million)
96
+ end
97
+
98
+ it "should show the enterprise value" do
99
+ pv_reoi_row = subject.rows.find{ |x| x.key == "PV(ReOI) ($MM)" }
100
+ pv_cv_row = subject.rows.find{ |x| x.key == "PV(CV) ($MM)" }
101
+ bv_cse_row = subject.rows.find{ |x| x.key == "Book Value of Common Equity ($MM)" }
102
+ ev_row = subject.rows.find{ |x| x.key == "Enterprise Value ($MM)" }
103
+
104
+ expected_ev = pv_reoi_row.vals[1..-2].inject(:+) + pv_cv_row.vals.select{ |x| x }.inject(:+) + bv_cse_row.vals.select{ |x| x }.inject(:+)
105
+
106
+ ev_row.vals[0].should be_within(2.0).of(expected_ev)
107
+ end
108
+
109
+ it "should show the book value of net financial assets" do
110
+ bv_nfa_row = subject.rows.find{ |x| x.key == "NFA ($MM)" }
111
+
112
+ bv_nfa_row.vals[0].should be_within(1.0).of(@filings.re_bs_arr.last.net_financial_assets.total.to_nearest_million)
113
+ end
114
+
115
+ it "should show the value of common equity" do
116
+ ev_row = subject.rows.find{ |x| x.key == "Enterprise Value ($MM)" }
117
+ bf_nfa_row = subject.rows.find{ |x| x.key == "NFA ($MM)" }
118
+ cse_row = subject.rows.find{ |x| x.key == "Value of Common Equity ($MM)" }
119
+
120
+ expected_cse = ev_row.vals[0] + bf_nfa_row.vals[0]
121
+
122
+ cse_row.vals[0].should be_within(1.0).of(expected_cse)
123
+ end
124
+
125
+ it "should show the book value of net financial assets" do
126
+ num_shares_row = subject.rows.find{ |x| x.key == "# Shares (MM)" }
127
+
128
+ num_shares_row.vals[0].should be_within(1.0).of(@num_shares.to_nearest_million)
129
+ end
130
+
131
+ it "should show the value per share" do
132
+ cse_row = subject.rows.find{ |x| x.key == "Value of Common Equity ($MM)" }
133
+ num_shares_row = subject.rows.find{ |x| x.key == "# Shares (MM)" }
134
+ value_per_share_row = subject.rows.find{ |x| x.key == "Value / Share ($)" }
135
+
136
+ expected_value_per_share = cse_row.vals[0] / num_shares_row.vals[0]
137
+
138
+ value_per_share_row.vals[0].should be_within(1.0).of(expected_value_per_share)
139
+ end
140
+
141
+ it "should print successfully" do # FIXME: delete this. it's just for hacking
142
+ subject.print
143
+ end
144
+ end
145
+
146
+ end
@@ -0,0 +1,59 @@
1
+ # shareholder_equity_statement_calculation_spec.rb
2
+
3
+ require 'spec_helper'
4
+
5
+ describe FinModeling::ShareholderEquityStatementCalculation do
6
+ before(:all) do
7
+ deere_2011_annual_rpt = "http://www.sec.gov/Archives/edgar/data/315189/000110465910063219/0001104659-10-063219-index.htm"
8
+ filing = FinModeling::AnnualReportFiling.download deere_2011_annual_rpt
9
+ @stmt = filing.shareholder_equity_statement
10
+ @period = @stmt.periods.last
11
+ end
12
+
13
+ describe ".equity_change_calculation" do
14
+ subject { @stmt.equity_change_calculation }
15
+ it { should be_a FinModeling::EquityChangeCalculation }
16
+ its(:label) { should match /(stock|share)holder.*equity/i }
17
+
18
+ #let(:right_side_sum) { @stmt.liabs_and_equity_calculation.leaf_items_sum(:period=>@period) }
19
+ #specify { subject.leaf_items_sum(:period=>@period).should be_within(1.0).of(right_side_sum) }
20
+
21
+ it "should have the same last total as the balance sheet''s cse" do
22
+ pending
23
+ end
24
+ end
25
+
26
+ describe ".is_valid?" do
27
+ context "always... ?" do
28
+ it "returns true" do
29
+ @stmt.is_valid?.should be_true
30
+ end
31
+ end
32
+ end
33
+
34
+ describe ".reformulated" do
35
+ subject { @stmt.reformulated(@period) }
36
+ it { should be_a FinModeling::ReformulatedShareholderEquityStatement }
37
+ end
38
+
39
+ describe ".write_constructor" do
40
+ before(:all) do
41
+ file_name = "/tmp/finmodeling-shareholder-equity-stmt.rb"
42
+ item_name = "@stmt"
43
+ file = File.open(file_name, "w")
44
+ @stmt.write_constructor(file, item_name)
45
+ file.close
46
+
47
+ eval(File.read(file_name))
48
+ @loaded_stmt = eval(item_name)
49
+ end
50
+
51
+ context "after write_constructor()ing it to a file and then eval()ing the results" do
52
+ subject { @loaded_stmt }
53
+ it { should have_the_same_periods_as @stmt }
54
+ #it { should have_the_same_reformulated_last_total(:net_operating_assets).as(@stmt) }
55
+ end
56
+ end
57
+
58
+ end
59
+
data/spec/spec_helper.rb CHANGED
@@ -1,5 +1,8 @@
1
+ require 'rspec'
2
+
3
+ $:.unshift(File.expand_path(File.join(File.dirname(__FILE__), "../lib")))
1
4
  require 'finmodeling'
2
5
 
3
6
  require 'mocks/sec_query'
4
7
  require 'mocks/calculation'
5
-
8
+ require 'matchers/custom_matchers'
@@ -3,21 +3,23 @@
3
3
  require 'spec_helper'
4
4
 
5
5
  describe String do
6
- describe "matches_regexes?" do
7
- it "returns false if no regexes are provided" do
8
- s = "asdfasdf"
9
- regexes = []
10
- s.matches_regexes?(regexes).should be_false
6
+ let(:s) { "asdfasdf" }
7
+
8
+ describe "matches_any_regex?" do
9
+ context "if no regexes are provided" do
10
+ let(:regexes) { [] }
11
+ subject { s.matches_any_regex?(regexes) }
12
+ it { should be_false }
11
13
  end
12
- it "returns false if the string does not match any of the regexes" do
13
- s = "asdfasdf"
14
- regexes = [/\d/, /[A-Z]/]
15
- s.matches_regexes?(regexes).should be_false
14
+ context "if the string does not match any of the regexes" do
15
+ let(:regexes) { [/\d/, /[A-Z]/] }
16
+ subject { s.matches_any_regex?(regexes) }
17
+ it { should be_false }
16
18
  end
17
- it "returns true if the string matches one or more of the regexes" do
18
- s = "asdfasdf"
19
- regexes = [/sdf/, /ddd/, /af+/]
20
- s.matches_regexes?(regexes).should be_true
19
+ context "if the string matches one or more of the regexes" do
20
+ let(:regexes) { [/sdf/, /ddd/, /af+/] }
21
+ subject { s.matches_any_regex?(regexes) }
22
+ it { should be_true }
21
23
  end
22
24
  end
23
25
  end
@@ -0,0 +1,61 @@
1
+ require 'spec_helper'
2
+
3
+ describe FinModeling::TimeSeriesEstimator do
4
+
5
+ describe ".new" do
6
+ let(:a) { 1.0 }
7
+ let(:b) { 0.2 }
8
+ subject { FinModeling::TimeSeriesEstimator.new(a, b) }
9
+
10
+ it { should be_a FinModeling::TimeSeriesEstimator }
11
+ its(:a) { should be_within(0.01).of(a) }
12
+ its(:b) { should be_within(0.01).of(b) }
13
+ end
14
+
15
+ describe ".estimate_on" do
16
+ let(:a) { 1.0 }
17
+ let(:b) { 0.2 }
18
+ let(:estimator) { FinModeling::TimeSeriesEstimator.new(a, b) }
19
+
20
+ context "when predicting today's outcome" do
21
+ let(:date) { Date.today }
22
+ subject { estimator.estimate_on(date) }
23
+
24
+ it { should be_a Float }
25
+ it { should be_within(0.01).of(a) }
26
+ end
27
+
28
+ context "when predicting any other day" do
29
+ let(:date) { Date.parse("2014-01-01") }
30
+ let(:num_days) { date - Date.today }
31
+ subject { estimator.estimate_on(date) }
32
+
33
+ it { should be_a Float }
34
+ it { should be_within(0.01).of(a + (b*num_days)) }
35
+ end
36
+ end
37
+
38
+ describe "#from_time_series" do
39
+ let(:ys) { [ 10, 20 ] }
40
+ let(:dates) { [ (Date.today - 1), (Date.today) ] }
41
+ subject { FinModeling::TimeSeriesEstimator.from_time_series(dates, ys) }
42
+ let(:expected_a) { 20 }
43
+ let(:expected_b) { 20-10 }
44
+
45
+ it { should be_a FinModeling::TimeSeriesEstimator }
46
+ its(:a) { should be_within(0.01).of(expected_a) }
47
+ its(:b) { should be_within(0.01).of(expected_b) }
48
+ end
49
+
50
+ describe "#from_const" do
51
+ let(:y) { [ 10 ] }
52
+ subject { FinModeling::TimeSeriesEstimator.from_const(y) }
53
+ let(:expected_a) { 10 }
54
+ let(:expected_b) { 0 }
55
+
56
+ it { should be_a FinModeling::TimeSeriesEstimator }
57
+ its(:a) { should be_within(0.01).of(expected_a) }
58
+ its(:b) { should be_within(0.01).of(expected_b) }
59
+ end
60
+
61
+ end
@@ -0,0 +1,37 @@
1
+ require 'spec_helper'
2
+
3
+ describe FinModeling::TrailingAvgForecastingPolicy do
4
+ before (:all) do
5
+ @vals = { :revenue_estimator => FinModeling::TimeSeriesEstimator.new(0.04, 0.0),
6
+ :sales_pm_estimator => FinModeling::TimeSeriesEstimator.new(0.20, 0.0),
7
+ :fi_over_nfa_estimator => FinModeling::TimeSeriesEstimator.new(0.01, 0.0),
8
+ :sales_over_noa_estimator => FinModeling::TimeSeriesEstimator.new(2.00, 0.0) }
9
+ end
10
+
11
+ let(:policy) { FinModeling::TrailingAvgForecastingPolicy.new(@vals) }
12
+ let(:date) { Date.today }
13
+
14
+ describe ".revenue_on" do
15
+ subject { policy.revenue_on(date) }
16
+ it { should be_a Float }
17
+ it { should be_within(0.01).of(@vals[:revenue_estimator].a) }
18
+ end
19
+
20
+ describe ".sales_pm_on" do
21
+ subject { policy.sales_pm_on(date) }
22
+ it { should be_a Float }
23
+ it { should be_within(0.01).of(@vals[:sales_pm_estimator].a) }
24
+ end
25
+
26
+ describe ".fi_over_nfa_on" do
27
+ subject { policy.fi_over_nfa_on(date) }
28
+ it { should be_a Float }
29
+ it { should be_within(0.01).of(@vals[:fi_over_nfa_estimator].a) }
30
+ end
31
+
32
+ describe ".sales_over_noa_on" do
33
+ subject { policy.sales_over_noa_on(date) }
34
+ it { should be_a Float }
35
+ it { should be_within(0.01).of(@vals[:sales_over_noa_estimator].a) }
36
+ end
37
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ describe FinModeling::WeightedAvgCostOfCapital do
4
+ let(:equity_market_val) { 2.2*1000*1000*1000 }
5
+ let(:debt_market_val) { 997.0*1000*1000 }
6
+ let(:cost_of_equity) { FinModeling::Rate.new(0.0087) }
7
+ let(:after_tax_cost_of_debt) { FinModeling::Rate.new(0.0031) }
8
+
9
+ describe '.new' do
10
+ subject { FinModeling::WeightedAvgCostOfCapital.new(equity_market_val, debt_market_val, cost_of_equity, after_tax_cost_of_debt) }
11
+
12
+ it { should be_a FinModeling::WeightedAvgCostOfCapital }
13
+ end
14
+
15
+ describe '.rate' do
16
+ let(:wacc) { FinModeling::WeightedAvgCostOfCapital.new(equity_market_val, debt_market_val, cost_of_equity, after_tax_cost_of_debt) }
17
+ subject { wacc.rate }
18
+
19
+ let(:total_val) { equity_market_val + debt_market_val }
20
+ let(:e_weight) { equity_market_val / total_val }
21
+ let(:d_weight) { debt_market_val / total_val }
22
+ let(:expected_wacc) { (e_weight * cost_of_equity.value) + (d_weight * after_tax_cost_of_debt.value) }
23
+ its(:value) { should be_within(1.0).of(expected_wacc) }
24
+ end
25
+
26
+ describe '.summary' do
27
+ let(:wacc) { FinModeling::WeightedAvgCostOfCapital.new(equity_market_val, debt_market_val, cost_of_equity, after_tax_cost_of_debt) }
28
+ subject { wacc.summary }
29
+
30
+ it { should be_a FinModeling::CalculationSummary }
31
+ end
32
+ end
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $LOAD_PATH << "."
4
+
5
+ require 'finmodeling'
6
+
7
+ def get_args
8
+ if ARGV.length != 1
9
+ puts "usage #{__FILE__} <stock symbol or report URL>"
10
+ exit
11
+ end
12
+
13
+ args = { :stock_symbol => nil, :filing_url => nil }
14
+ arg = ARGV[0]
15
+ if arg =~ /http/
16
+ args[:filing_url] = arg
17
+ else
18
+ args[:stock_symbol] = arg.downcase
19
+ end
20
+
21
+ return args
22
+ end
23
+
24
+ def get_company_filing_url(stock_symbol)
25
+ company = FinModeling::Company.find(stock_symbol)
26
+ raise RuntimeError.new("couldn't find company") if company.nil?
27
+ raise RuntimeError.new("company has no annual reports") if company.annual_reports.length == 0
28
+ filing_url = company.annual_reports.last.link
29
+
30
+ return filing_url
31
+ end
32
+
33
+ def get_filing(filing_url)
34
+ filing = FinModeling::AnnualReportFiling.download(filing_url)
35
+ return filing
36
+ end
37
+
38
+ def print_items(filing)
39
+ return if !filing.has_a_shareholder_equity_statement?
40
+ items = filing.shareholder_equity_statement.equity_change_calculation.leaf_items
41
+ items.each do |item|
42
+ puts " { :eci_type=>:c, :item_string=>\"#{item.pretty_name}\" },"
43
+ end
44
+ end
45
+
46
+ args = get_args
47
+ filing_url = args[:filing_url] || get_company_filing_url(args[:stock_symbol])
48
+ filing = get_filing(filing_url)
49
+ print_items(filing)
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+
3
+ for i in `/bin/ls spec/*spec.rb`; do
4
+ /usr/bin/time -f "%E" rspec -c -fd -I. -Ispec $i > /dev/null 2>/tmp/jbl_time.txt
5
+ ELAPSED=`cat /tmp/jbl_time.txt`; echo "$ELAPSED $i"
6
+ rm /tmp/jbl_time.txt
7
+ done