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.
- data/.gitignore +0 -0
- data/Gemfile +2 -0
- data/README.md +289 -269
- data/Rakefile +12 -0
- data/TODO.txt +113 -20
- data/examples/{dump_report.rb → dump_latest_10k.rb} +1 -1
- data/examples/list_disclosures.rb +50 -0
- data/examples/lists/nasdaq-mid-to-mega-tech-symbols.txt +0 -0
- data/examples/show_report.rb +112 -32
- data/examples/show_reports.rb +162 -33
- data/finmodeling.gemspec +4 -1
- data/lib/finmodeling/annual_report_filing.rb +97 -18
- data/lib/finmodeling/array_with_stats.rb +0 -0
- data/lib/finmodeling/assets_calculation.rb +12 -3
- data/lib/finmodeling/assets_item.rb +0 -0
- data/lib/finmodeling/assets_item_vectors.rb +0 -0
- data/lib/finmodeling/balance_sheet_analyses.rb +19 -4
- data/lib/finmodeling/balance_sheet_calculation.rb +52 -37
- data/lib/finmodeling/calculation_summary.rb +119 -14
- data/lib/finmodeling/can_cache_classifications.rb +0 -0
- data/lib/finmodeling/can_cache_summaries.rb +0 -0
- data/lib/finmodeling/can_choose_successive_periods.rb +15 -0
- data/lib/finmodeling/can_classify_rows.rb +0 -0
- data/lib/finmodeling/capm.rb +80 -0
- data/lib/finmodeling/cash_change_calculation.rb +3 -3
- data/lib/finmodeling/cash_change_item.rb +0 -0
- data/lib/finmodeling/cash_change_item_vectors.rb +0 -0
- data/lib/finmodeling/cash_change_summary_from_differences.rb +36 -0
- data/lib/finmodeling/cash_flow_statement_analyses.rb +36 -0
- data/lib/finmodeling/cash_flow_statement_calculation.rb +28 -52
- data/lib/finmodeling/classifiers.rb +2 -0
- data/lib/finmodeling/company.rb +0 -0
- data/lib/finmodeling/company_filing.rb +30 -7
- data/lib/finmodeling/company_filing_calculation.rb +16 -6
- data/lib/finmodeling/company_filings.rb +112 -46
- data/lib/finmodeling/comprehensive_income_calculation.rb +60 -0
- data/lib/finmodeling/comprehensive_income_statement_calculation.rb +74 -0
- data/lib/finmodeling/comprehensive_income_statement_item.rb +20 -0
- data/lib/finmodeling/comprehensive_income_statement_item_vectors.rb +235 -0
- data/lib/finmodeling/config.rb +0 -0
- data/lib/finmodeling/debt_cost_of_capital.rb +14 -0
- data/lib/finmodeling/equity_change_calculation.rb +43 -0
- data/lib/finmodeling/equity_change_item.rb +25 -0
- data/lib/finmodeling/equity_change_item_vectors.rb +156 -0
- data/lib/finmodeling/factory.rb +0 -0
- data/lib/finmodeling/fama_french_cost_of_equity.rb +119 -0
- data/lib/finmodeling/float_helpers.rb +14 -8
- data/lib/finmodeling/forecasted_reformulated_balance_sheet.rb +55 -0
- data/lib/finmodeling/forecasted_reformulated_income_statement.rb +110 -0
- data/lib/finmodeling/forecasts.rb +4 -4
- data/lib/finmodeling/has_string_classifer.rb +0 -0
- data/lib/finmodeling/income_statement_analyses.rb +23 -17
- data/lib/finmodeling/income_statement_calculation.rb +46 -43
- data/lib/finmodeling/income_statement_item.rb +1 -1
- data/lib/finmodeling/income_statement_item_vectors.rb +24 -13
- data/lib/finmodeling/invalid_filing_error.rb +4 -0
- data/lib/finmodeling/liabs_and_equity_calculation.rb +18 -8
- data/lib/finmodeling/liabs_and_equity_item.rb +1 -1
- data/lib/finmodeling/liabs_and_equity_item_vectors.rb +24 -24
- data/lib/finmodeling/linear_trend_forecasting_policy.rb +23 -0
- data/lib/finmodeling/net_income_calculation.rb +23 -10
- data/lib/finmodeling/net_income_summary_from_differences.rb +51 -0
- data/lib/finmodeling/paths.rb +0 -0
- data/lib/finmodeling/period_array.rb +8 -4
- data/lib/finmodeling/quarterly_report_filing.rb +9 -4
- data/lib/finmodeling/rate.rb +8 -0
- data/lib/finmodeling/ratio.rb +0 -0
- data/lib/finmodeling/reformulated_balance_sheet.rb +47 -88
- data/lib/finmodeling/reformulated_cash_flow_statement.rb +18 -41
- data/lib/finmodeling/reformulated_income_statement.rb +44 -206
- data/lib/finmodeling/reformulated_shareholder_equity_statement.rb +50 -0
- data/lib/finmodeling/reoi_valuation.rb +104 -0
- data/lib/finmodeling/shareholder_equity_statement_calculation.rb +34 -0
- data/lib/finmodeling/string_helpers.rb +18 -1
- data/lib/finmodeling/time_series_estimator.rb +25 -0
- data/lib/finmodeling/trailing_avg_forecasting_policy.rb +23 -0
- data/lib/finmodeling/version.rb +1 -1
- data/lib/finmodeling/weighted_avg_cost_of_capital.rb +35 -0
- data/lib/finmodeling/yahoo_finance_helpers.rb +20 -0
- data/lib/finmodeling.rb +33 -2
- data/spec/annual_report_filing_spec.rb +81 -45
- data/spec/assets_calculation_spec.rb +7 -4
- data/spec/assets_item_spec.rb +9 -14
- data/spec/balance_sheet_analyses_spec.rb +13 -13
- data/spec/balance_sheet_calculation_spec.rb +45 -51
- data/spec/calculation_summary_spec.rb +113 -21
- data/spec/can_classify_rows_spec.rb +0 -0
- data/spec/cash_change_calculation_spec.rb +1 -10
- data/spec/cash_change_item_spec.rb +10 -18
- data/spec/cash_flow_statement_calculation_spec.rb +10 -24
- data/spec/company_beta_spec.rb +53 -0
- data/spec/company_filing_calculation_spec.rb +39 -49
- data/spec/company_filing_spec.rb +0 -0
- data/spec/company_filings_spec.rb +75 -25
- data/spec/company_spec.rb +37 -47
- data/spec/comprehensive_income_statement_calculation_spec.rb +54 -0
- data/spec/comprehensive_income_statement_item_spec.rb +56 -0
- data/spec/debt_cost_of_capital_spec.rb +19 -0
- data/spec/equity_change_calculation_spec.rb +33 -0
- data/spec/equity_change_item_spec.rb +58 -0
- data/spec/factory_spec.rb +2 -2
- data/spec/forecasts_spec.rb +2 -2
- data/spec/income_statement_analyses_spec.rb +23 -21
- data/spec/income_statement_calculation_spec.rb +17 -49
- data/spec/income_statement_item_spec.rb +17 -29
- data/spec/liabs_and_equity_calculation_spec.rb +6 -3
- data/spec/liabs_and_equity_item_spec.rb +14 -22
- data/spec/linear_trend_forecasting_policy_spec.rb +37 -0
- data/spec/matchers/custom_matchers.rb +79 -0
- data/spec/mocks/calculation.rb +0 -0
- data/spec/mocks/income_statement_analyses.rb +0 -0
- data/spec/mocks/sec_query.rb +0 -0
- data/spec/net_income_calculation_spec.rb +16 -10
- data/spec/period_array.rb +0 -0
- data/spec/quarterly_report_filing_spec.rb +21 -38
- data/spec/rate_spec.rb +0 -0
- data/spec/ratio_spec.rb +0 -0
- data/spec/reformulated_balance_sheet_spec.rb +56 -33
- data/spec/reformulated_cash_flow_statement_spec.rb +18 -10
- data/spec/reformulated_income_statement_spec.rb +16 -15
- data/spec/reformulated_shareholder_equity_statement_spec.rb +43 -0
- data/spec/reoi_valuation_spec.rb +146 -0
- data/spec/shareholder_equity_statement_calculation_spec.rb +59 -0
- data/spec/spec_helper.rb +4 -1
- data/spec/string_helpers_spec.rb +15 -13
- data/spec/time_series_estimator_spec.rb +61 -0
- data/spec/trailing_avg_forecasting_policy_spec.rb +37 -0
- data/spec/weighted_avg_cost_of_capital_spec.rb +32 -0
- data/tools/create_equity_change_training_vectors.rb +49 -0
- data/tools/time_specs.sh +7 -0
- metadata +182 -36
- data/lib/finmodeling/constant_forecasting_policy.rb +0 -23
- data/lib/finmodeling/generic_forecasting_policy.rb +0 -19
- data/spec/constant_forecasting_policy_spec.rb +0 -37
- data/spec/generic_forecasting_policy_spec.rb +0 -33
|
@@ -9,14 +9,14 @@ describe FinModeling::CalculationSummary do
|
|
|
9
9
|
@summary.rows = [ FinModeling::CalculationRow.new(:key => "Row", :vals => [nil, 0, nil, -101, 2.4]) ]
|
|
10
10
|
end
|
|
11
11
|
|
|
12
|
-
describe "valid_vals" do
|
|
12
|
+
describe ".valid_vals" do # FIXME: ... this should be on the row, not on the summary ?
|
|
13
13
|
subject { @summary.rows.first.valid_vals }
|
|
14
14
|
it "should return all non-nil values" do
|
|
15
15
|
subject.should == @summary.rows[0].vals.select{ |x| !x.nil? }
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
|
|
19
|
-
describe "filter_by_type" do
|
|
19
|
+
describe ".filter_by_type" do
|
|
20
20
|
before(:all) do
|
|
21
21
|
@summary2 = FinModeling::CalculationSummary.new
|
|
22
22
|
@summary2.rows = [ ]
|
|
@@ -25,11 +25,84 @@ describe FinModeling::CalculationSummary do
|
|
|
25
25
|
@summary2.rows << FinModeling::CalculationRow.new(:key => "Row", :type => :oa, :vals => [ 93])
|
|
26
26
|
@summary2.rows << FinModeling::CalculationRow.new(:key => "Row", :type => :fa, :vals => [ 1])
|
|
27
27
|
end
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
end
|
|
28
|
+
subject { @summary2.filter_by_type(:oa) }
|
|
29
|
+
it { should be_a FinModeling::CalculationSummary }
|
|
31
30
|
it "should return a summary of only the requested type" do
|
|
32
|
-
|
|
31
|
+
subject.rows.map{ |row| row.type }.uniq.should == [:oa]
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
describe ".insert_column_before" do
|
|
36
|
+
before(:each) do
|
|
37
|
+
@summary2 = FinModeling::CalculationSummary.new
|
|
38
|
+
@summary2.rows = [ ]
|
|
39
|
+
@summary2.rows << FinModeling::CalculationRow.new(:key => "Row", :type => :oa, :vals => [ 4])
|
|
40
|
+
@summary2.rows << FinModeling::CalculationRow.new(:key => "Row", :type => :oa, :vals => [ 93])
|
|
41
|
+
end
|
|
42
|
+
context "when given a column index of 0 through (length-1)" do
|
|
43
|
+
before(:each) do
|
|
44
|
+
@summary2.insert_column_before(0)
|
|
45
|
+
end
|
|
46
|
+
subject { @summary2 }
|
|
47
|
+
it "should insert a nil value in every row's vals, at the right column" do
|
|
48
|
+
subject.rows.map{ |row| row.vals }.should == [ [ nil, 4], [ nil, 93 ] ]
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
context "when given a column index greater than (length-1)" do
|
|
52
|
+
before(:each) do
|
|
53
|
+
@summary2.insert_column_before(2)
|
|
54
|
+
end
|
|
55
|
+
subject { @summary2 }
|
|
56
|
+
it "should append columns as needed" do
|
|
57
|
+
subject.rows.map{ |row| row.vals }.should == [ [ 4, nil, nil ], [ 93, nil, nil ] ]
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
describe ".auto_scale!" do
|
|
63
|
+
before(:each) do
|
|
64
|
+
@summary2 = FinModeling::CalculationSummary.new
|
|
65
|
+
end
|
|
66
|
+
context "when the minimum abs value is < 1M and >= 1k" do
|
|
67
|
+
before(:each) do
|
|
68
|
+
@summary2.rows = [ FinModeling::CalculationRow.new(:key => "Row", :type => :oa, :vals => [10000, -1000000, 20000]) ]
|
|
69
|
+
@summary2.auto_scale!
|
|
70
|
+
end
|
|
71
|
+
subject { @summary2 }
|
|
72
|
+
it "should scale all values down by 1k" do
|
|
73
|
+
subject.rows.first.vals.should == [10.0, -1000.0, 20.0]
|
|
74
|
+
end
|
|
75
|
+
it "should append ' ($KK)' to all keys" do
|
|
76
|
+
subject.rows.map{ |row| row.key }.all?{ |key| key =~ / \(\$KK\)$/ }.should be_true
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
context "when the minimum abs value is >= 1M" do
|
|
80
|
+
before(:each) do
|
|
81
|
+
@summary2.rows = [ FinModeling::CalculationRow.new(:key => "Row", :type => :oa, :vals => [10000000, 1000000, -25000000]) ]
|
|
82
|
+
@summary2.auto_scale!
|
|
83
|
+
end
|
|
84
|
+
subject { @summary2 }
|
|
85
|
+
it "should scale all values down by 1k" do
|
|
86
|
+
subject.rows.first.vals.should == [10.0, 1.0, -25.0]
|
|
87
|
+
end
|
|
88
|
+
it "should append ' ($MM)' to all keys" do
|
|
89
|
+
subject.rows.map{ |row| row.key }.all?{ |key| key =~ / \(\$MM\)$/ }.should be_true
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
describe ".num_value_columns" do
|
|
95
|
+
before(:all) do
|
|
96
|
+
@summary2 = FinModeling::CalculationSummary.new
|
|
97
|
+
@summary2.rows = [ ]
|
|
98
|
+
@summary2.rows << FinModeling::CalculationRow.new(:key => "Row", :type => :oa, :vals => [])
|
|
99
|
+
@summary2.rows << FinModeling::CalculationRow.new(:key => "Row", :type => :fa, :vals => [9])
|
|
100
|
+
@summary2.rows << FinModeling::CalculationRow.new(:key => "Row", :type => :oa, :vals => [3,2,1])
|
|
101
|
+
@summary2.rows << FinModeling::CalculationRow.new(:key => "Row", :type => :fa, :vals => [1])
|
|
102
|
+
end
|
|
103
|
+
subject { @summary2.num_value_columns }
|
|
104
|
+
it "should return the width of the table (the maximum length of any row's vals)" do
|
|
105
|
+
subject.should == @summary2.rows.map{ |row| row.vals.length }.max
|
|
33
106
|
end
|
|
34
107
|
end
|
|
35
108
|
|
|
@@ -38,25 +111,44 @@ describe FinModeling::CalculationSummary do
|
|
|
38
111
|
@mccs1 = FinModeling::CalculationSummary.new
|
|
39
112
|
@mccs1.title = "MCCS 1"
|
|
40
113
|
@mccs1.rows = [ FinModeling::CalculationRow.new(:key => "Row 1", :vals => [nil, 0, nil, -101, 2.4]) ]
|
|
41
|
-
|
|
42
|
-
@mccs2 = FinModeling::CalculationSummary.new
|
|
43
|
-
@mccs2.title = "MCCS 2"
|
|
44
|
-
@mccs2.rows = [ FinModeling::CalculationRow.new(:key => "Row 1", :vals => [nil, 0, nil, -101, 2.4]) ]
|
|
45
114
|
end
|
|
46
115
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
116
|
+
context "when the two calculations have different keys (or the same keys in different orders)" do
|
|
117
|
+
before(:all) do
|
|
118
|
+
@mccs2 = FinModeling::CalculationSummary.new
|
|
119
|
+
@mccs2.title = "MCCS 2"
|
|
120
|
+
@mccs2.rows = [ FinModeling::CalculationRow.new(:key => "Row 1", :vals => [nil, 0, nil, -101, 2.4]) ]
|
|
121
|
+
end
|
|
122
|
+
subject { @mccs1 + @mccs2 }
|
|
123
|
+
|
|
124
|
+
it { should be_a FinModeling::CalculationSummary }
|
|
125
|
+
its(:title) { should == @mccs1.title }
|
|
126
|
+
it "should set the row labels to the first summary's row labels" do
|
|
127
|
+
subject.rows.map{ |row| row.key }.should == @mccs1.rows.map{ |row| row.key }
|
|
128
|
+
end
|
|
129
|
+
it "should merge the values of summary into an array of values in the result" do
|
|
130
|
+
0.upto(subject.rows.length-1).each do |row_idx|
|
|
131
|
+
subject.rows[row_idx].vals.should == ( @mccs1.rows[row_idx].vals + @mccs2.rows[row_idx].vals )
|
|
132
|
+
end
|
|
133
|
+
end
|
|
55
134
|
end
|
|
56
135
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
136
|
+
context "when the two calculations have different keys (or the same keys in different orders)" do
|
|
137
|
+
before(:all) do
|
|
138
|
+
@mccs3 = FinModeling::CalculationSummary.new
|
|
139
|
+
@mccs3.title = "MCCS 3"
|
|
140
|
+
@mccs3.rows = [ FinModeling::CalculationRow.new(:key => "Row 2", :vals => [1, 0, nil, -101, 2.4]) ]
|
|
141
|
+
@mccs3.rows += [ FinModeling::CalculationRow.new(:key => "Row 1", :vals => [32, 0, nil, nil, 2 ]) ]
|
|
142
|
+
end
|
|
143
|
+
subject { @mccs1 + @mccs3 }
|
|
144
|
+
it "should have one row per unique key" do
|
|
145
|
+
subject.rows.map{ |row| row.key }.sort.should == (@mccs1.rows + @mccs3.rows).map{ |row| row.key }.sort.uniq
|
|
146
|
+
end
|
|
147
|
+
it "should merge the values of summary into an array of values in the result" do
|
|
148
|
+
expected_vals = []
|
|
149
|
+
expected_vals << ([""]*@mccs1.num_value_columns + @mccs3.rows[0].vals)
|
|
150
|
+
expected_vals << (@mccs1.rows[0].vals + @mccs3.rows[1].vals)
|
|
151
|
+
subject.rows.map{ |row| row.vals }.should == expected_vals
|
|
60
152
|
end
|
|
61
153
|
end
|
|
62
154
|
end
|
|
File without changes
|
|
@@ -17,18 +17,9 @@ describe FinModeling::CashChangeCalculation do
|
|
|
17
17
|
|
|
18
18
|
@cash_initial = @filing.balance_sheet.assets_calculation.summary(:period => bs_period_initial).rows[0].vals.first
|
|
19
19
|
@cash_final = @filing.balance_sheet.assets_calculation.summary(:period => bs_period_final ).rows[0].vals.first
|
|
20
|
-
|
|
21
|
-
#puts "initial cash: #{@cash_initial}"
|
|
22
|
-
#puts "final cash: #{@cash_final}"
|
|
23
|
-
|
|
24
|
-
#@filing.cash_flow_statement.cash_change_calculation.summary(:period => @cfs_period_q1_thru_q3).print
|
|
25
|
-
|
|
26
|
-
#@cash_changes.leaf_items(:period => @cfs_period_q1_thru_q3).each do |item|
|
|
27
|
-
# puts "#{item.name}: #{item.def ? item.def["xbrli:balance"] : "nil"}"
|
|
28
|
-
#end
|
|
29
20
|
end
|
|
30
21
|
|
|
31
|
-
describe "summary
|
|
22
|
+
describe ".summary" do
|
|
32
23
|
subject{ @cash_changes.summary(:period => @cfs_period_q1_thru_q3) }
|
|
33
24
|
it { should be_an_instance_of FinModeling::CalculationSummary }
|
|
34
25
|
|
|
@@ -4,38 +4,30 @@ require 'spec_helper'
|
|
|
4
4
|
|
|
5
5
|
describe FinModeling::CashChangeItem do
|
|
6
6
|
|
|
7
|
-
before(:all) do
|
|
8
|
-
#FinModeling::CashChangeItem.load_vectors_and_train(FinModeling::CashChangeItem::TRAINING_VECTORS)
|
|
9
|
-
end
|
|
10
|
-
|
|
11
7
|
describe "new" do
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
cci.should be_an_instance_of FinModeling::CashChangeItem
|
|
15
|
-
end
|
|
8
|
+
subject { FinModeling::CashChangeItem.new("Depreciation and amortization of property and equipment") }
|
|
9
|
+
it { should be_a FinModeling::CashChangeItem }
|
|
16
10
|
end
|
|
17
11
|
|
|
18
12
|
describe "train" do
|
|
13
|
+
let(:item) { FinModeling::CashChangeItem.new("Depreciation and amortization of property and equipment") }
|
|
19
14
|
it "trains the classifier that this CashChangeItem is of the given type" do
|
|
20
|
-
|
|
15
|
+
item.train(:c)
|
|
21
16
|
end
|
|
22
17
|
end
|
|
23
18
|
|
|
24
19
|
describe "classification_estimates" do
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
FinModeling::CashChangeItem::TYPES.each do |klass|
|
|
29
|
-
cci.classification_estimates.keys.include?(klass).should be_true
|
|
30
|
-
end
|
|
31
|
-
end
|
|
20
|
+
let(:item) { FinModeling::CashChangeItem.new("Depreciation and amortization of property and equipment") }
|
|
21
|
+
subject { item.classification_estimates }
|
|
22
|
+
its(:keys) { should == FinModeling::CashChangeItem::TYPES }
|
|
32
23
|
end
|
|
33
24
|
|
|
34
25
|
describe "classify" do
|
|
26
|
+
let(:cci) { FinModeling::CashChangeItem.new("Depreciation and amortization of property and equipment") }
|
|
27
|
+
subject { cci.classify }
|
|
35
28
|
it "returns the CashChangeItem type with the highest probability estimate" do
|
|
36
|
-
cci = FinModeling::CashChangeItem.new("Depreciation and amortization of property and equipment")
|
|
37
29
|
estimates = cci.classification_estimates
|
|
38
|
-
estimates[
|
|
30
|
+
estimates[subject].should be_within(0.1).of(estimates.values.max)
|
|
39
31
|
end
|
|
40
32
|
end
|
|
41
33
|
|
|
@@ -13,9 +13,7 @@ describe FinModeling::CashFlowStatementCalculation do
|
|
|
13
13
|
describe "cash_change_calculation" do
|
|
14
14
|
subject { @cash_flow_stmt.cash_change_calculation }
|
|
15
15
|
it { should be_an_instance_of FinModeling::CashChangeCalculation }
|
|
16
|
-
|
|
17
|
-
@cash_flow_stmt.cash_change_calculation.label.downcase.should match /^cash/
|
|
18
|
-
end
|
|
16
|
+
its(:label) { should match /^cash/i }
|
|
19
17
|
end
|
|
20
18
|
|
|
21
19
|
describe "is_valid?" do
|
|
@@ -26,13 +24,14 @@ describe FinModeling::CashFlowStatementCalculation do
|
|
|
26
24
|
(re_cfs.cash_investments_in_operations.total != 0) &&
|
|
27
25
|
(re_cfs.payments_to_debtholders.total != 0) &&
|
|
28
26
|
(re_cfs.payments_to_stockholders.total != 0)
|
|
27
|
+
|
|
29
28
|
@cash_flow_stmt.is_valid?.should == (flows_are_balanced && none_are_zero)
|
|
30
29
|
end
|
|
31
30
|
end
|
|
32
31
|
|
|
33
32
|
describe "reformulated" do
|
|
34
33
|
subject { @cash_flow_stmt.reformulated(@period) }
|
|
35
|
-
it { should
|
|
34
|
+
it { should be_a FinModeling::ReformulatedCashFlowStatement }
|
|
36
35
|
end
|
|
37
36
|
|
|
38
37
|
describe "latest_quarterly_reformulated" do
|
|
@@ -50,9 +49,7 @@ describe FinModeling::CashFlowStatementCalculation do
|
|
|
50
49
|
context "when given a Q1 report" do
|
|
51
50
|
subject { @cash_flow_stmt_2011_q1.latest_quarterly_reformulated(nil) }
|
|
52
51
|
it { should be_an_instance_of FinModeling::ReformulatedCashFlowStatement }
|
|
53
|
-
|
|
54
|
-
subject.cash_investments_in_operations.total.abs.should be > 1.0
|
|
55
|
-
end
|
|
52
|
+
its(:cash_investments_in_operations) { should have_a_plausible_total }
|
|
56
53
|
end
|
|
57
54
|
|
|
58
55
|
context "when given a Q2 report (and a previous Q1 report)" do
|
|
@@ -66,21 +63,17 @@ describe FinModeling::CashFlowStatementCalculation do
|
|
|
66
63
|
context "when given a Q3 report (and a previous Q2 report)" do
|
|
67
64
|
subject { @cash_flow_stmt_2011_q3.latest_quarterly_reformulated(@cash_flow_stmt_2011_q2) }
|
|
68
65
|
it { should be_an_instance_of FinModeling::ReformulatedCashFlowStatement }
|
|
69
|
-
|
|
70
|
-
subject.cash_investments_in_operations.total.abs.should be > 1.0
|
|
71
|
-
end
|
|
66
|
+
its(:cash_investments_in_operations) { should have_a_plausible_total }
|
|
72
67
|
end
|
|
73
68
|
|
|
74
69
|
context "when given an annual report (and a previous Q3 report)" do
|
|
75
70
|
subject { @cash_flow_stmt.latest_quarterly_reformulated(@cash_flow_stmt_2011_q3) }
|
|
76
71
|
it { should be_an_instance_of FinModeling::ReformulatedCashFlowStatement }
|
|
77
|
-
|
|
78
|
-
subject.cash_investments_in_operations.total.abs.should be > 1.0
|
|
79
|
-
end
|
|
72
|
+
its(:cash_investments_in_operations) { should have_a_plausible_total }
|
|
80
73
|
end
|
|
81
74
|
end
|
|
82
75
|
|
|
83
|
-
|
|
76
|
+
context "after write_constructor()ing it to a file and then eval()ing the results" do
|
|
84
77
|
before(:all) do
|
|
85
78
|
file_name = "/tmp/finmodeling-cash_flow_stmt.rb"
|
|
86
79
|
item_name = "@cfs"
|
|
@@ -89,19 +82,12 @@ describe FinModeling::CashFlowStatementCalculation do
|
|
|
89
82
|
file.close
|
|
90
83
|
|
|
91
84
|
eval(File.read(file_name))
|
|
92
|
-
|
|
93
85
|
@loaded_cfs = eval(item_name)
|
|
94
86
|
end
|
|
95
87
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
end
|
|
100
|
-
it "writes itself to a file, and when reloaded, has the same change in cash" do
|
|
101
|
-
period = @cash_flow_stmt.periods.last
|
|
102
|
-
expected_cash_change = @cash_flow_stmt.cash_change_calculation.summary(:period=>period).total
|
|
103
|
-
@loaded_cfs.cash_change_calculation.summary(:period=>period).total.should be_within(1.0).of(expected_cash_change)
|
|
104
|
-
end
|
|
88
|
+
subject { @loaded_cfs }
|
|
89
|
+
it { should have_the_same_periods_as(@cash_flow_stmt) }
|
|
90
|
+
its(:cash_change_calculation) { should have_the_same_last_total_as(@cash_flow_stmt.cash_change_calculation) }
|
|
105
91
|
end
|
|
106
92
|
|
|
107
93
|
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe FinModeling::CAPM::Beta do
|
|
4
|
+
describe "#from_ticker" do
|
|
5
|
+
it "returns the right value" do
|
|
6
|
+
index_ticker = "SPY"
|
|
7
|
+
mock_index_quotes = []
|
|
8
|
+
mock_index_quotes << YahooFinance::HistoricalQuote.new("SPY", ["2013-02-05",153.66,154.7,153.64,154.29,121431900, 54.29])
|
|
9
|
+
mock_index_quotes << YahooFinance::HistoricalQuote.new("SPY", ["2013-02-06",153.66,154.7,153.64,154.29,121431900, 52.29])
|
|
10
|
+
mock_index_quotes << YahooFinance::HistoricalQuote.new("SPY", ["2013-02-07",153.66,154.7,153.64,154.29,121431900, 58.29])
|
|
11
|
+
mock_index_quotes << YahooFinance::HistoricalQuote.new("SPY", ["2013-02-08",153.66,154.7,153.64,154.29,121431900, 57.29])
|
|
12
|
+
mock_index_quotes << YahooFinance::HistoricalQuote.new("SPY", ["2013-02-09",153.66,154.7,153.64,154.29,121431900, 84.29])
|
|
13
|
+
mock_index_quotes << YahooFinance::HistoricalQuote.new("SPY", ["2013-02-10",153.66,154.7,153.64,154.29,121431900, 73.29])
|
|
14
|
+
mock_index_quotes << YahooFinance::HistoricalQuote.new("SPY", ["2013-02-11",153.66,154.7,153.64,154.29,121431900, 64.29])
|
|
15
|
+
mock_index_quotes << YahooFinance::HistoricalQuote.new("SPY", ["2013-02-12",153.66,154.7,153.64,154.29,121431900, 54.29])
|
|
16
|
+
mock_index_quotes << YahooFinance::HistoricalQuote.new("SPY", ["2013-02-13",153.66,154.7,153.64,154.29,121431900, 61.29])
|
|
17
|
+
mock_index_quotes << YahooFinance::HistoricalQuote.new("SPY", ["2013-02-14",153.66,154.7,153.64,154.29,121431900, 44.29])
|
|
18
|
+
|
|
19
|
+
company_ticker = "AAPL"
|
|
20
|
+
mock_company_quotes = []
|
|
21
|
+
mock_company_quotes << YahooFinance::HistoricalQuote.new("AAPL",["2013-02-05",153.66,154.7,153.64,154.29,121431900,154.29])
|
|
22
|
+
mock_company_quotes << YahooFinance::HistoricalQuote.new("AAPL",["2013-02-06",153.66,154.7,153.64,154.29,121431900,152.29])
|
|
23
|
+
mock_company_quotes << YahooFinance::HistoricalQuote.new("AAPL",["2013-02-07",153.66,154.7,153.64,154.29,121431900,158.29])
|
|
24
|
+
mock_company_quotes << YahooFinance::HistoricalQuote.new("AAPL",["2013-02-08",153.66,154.7,153.64,154.29,121431900,157.29])
|
|
25
|
+
mock_company_quotes << YahooFinance::HistoricalQuote.new("AAPL",["2013-02-09",153.66,154.7,153.64,154.29,121431900,184.29])
|
|
26
|
+
mock_company_quotes << YahooFinance::HistoricalQuote.new("AAPL",["2013-02-10",153.66,154.7,153.64,154.29,121431900,173.29])
|
|
27
|
+
mock_company_quotes << YahooFinance::HistoricalQuote.new("AAPL",["2013-02-11",153.66,154.7,153.64,154.29,121431900,164.29])
|
|
28
|
+
mock_company_quotes << YahooFinance::HistoricalQuote.new("AAPL",["2013-02-12",153.66,154.7,153.64,154.29,121431900,154.29])
|
|
29
|
+
mock_company_quotes << YahooFinance::HistoricalQuote.new("AAPL",["2013-02-13",153.66,154.7,153.64,154.29,121431900,161.29])
|
|
30
|
+
mock_company_quotes << YahooFinance::HistoricalQuote.new("AAPL",["2013-02-14",153.66,154.7,153.64,154.29,121431900,144.29])
|
|
31
|
+
|
|
32
|
+
num_days = 10
|
|
33
|
+
|
|
34
|
+
YahooFinance.should_receive(:get_HistoricalQuotes_days).with(index_ticker, num_days).and_return(mock_index_quotes)
|
|
35
|
+
YahooFinance.should_receive(:get_HistoricalQuotes_days).with(company_ticker, num_days).and_return(mock_company_quotes)
|
|
36
|
+
|
|
37
|
+
common_dates = mock_index_quotes.map{ |x| x.date } & mock_company_quotes.map{ |x| x.date }
|
|
38
|
+
|
|
39
|
+
index_quotes = mock_index_quotes .select{ |x| common_dates.include?(x.date) }.sort{ |x,y| x.date <=> y.date }
|
|
40
|
+
company_quotes = mock_company_quotes.select{ |x| common_dates.include?(x.date) }.sort{ |x,y| x.date <=> y.date }
|
|
41
|
+
|
|
42
|
+
index_daily_change = index_quotes.each_cons(2).map { |pair| (pair[1].adjClose-pair[0].adjClose)/pair[0].adjClose }
|
|
43
|
+
company_daily_change = company_quotes.each_cons(2).map{ |pair| (pair[1].adjClose-pair[0].adjClose)/pair[0].adjClose }
|
|
44
|
+
|
|
45
|
+
x = GSL::Vector.alloc(index_daily_change)
|
|
46
|
+
y = GSL::Vector.alloc(company_daily_change)
|
|
47
|
+
intercept, slope = GSL::Fit::linear(x, y)
|
|
48
|
+
expected_beta = slope
|
|
49
|
+
|
|
50
|
+
FinModeling::CAPM::Beta.from_ticker(company_ticker, num_days).should be_within(0.1).of(expected_beta) # FIXME: this is now ignorant of dividends...
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -8,67 +8,57 @@ describe FinModeling::CompanyFilingCalculation do
|
|
|
8
8
|
@filing = FinModeling::AnnualReportFiling.download google_2011_annual_rpt
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
@calculation = FinModeling::Mocks::Calculation.new
|
|
14
|
-
end
|
|
15
|
-
it "takes a taxonomy and a xbrlware calculation and returns a CompanyFilingCalculation" do
|
|
16
|
-
FinModeling::CompanyFilingCalculation.new(@calculation).should be_an_instance_of FinModeling::CompanyFilingCalculation
|
|
17
|
-
end
|
|
18
|
-
end
|
|
11
|
+
let(:calculation) { FinModeling::Mocks::Calculation.new }
|
|
12
|
+
subject { FinModeling::CompanyFilingCalculation.new(calculation) }
|
|
19
13
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@calculation = FinModeling::Mocks::Calculation.new
|
|
23
|
-
end
|
|
24
|
-
it "returns the calculation's label" do
|
|
25
|
-
cfc = FinModeling::CompanyFilingCalculation.new(@calculation)
|
|
26
|
-
cfc.label.should == @calculation.label
|
|
27
|
-
end
|
|
28
|
-
end
|
|
14
|
+
it { should be_a FinModeling::CompanyFilingCalculation }
|
|
15
|
+
its(:label) { should == calculation.label }
|
|
29
16
|
|
|
30
17
|
describe "periods" do
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
it "returns an array of the periods over/at which this calculation can be queried" do
|
|
35
|
-
@filing.balance_sheet.periods.map{|x| x.to_s }.sort.should == ["2008-12-31", "2009-12-31", "2010-12-31", "2011-12-31"]
|
|
36
|
-
end
|
|
18
|
+
subject { @filing.balance_sheet.periods }
|
|
19
|
+
it { should be_a FinModeling::PeriodArray }
|
|
20
|
+
specify { subject.map{ |x| x.to_s }.sort.should == ["2008-12-31", "2009-12-31", "2010-12-31", "2011-12-31"] }
|
|
37
21
|
end
|
|
38
22
|
|
|
39
23
|
describe "leaf_items" do
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
24
|
+
let(:assets) { @filing.balance_sheet.assets_calculation }
|
|
25
|
+
context "when given a period" do
|
|
26
|
+
subject { assets.leaf_items(:period => @filing.balance_sheet.periods.last) }
|
|
27
|
+
it "should contain Xbrlware::Item's" do
|
|
28
|
+
subject.all?{ |x| x.class == Xbrlware::Item }.should be_true
|
|
29
|
+
end
|
|
30
|
+
it "returns the leaf items that match the period" do
|
|
31
|
+
subject.should have(12).items
|
|
32
|
+
end
|
|
46
33
|
end
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
34
|
+
context "when not given a period" do
|
|
35
|
+
subject { assets.leaf_items }
|
|
36
|
+
it "should contain Xbrlware::Item's" do
|
|
37
|
+
subject.all?{ |x| x.class == Xbrlware::Item }.should be_true
|
|
38
|
+
end
|
|
39
|
+
it "returns all leaf items" do
|
|
40
|
+
subject.should have(26).items
|
|
41
|
+
end
|
|
52
42
|
end
|
|
53
43
|
end
|
|
54
44
|
|
|
55
45
|
describe "leaf_items_sum" do
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
# this balance sheet has some items (accumulated depreciation) that
|
|
61
|
-
# should be subtracted from total assets, which makes it a better test
|
|
62
|
-
# than the google annual report
|
|
63
|
-
balance_sheet = @filing_with_mixed_order.balance_sheet
|
|
64
|
-
@assets = balance_sheet.assets_calculation
|
|
65
|
-
@period = balance_sheet.periods.last
|
|
66
|
-
end
|
|
67
|
-
it "returns the sum of the calculation tree, in the given period" do
|
|
68
|
-
mapping = Xbrlware::ValueMapping.new
|
|
69
|
-
mapping.policy[:credit] = :flip
|
|
46
|
+
context "given a balance sheet with items that subtract from the total (like accum. depreciation)" do
|
|
47
|
+
it "returns the sum of the calculation tree, in the given period" do
|
|
48
|
+
pending "can't parse this 10-k. need to find another suitable example"
|
|
70
49
|
|
|
71
|
-
|
|
50
|
+
vepc_2010_annual_rpt = "http://www.sec.gov/Archives/edgar/data/103682/000119312511049905/d10k.htm"
|
|
51
|
+
@filing_with_mixed_order = FinModeling::AnnualReportFiling.download vepc_2010_annual_rpt
|
|
52
|
+
|
|
53
|
+
balance_sheet = @filing_with_mixed_order.balance_sheet
|
|
54
|
+
@assets = balance_sheet.assets_calculation
|
|
55
|
+
@period = balance_sheet.periods.last
|
|
56
|
+
|
|
57
|
+
mapping = Xbrlware::ValueMapping.new
|
|
58
|
+
mapping.policy[:credit] = :flip
|
|
59
|
+
|
|
60
|
+
@assets.leaf_items_sum(:period=>@period, :mapping=>mapping).should be_within(1.0).of(42817000000.0)
|
|
61
|
+
end
|
|
72
62
|
end
|
|
73
63
|
end
|
|
74
64
|
end
|
data/spec/company_filing_spec.rb
CHANGED
|
File without changes
|
|
@@ -2,53 +2,103 @@
|
|
|
2
2
|
|
|
3
3
|
require 'spec_helper'
|
|
4
4
|
|
|
5
|
-
describe FinModeling::
|
|
5
|
+
describe FinModeling::CompanyFilings do
|
|
6
6
|
before (:all) do
|
|
7
7
|
@company = FinModeling::Company.find("aapl")
|
|
8
8
|
@filings = FinModeling::CompanyFilings.new(@company.filings_since_date(Time.parse("2010-10-01")))
|
|
9
9
|
end
|
|
10
10
|
|
|
11
|
-
describe "balance_sheet_analyses" do
|
|
11
|
+
describe ".balance_sheet_analyses" do
|
|
12
12
|
subject { @filings.balance_sheet_analyses }
|
|
13
|
-
it { should
|
|
13
|
+
it { should be_a FinModeling::BalanceSheetAnalyses }
|
|
14
|
+
it "should have one column per filing" do
|
|
15
|
+
subject.num_value_columns.should == @filings.length
|
|
16
|
+
end
|
|
14
17
|
end
|
|
15
18
|
|
|
16
|
-
describe "cash_flow_statement_analyses" do
|
|
19
|
+
describe ".cash_flow_statement_analyses" do
|
|
17
20
|
subject { @filings.cash_flow_statement_analyses }
|
|
18
|
-
it { should
|
|
21
|
+
it { should be_a FinModeling::CalculationSummary }
|
|
22
|
+
it "should have one column per filing" do
|
|
23
|
+
subject.num_value_columns.should == @filings.length
|
|
24
|
+
end
|
|
19
25
|
end
|
|
20
26
|
|
|
21
|
-
describe "income_statement_analyses" do
|
|
22
|
-
subject { @filings.income_statement_analyses }
|
|
23
|
-
it { should
|
|
27
|
+
describe ".income_statement_analyses" do
|
|
28
|
+
subject { @filings.income_statement_analyses(e_ror=0.10) }
|
|
29
|
+
it { should be_a FinModeling::IncomeStatementAnalyses }
|
|
30
|
+
it "should have one column per filing" do
|
|
31
|
+
subject.num_value_columns.should == @filings.length
|
|
32
|
+
end
|
|
24
33
|
end
|
|
25
34
|
|
|
26
|
-
describe "
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
35
|
+
describe ".re_bs_arr" do
|
|
36
|
+
subject { @filings.re_bs_arr }
|
|
37
|
+
it "should be an array of FinModeling::ReformulatedBalanceSheet" do
|
|
38
|
+
subject.all?{ |re_bs| re_bs.should be_a FinModeling::ReformulatedBalanceSheet }
|
|
39
|
+
end
|
|
40
|
+
it "should have one per filing" do
|
|
41
|
+
subject.length.should == @filings.length
|
|
42
|
+
end
|
|
43
|
+
end
|
|
30
44
|
|
|
31
|
-
|
|
45
|
+
describe ".re_is_arr" do
|
|
46
|
+
subject { @filings.re_is_arr }
|
|
47
|
+
it "should be an array whose first element is nil" do
|
|
48
|
+
subject.first.should be_nil
|
|
49
|
+
end
|
|
50
|
+
it "should be an array whose remaining elements are FinModeling::ReformulatedIncomeStatement's" do
|
|
51
|
+
subject[1..-1].all?{ |re_is| re_is.should be_a FinModeling::ReformulatedIncomeStatement }
|
|
52
|
+
end
|
|
53
|
+
it "should have one per filing" do
|
|
54
|
+
subject.length.should == @filings.length
|
|
32
55
|
end
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
describe ".disclosures" do
|
|
59
|
+
context "when a yearly disclosure is requested" do
|
|
60
|
+
subject { @filings.disclosures(/Disclosure Provision For Income Taxes/, :yearly) }
|
|
61
|
+
it { should be_a FinModeling::CalculationSummary }
|
|
62
|
+
it "should have one column per 4 filings" do
|
|
63
|
+
subject.num_value_columns.should be_within(2).of((@filings.length/4).floor)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
context "when a quarterly disclosure is requested" do
|
|
67
|
+
subject { @filings.disclosures(/Disclosure Components Of Total Comprehensive I/, :quarterly) }
|
|
68
|
+
it { should be_a FinModeling::CalculationSummary }
|
|
69
|
+
it "should have one column per filing" do
|
|
70
|
+
#subject.num_value_columns.should be_within(3).of(@filings.length)
|
|
71
|
+
pending "this is returning more than expected. not sure why..."
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
context "when no period modifier is given (and the disclosure is yearly)" do
|
|
75
|
+
subject { @filings.disclosures(/Disclosure Components Of Gross And Net Intangible Asset Balances/) }
|
|
76
|
+
it { should be_a FinModeling::CalculationSummary }
|
|
77
|
+
it "should have one column per 4 filings" do
|
|
78
|
+
subject.num_value_columns.should be_within(2).of((@filings.length/4).floor)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
37
82
|
|
|
38
|
-
|
|
83
|
+
describe ".choose_forecasting_policy" do
|
|
84
|
+
context "when one or two filings" do
|
|
85
|
+
let(:filings) { FinModeling::CompanyFilings.new(@filings.last(2)) }
|
|
86
|
+
subject { filings.choose_forecasting_policy(e_ror=0.10) }
|
|
39
87
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
88
|
+
it { should be_a FinModeling::GenericForecastingPolicy }
|
|
89
|
+
end
|
|
90
|
+
context "when three or more filings" do
|
|
91
|
+
let(:filings) { FinModeling::CompanyFilings.new(@filings.last(3)) }
|
|
92
|
+
subject { filings.choose_forecasting_policy(e_ror=0.10) }
|
|
93
|
+
it { should be_a FinModeling::ConstantForecastingPolicy }
|
|
44
94
|
end
|
|
45
95
|
end
|
|
46
96
|
|
|
47
|
-
describe "forecasts" do
|
|
48
|
-
let(:policy) { @filings.choose_forecasting_policy }
|
|
97
|
+
describe ".forecasts" do
|
|
98
|
+
let(:policy) { @filings.choose_forecasting_policy(e_ror=0.10) }
|
|
49
99
|
let(:num_quarters) { 3 }
|
|
50
100
|
subject { @filings.forecasts(policy, num_quarters) }
|
|
51
|
-
it { should
|
|
101
|
+
it { should be_a FinModeling::Forecasts }
|
|
52
102
|
its(:reformulated_income_statements) { should have(num_quarters).items }
|
|
53
103
|
its(:reformulated_balance_sheets) { should have(num_quarters).items }
|
|
54
104
|
end
|