finmodeling 0.1 → 0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. data/.gitignore +0 -0
  2. data/Gemfile +2 -0
  3. data/README.md +289 -269
  4. data/Rakefile +12 -0
  5. data/TODO.txt +113 -20
  6. data/examples/{dump_report.rb → dump_latest_10k.rb} +1 -1
  7. data/examples/list_disclosures.rb +50 -0
  8. data/examples/lists/nasdaq-mid-to-mega-tech-symbols.txt +0 -0
  9. data/examples/show_report.rb +112 -32
  10. data/examples/show_reports.rb +162 -33
  11. data/finmodeling.gemspec +4 -1
  12. data/lib/finmodeling/annual_report_filing.rb +97 -18
  13. data/lib/finmodeling/array_with_stats.rb +0 -0
  14. data/lib/finmodeling/assets_calculation.rb +12 -3
  15. data/lib/finmodeling/assets_item.rb +0 -0
  16. data/lib/finmodeling/assets_item_vectors.rb +0 -0
  17. data/lib/finmodeling/balance_sheet_analyses.rb +19 -4
  18. data/lib/finmodeling/balance_sheet_calculation.rb +52 -37
  19. data/lib/finmodeling/calculation_summary.rb +119 -14
  20. data/lib/finmodeling/can_cache_classifications.rb +0 -0
  21. data/lib/finmodeling/can_cache_summaries.rb +0 -0
  22. data/lib/finmodeling/can_choose_successive_periods.rb +15 -0
  23. data/lib/finmodeling/can_classify_rows.rb +0 -0
  24. data/lib/finmodeling/capm.rb +80 -0
  25. data/lib/finmodeling/cash_change_calculation.rb +3 -3
  26. data/lib/finmodeling/cash_change_item.rb +0 -0
  27. data/lib/finmodeling/cash_change_item_vectors.rb +0 -0
  28. data/lib/finmodeling/cash_change_summary_from_differences.rb +36 -0
  29. data/lib/finmodeling/cash_flow_statement_analyses.rb +36 -0
  30. data/lib/finmodeling/cash_flow_statement_calculation.rb +28 -52
  31. data/lib/finmodeling/classifiers.rb +2 -0
  32. data/lib/finmodeling/company.rb +0 -0
  33. data/lib/finmodeling/company_filing.rb +30 -7
  34. data/lib/finmodeling/company_filing_calculation.rb +16 -6
  35. data/lib/finmodeling/company_filings.rb +112 -46
  36. data/lib/finmodeling/comprehensive_income_calculation.rb +60 -0
  37. data/lib/finmodeling/comprehensive_income_statement_calculation.rb +74 -0
  38. data/lib/finmodeling/comprehensive_income_statement_item.rb +20 -0
  39. data/lib/finmodeling/comprehensive_income_statement_item_vectors.rb +235 -0
  40. data/lib/finmodeling/config.rb +0 -0
  41. data/lib/finmodeling/debt_cost_of_capital.rb +14 -0
  42. data/lib/finmodeling/equity_change_calculation.rb +43 -0
  43. data/lib/finmodeling/equity_change_item.rb +25 -0
  44. data/lib/finmodeling/equity_change_item_vectors.rb +156 -0
  45. data/lib/finmodeling/factory.rb +0 -0
  46. data/lib/finmodeling/fama_french_cost_of_equity.rb +119 -0
  47. data/lib/finmodeling/float_helpers.rb +14 -8
  48. data/lib/finmodeling/forecasted_reformulated_balance_sheet.rb +55 -0
  49. data/lib/finmodeling/forecasted_reformulated_income_statement.rb +110 -0
  50. data/lib/finmodeling/forecasts.rb +4 -4
  51. data/lib/finmodeling/has_string_classifer.rb +0 -0
  52. data/lib/finmodeling/income_statement_analyses.rb +23 -17
  53. data/lib/finmodeling/income_statement_calculation.rb +46 -43
  54. data/lib/finmodeling/income_statement_item.rb +1 -1
  55. data/lib/finmodeling/income_statement_item_vectors.rb +24 -13
  56. data/lib/finmodeling/invalid_filing_error.rb +4 -0
  57. data/lib/finmodeling/liabs_and_equity_calculation.rb +18 -8
  58. data/lib/finmodeling/liabs_and_equity_item.rb +1 -1
  59. data/lib/finmodeling/liabs_and_equity_item_vectors.rb +24 -24
  60. data/lib/finmodeling/linear_trend_forecasting_policy.rb +23 -0
  61. data/lib/finmodeling/net_income_calculation.rb +23 -10
  62. data/lib/finmodeling/net_income_summary_from_differences.rb +51 -0
  63. data/lib/finmodeling/paths.rb +0 -0
  64. data/lib/finmodeling/period_array.rb +8 -4
  65. data/lib/finmodeling/quarterly_report_filing.rb +9 -4
  66. data/lib/finmodeling/rate.rb +8 -0
  67. data/lib/finmodeling/ratio.rb +0 -0
  68. data/lib/finmodeling/reformulated_balance_sheet.rb +47 -88
  69. data/lib/finmodeling/reformulated_cash_flow_statement.rb +18 -41
  70. data/lib/finmodeling/reformulated_income_statement.rb +44 -206
  71. data/lib/finmodeling/reformulated_shareholder_equity_statement.rb +50 -0
  72. data/lib/finmodeling/reoi_valuation.rb +104 -0
  73. data/lib/finmodeling/shareholder_equity_statement_calculation.rb +34 -0
  74. data/lib/finmodeling/string_helpers.rb +18 -1
  75. data/lib/finmodeling/time_series_estimator.rb +25 -0
  76. data/lib/finmodeling/trailing_avg_forecasting_policy.rb +23 -0
  77. data/lib/finmodeling/version.rb +1 -1
  78. data/lib/finmodeling/weighted_avg_cost_of_capital.rb +35 -0
  79. data/lib/finmodeling/yahoo_finance_helpers.rb +20 -0
  80. data/lib/finmodeling.rb +33 -2
  81. data/spec/annual_report_filing_spec.rb +81 -45
  82. data/spec/assets_calculation_spec.rb +7 -4
  83. data/spec/assets_item_spec.rb +9 -14
  84. data/spec/balance_sheet_analyses_spec.rb +13 -13
  85. data/spec/balance_sheet_calculation_spec.rb +45 -51
  86. data/spec/calculation_summary_spec.rb +113 -21
  87. data/spec/can_classify_rows_spec.rb +0 -0
  88. data/spec/cash_change_calculation_spec.rb +1 -10
  89. data/spec/cash_change_item_spec.rb +10 -18
  90. data/spec/cash_flow_statement_calculation_spec.rb +10 -24
  91. data/spec/company_beta_spec.rb +53 -0
  92. data/spec/company_filing_calculation_spec.rb +39 -49
  93. data/spec/company_filing_spec.rb +0 -0
  94. data/spec/company_filings_spec.rb +75 -25
  95. data/spec/company_spec.rb +37 -47
  96. data/spec/comprehensive_income_statement_calculation_spec.rb +54 -0
  97. data/spec/comprehensive_income_statement_item_spec.rb +56 -0
  98. data/spec/debt_cost_of_capital_spec.rb +19 -0
  99. data/spec/equity_change_calculation_spec.rb +33 -0
  100. data/spec/equity_change_item_spec.rb +58 -0
  101. data/spec/factory_spec.rb +2 -2
  102. data/spec/forecasts_spec.rb +2 -2
  103. data/spec/income_statement_analyses_spec.rb +23 -21
  104. data/spec/income_statement_calculation_spec.rb +17 -49
  105. data/spec/income_statement_item_spec.rb +17 -29
  106. data/spec/liabs_and_equity_calculation_spec.rb +6 -3
  107. data/spec/liabs_and_equity_item_spec.rb +14 -22
  108. data/spec/linear_trend_forecasting_policy_spec.rb +37 -0
  109. data/spec/matchers/custom_matchers.rb +79 -0
  110. data/spec/mocks/calculation.rb +0 -0
  111. data/spec/mocks/income_statement_analyses.rb +0 -0
  112. data/spec/mocks/sec_query.rb +0 -0
  113. data/spec/net_income_calculation_spec.rb +16 -10
  114. data/spec/period_array.rb +0 -0
  115. data/spec/quarterly_report_filing_spec.rb +21 -38
  116. data/spec/rate_spec.rb +0 -0
  117. data/spec/ratio_spec.rb +0 -0
  118. data/spec/reformulated_balance_sheet_spec.rb +56 -33
  119. data/spec/reformulated_cash_flow_statement_spec.rb +18 -10
  120. data/spec/reformulated_income_statement_spec.rb +16 -15
  121. data/spec/reformulated_shareholder_equity_statement_spec.rb +43 -0
  122. data/spec/reoi_valuation_spec.rb +146 -0
  123. data/spec/shareholder_equity_statement_calculation_spec.rb +59 -0
  124. data/spec/spec_helper.rb +4 -1
  125. data/spec/string_helpers_spec.rb +15 -13
  126. data/spec/time_series_estimator_spec.rb +61 -0
  127. data/spec/trailing_avg_forecasting_policy_spec.rb +37 -0
  128. data/spec/weighted_avg_cost_of_capital_spec.rb +32 -0
  129. data/tools/create_equity_change_training_vectors.rb +49 -0
  130. data/tools/time_specs.sh +7 -0
  131. metadata +182 -36
  132. data/lib/finmodeling/constant_forecasting_policy.rb +0 -23
  133. data/lib/finmodeling/generic_forecasting_policy.rb +0 -19
  134. data/spec/constant_forecasting_policy_spec.rb +0 -37
  135. data/spec/generic_forecasting_policy_spec.rb +0 -33
@@ -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