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
data/spec/company_spec.rb
CHANGED
|
@@ -3,71 +3,61 @@
|
|
|
3
3
|
require 'spec_helper'
|
|
4
4
|
|
|
5
5
|
describe FinModeling::Company do
|
|
6
|
-
before(:each) do
|
|
7
|
-
end
|
|
8
|
-
|
|
9
6
|
describe "initialize" do
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
end
|
|
7
|
+
let(:entity) { SecQuery::Entity.find("aapl", {:relationships=>false, :transactions=>false, :filings=>true}) }
|
|
8
|
+
subject { FinModeling::Company.new(entity) }
|
|
9
|
+
it { should be_a FinModeling::Company }
|
|
14
10
|
end
|
|
15
11
|
|
|
16
12
|
describe "find" do
|
|
17
|
-
|
|
18
|
-
FinModeling::Company.find("aapl")
|
|
13
|
+
context "when given a valid stock ticker" do
|
|
14
|
+
subject { FinModeling::Company.find("aapl") }
|
|
15
|
+
it { should be_a FinModeling::Company }
|
|
19
16
|
end
|
|
20
|
-
|
|
21
|
-
FinModeling::Company.find("bogus symbol")
|
|
17
|
+
context "when given a bogus stock ticker" do
|
|
18
|
+
subject { FinModeling::Company.find("bogus symbol") }
|
|
19
|
+
it { should be_nil }
|
|
22
20
|
end
|
|
23
21
|
end
|
|
24
22
|
|
|
23
|
+
let(:company) { FinModeling::Company.find("aapl") }
|
|
24
|
+
|
|
25
25
|
describe "name" do
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
c.name.should == "APPLE INC"
|
|
29
|
-
end
|
|
26
|
+
subject { company.name }
|
|
27
|
+
it { should == "APPLE INC" }
|
|
30
28
|
end
|
|
31
29
|
|
|
32
30
|
describe "annual_reports" do
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
it "returns a CompanyFilings object " do
|
|
37
|
-
@company.annual_reports.should be_an_instance_of FinModeling::CompanyFilings
|
|
38
|
-
end
|
|
39
|
-
it "returns an array of 10-K filings" do
|
|
40
|
-
@company.annual_reports.last.term.should == "10-K"
|
|
41
|
-
end
|
|
31
|
+
subject { company.annual_reports }
|
|
32
|
+
it { should be_a FinModeling::CompanyFilings }
|
|
33
|
+
specify { subject.all?{ |report| report.term == "10-K" }.should be_true }
|
|
42
34
|
end
|
|
43
35
|
|
|
44
36
|
describe "quarterly_reports" do
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
it "returns a CompanyFilings object " do
|
|
49
|
-
@company.quarterly_reports.should be_an_instance_of FinModeling::CompanyFilings
|
|
50
|
-
end
|
|
51
|
-
it "returns an array of 10-Q filings" do
|
|
52
|
-
@company.quarterly_reports.last.term.should == "10-Q"
|
|
53
|
-
end
|
|
37
|
+
subject { company.quarterly_reports }
|
|
38
|
+
it { should be_a FinModeling::CompanyFilings }
|
|
39
|
+
specify { subject.all?{ |report| report.term == "10-Q" }.should be_true }
|
|
54
40
|
end
|
|
55
41
|
|
|
56
42
|
describe "filings_since_date" do
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
end
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
43
|
+
let(:start_date) { Time.parse("1994-01-01") }
|
|
44
|
+
subject { company.filings_since_date(start_date) }
|
|
45
|
+
it { should be_a FinModeling::CompanyFilings }
|
|
46
|
+
|
|
47
|
+
context "when start date is 1994" do
|
|
48
|
+
let(:start_date) { Time.parse("1994-01-01") }
|
|
49
|
+
subject { company.filings_since_date(start_date) }
|
|
50
|
+
it { should have_at_least(11).items }
|
|
51
|
+
end
|
|
52
|
+
context "when start date is 2010" do
|
|
53
|
+
let(:start_date) { Time.parse("2010-01-01") }
|
|
54
|
+
subject { company.filings_since_date(start_date) }
|
|
55
|
+
it { should have_at_least( 9).items }
|
|
56
|
+
end
|
|
57
|
+
context "when start date is 2011" do
|
|
58
|
+
let(:start_date) { Time.parse("2011-01-01") }
|
|
59
|
+
subject { company.filings_since_date(start_date) }
|
|
60
|
+
it { should have_at_least( 5).items }
|
|
71
61
|
end
|
|
72
62
|
end
|
|
73
63
|
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe FinModeling::ComprehensiveIncomeStatementCalculation do
|
|
4
|
+
before(:all) do
|
|
5
|
+
xray_2012_q2_rpt = "http://www.sec.gov/Archives/edgar/data/1288776/000119312511282235/0001193125-11-282235-index.htm"
|
|
6
|
+
filing_q2 = FinModeling::AnnualReportFiling.download xray_2012_q2_rpt
|
|
7
|
+
@prev_ci_stmt = filing_q2.comprehensive_income_statement
|
|
8
|
+
|
|
9
|
+
xray_2012_q3_rpt = "http://www.sec.gov/Archives/edgar/data/818479/000081847912000023/0000818479-12-000023-index.htm"
|
|
10
|
+
filing = FinModeling::AnnualReportFiling.download xray_2012_q3_rpt
|
|
11
|
+
@ci_stmt = filing.comprehensive_income_statement
|
|
12
|
+
@period = @ci_stmt.periods.last
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
describe ".comprehensive_income_calculation" do
|
|
16
|
+
subject { @ci_stmt.comprehensive_income_calculation }
|
|
17
|
+
it { should be_a FinModeling::ComprehensiveIncomeCalculation }
|
|
18
|
+
its(:label) { should match /comprehensive.*income/i }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
describe ".is_valid?" do
|
|
22
|
+
subject { @ci_stmt.is_valid? }
|
|
23
|
+
it { should == (@ci_stmt.comprehensive_income_calculation.has_net_income_item? || @ci_stmt.comprehensive_income_calculation.has_revenue_item?) }
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
describe ".reformulated" do
|
|
27
|
+
subject { @ci_stmt.reformulated(@period, ci_calc=nil) }
|
|
28
|
+
it { should be_a FinModeling::ReformulatedIncomeStatement }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
describe ".latest_quarterly_reformulated" do
|
|
32
|
+
subject{ @ci_stmt.latest_quarterly_reformulated(@ci_stmt, @prev_ci_stmt, @prev_ci_calc) }
|
|
33
|
+
it { should be_a FinModeling::ReformulatedIncomeStatement }
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
describe ".write_constructor" do
|
|
37
|
+
before(:all) do
|
|
38
|
+
file_name = "/tmp/finmodeling-ci-stmt.rb"
|
|
39
|
+
item_name = "@ci_stmt"
|
|
40
|
+
file = File.open(file_name, "w")
|
|
41
|
+
@ci_stmt.write_constructor(file, item_name)
|
|
42
|
+
file.close
|
|
43
|
+
|
|
44
|
+
eval(File.read(file_name))
|
|
45
|
+
@loaded_cis = eval(item_name)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
subject { @loaded_cis }
|
|
49
|
+
it { should have_the_same_periods_as(@ci_stmt) }
|
|
50
|
+
#it { should have_the_same_reformulated_last_total(:net_financing_income).as(@ci_stmt) }
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
end
|
|
54
|
+
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe FinModeling::ComprehensiveIncomeStatementItem do
|
|
4
|
+
|
|
5
|
+
describe "new" do
|
|
6
|
+
subject { FinModeling::ComprehensiveIncomeStatementItem.new("Comprehensive Income Net Of Tax Attributable To Noncontrolling Interest") }
|
|
7
|
+
it { should be_a FinModeling::ComprehensiveIncomeStatementItem }
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
describe "train" do
|
|
11
|
+
let(:item) { FinModeling::ComprehensiveIncomeStatementItem.new("Comprehensive Income Net Of Tax Attributable To Noncontrolling Interest") }
|
|
12
|
+
it "trains the classifier that this ComprehensiveIncomeStatementItem is of the given type" do
|
|
13
|
+
item.train(:ooci_nci)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
describe "classification_estimates" do
|
|
18
|
+
let(:item) { FinModeling::ComprehensiveIncomeStatementItem.new("Comprehensive Income Net Of Tax Attributable To Noncontrolling Interest") }
|
|
19
|
+
subject { item.classification_estimates }
|
|
20
|
+
its(:keys) { should == FinModeling::ComprehensiveIncomeStatementItem::TYPES }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
describe "classify" do
|
|
24
|
+
let(:cisi) { FinModeling::ComprehensiveIncomeStatementItem.new("Comprehensive Income Net Of Tax Attributable To Noncontrolling Interest") }
|
|
25
|
+
subject { cisi.classify }
|
|
26
|
+
it "returns the ComprehensiveIncomeStatementItem type with the highest probability estimate" do
|
|
27
|
+
estimates = cisi.classification_estimates
|
|
28
|
+
estimates[subject].should be_within(0.1).of(estimates.values.max)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
describe "load_vectors_and_train" do
|
|
33
|
+
# the before(:all) clause calls load_vectors_and_train already
|
|
34
|
+
# we can just focus, here, on its effects
|
|
35
|
+
|
|
36
|
+
it "classifies >95% correctly" do # FIXME: add more vectors to tighten this up
|
|
37
|
+
num_items = 0
|
|
38
|
+
errors = []
|
|
39
|
+
FinModeling::ComprehensiveIncomeStatementItem::TRAINING_VECTORS.each do |vector|
|
|
40
|
+
num_items += 1
|
|
41
|
+
cisi = FinModeling::ComprehensiveIncomeStatementItem.new(vector[:item_string])
|
|
42
|
+
if cisi.classify != vector[:klass]
|
|
43
|
+
errors.push({ :cisi=>cisi.to_s, :expected=>vector[:klass], :got=>cisi.classify })
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
pct_errors = errors.length.to_f / num_items
|
|
48
|
+
if pct_errors > 0.05
|
|
49
|
+
puts "errors: " + errors.inspect
|
|
50
|
+
end
|
|
51
|
+
pct_errors.should be < 0.05
|
|
52
|
+
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require 'spec_helper'
|
|
2
|
+
|
|
3
|
+
describe FinModeling::DebtCostOfCapital do
|
|
4
|
+
describe "#calculate" do
|
|
5
|
+
context "if given the after tax cost" do
|
|
6
|
+
let(:r) { FinModeling::Rate.new(0.05) }
|
|
7
|
+
subject { FinModeling::DebtCostOfCapital.calculate(:after_tax_cost => r) }
|
|
8
|
+
it { should be_a FinModeling::Rate }
|
|
9
|
+
its(:value) { should be_within(0.1).of(r.value) }
|
|
10
|
+
end
|
|
11
|
+
context "if given the after tax cost and marginal tax rate" do
|
|
12
|
+
let(:r) { FinModeling::Rate.new(0.05) }
|
|
13
|
+
let(:t) { FinModeling::Rate.new(0.35) }
|
|
14
|
+
subject { FinModeling::DebtCostOfCapital.calculate(:before_tax_cost => r, :marginal_tax_rate => t) }
|
|
15
|
+
it { should be_a FinModeling::Rate }
|
|
16
|
+
its(:value) { should be_within(0.1).of(r.value * (1.0 - t.value)) }
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# equity_change_calculation_spec.rb
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe FinModeling::EquityChangeCalculation 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
|
+
@ses_period = @filing.shareholder_equity_statement.periods.last
|
|
10
|
+
|
|
11
|
+
@equity_changes = @filing.shareholder_equity_statement.equity_change_calculation
|
|
12
|
+
|
|
13
|
+
bs_period_initial = @filing.balance_sheet.periods[-2]
|
|
14
|
+
bs_period_final = @filing.balance_sheet.periods[-1]
|
|
15
|
+
|
|
16
|
+
@equity_plus_minority_int_initial = @filing.balance_sheet.reformulated(bs_period_initial).common_shareholders_equity.total +
|
|
17
|
+
@filing.balance_sheet.reformulated(bs_period_initial).minority_interest .total
|
|
18
|
+
@equity_plus_minority_int_final = @filing.balance_sheet.reformulated(bs_period_final) .common_shareholders_equity.total +
|
|
19
|
+
@filing.balance_sheet.reformulated(bs_period_final) .minority_interest .total
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
describe ".summary" do
|
|
23
|
+
subject{ @equity_changes.summary(:period => @ses_period) }
|
|
24
|
+
|
|
25
|
+
it { should be_an_instance_of FinModeling::CalculationSummary }
|
|
26
|
+
|
|
27
|
+
describe ".total" do
|
|
28
|
+
subject{ @equity_changes.summary(:period => @ses_period).total }
|
|
29
|
+
it { should be_within(1.0).of(@equity_plus_minority_int_final - @equity_plus_minority_int_initial) }
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# equity_change_item_spec.rb
|
|
2
|
+
|
|
3
|
+
require 'spec_helper'
|
|
4
|
+
|
|
5
|
+
describe FinModeling::EquityChangeItem do
|
|
6
|
+
|
|
7
|
+
describe "new" do
|
|
8
|
+
subject { FinModeling::EquityChangeItem.new("Depreciation and amortization of property and equipment") }
|
|
9
|
+
it { should be_a FinModeling::EquityChangeItem }
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
describe "train" do
|
|
13
|
+
let(:item) { FinModeling::EquityChangeItem.new("Depreciation and amortization of property and equipment") }
|
|
14
|
+
it "trains the classifier that this EquityChangeItem is of the given type" do
|
|
15
|
+
item.train(:oci)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
describe "classification_estimates" do
|
|
20
|
+
let(:item) { FinModeling::EquityChangeItem.new("Depreciation and amortization of property and equipment") }
|
|
21
|
+
subject { item.classification_estimates }
|
|
22
|
+
its(:keys) { should == FinModeling::EquityChangeItem::TYPES }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
describe "classify" do
|
|
26
|
+
let(:eci) { FinModeling::EquityChangeItem.new("Depreciation and amortization of property and equipment") }
|
|
27
|
+
subject { eci.classify }
|
|
28
|
+
it "returns the EquityChangeItem type with the highest probability estimate" do
|
|
29
|
+
estimates = eci.classification_estimates
|
|
30
|
+
estimates[subject].should be_within(0.1).of(estimates.values.max)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
describe "load_vectors_and_train" do
|
|
35
|
+
# the before(:all) clause calls load_vectors_and_train already
|
|
36
|
+
# we can just focus, here, on its effects
|
|
37
|
+
|
|
38
|
+
it "classifies >95% correctly" do # FIXME: add more vectors to tighten this up
|
|
39
|
+
num_items = 0
|
|
40
|
+
errors = []
|
|
41
|
+
FinModeling::EquityChangeItem::TRAINING_VECTORS.each do |vector|
|
|
42
|
+
num_items += 1
|
|
43
|
+
eci = FinModeling::EquityChangeItem.new(vector[:item_string])
|
|
44
|
+
if eci.classify != vector[:klass]
|
|
45
|
+
errors.push({ :eci=>eci.to_s, :expected=>vector[:klass], :got=>eci.classify })
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
pct_errors = errors.length.to_f / num_items
|
|
50
|
+
if pct_errors > 0.05
|
|
51
|
+
puts "errors: " + errors.inspect
|
|
52
|
+
end
|
|
53
|
+
pct_errors.should be < 0.05
|
|
54
|
+
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
end
|
data/spec/factory_spec.rb
CHANGED
|
@@ -6,13 +6,13 @@ describe FinModeling::Factory do
|
|
|
6
6
|
describe "BalanceSheetCalculation" do
|
|
7
7
|
subject { FinModeling::Factory.BalanceSheetCalculation }
|
|
8
8
|
|
|
9
|
-
it { should
|
|
9
|
+
it { should be_a FinModeling::BalanceSheetCalculation }
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
describe "incomeStatementCalculation" do
|
|
13
13
|
subject { FinModeling::Factory.IncomeStatementCalculation }
|
|
14
14
|
|
|
15
|
-
it { should
|
|
15
|
+
it { should be_a FinModeling::IncomeStatementCalculation }
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
18
|
|
data/spec/forecasts_spec.rb
CHANGED
|
@@ -6,7 +6,7 @@ describe FinModeling::Forecasts 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
|
-
@forecasts = @filings.forecasts(@filings.choose_forecasting_policy, num_quarters=3)
|
|
9
|
+
@forecasts = @filings.forecasts(@filings.choose_forecasting_policy(e_ror=0.10), num_quarters=3)
|
|
10
10
|
end
|
|
11
11
|
|
|
12
12
|
describe "balance_sheet_analyses" do
|
|
@@ -15,7 +15,7 @@ describe FinModeling::Forecasts do
|
|
|
15
15
|
end
|
|
16
16
|
|
|
17
17
|
describe "income_statement_analyses" do
|
|
18
|
-
subject { @forecasts.income_statement_analyses(@filings) }
|
|
18
|
+
subject { @forecasts.income_statement_analyses(@filings, e_ror=0.10) }
|
|
19
19
|
it { should be_a FinModeling::CalculationSummary }
|
|
20
20
|
end
|
|
21
21
|
end
|
|
@@ -8,55 +8,57 @@ describe FinModeling::IncomeStatementAnalyses do
|
|
|
8
8
|
@summary.title = "Title 123"
|
|
9
9
|
@summary.rows = [ ]
|
|
10
10
|
@summary.rows << FinModeling::CalculationRow.new(:key => "Revenue Growth", :type => :oa, :vals => [ 4])
|
|
11
|
-
@summary.rows << FinModeling::CalculationRow.new(:key => "Sales / NOA",
|
|
12
|
-
@summary.rows << FinModeling::CalculationRow.new(:key => "Operating PM",
|
|
13
|
-
@summary.rows << FinModeling::CalculationRow.new(:key => "FI / NFA",
|
|
14
|
-
@summary.rows << FinModeling::CalculationRow.new(:key => "Row",
|
|
15
|
-
@summary.rows << FinModeling::CalculationRow.new(:key => "Row",
|
|
16
|
-
@summary.rows << FinModeling::CalculationRow.new(:key => "Row",
|
|
11
|
+
@summary.rows << FinModeling::CalculationRow.new(:key => "Sales / NOA", :type => :oa, :vals => [ 4])
|
|
12
|
+
@summary.rows << FinModeling::CalculationRow.new(:key => "Operating PM", :type => :oa, :vals => [ 4])
|
|
13
|
+
@summary.rows << FinModeling::CalculationRow.new(:key => "FI / NFA", :type => :oa, :vals => [ 4])
|
|
14
|
+
@summary.rows << FinModeling::CalculationRow.new(:key => "Row", :type => :fa, :vals => [109])
|
|
15
|
+
@summary.rows << FinModeling::CalculationRow.new(:key => "Row", :type => :oa, :vals => [ 93])
|
|
16
|
+
@summary.rows << FinModeling::CalculationRow.new(:key => "Row", :type => :fa, :vals => [ 1])
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
describe ".new" do
|
|
20
20
|
subject { FinModeling::IncomeStatementAnalyses.new(@summary) }
|
|
21
21
|
|
|
22
22
|
it { should be_a_kind_of FinModeling::CalculationSummary }
|
|
23
|
-
its(:title)
|
|
24
|
-
its(:rows)
|
|
25
|
-
its(:header_row)
|
|
26
|
-
its(:rows)
|
|
27
|
-
its(:num_value_columns)
|
|
28
|
-
its(:key_width)
|
|
29
|
-
its(:val_width)
|
|
30
|
-
its(:max_decimals)
|
|
23
|
+
its(:title) { should == @summary.title }
|
|
24
|
+
its(:rows) { should == @summary.rows }
|
|
25
|
+
its(:header_row) { should == @summary.header_row }
|
|
26
|
+
its(:rows) { should == @summary.rows }
|
|
27
|
+
its(:num_value_columns) { should == @summary.num_value_columns }
|
|
28
|
+
its(:key_width) { should == @summary.key_width }
|
|
29
|
+
its(:val_width) { should == @summary.val_width }
|
|
30
|
+
its(:max_decimals) { should == @summary.max_decimals }
|
|
31
31
|
its(:totals_row_enabled) { should be_false }
|
|
32
32
|
end
|
|
33
33
|
|
|
34
|
-
|
|
35
|
-
|
|
34
|
+
let(:isa) { FinModeling::IncomeStatementAnalyses.new(@summary) }
|
|
35
|
+
|
|
36
|
+
describe ".print_regressions" do
|
|
37
|
+
subject { isa }
|
|
36
38
|
|
|
37
|
-
it { should respond_to(:
|
|
39
|
+
it { should respond_to(:print_regressions) }
|
|
38
40
|
end
|
|
39
41
|
|
|
40
42
|
describe ".revenue_growth_row" do
|
|
41
|
-
subject {
|
|
43
|
+
subject { isa.revenue_growth_row }
|
|
42
44
|
it { should be_a FinModeling::CalculationRow }
|
|
43
45
|
its(:key) { should == "Revenue Growth" }
|
|
44
46
|
end
|
|
45
47
|
|
|
46
48
|
describe ".operating_pm_row" do
|
|
47
|
-
subject {
|
|
49
|
+
subject { isa.operating_pm_row }
|
|
48
50
|
it { should be_a FinModeling::CalculationRow }
|
|
49
51
|
its(:key) { should == "Operating PM" }
|
|
50
52
|
end
|
|
51
53
|
|
|
52
54
|
describe ".sales_over_noa_row" do
|
|
53
|
-
subject {
|
|
55
|
+
subject { isa.sales_over_noa_row }
|
|
54
56
|
it { should be_a FinModeling::CalculationRow }
|
|
55
57
|
its(:key) { should == "Sales / NOA" }
|
|
56
58
|
end
|
|
57
59
|
|
|
58
60
|
describe ".fi_over_nfa_row" do
|
|
59
|
-
subject {
|
|
61
|
+
subject { isa.fi_over_nfa_row }
|
|
60
62
|
it { should be_a FinModeling::CalculationRow }
|
|
61
63
|
its(:key) { should == "FI / NFA" }
|
|
62
64
|
end
|
|
@@ -14,53 +14,28 @@ describe FinModeling::IncomeStatementCalculation do
|
|
|
14
14
|
@period = @inc_stmt.periods.last
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
describe "net_income_calculation" do
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
it "returns the root node of the net income calculation" do
|
|
22
|
-
@inc_stmt.net_income_calculation.label.downcase.should match /net.*income/
|
|
23
|
-
end
|
|
17
|
+
describe ".net_income_calculation" do
|
|
18
|
+
subject { @inc_stmt.net_income_calculation }
|
|
19
|
+
it { should be_a FinModeling::NetIncomeCalculation }
|
|
20
|
+
its(:label) { should match /net.*income/i }
|
|
24
21
|
end
|
|
25
22
|
|
|
26
|
-
describe "is_valid?" do
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
# @inc_stmt_no_taxes = FinModeling::Factory.IncomeStatementCalculation(:sheet => 'google 10-k 2011-12-31 income statement',
|
|
30
|
-
# :delete_tax_item => true)
|
|
31
|
-
#end
|
|
32
|
-
it "returns false if none of the net income leaf nodes contains the term 'tax'" do
|
|
33
|
-
#@inc_stmt_no_taxes.is_valid?.should be_false
|
|
34
|
-
pending "no good way of setting up this test..."
|
|
35
|
-
end
|
|
36
|
-
end
|
|
37
|
-
context "when no node contains the terms 'sales' or 'revenue'" do
|
|
38
|
-
#before(:all) do
|
|
39
|
-
# @inc_stmt_no_sales = FinModeling::Factory.IncomeStatementCalculation(:sheet => 'google 10-k 2011-12-31 income statement',
|
|
40
|
-
# :delete_sales_item => true)
|
|
41
|
-
#end
|
|
42
|
-
it "returns false if none of the net income leaf nodes contains the term 'tax'" do
|
|
43
|
-
#@inc_stmt_no_sales.is_valid?.should be_false
|
|
44
|
-
pending "no good way of setting up this test..."
|
|
45
|
-
end
|
|
46
|
-
end
|
|
47
|
-
context "otherwise" do
|
|
48
|
-
subject { @inc_stmt.is_valid? }
|
|
49
|
-
it { should be_true }
|
|
50
|
-
end
|
|
23
|
+
describe ".is_valid?" do
|
|
24
|
+
subject { @inc_stmt.is_valid? }
|
|
25
|
+
it { should == (@inc_stmt.net_income_calculation.has_tax_item? && @inc_stmt.net_income_calculation.has_revenue_item?) }
|
|
51
26
|
end
|
|
52
27
|
|
|
53
|
-
describe "reformulated" do
|
|
54
|
-
subject { @inc_stmt.reformulated(@period) }
|
|
55
|
-
it { should
|
|
28
|
+
describe ".reformulated" do
|
|
29
|
+
subject { @inc_stmt.reformulated(@period, ci_calc=nil) }
|
|
30
|
+
it { should be_a FinModeling::ReformulatedIncomeStatement }
|
|
56
31
|
end
|
|
57
32
|
|
|
58
|
-
describe "latest_quarterly_reformulated" do
|
|
59
|
-
subject{ @inc_stmt.latest_quarterly_reformulated(@prev_inc_stmt) }
|
|
60
|
-
it { should
|
|
33
|
+
describe ".latest_quarterly_reformulated" do
|
|
34
|
+
subject{ @inc_stmt.latest_quarterly_reformulated(ci_calc=nil, @prev_inc_stmt, prev_ci_calc=nil) }
|
|
35
|
+
it { should be_a FinModeling::ReformulatedIncomeStatement }
|
|
61
36
|
end
|
|
62
37
|
|
|
63
|
-
describe "write_constructor" do
|
|
38
|
+
describe ".write_constructor" do
|
|
64
39
|
before(:all) do
|
|
65
40
|
file_name = "/tmp/finmodeling-inc-stmt.rb"
|
|
66
41
|
item_name = "@inc_stmt"
|
|
@@ -69,19 +44,12 @@ describe FinModeling::IncomeStatementCalculation do
|
|
|
69
44
|
file.close
|
|
70
45
|
|
|
71
46
|
eval(File.read(file_name))
|
|
72
|
-
|
|
73
47
|
@loaded_is = eval(item_name)
|
|
74
48
|
end
|
|
75
49
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
end
|
|
80
|
-
it "writes itself to a file, and when reloaded, has the same net financing income" do
|
|
81
|
-
period = @inc_stmt.periods.yearly.last
|
|
82
|
-
expected_nfi = @inc_stmt.reformulated(period).net_financing_income.total
|
|
83
|
-
@loaded_is.reformulated(period).net_financing_income.total.should be_within(1.0).of(expected_nfi)
|
|
84
|
-
end
|
|
50
|
+
subject { @loaded_is }
|
|
51
|
+
it { should have_the_same_periods_as(@inc_stmt) }
|
|
52
|
+
it { should have_the_same_reformulated_last_total(:net_financing_income).as(@inc_stmt) }
|
|
85
53
|
end
|
|
86
54
|
|
|
87
55
|
end
|
|
@@ -3,54 +3,42 @@
|
|
|
3
3
|
require 'spec_helper'
|
|
4
4
|
|
|
5
5
|
describe FinModeling::IncomeStatementItem do
|
|
6
|
-
|
|
7
|
-
before(:all) do
|
|
8
|
-
#FinModeling::IncomeStatementItem.load_vectors_and_train(FinModeling::IncomeStatementItem::TRAINING_VECTORS)
|
|
9
|
-
end
|
|
6
|
+
let(:isi) { FinModeling::IncomeStatementItem.new("Cost of Goods Sold") }
|
|
10
7
|
|
|
11
8
|
describe "new" do
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
isi.should be_an_instance_of FinModeling::IncomeStatementItem
|
|
15
|
-
end
|
|
9
|
+
subject { isi }
|
|
10
|
+
it { should be_a FinModeling::IncomeStatementItem }
|
|
16
11
|
end
|
|
17
12
|
|
|
18
13
|
describe "train" do
|
|
19
14
|
it "trains the classifier that this ISI is of the given type" do
|
|
20
|
-
|
|
15
|
+
isi.train(:tax)
|
|
21
16
|
end
|
|
22
17
|
end
|
|
23
18
|
|
|
24
19
|
describe "classification_estimates" do
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
FinModeling::IncomeStatementItem::TYPES.each do |klass|
|
|
29
|
-
isi.classification_estimates.keys.include?(klass).should be_true
|
|
30
|
-
end
|
|
31
|
-
end
|
|
20
|
+
subject { isi.classification_estimates }
|
|
21
|
+
it { should be_a Hash }
|
|
22
|
+
its(:keys) { should == FinModeling::IncomeStatementItem::TYPES }
|
|
32
23
|
end
|
|
33
24
|
|
|
34
25
|
describe "classify" do
|
|
26
|
+
subject { isi.classify }
|
|
35
27
|
it "returns the ISI type with the highest probability estimate" do
|
|
36
|
-
isi = FinModeling::IncomeStatementItem.new("provision for income tax")
|
|
37
28
|
estimates = isi.classification_estimates
|
|
38
|
-
estimates[
|
|
29
|
+
estimates[subject].should be_within(0.1).of(estimates.values.max)
|
|
39
30
|
end
|
|
40
31
|
end
|
|
41
32
|
|
|
42
33
|
describe "load_vectors_and_train" do
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
it "loads vectors from a given file, trains on each example, and correctly classifies tax" do
|
|
47
|
-
isi = FinModeling::IncomeStatementItem.new("provision for income tax")
|
|
48
|
-
isi.classify.should == :tax
|
|
34
|
+
context "tax" do
|
|
35
|
+
subject { FinModeling::IncomeStatementItem.new("provision for income tax").classify }
|
|
36
|
+
it { should == :tax }
|
|
49
37
|
end
|
|
50
38
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
39
|
+
context "or" do
|
|
40
|
+
subject { FinModeling::IncomeStatementItem.new("software licensing revenues net").classify }
|
|
41
|
+
it { should == :or }
|
|
54
42
|
end
|
|
55
43
|
|
|
56
44
|
it "classifies >95% correctly" do
|
|
@@ -74,12 +62,12 @@ describe FinModeling::IncomeStatementItem do
|
|
|
74
62
|
end
|
|
75
63
|
|
|
76
64
|
describe "tokenize" do
|
|
65
|
+
subject { FinModeling::IncomeStatementItem.new("Cost of Goods Sold").tokenize }
|
|
77
66
|
it "returns an array of downcased 1-word, 2-word, and 3-word tokens" do
|
|
78
|
-
isi = FinModeling::IncomeStatementItem.new("Cost of Goods Sold")
|
|
79
67
|
expected_tokens = ["^", "cost", "of", "goods", "sold", "$"]
|
|
80
68
|
expected_tokens += ["^ cost", "cost of", "of goods", "goods sold", "sold $"]
|
|
81
69
|
expected_tokens += ["^ cost of", "cost of goods", "of goods sold", "goods sold $"]
|
|
82
|
-
|
|
70
|
+
subject.sort.should == expected_tokens.sort
|
|
83
71
|
end
|
|
84
72
|
end
|
|
85
73
|
|
|
@@ -12,9 +12,12 @@ describe FinModeling::LiabsAndEquityCalculation do
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
describe "summary" do
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
15
|
+
subject { @lse.summary(:period=>@period) }
|
|
16
|
+
it { should be_a FinModeling::CalculationSummary }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
describe ".has_equity_item" do
|
|
20
|
+
pending "Find a test case..."
|
|
18
21
|
end
|
|
19
22
|
end
|
|
20
23
|
|